@@ -29,25 +29,35 @@ import com.omarea.krscript.ui.ParamsFileChooserRender
2929import com.omarea.krscript.ui.PageMenuLoader
3030import com.tool.tree.databinding.ActivityActionPageBinding
3131import kotlinx.coroutines.Dispatchers
32+ import kotlinx.coroutines.Job
33+ import kotlinx.coroutines.async
34+ import kotlinx.coroutines.awaitAll
35+ import kotlinx.coroutines.coroutineScope
3236import kotlinx.coroutines.isActive
3337import kotlinx.coroutines.launch
3438import kotlinx.coroutines.withContext
35- import kotlinx.coroutines.async
36- import kotlinx.coroutines.awaitAll
3739
3840class ActionPage : AppCompatActivity () {
3941 private val progressBarDialog by lazy { ProgressBarDialog (this ) }
4042 private var actionsLoaded = false
4143 private val handler = Handler (Looper .getMainLooper())
42-
44+
4345 private var currentPageConfig: PageNode ? = null
4446 private var autoRunItemId = " "
4547 private lateinit var binding: ActivityActionPageBinding
4648 private var openedSubPage = false
4749
48- // Sử dụng HashCode của String ID để khóa trạng thái tạm thời, không sợ lệch index
50+ // Khóa theo ID thật của item để không lệ thuộc vào vị trí trong mảng
4951 private val justClickedItemIds = HashSet <Int >()
5052
53+ private var fileSelectedInterface: ParamsFileChooserRender .FileSelectedInterface ? = null
54+ private val ACTION_FILE_PATH_CHOOSER = 65400
55+ private val ACTION_FILE_PATH_CHOOSER_INNER = 65300
56+
57+ private var menuOptions: ArrayList <PageMenuOption >? = null
58+ private var menuCheckboxRefreshing = false
59+ private var checkboxRefreshJob: Job ? = null
60+
5161 override fun onCreate (savedInstanceState : Bundle ? ) {
5262 super .onCreate(savedInstanceState)
5363
@@ -83,7 +93,7 @@ class ActionPage : AppCompatActivity() {
8393 } else if (extras.containsKey(" shortcutId" )) {
8494 ActionShortcutManager (this ).getShortcutTarget(extras.getString(" shortcutId" ) ? : " " )
8595 } else null
86-
96+
8797 autoRunItemId = extras.getString(" autoRunItemId" , " " )
8898 }
8999
@@ -149,13 +159,6 @@ class ActionPage : AppCompatActivity() {
149159 }
150160 }
151161
152- private var fileSelectedInterface: ParamsFileChooserRender .FileSelectedInterface ? = null
153- private val ACTION_FILE_PATH_CHOOSER = 65400
154- private val ACTION_FILE_PATH_CHOOSER_INNER = 65300
155-
156- private var menuOptions: ArrayList <PageMenuOption >? = null
157- private var menuCheckboxRefreshing = false
158-
159162 override fun onCreateOptionsMenu (menu : Menu ? ): Boolean {
160163 val config = currentPageConfig ? : return false
161164 if (menuOptions == null ) {
@@ -168,7 +171,6 @@ class ActionPage : AppCompatActivity() {
168171 if (option.isFab) {
169172 addFab(option)
170173 } else {
171- // ĐỔI: Sử dụng hashCode của chuỗi ID duy nhất (option.key hoặc option.id) làm itemId cho Menu
172174 val uniqueItemId = option.key.hashCode()
173175 val menuItem = menu?.add(Menu .NONE , uniqueItemId, Menu .NONE , option.title)
174176 if (option.type == " checkbox" ) {
@@ -180,58 +182,72 @@ class ActionPage : AppCompatActivity() {
180182 }
181183
182184 override fun onPrepareOptionsMenu (menu : Menu ): Boolean {
183- // Tìm và gán chính xác trạng thái checkbox theo uniqueId
185+ // Chỉ cập nhật trạng thái hiển thị từ dữ liệu hiện có, không chạy shell ở đây
184186 menuOptions?.forEach { option ->
185187 if (! option.isFab && option.type == " checkbox" ) {
186188 val uniqueItemId = option.key.hashCode()
187189 val menuItem = menu.findItem(uniqueItemId)
188190 menuItem?.isChecked = option.checked
189191 }
190192 }
193+
194+ // Vẫn giữ hành vi cũ: menu sẵn sàng thì refresh checkbox
191195 refreshCheckboxMenuStates()
192196 return super .onPrepareOptionsMenu(menu)
193197 }
194198
195199 private fun refreshCheckboxMenuStates () {
196200 val config = currentPageConfig ? : return
197-
198- // Lọc danh sách các checkbox hợp lệ
199- val checkboxOptions = menuOptions?.filter { option ->
201+
202+ val checkboxOptions = menuOptions?.filter { option ->
200203 val uniqueItemId = option.key.hashCode()
201- option.type == " checkbox" && option.checkedSh.isNotEmpty() && ! justClickedItemIds.contains(uniqueItemId)
202- }
203-
204- if (checkboxOptions.isNullOrEmpty() || menuCheckboxRefreshing) return
204+ option.type == " checkbox" &&
205+ option.checkedSh.isNotEmpty() &&
206+ ! justClickedItemIds.contains(uniqueItemId)
207+ }.orEmpty()
208+
209+ if (checkboxOptions.isEmpty() || menuCheckboxRefreshing) return
210+
205211 menuCheckboxRefreshing = true
206-
207- lifecycleScope.launch(Dispatchers .IO ) {
208- // TỐI ƯU SONG SONG: Sử dụng async để phát TẤT CẢ các lệnh getprop đi CÙNG MỘT LÚC
209- val deferredResults = checkboxOptions.map { option ->
210- async {
211- val result = ScriptEnvironmen .executeResultRoot(this @ActionPage, option.checkedSh, config)?.trim()
212- option to result
212+ checkboxRefreshJob?.cancel()
213+
214+ checkboxRefreshJob = lifecycleScope.launch(Dispatchers .IO ) {
215+ try {
216+ // Chạy song song thay vì chạy nối tiếp từng item
217+ val results = coroutineScope {
218+ checkboxOptions.map { option ->
219+ async {
220+ val result = ScriptEnvironmen .executeResultRoot(
221+ this @ActionPage,
222+ option.checkedSh,
223+ config
224+ )?.trim()
225+ option to result
226+ }
227+ }.awaitAll()
213228 }
214- }
215-
216- // Đợi tất cả các ô trả về kết quả đầy đủ rồi mới xử lý tiếp
217- val results = deferredResults.awaitAll()
218-
219- withContext(Dispatchers .Main ) {
220- menuCheckboxRefreshing = false
221- var changed = false
222-
223- results.forEach { (option, result) ->
224- val uniqueItemId = option.key.hashCode()
225- if (! justClickedItemIds.contains(uniqueItemId)) {
226- val newChecked = result == " 1" || result?.lowercase() == " true"
227- if (option.checked != newChecked) changed = true
228- option.checked = newChecked
229+
230+ withContext(Dispatchers .Main ) {
231+ if (isFinishing || isDestroyed) return @withContext
232+
233+ var changed = false
234+ results.forEach { (option, result) ->
235+ val uniqueItemId = option.key.hashCode()
236+
237+ if (! justClickedItemIds.contains(uniqueItemId)) {
238+ val newChecked = result == " 1" || result.equals(" true" , ignoreCase = true )
239+ if (option.checked != newChecked) changed = true
240+ option.checked = newChecked
241+ }
242+ }
243+
244+ if (changed) {
245+ invalidateOptionsMenu()
229246 }
230247 }
231-
232- // Chỉ vẽ lại và kích hoạt luồng reload khi TẤT CẢ dữ liệu đã sẵn sàng
233- if (changed && ! isFinishing) {
234- invalidateOptionsMenu()
248+ } finally {
249+ withContext(Dispatchers .Main ) {
250+ menuCheckboxRefreshing = false
235251 }
236252 }
237253 }
@@ -242,26 +258,29 @@ class ActionPage : AppCompatActivity() {
242258 visibility = View .VISIBLE
243259 setOnClickListener { onMenuItemClick(menuOption) }
244260
245- val iconRes = if (menuOption.type == " file" && menuOption.iconPath.isEmpty()) R .drawable.kr_folder else R .drawable.kr_fab
246- val customIcon = if (menuOption.iconPath.isNotEmpty()) IconPathAnalysis ().loadLogo(context, menuOption, false ) else null
247-
261+ val iconRes = if (menuOption.type == " file" && menuOption.iconPath.isEmpty()) {
262+ R .drawable.kr_folder
263+ } else {
264+ R .drawable.kr_fab
265+ }
266+ val customIcon = if (menuOption.iconPath.isNotEmpty()) {
267+ IconPathAnalysis ().loadLogo(context, menuOption, false )
268+ } else null
269+
248270 setImageDrawable(customIcon ? : ContextCompat .getDrawable(context, iconRes))
249271 }
250272 }
251273
252274 override fun onOptionsItemSelected (item : MenuItem ): Boolean {
253275 val options = menuOptions ? : return false
254- val targetItemId = item.itemId // Đây chính là hashCode của checkbox vừa click
255-
256- // ĐỔI: Tìm option khớp với hashCode của ID thay vì dùng vị trí mảng index
276+ val targetItemId = item.itemId
257277 val option = options.find { it.key.hashCode() == targetItemId }
258-
278+
259279 if (option != null ) {
260280 if (option.type == " checkbox" ) {
261281 option.checked = ! option.checked
262282 item.isChecked = option.checked
263283
264- // Khóa đồng bộ theo ID cụ thể
265284 justClickedItemIds.add(targetItemId)
266285 handler.postDelayed({
267286 justClickedItemIds.remove(targetItemId)
@@ -277,12 +296,13 @@ class ActionPage : AppCompatActivity() {
277296 private fun onMenuItemClick (menuOption : PageMenuOption ) {
278297 if (menuOption.link.isNotEmpty() || menuOption.activity.isNotEmpty() ||
279298 menuOption.onlineHtmlPage.isNotEmpty() || menuOption.pageConfigSh.isNotEmpty() ||
280- menuOption.pageConfigPath.isNotEmpty()) {
299+ menuOption.pageConfigPath.isNotEmpty()
300+ ) {
281301 openMenuOptionAsPage(menuOption)
282302 return
283303 }
284304
285- when (menuOption.type) {
305+ when (menuOption.type) {
286306 " refresh" , " reload" -> recreate()
287307 " restart" -> restartApp()
288308 " exit" , " finish" , " close" -> finish()
@@ -337,7 +357,7 @@ class ActionPage : AppCompatActivity() {
337357 if (! isActive) return @launch
338358
339359 withContext(Dispatchers .Main ) {
340- if (isFinishing) return @withContext
360+ if (isFinishing || isDestroyed ) return @withContext
341361 when {
342362 menuOption.autoFinish -> finish()
343363 menuOption.reloadPage -> recreate()
@@ -356,12 +376,16 @@ class ActionPage : AppCompatActivity() {
356376
357377 lifecycleScope.launch(Dispatchers .IO ) {
358378 if (config.beforeRead.isNotEmpty()) {
359- withContext(Dispatchers .Main ) { progressBarDialog.showDialog(getString(R .string.kr_page_before_load)) }
379+ withContext(Dispatchers .Main ) {
380+ progressBarDialog.showDialog(getString(R .string.kr_page_before_load))
381+ }
360382 ScriptEnvironmen .executeResultRoot(this @ActionPage, config.beforeRead, config)
361383 }
362384
363385 if (showLoading) {
364- withContext(Dispatchers .Main ) { progressBarDialog.showDialog(getString(R .string.kr_page_loading)) }
386+ withContext(Dispatchers .Main ) {
387+ progressBarDialog.showDialog(getString(R .string.kr_page_loading))
388+ }
365389 }
366390
367391 var items: ArrayList <NodeInfoBase >? = null
@@ -378,12 +402,13 @@ class ActionPage : AppCompatActivity() {
378402
379403 withContext(Dispatchers .Main ) {
380404 if (! isActive || isFinishing) return @withContext
381-
405+
382406 if (items != null && items.isNotEmpty()) {
383407 if (config.loadSuccess.isNotEmpty()) {
384408 ScriptEnvironmen .executeResultRoot(this @ActionPage, config.loadSuccess, config)
385409 }
386410 updateActionList(items, showLoading)
411+ refreshCheckboxMenuStates()
387412 } else {
388413 handleLoadError(config)
389414 }
@@ -432,7 +457,9 @@ class ActionPage : AppCompatActivity() {
432457 }
433458
434459 private fun killApp () {
435- startService(Intent (this , WakeLockService ::class .java).apply { action = WakeLockService .ACTION_END_WAKELOCK })
460+ startService(Intent (this , WakeLockService ::class .java).apply {
461+ action = WakeLockService .ACTION_END_WAKELOCK
462+ })
436463 finishAffinity()
437464 System .exit(0 )
438465 }
@@ -445,9 +472,21 @@ class ActionPage : AppCompatActivity() {
445472 menuOption.autoKill -> killApp()
446473 menuOption.autoRestart -> restartApp()
447474 }
475+
476+ if (menuOption.type == " checkbox" ) {
477+ refreshCheckboxMenuStates()
478+ }
448479 }
480+
449481 val config = currentPageConfig ? : return
450- val dialog = DialogLogFragment .create(menuOption, {}, onDismiss, config.pageHandlerSh, params, ThemeModeState .getThemeMode().isDarkMode)
482+ val dialog = DialogLogFragment .create(
483+ menuOption,
484+ {},
485+ onDismiss,
486+ config.pageHandlerSh,
487+ params,
488+ ThemeModeState .getThemeMode().isDarkMode
489+ )
451490 dialog.show(supportFragmentManager, " " )
452491 dialog.isCancelable = false
453492 }
@@ -457,27 +496,48 @@ class ActionPage : AppCompatActivity() {
457496 override fun onFileSelected (path : String? ) {
458497 path?.let {
459498 handler.post {
460- menuItemExecute(menuOption, hashMapOf(" state" to menuOption.key, " menu_id" to menuOption.key, " file" to it, " folder" to it))
499+ menuItemExecute(
500+ menuOption,
501+ hashMapOf(
502+ " state" to menuOption.key,
503+ " menu_id" to menuOption.key,
504+ " file" to it,
505+ " folder" to it
506+ )
507+ )
461508 }
462509 }
463510 }
511+
464512 override fun mimeType () = menuOption.mime.ifEmpty { null }
465513 override fun suffix () = menuOption.suffix.ifEmpty { null }
466- override fun type () = if (menuOption.type == " folder" ) ParamsFileChooserRender .FileSelectedInterface .TYPE_FOLDER else ParamsFileChooserRender .FileSelectedInterface .TYPE_FILE
514+ override fun type () = if (menuOption.type == " folder" ) {
515+ ParamsFileChooserRender .FileSelectedInterface .TYPE_FOLDER
516+ } else {
517+ ParamsFileChooserRender .FileSelectedInterface .TYPE_FILE
518+ }
467519 })
468520 }
469521
470522 private fun chooseFilePath (fileSelectedInterface : ParamsFileChooserRender .FileSelectedInterface ): Boolean {
471523 return try {
472524 if (fileSelectedInterface.type() == ParamsFileChooserRender .FileSelectedInterface .TYPE_FOLDER ) {
473- startActivityForResult(Intent (this , ActivityFileSelector ::class .java).apply { putExtra(" mode" , ActivityFileSelector .MODE_FOLDER ) }, ACTION_FILE_PATH_CHOOSER_INNER )
525+ startActivityForResult(
526+ Intent (this , ActivityFileSelector ::class .java).apply {
527+ putExtra(" mode" , ActivityFileSelector .MODE_FOLDER )
528+ },
529+ ACTION_FILE_PATH_CHOOSER_INNER
530+ )
474531 } else {
475532 val suffix = fileSelectedInterface.suffix()
476533 if (! suffix.isNullOrEmpty()) {
477- startActivityForResult(Intent (this , ActivityFileSelector ::class .java).apply {
478- putExtra(" extension" , suffix)
479- putExtra(" mode" , ActivityFileSelector .MODE_FILE )
480- }, ACTION_FILE_PATH_CHOOSER_INNER )
534+ startActivityForResult(
535+ Intent (this , ActivityFileSelector ::class .java).apply {
536+ putExtra(" extension" , suffix)
537+ putExtra(" mode" , ActivityFileSelector .MODE_FILE )
538+ },
539+ ACTION_FILE_PATH_CHOOSER_INNER
540+ )
481541 } else {
482542 val intent = Intent (Intent .ACTION_GET_CONTENT ).apply {
483543 type = fileSelectedInterface.mimeType() ? : " */*"
@@ -488,7 +548,9 @@ class ActionPage : AppCompatActivity() {
488548 }
489549 this .fileSelectedInterface = fileSelectedInterface
490550 true
491- } catch (_: Exception ) { false }
551+ } catch (_: Exception ) {
552+ false
553+ }
492554 }
493555
494556 override fun onActivityResult (requestCode : Int , resultCode : Int , data : Intent ? ) {
@@ -524,6 +586,7 @@ class ActionPage : AppCompatActivity() {
524586 }
525587
526588 override fun onDestroy () {
589+ checkboxRefreshJob?.cancel()
527590 handler.removeCallbacksAndMessages(null )
528591 setExcludeFromRecents()
529592 super .onDestroy()
0 commit comments