@@ -28,6 +28,7 @@ import app.simple.inure.viewmodels.viewers.PermissionsViewModel
2828import com.anggrayudi.storage.extension.postToUi
2929import com.topjohnwu.superuser.Shell
3030import kotlinx.coroutines.Dispatchers
31+ import kotlinx.coroutines.flow.collectLatest
3132import kotlinx.coroutines.launch
3233import kotlinx.coroutines.withContext
3334import rikka.shizuku.Shizuku
@@ -64,77 +65,119 @@ class Permissions : SearchBarScopedFragment() {
6465 override fun onViewCreated (view : View , savedInstanceState : Bundle ? ) {
6566 super .onViewCreated(view, savedInstanceState)
6667
67- permissionsViewModel.getPermissions().observe(viewLifecycleOwner) { permissionInfos ->
68- adapterPermissions = AdapterPermissions (permissionInfos, searchBox.text.toString().trim(), isPackageInstalled)
69- setCount(permissionInfos.size)
70-
71- adapterPermissions.setOnPermissionCallbacksListener(object : AdapterPermissions .Companion .PermissionCallbacks {
72- override fun onPermissionClicked (container : View , permissionInfo : PermissionInfo , position : Int ) {
73- childFragmentManager.showPermissionStatus(packageInfo, permissionInfo)
74- .setOnPermissionStatusCallbackListener(object : PermissionStatus .Companion .PermissionStatusCallbacks {
75- override fun onSuccess (grantedStatus : Boolean ) {
76- adapterPermissions.permissionStatusChanged(position, if (grantedStatus) 1 else 0 )
77- }
78- })
68+ viewLifecycleOwner.lifecycleScope.launch {
69+ permissionsViewModel.permissions.collectLatest { permissionInfos ->
70+ if (permissionInfos.isEmpty() && ! ::adapterPermissions.isInitialized) {
71+ return @collectLatest
7972 }
8073
81- override fun onPermissionSwitchClicked (checked : Boolean , permissionInfo : PermissionInfo , position : Int ) {
82- viewLifecycleOwner.lifecycleScope.launch(Dispatchers .IO ) {
83- val mode = if (checked) " grant" else " revoke"
74+ if (! ::adapterPermissions.isInitialized) {
75+ adapterPermissions = AdapterPermissions (permissionInfos, searchBox.text.toString().trim(), isPackageInstalled)
8476
85- if (ConfigurationPreferences .isUsingRoot()) {
86- kotlin.runCatching {
87- Shell .cmd(" pm $mode ${packageInfo.packageName} ${permissionInfo.name} " ).exec().let {
88- if (it.isSuccess) {
89- withContext(Dispatchers .Main ) {
90- adapterPermissions.permissionStatusChanged(position, if (permissionInfo.isGranted == 1 ) 0 else 1 )
77+ adapterPermissions.setOnPermissionCallbacksListener(object : AdapterPermissions .Companion .PermissionCallbacks {
78+ override fun onPermissionClicked (container : View , permissionInfo : PermissionInfo , position : Int ) {
79+ childFragmentManager.showPermissionStatus(packageInfo, permissionInfo)
80+ .setOnPermissionStatusCallbackListener(object : PermissionStatus .Companion .PermissionStatusCallbacks {
81+ override fun onSuccess (grantedStatus : Boolean ) {
82+ // Record the expected change
83+ val expectedStatus = if (grantedStatus) 1 else 0
84+ permissionsViewModel.recordPermissionChangeRequest(permissionInfo.name, position, expectedStatus)
85+
86+ // Optimistically update UI
87+ adapterPermissions.permissionStatusChanged(position, expectedStatus)
88+
89+ // Schedule a delayed refresh to verify the change
90+ viewLifecycleOwner.lifecycleScope.launch {
91+ permissionsViewModel.refreshPermissionStatus(permissionInfo.name, position)
9192 }
92- } else {
93+ }
94+ })
95+ }
96+
97+ override fun onPermissionSwitchClicked (checked : Boolean , permissionInfo : PermissionInfo , position : Int ) {
98+ val expectedStatus = if (checked) 1 else 0
99+
100+ // Record the expected change
101+ permissionsViewModel.recordPermissionChangeRequest(permissionInfo.name, position, expectedStatus)
102+
103+ viewLifecycleOwner.lifecycleScope.launch(Dispatchers .IO ) {
104+ val mode = if (checked) " grant" else " revoke"
105+
106+ if (ConfigurationPreferences .isUsingRoot()) {
107+ kotlin.runCatching {
108+ Shell .cmd(" pm $mode ${packageInfo.packageName} ${permissionInfo.name} " ).exec().let {
109+ // Refresh to get actual status
110+ permissionsViewModel.refreshPermissionStatus(permissionInfo.name, position)
111+ }
112+ }.getOrElse {
93113 withContext(Dispatchers .Main ) {
94- showWarning(" ERR: failed to $mode permission " , goBack = false )
114+ showWarning(" failed to acquire root " , goBack = false )
95115 adapterPermissions.permissionStatusChanged(position, permissionInfo.isGranted)
96116 }
97117 }
98- }
99- }.getOrElse {
100- withContext(Dispatchers .Main ) {
101- showWarning(" ERR: failed to acquire root" , goBack = false )
102- adapterPermissions.permissionStatusChanged(position, permissionInfo.isGranted)
103- }
104- }
105- } else if (ConfigurationPreferences .isUsingShizuku()) {
106- kotlin.runCatching {
107- if (Shizuku .pingBinder()) {
108- ShizukuServiceHelper .getInstance().getBoundService { shizukuService ->
109- shizukuService.simpleExecute(" pm $mode ${packageInfo.packageName} ${permissionInfo.name} " ).let {
110- postToUi {
111- if (it.isSuccess) {
112- adapterPermissions.permissionStatusChanged(position, if (permissionInfo.isGranted == 1 ) 0 else 1 )
113- } else {
114- showWarning(" ERR: failed to $mode permission" , goBack = false )
115- adapterPermissions.permissionStatusChanged(position, permissionInfo.isGranted)
118+ } else if (ConfigurationPreferences .isUsingShizuku()) {
119+ kotlin.runCatching {
120+ if (Shizuku .pingBinder()) {
121+ ShizukuServiceHelper .getInstance().getBoundService { shizukuService ->
122+ shizukuService.simpleExecute(" pm $mode ${packageInfo.packageName} ${permissionInfo.name} " ).let {
123+ // Wait a bit for the system to process
124+ Thread .sleep(500 )
125+
126+ // Refresh to get actual status
127+ permissionsViewModel.refreshPermissionStatus(permissionInfo.name, position)
116128 }
117129 }
130+ } else {
131+ postToUi {
132+ showWarning(" failed to acquire Shizuku" , goBack = false )
133+ adapterPermissions.permissionStatusChanged(position, permissionInfo.isGranted)
134+ }
135+ }
136+ }.getOrElse {
137+ postToUi {
138+ showWarning(" failed to acquire Shizuku" , goBack = false )
139+ adapterPermissions.permissionStatusChanged(position, permissionInfo.isGranted)
118140 }
119141 }
120- } else {
121- postToUi {
122- showWarning(" ERR: failed to acquire Shizuku" , goBack = false )
123- adapterPermissions.permissionStatusChanged(position, permissionInfo.isGranted)
124- }
125- }
126- }.getOrElse {
127- postToUi {
128- showWarning(" ERR: failed to acquire Shizuku" , goBack = false )
129- adapterPermissions.permissionStatusChanged(position, permissionInfo.isGranted)
130142 }
131143 }
132144 }
133- }
145+ })
146+
147+ recyclerView.setExclusiveAdapter(adapterPermissions)
148+ } else {
149+ // Update existing adapter with new data
150+ adapterPermissions.updateData(permissionInfos, searchBox.text.toString().trim())
134151 }
135- })
136152
137- recyclerView.setExclusiveAdapter(adapterPermissions)
153+ setCount(permissionInfos.size)
154+ }
155+ }
156+
157+ // Collect single permission updates
158+ viewLifecycleOwner.lifecycleScope.launch {
159+ permissionsViewModel.singlePermissionUpdate.collect { update ->
160+ if (::adapterPermissions.isInitialized) {
161+ adapterPermissions.permissionStatusChanged(update.position, update.newStatus)
162+ }
163+ }
164+ }
165+
166+ // Collect permission change results
167+ viewLifecycleOwner.lifecycleScope.launch {
168+ permissionsViewModel.permissionChangeResult.collectLatest { result ->
169+ result?.let {
170+ if (! it.success) {
171+ val expectedStatusText = if (permissionsViewModel.lastPermissionChangeRequest.value?.expectedStatus == 1 ) " granted" else " revoked"
172+ val actualStatusText = if (it.actualStatus == 1 ) " granted" else " revoked"
173+ // Permission change failed - show warning
174+ showWarning(" Failed to change permission state. Expected: $expectedStatusText , Actual: " +
175+ " $actualStatusText . The system maybe disallowing permission change for this app." , goBack = false )
176+ }
177+ // Clear the result after handling
178+ permissionsViewModel.clearPermissionChangeResult()
179+ }
180+ }
138181 }
139182
140183 permissionsViewModel.getError().observe(viewLifecycleOwner) {
0 commit comments