### 需求定义
双击锁屏这个东西需要权限,然后有时候这个东西,比如无障碍权限,有些ROM他可能用不了,所以用户想要自己选择对应的权限方式
#### 枚举数据
展示的这个双击锁屏方式用枚举
‘’’
enum class SleepMode(
@StringRes val labelResourceId: Int, // 每个枚举值带一个字符串资源 ID
) {
AUTO(labelResourceId = R.string.sleep_mode_auto), // “Automatic”
ROOT(labelResourceId = R.string.sleep_mode_root), // “Root”
ACCESSIBILITY(labelResourceId = R.string.sleep_mode_accessibility), // “Accessibility service”
DEVICE_ADMIN(labelResourceId = R.string.sleep_mode_device_admin), // “Device admin”
;

  companion object {
      // 拿所有枚举值,编译器维护
      fun values() = enumValues<SleepMode>().toList()

      // 字符串 → 枚举(DataStore 读出来时用)
      // "DEVICE_ADMIN" → SleepMode.DEVICE_ADMIN
      fun fromString(string: String) = values().firstOrNull { it.toString() == string }

      // 枚举列表 → UI 选项列表(给 ListPreference 用)
      fun entries(): List<ListPreferenceEntry<SleepMode>> = values().map {
          ListPreferenceEntry(value = it) { stringResource(id = it.labelResourceId) }
      }
  }

}
‘’’
enumValues 是内置的这个方法,就拿到这个枚举值 Array 和 enum 自带的 values() 做的事一样 区别是 enumValues() 可以用泛型,适合写通用工具函数
然后toList顺序枚举值的数组
fromString 就能从字符串拿对应的枚举对象
entries 就返回prefEntry,就展示pref能用到的这个item,就映射,返回一个value是这个对象,然后最后一个参数是lambda 的 compose
#### 存储
// 定义一个偏好:sleep_mode,类型是 SleepMode 枚举
‘’’
val sleepMode = preference(
key = stringPreferencesKey(name = “sleep_mode”), // DataStore 里的 key
defaultValue = SleepMode.AUTO, // 没存过时默认 AUTO
parse = { SleepMode.fromString(it) ?: SleepMode.AUTO }, // 读:字符串 → 枚举
save = { it.toString() }, // 写:枚举 → 字符串
)
// DataStore 磁盘上存的是 sleep_mode = “DEVICE_ADMIN” 这样的字符串
// parse/save 负责 字符串 ↔ 枚举 的互转
UI
@Composable
fun GesturePreferences(modifier: Modifier = Modifier) {
val prefs = preferenceManager2()
PreferenceLayout(…) {
// 第一组:手势配置列表(原有的,不动)
PreferenceGroup {
Item { GestureHandlerPreference(adapter = prefs.doubleTapGestureHandler.getAdapter(), …) }
Item { GestureHandlerPreference(adapter = prefs.swipeUpGestureHandler.getAdapter(), …) }
// … 其他手势
}

      // 第二组:我们新加的 Sleep mode 选项
      PreferenceGroup(heading = stringResource(id = R.string.sleep_mode_label)) {
          Item {
              ListPreference(
                  adapter = prefs.sleepMode.getAdapter(),   // adapter:读当前值 + 写新值
                  entries = SleepMode.entries(),              // 4 个选项的列表
                  label = stringResource(id = R.string.sleep_mode_label),  // 标题 "Sleep mode"
              )
              // adapter.state.value = 当前选中的枚举值 → UI 显示对应文字
              // 用户点了新选项 → adapter.onChange(新值) → DataStore 存储 → state 更新 → UI 刷新
          }
      }
  }

}
‘’’
这里用到的就是上面的entry,要用他里面的东西。然后这个adapter就是一个拓展函数,就实际上它是用来减少模板函数和解耦的,就里面写了一个state和onChange修改state,就很像useState这个
‘’’
@Composable
fun ListPreference(
adapter: PreferenceAdapter, // 我们传进来的
entries: List<ListPreferenceEntry>,
label: String,

) {
ListPreference(
entries = entries,
value = adapter.state.value, // 从 adapter 里拿当前值
onValueChange = adapter::onChange, // 从 adapter 里拿写回函数
label = label,

)
}
‘’’
上面就是大概的减少模板代码的这个
静态展示(设置页上那一行)
‘’’
// 计算当前描述文字
val currentDescription = description ?: entries
.firstOrNull { it.value == value } // 找到当前选中的 entry
?.label?.invoke() // 调它的 label lambda 拿文字
// 比如 value = DEVICE_ADMIN → 找到对应 entry → label() → “Device admin”

PreferenceTemplate(
title = { Text(text = label) }, // “Sleep mode”
description = { currentDescription?.let { Text(text = it) } }, // “Device admin”

)
‘’’
然后这个就是具体,用传进来的entry来展示页面
#### 具体业务
‘’’
class SleepGestureHandler(context: Context) : GestureHandler(context) {

  override suspend fun onTrigger(launcher: LawnchairLauncher) {
      // 从 DataStore 读用户选的值
      val pref = PreferenceManager2.getInstance(context).sleepMode.get().first()
      // sleepMode.get() → Flow<SleepMode>
      // .first() → 取当前值,比如 SleepMode.DEVICE_ADMIN

      // 根据用户选择,找到对应的实现类
      val method = when (pref) {
          SleepMode.AUTO -> methods.first { it.isSupported() }
          // AUTO:遍历 methods,找第一个 isSupported()=true 的(原逻辑)

          SleepMode.ROOT -> methods.filterIsInstance<SleepMethodRoot>().first()
          // 从 list 里找 SleepMethodRoot 类型的实例

          SleepMode.ACCESSIBILITY -> methods.filterIsInstance<SleepMethodPieAccessibility>().first()
          SleepMode.DEVICE_ADMIN -> methods.filterIsInstance<SleepMethodDeviceAdmin>().first()
      }

      // 退化逻辑:用户选了具体方式但当前不支持(比如选了 ROOT 但 unroot 了)
      if (pref != SleepMode.AUTO && !method.isSupported()) {
          methods.first { it.isSupported() }.sleep(launcher)  // 退化到自动模式
          return
      }

      // 正常执行
      method.sleep(launcher)
      // SleepMethodRoot.sleep() → Shell 模拟电源键
      // SleepMethodPieAccessibility.sleep() → 无障碍服务锁屏(没开会弹引导)
      // SleepMethodDeviceAdmin.sleep() → DevicePolicyManager 锁屏(没激活会弹引导)
  }

  // 三种息屏实现,原有代码不动
  private val methods = listOf(
      SleepMethodRoot(context),
      SleepMethodPieAccessibility(context),
      SleepMethodDeviceAdmin(context),
  )

  sealed class SleepMethod(protected val context: Context) {
      abstract suspend fun isSupported(): Boolean
      abstract suspend fun sleep(launcher: LawnchairLauncher)
  }

}
‘’’
这里就是双击具体触发,就从pref里面找,auto就找到第一个能用的,其他的就找这个具体类型的

大概总结一下,就是写了个要展示的数据,然后加到pref里面,展示了一下,然后有这个双向绑定的能力。然后双击的时候就拿这个东西,看选的哪个能不能用来锁屏。