11package com.appcontrolx.ui
22
3+ import android.content.pm.PackageManager
34import android.os.Bundle
45import android.view.LayoutInflater
56import android.view.View
@@ -11,9 +12,12 @@ import com.appcontrolx.R
1112import com.appcontrolx.databinding.BottomSheetBatchProgressBinding
1213import com.appcontrolx.databinding.ItemBatchAppBinding
1314import com.google.android.material.bottomsheet.BottomSheetDialogFragment
15+ import kotlinx.coroutines.Dispatchers
1416import kotlinx.coroutines.Job
1517import kotlinx.coroutines.delay
1618import kotlinx.coroutines.launch
19+ import kotlinx.coroutines.withContext
20+ import java.io.File
1721
1822class BatchProgressBottomSheet : BottomSheetDialogFragment () {
1923
@@ -23,6 +27,7 @@ class BatchProgressBottomSheet : BottomSheetDialogFragment() {
2327 private var actionName = " "
2428 private var appNames = listOf<String >()
2529 private var packageNames = listOf<String >()
30+ private var showCacheSize = false
2631 private var onExecute: (suspend (String ) -> Result <Unit >)? = null
2732 private var onComplete: (() -> Unit )? = null
2833
@@ -32,7 +37,6 @@ class BatchProgressBottomSheet : BottomSheetDialogFragment() {
3237
3338 companion object {
3439 const val TAG = " BatchProgressBottomSheet"
35- private const val COUNTDOWN_SECONDS = 3
3640
3741 fun newInstance (
3842 actionName : String ,
@@ -45,6 +49,7 @@ class BatchProgressBottomSheet : BottomSheetDialogFragment() {
4549 this .actionName = actionName
4650 this .appNames = appNames
4751 this .packageNames = packageNames
52+ this .showCacheSize = actionName == " CLEAR_CACHE" || actionName == " CLEAR_DATA"
4853 this .onExecute = onExecute
4954 this .onComplete = onComplete
5055 }
@@ -59,7 +64,7 @@ class BatchProgressBottomSheet : BottomSheetDialogFragment() {
5964 override fun onViewCreated (view : View , savedInstanceState : Bundle ? ) {
6065 super .onViewCreated(view, savedInstanceState)
6166 setupUI()
62- startCountdown ()
67+ initListThenExecute ()
6368 }
6469
6570 private fun setupUI () {
@@ -68,12 +73,10 @@ class BatchProgressBottomSheet : BottomSheetDialogFragment() {
6873 b.tvAction.text = actionName.replace(" _" , " " )
6974 b.progressBar.max = packageNames.size
7075 b.progressBar.progress = 0
71- b.tvProgress.text = getString(R .string.batch_waiting)
76+ b.tvProgress.text = getString(R .string.batch_preparing)
77+ b.tvCountdown.text = " "
7278
73- // Setup RecyclerView with app list
74- adapter.submitList(appNames.mapIndexed { index, name ->
75- BatchAppItem (name, packageNames[index], BatchStatus .PENDING )
76- })
79+ // Setup RecyclerView
7780 b.recyclerView.layoutManager = LinearLayoutManager (context)
7881 b.recyclerView.adapter = adapter
7982
@@ -84,20 +87,70 @@ class BatchProgressBottomSheet : BottomSheetDialogFragment() {
8487 }
8588 }
8689
87- private fun startCountdown () {
90+ private fun initListThenExecute () {
8891 executionJob = lifecycleScope.launch {
89- // Countdown
90- for (i in COUNTDOWN_SECONDS downTo 1 ) {
91- if (isCancelled) return @launch
92- binding?.tvCountdown?.text = getString(R .string.batch_countdown, i)
93- delay(1000 )
92+ // Init list first (with cache size if needed)
93+ val items = withContext(Dispatchers .IO ) {
94+ appNames.mapIndexed { index, name ->
95+ val pkg = packageNames[index]
96+ val sizeInfo = if (showCacheSize) getCacheSize(pkg) else null
97+ BatchAppItem (name, pkg, BatchStatus .PENDING , sizeInfo)
98+ }
9499 }
95100
96- binding?.tvCountdown?.text = " "
101+ if (isCancelled) return @launch
102+
103+ adapter.submitList(items)
104+
105+ // Small delay for UI to render list
106+ delay(100 )
107+
108+ // Execute immediately
97109 executeActions()
98110 }
99111 }
100112
113+ private fun getCacheSize (packageName : String ): String? {
114+ return try {
115+ val pm = requireContext().packageManager
116+ val appInfo = pm.getApplicationInfo(packageName, 0 )
117+ val dataDir = File (appInfo.dataDir)
118+ val cacheDir = File (dataDir, " cache" )
119+
120+ val cacheSize = if (cacheDir.exists()) getFolderSize(cacheDir) else 0L
121+ val totalSize = getFolderSize(dataDir)
122+
123+ if (actionName == " CLEAR_CACHE" ) {
124+ formatSize(cacheSize)
125+ } else {
126+ formatSize(totalSize)
127+ }
128+ } catch (e: Exception ) {
129+ null
130+ }
131+ }
132+
133+ private fun getFolderSize (dir : File ): Long {
134+ var size = 0L
135+ try {
136+ dir.walkTopDown().forEach { file ->
137+ if (file.isFile) size + = file.length()
138+ }
139+ } catch (e: Exception ) {
140+ // Permission denied or other error
141+ }
142+ return size
143+ }
144+
145+ private fun formatSize (bytes : Long ): String {
146+ return when {
147+ bytes < 1024 -> " $bytes B"
148+ bytes < 1024 * 1024 -> " ${bytes / 1024 } KB"
149+ bytes < 1024 * 1024 * 1024 -> String .format(" %.1f MB" , bytes / (1024.0 * 1024.0 ))
150+ else -> String .format(" %.2f GB" , bytes / (1024.0 * 1024.0 * 1024.0 ))
151+ }
152+ }
153+
101154 private suspend fun executeActions () {
102155 val b = binding ? : return
103156 var successCount = 0
@@ -108,8 +161,9 @@ class BatchProgressBottomSheet : BottomSheetDialogFragment() {
108161
109162 b.tvProgress.text = getString(R .string.batch_progress, index + 1 , packageNames.size)
110163
111- // Update status to processing
164+ // Update status to processing & auto scroll
112165 adapter.updateStatus(index, BatchStatus .PROCESSING )
166+ b.recyclerView.smoothScrollToPosition(index)
113167
114168 val result = onExecute?.invoke(pkg) ? : Result .failure(Exception (" No executor" ))
115169
@@ -122,7 +176,7 @@ class BatchProgressBottomSheet : BottomSheetDialogFragment() {
122176 }
123177
124178 b.progressBar.progress = index + 1
125- delay(100 ) // Small delay for UI
179+ delay(50 ) // Small delay for UI
126180 }
127181
128182 // Complete
@@ -149,7 +203,8 @@ class BatchProgressBottomSheet : BottomSheetDialogFragment() {
149203 data class BatchAppItem (
150204 val appName : String ,
151205 val packageName : String ,
152- var status : BatchStatus
206+ var status : BatchStatus ,
207+ val sizeInfo : String? = null
153208 )
154209
155210 // Adapter
@@ -182,7 +237,12 @@ class BatchProgressBottomSheet : BottomSheetDialogFragment() {
182237
183238 inner class ViewHolder (private val binding : ItemBatchAppBinding ) : RecyclerView.ViewHolder(binding.root) {
184239 fun bind (item : BatchAppItem ) {
185- binding.tvAppName.text = item.appName
240+ // Show app name with size info if available
241+ binding.tvAppName.text = if (item.sizeInfo != null && item.status == BatchStatus .PENDING ) {
242+ " ${item.appName} (${item.sizeInfo} )"
243+ } else {
244+ item.appName
245+ }
186246
187247 val (statusText, statusColor) = when (item.status) {
188248 BatchStatus .PENDING -> " -" to R .color.on_surface_secondary
0 commit comments