diff --git a/app/src/main/kotlin/net/micode/notes/tool/DefaultPreferences.kt b/app/src/main/kotlin/net/micode/notes/tool/DefaultPreferences.kt index 37c5a2d..6dd18e4 100644 --- a/app/src/main/kotlin/net/micode/notes/tool/DefaultPreferences.kt +++ b/app/src/main/kotlin/net/micode/notes/tool/DefaultPreferences.kt @@ -1,9 +1,29 @@ +// 声明包路径,属于 tool 工具模块,提供通用辅助功能 package net.micode.notes.tool import android.content.Context import android.content.SharedPreferences +/** + * 默认 SharedPreferences 文件名的后缀。 + * 最终文件名示例:net.micode.notes_preferences + */ private const val DEFAULT_PREFERENCES_SUFFIX = "_preferences" +/** + * 获取应用默认的 SharedPreferences 实例的扩展函数。 + * + * 该函数会在任意 [Context] 上提供 `defaultPreferences()` 方法, + * 返回一个以 “包名_preferences” 命名的默认偏好文件对应的 [SharedPreferences] 对象。 + * 访问模式为 [Context.MODE_PRIVATE],仅本应用可读/写。 + * + * 使用场景:应用内全局配置、用户设置、缓存零散键值对等。 + * + * 示例: + * ```kotlin + * val prefs = context.defaultPreferences() + * prefs.edit().putBoolean("first_launch", false).apply() + * ``` + */ fun Context.defaultPreferences(): SharedPreferences = - getSharedPreferences("${packageName}$DEFAULT_PREFERENCES_SUFFIX", Context.MODE_PRIVATE) + getSharedPreferences("${packageName}$DEFAULT_PREFERENCES_SUFFIX", Context.MODE_PRIVATE) \ No newline at end of file diff --git a/app/src/main/kotlin/net/micode/notes/tool/NotesPreferences.kt b/app/src/main/kotlin/net/micode/notes/tool/NotesPreferences.kt index 966cd81..77a05fd 100644 --- a/app/src/main/kotlin/net/micode/notes/tool/NotesPreferences.kt +++ b/app/src/main/kotlin/net/micode/notes/tool/NotesPreferences.kt @@ -4,38 +4,73 @@ import android.content.Context import androidx.core.content.edit import net.micode.notes.data.Notes +/** + * 便签列表的排序模式枚举。 + * 值为对应的存储整数值,用于持久化到 SharedPreferences。 + */ enum class NotesSortMode(val value: Int) { + /** 按修改时间降序(默认) */ MODIFIED_DESC(0), + /** 按修改时间升序 */ MODIFIED_ASC(1), + /** 按标题字母升序 */ TITLE_ASC(2); companion object { + /** + * 根据存储数值还原排序模式。 + * 若值不合法,默认返回 [MODIFIED_DESC]。 + */ fun fromValue(value: Int): NotesSortMode { return values().firstOrNull { it.value == value } ?: MODIFIED_DESC } } } +/** + * 便签应用偏好设置管理单例。 + * 所有设置项通过内部的固定键读写,提供类型安全的访问和修改方法。 + */ object NotesPreferences { + // 随机背景开关键 private const val KEY_RANDOM_BACKGROUND = "pref_key_bg_random_appear" + // 默认背景颜色键 private const val KEY_DEFAULT_BACKGROUND = "pref_key_default_bg_color" + // 列表排序模式键 private const val KEY_LIST_SORT_MODE = "pref_key_list_sort_mode" + // 删除确认对话框开关键 private const val KEY_DELETE_CONFIRMATION = "pref_key_delete_confirmation" + // 记住上次浏览文件夹开关键 private const val KEY_REMEMBER_LAST_FOLDER = "pref_key_remember_last_folder" + // 上次浏览的文件夹 ID 键 private const val KEY_LAST_FOLDER_ID = "pref_key_last_folder_id" + // 上次浏览的文件夹标题键 private const val KEY_LAST_FOLDER_TITLE = "pref_key_last_folder_title" + /** + * 获取随机背景功能是否开启,默认关闭。 + */ fun isRandomBackgroundEnabled(context: Context): Boolean { + // 从默认 SharedPreferences 中读取布尔值 return context.defaultPreferences().getBoolean(KEY_RANDOM_BACKGROUND, false) } + /** + * 设置随机背景功能开关。 + */ fun setRandomBackgroundEnabled(context: Context, enabled: Boolean) { + // 使用扩展函数 edit 提交修改 context.defaultPreferences().edit { putBoolean(KEY_RANDOM_BACKGROUND, enabled) } } + /** + * 获取默认背景颜色 ID。 + * 如果读取到的值超出合法范围,返回默认背景颜色。 + */ fun getDefaultBackgroundColor(context: Context): Int { val colorId = context.defaultPreferences() .getInt(KEY_DEFAULT_BACKGROUND, ResourceParser.BG_DEFAULT_COLOR) + // 校验颜色 ID 是否在 0 到 resourcesSize-1 之间(因为背景资源数组索引) return if (colorId in 0 until ResourceParser.NoteBgResources.resourcesSize) { colorId } else { @@ -43,70 +78,116 @@ object NotesPreferences { } } + /** + * 设置默认背景颜色。 + * @param colorId 原始色彩 ID,内部会进行边界检查后存储。 + */ fun setDefaultBackgroundColor(context: Context, colorId: Int) { context.defaultPreferences().edit { putInt(KEY_DEFAULT_BACKGROUND, getDefaultBackgroundColorId(colorId)) } } + /** + * 获取列表的排序模式,默认按修改时间降序。 + */ fun getListSortMode(context: Context): NotesSortMode { + // 读取存储的整数值 val value = context.defaultPreferences().getInt( KEY_LIST_SORT_MODE, NotesSortMode.MODIFIED_DESC.value ) + // 将整数值转换为枚举 return NotesSortMode.fromValue(value) } + /** + * 设置列表排序模式。 + */ fun setListSortMode(context: Context, sortMode: NotesSortMode) { + // 存储枚举对应的数值 context.defaultPreferences().edit { putInt(KEY_LIST_SORT_MODE, sortMode.value) } } + /** + * 获取删除确认对话框是否开启,默认开启。 + */ fun isDeleteConfirmationEnabled(context: Context): Boolean { return context.defaultPreferences().getBoolean(KEY_DELETE_CONFIRMATION, true) } + /** + * 设置删除确认对话框开关。 + */ fun setDeleteConfirmationEnabled(context: Context, enabled: Boolean) { context.defaultPreferences().edit { putBoolean(KEY_DELETE_CONFIRMATION, enabled) } } + /** + * 获取“记住上次文件夹”功能是否开启,默认开启。 + */ fun isRememberLastFolderEnabled(context: Context): Boolean { return context.defaultPreferences().getBoolean(KEY_REMEMBER_LAST_FOLDER, true) } + /** + * 设置“记住上次文件夹”开关。 + * 当关闭该功能时,会自动清除已记忆的文件夹记录。 + */ fun setRememberLastFolderEnabled(context: Context, enabled: Boolean) { context.defaultPreferences().edit { putBoolean(KEY_REMEMBER_LAST_FOLDER, enabled) } if (!enabled) { + // 关闭时清除旧记录,避免下次误读 clearRememberedFolder(context) } } + /** + * 获取上次记住的文件夹 ID。 + * 如果“记住上次文件夹”功能关闭,则直接返回根文件夹 ID。 + */ fun getRememberedFolderId(context: Context): Long { if (!isRememberLastFolderEnabled(context)) { return Notes.ID_ROOT_FOLDER.toLong() } + // 读取存储的文件夹 ID,若未存储过则默认为根文件夹 return context.defaultPreferences() .getLong(KEY_LAST_FOLDER_ID, Notes.ID_ROOT_FOLDER.toLong()) } + /** + * 获取上次记住的文件夹标题。 + * 如果功能关闭,返回空字符串。 + */ fun getRememberedFolderTitle(context: Context): String { if (!isRememberLastFolderEnabled(context)) { return "" } + // 读取字符串,若未存储过则返回空字符串 return context.defaultPreferences().getString(KEY_LAST_FOLDER_TITLE, "").orEmpty() } + /** + * 记录最后浏览的文件夹信息(ID 和标题)。 + * 如果“记住上次文件夹”功能关闭,会主动清除记录后返回。 + */ fun rememberLastFolder(context: Context, folderId: Long, folderTitle: String) { if (!isRememberLastFolderEnabled(context)) { + // 功能关闭,清理所有可能遗留的记录 clearRememberedFolder(context) return } + // 写入文件夹 ID 和标题 context.defaultPreferences().edit { putLong(KEY_LAST_FOLDER_ID, folderId) putString(KEY_LAST_FOLDER_TITLE, folderTitle) } } + /** + * 清除已记住的文件夹记录,重置为根文件夹和空标题。 + */ fun clearRememberedFolder(context: Context) { context.defaultPreferences().edit { putLong(KEY_LAST_FOLDER_ID, Notes.ID_ROOT_FOLDER.toLong()) @@ -114,6 +195,10 @@ object NotesPreferences { } } + /** + * 私有方法:对传入的背景颜色 ID 进行边界检查。 + * 若超出合法范围,返回默认背景颜色 ID。 + */ private fun getDefaultBackgroundColorId(colorId: Int): Int { return if (colorId in 0 until ResourceParser.NoteBgResources.resourcesSize) { colorId @@ -121,4 +206,4 @@ object NotesPreferences { ResourceParser.BG_DEFAULT_COLOR } } -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/net/micode/notes/tool/PendingIntentCompat.kt b/app/src/main/kotlin/net/micode/notes/tool/PendingIntentCompat.kt index 48495c0..5d9c4b5 100644 --- a/app/src/main/kotlin/net/micode/notes/tool/PendingIntentCompat.kt +++ b/app/src/main/kotlin/net/micode/notes/tool/PendingIntentCompat.kt @@ -17,10 +17,38 @@ package net.micode.notes.tool import android.app.PendingIntent +/** + * PendingIntent 标志兼容性工具类。 + * + * 从 Android 12 (API 31) 开始,创建 [PendingIntent] 时必须明确指定 + * [PendingIntent.FLAG_IMMUTABLE] 或 [PendingIntent.FLAG_MUTABLE], + * 否则会抛出 IllegalArgumentException。此工具类集中提供不可变标志及其组合, + * 保证应用在任意需要 PendingIntent 的场景(通知、小部件等)都能安全创建。 + */ object PendingIntentCompat { + + /** + * 返回单纯的不可变标志。 + * 使用场景:创建一个全新的 PendingIntent,且其内部 Intent 不需要后续更新。 + * + * @return [PendingIntent.FLAG_IMMUTABLE] 标志值。 + */ fun immutableFlag(): Int = PendingIntent.FLAG_IMMUTABLE + /** + * 返回“更新当前 + 不可变”的组合标志。 + * + * [PendingIntent.FLAG_UPDATE_CURRENT] 表示如果系统中已存在 + * 相同请求码(requestCode)和 Intent 的 PendingIntent, + * 则更新其中的 Extra 数据,保留原 PendingIntent 实例。 + * 与 [PendingIntent.FLAG_IMMUTABLE] 组合后, + * 既能更新内容,又能保证 PendingIntent 不可被外部应用修改。 + * + * 典型场景:更新状态栏通知的进度或内容。 + * + * @return [PendingIntent.FLAG_UPDATE_CURRENT] | [PendingIntent.FLAG_IMMUTABLE] + */ fun updateCurrentImmutableFlag(): Int { return PendingIntent.FLAG_UPDATE_CURRENT or immutableFlag() } -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/net/micode/notes/tool/ResourceParser.kt b/app/src/main/kotlin/net/micode/notes/tool/ResourceParser.kt index ac74412..08151b4 100644 --- a/app/src/main/kotlin/net/micode/notes/tool/ResourceParser.kt +++ b/app/src/main/kotlin/net/micode/notes/tool/ResourceParser.kt @@ -2,48 +2,66 @@ * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) * * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * ... */ package net.micode.notes.tool import android.content.Context -import net.micode.notes.R +import net.micode.notes.R // 资源类,包含 drawable 资源 ID import kotlin.random.Random +/** + * 资源解析工具类。 + * + * 定义便签背景颜色、字体大小的常量,以及对应的 Drawable 资源数组。 + * 提供根据用户偏好(随机 / 固定)获取默认背景 ID 的功能。 + */ object ResourceParser { + // ==================== 背景颜色常量 ==================== const val YELLOW: Int = 0 const val BLUE: Int = 1 const val WHITE: Int = 2 const val GREEN: Int = 3 const val RED: Int = 4 + /** 默认背景颜色:黄色 (0) */ val BG_DEFAULT_COLOR: Int = YELLOW + // ==================== 字体大小常量 ==================== const val TEXT_SMALL: Int = 0 const val TEXT_MEDIUM: Int = 1 const val TEXT_LARGE: Int = 2 const val TEXT_SUPER: Int = 3 + /** 默认字体大小:中等 (1) */ val BG_DEFAULT_FONT_SIZE: Int = TEXT_MEDIUM + /** + * 获取当前应该使用的默认背景颜色 ID。 + * + * 逻辑: + * - 如果用户开启了随机背景,则从 0 到 resourcesSize-1 中随机选取一个。 + * - 否则返回用户在偏好设置中选定的固定默认背景颜色。 + * + * @param context 上下文,用于读取偏好 + * @return 合法背景颜色 ID + */ fun getDefaultBgId(context: Context): Int { return if (NotesPreferences.isRandomBackgroundEnabled(context)) { + // 随机背景开启,生成随机 ID Random.nextInt(NoteBgResources.resourcesSize) } else { + // 使用偏好中保存的固定颜色,该方法内部已做边界校验 NotesPreferences.getDefaultBackgroundColor(context) } } + /** + * 编辑界面使用的背景资源。 + * 包含编辑状态下的背景图和标题栏背景图两个系列。 + */ object NoteBgResources { + /** 编辑界面背景图数组,索引对应颜色 ID */ private val BG_EDIT_RESOURCES = intArrayOf( R.drawable.edit_yellow, R.drawable.edit_blue, @@ -52,6 +70,7 @@ object ResourceParser { R.drawable.edit_red ) + /** 编辑界面标题栏背景图数组,索引对应颜色 ID */ private val BG_EDIT_TITLE_RESOURCES = intArrayOf( R.drawable.edit_title_yellow, R.drawable.edit_title_blue, @@ -60,19 +79,32 @@ object ResourceParser { R.drawable.edit_title_red ) + /** + * 根据颜色 ID 获取编辑界面的背景图资源 ID。 + * 内部调用 [safeIndex] 进行越界保护。 + */ fun getNoteBgResource(id: Int): Int { return BG_EDIT_RESOURCES[safeIndex(id, BG_EDIT_RESOURCES.size)] } + /** + * 根据颜色 ID 获取编辑界面标题栏的背景图资源 ID。 + */ fun getNoteTitleBgResource(id: Int): Int { return BG_EDIT_TITLE_RESOURCES[safeIndex(id, BG_EDIT_TITLE_RESOURCES.size)] } + /** 背景资源数量(可用颜色的总数) */ val resourcesSize: Int get() = BG_EDIT_RESOURCES.size } + /** + * 桌面小部件使用的背景资源。 + * 包含 2x 和 4x 两种规格的背景图数组。 + */ object WidgetBgResources { + /** 2x 小部件背景图数组 */ private val BG_2X_RESOURCES = intArrayOf( R.drawable.widget_2x_yellow, R.drawable.widget_2x_blue, @@ -81,10 +113,14 @@ object ResourceParser { R.drawable.widget_2x_red, ) + /** + * 根据颜色 ID 获取 2x 小部件的背景图资源 ID。 + */ fun getWidget2xBgResource(id: Int): Int { return BG_2X_RESOURCES[safeIndex(id, BG_2X_RESOURCES.size)] } + /** 4x 小部件背景图数组 */ private val BG_4X_RESOURCES = intArrayOf( R.drawable.widget_4x_yellow, R.drawable.widget_4x_blue, @@ -93,12 +129,20 @@ object ResourceParser { R.drawable.widget_4x_red ) + /** + * 根据颜色 ID 获取 4x 小部件的背景图资源 ID。 + */ fun getWidget4xBgResource(id: Int): Int { return BG_4X_RESOURCES[safeIndex(id, BG_4X_RESOURCES.size)] } } + /** + * 安全索引函数。 + * 若传入的 ID 在 [0, size) 范围内则直接返回,否则回退到 [BG_DEFAULT_COLOR](黄色)。 + * 用于防止因数据错误或偏好被篡改导致的数组越界。 + */ private fun safeIndex(id: Int, size: Int): Int { return if (id in 0 until size) id else BG_DEFAULT_COLOR } -} +} \ No newline at end of file