LauncherScreen.kt 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. package com.hzliuzhi.applet.container.ui
  2. import android.content.Intent
  3. import android.provider.Settings
  4. import android.util.Patterns
  5. import android.widget.Toast
  6. import androidx.compose.foundation.clickable
  7. import androidx.compose.foundation.layout.Arrangement
  8. import androidx.compose.foundation.layout.Box
  9. import androidx.compose.foundation.layout.Column
  10. import androidx.compose.foundation.layout.Row
  11. import androidx.compose.foundation.layout.Spacer
  12. import androidx.compose.foundation.layout.fillMaxSize
  13. import androidx.compose.foundation.layout.fillMaxWidth
  14. import androidx.compose.foundation.layout.height
  15. import androidx.compose.foundation.layout.padding
  16. import androidx.compose.foundation.layout.size
  17. import androidx.compose.foundation.layout.width
  18. import androidx.compose.foundation.text.KeyboardActions
  19. import androidx.compose.foundation.text.KeyboardOptions
  20. import androidx.compose.material.icons.Icons
  21. import androidx.compose.material.icons.automirrored.filled.Send
  22. import androidx.compose.material.icons.filled.Info
  23. import androidx.compose.material.icons.filled.QrCode
  24. import androidx.compose.material3.Card
  25. import androidx.compose.material3.Icon
  26. import androidx.compose.material3.MaterialTheme
  27. import androidx.compose.material3.OutlinedTextField
  28. import androidx.compose.material3.Surface
  29. import androidx.compose.material3.Switch
  30. import androidx.compose.material3.Text
  31. import androidx.compose.runtime.Composable
  32. import androidx.compose.runtime.DisposableEffect
  33. import androidx.compose.runtime.getValue
  34. import androidx.compose.runtime.mutableStateOf
  35. import androidx.compose.runtime.remember
  36. import androidx.compose.runtime.setValue
  37. import androidx.compose.ui.Alignment
  38. import androidx.compose.ui.Modifier
  39. import androidx.compose.ui.graphics.Color
  40. import androidx.compose.ui.platform.LocalContext
  41. import androidx.compose.ui.platform.LocalSoftwareKeyboardController
  42. import androidx.compose.ui.text.input.ImeAction
  43. import androidx.compose.ui.text.input.KeyboardType
  44. import androidx.compose.ui.text.style.TextDecoration
  45. import androidx.compose.ui.unit.dp
  46. import androidx.core.net.toUri
  47. import androidx.lifecycle.compose.LocalLifecycleOwner
  48. import com.hzliuzhi.applet.core.store.SettingStore
  49. import com.hzliuzhi.applet.scanner.ScanResult
  50. import com.hzliuzhi.applet.scanner.Scanner
  51. @Composable
  52. fun LauncherScreen(
  53. modifier: Modifier = Modifier,
  54. start: (String) -> String?,
  55. ) {
  56. val context = LocalContext.current
  57. var text by remember { mutableStateOf("") }
  58. var storeEnabled by remember { mutableStateOf(true) }
  59. Column(
  60. modifier = modifier
  61. .fillMaxSize()
  62. .padding(24.dp),
  63. verticalArrangement = Arrangement.Center
  64. ) {
  65. LauncherInputField(
  66. value = text,
  67. onValueChange = { text = it },
  68. onDone = {
  69. start(text)?.also {
  70. SettingStore.getInstance(context).screen = if (storeEnabled) it else null
  71. }
  72. },
  73. )
  74. Spacer(modifier = Modifier.height(24.dp))
  75. StorageCard(
  76. enabled = storeEnabled,
  77. enabledChange = { storeEnabled = it }
  78. )
  79. }
  80. }
  81. @Composable
  82. fun LauncherInputField(
  83. value: String,
  84. onValueChange: (String) -> Unit,
  85. onDone: () -> Unit,
  86. modifier: Modifier = Modifier,
  87. ) {
  88. val context = LocalContext.current
  89. val scanner = Scanner.getInstance(context)
  90. val owner = LocalLifecycleOwner.current
  91. DisposableEffect(owner, scanner) {
  92. val observer: (ScanResult?) -> Unit = { result -> result?.code?.also { onValueChange(it) } }
  93. scanner.observe(owner, observer)
  94. onDispose { scanner.removeObserver(observer) }
  95. }
  96. val isUrl = remember(value) { Patterns.WEB_URL.matcher(value).matches() }
  97. val keyboardController = LocalSoftwareKeyboardController.current
  98. fun done() {
  99. if (isUrl) {
  100. keyboardController?.hide()
  101. onDone()
  102. } else Toast.makeText(context, "请输入合法的网址", Toast.LENGTH_SHORT).show()
  103. }
  104. OutlinedTextField(
  105. value = value,
  106. onValueChange = onValueChange,
  107. label = { Text("请输入URL") },
  108. singleLine = true,
  109. isError = value.isNotBlank() && !isUrl,
  110. modifier = modifier.fillMaxWidth(),
  111. keyboardOptions = KeyboardOptions(
  112. keyboardType = KeyboardType.Uri,
  113. imeAction = ImeAction.Done
  114. ),
  115. keyboardActions = KeyboardActions(onDone = { done() }),
  116. trailingIcon = {
  117. if (value.isBlank()) {
  118. Icon(
  119. imageVector = Icons.Filled.QrCode,
  120. contentDescription = "扫码",
  121. modifier = Modifier.clickable {
  122. scanner.start()
  123. keyboardController?.hide()
  124. }
  125. )
  126. } else {
  127. Icon(
  128. imageVector = Icons.AutoMirrored.Filled.Send,
  129. contentDescription = "完成",
  130. modifier = Modifier.clickable { done() }
  131. )
  132. }
  133. }
  134. )
  135. }
  136. @Composable
  137. fun StorageCard(
  138. enabled: Boolean,
  139. enabledChange: (Boolean) -> Unit,
  140. ) {
  141. val context = LocalContext.current
  142. val appName = runCatching {
  143. val info = context.applicationInfo
  144. info.labelRes.takeIf { it != 0 }?.let { context.getString(it) } ?: info.nonLocalizedLabel?.toString()
  145. }.getOrNull() ?: "本App"
  146. Card(
  147. modifier = Modifier.fillMaxWidth(),
  148. shape = MaterialTheme.shapes.medium,
  149. ) {
  150. Column(
  151. modifier = Modifier.padding(start = 12.dp, end = 12.dp)
  152. ) {
  153. // 头部:标题+开关
  154. Row(
  155. modifier = Modifier.fillMaxWidth(),
  156. verticalAlignment = Alignment.CenterVertically
  157. ) {
  158. Text(
  159. text = "存储数据",
  160. style = MaterialTheme.typography.titleMedium,
  161. modifier = Modifier.weight(1f)
  162. )
  163. Switch(
  164. checked = enabled,
  165. onCheckedChange = enabledChange
  166. )
  167. }
  168. if (enabled) {
  169. Spacer(modifier = Modifier.height(8.dp))
  170. Row(verticalAlignment = Alignment.CenterVertically) {
  171. Icon(
  172. imageVector = Icons.Default.Info,
  173. contentDescription = null,
  174. tint = MaterialTheme.colorScheme.secondary
  175. )
  176. Spacer(modifier = Modifier.width(8.dp))
  177. Text(
  178. text = "如需清除缓存,请在",
  179. style = MaterialTheme.typography.bodyMedium,
  180. color = MaterialTheme.colorScheme.secondary
  181. )
  182. Text(
  183. text = "设置中操作",
  184. style = MaterialTheme.typography.bodyMedium.copy(color = Color(0xFF1976D2), textDecoration = TextDecoration.Underline),
  185. modifier = Modifier
  186. .clickable {
  187. val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
  188. data = ("package:" + context.packageName).toUri()
  189. }
  190. context.startActivity(intent)
  191. }
  192. )
  193. }
  194. Spacer(modifier = Modifier.height(12.dp))
  195. Text(
  196. text = "清除数据:",
  197. style = MaterialTheme.typography.bodyMedium,
  198. color = MaterialTheme.colorScheme.primary
  199. )
  200. Spacer(modifier = Modifier.height(8.dp))
  201. Column {
  202. val steps = listOf(
  203. "打开系统设置",
  204. "找到应用管理",
  205. "选择$appName",
  206. "进入存储与缓存",
  207. "点击清除全部数据(存储空间)"
  208. )
  209. steps.forEachIndexed { idx, step ->
  210. Row(verticalAlignment = Alignment.CenterVertically) {
  211. Surface(
  212. shape = MaterialTheme.shapes.small,
  213. color = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f),
  214. modifier = Modifier.size(24.dp)
  215. ) {
  216. Box(contentAlignment = Alignment.Center) {
  217. Text("${idx + 1}", style = MaterialTheme.typography.labelMedium)
  218. }
  219. }
  220. Spacer(modifier = Modifier.width(8.dp))
  221. Text(step, style = MaterialTheme.typography.bodySmall)
  222. }
  223. if (idx < steps.lastIndex) Spacer(modifier = Modifier.height(6.dp))
  224. }
  225. }
  226. Spacer(modifier = Modifier.height(8.dp))
  227. }
  228. }
  229. }
  230. }