1
1
package dev.zwander.installwithoptions
2
2
3
3
import android.content.Intent
4
- import android.content.pm.PackageParser
5
4
import android.os.Build
6
5
import android.os.Bundle
7
- import android.util.Log
8
6
import androidx.activity.SystemBarStyle
9
7
import androidx.activity.compose.rememberLauncherForActivityResult
10
8
import androidx.activity.compose.setContent
@@ -14,6 +12,7 @@ import androidx.appcompat.app.AppCompatActivity
14
12
import androidx.compose.animation.AnimatedVisibility
15
13
import androidx.compose.animation.fadeIn
16
14
import androidx.compose.animation.fadeOut
15
+ import androidx.compose.foundation.ExperimentalFoundationApi
17
16
import androidx.compose.foundation.background
18
17
import androidx.compose.foundation.border
19
18
import androidx.compose.foundation.clickable
@@ -40,11 +39,15 @@ import androidx.compose.foundation.layout.size
40
39
import androidx.compose.foundation.lazy.LazyColumn
41
40
import androidx.compose.foundation.lazy.items
42
41
import androidx.compose.foundation.shape.CircleShape
42
+ import androidx.compose.material.icons.Icons
43
+ import androidx.compose.material.icons.filled.Delete
43
44
import androidx.compose.material3.AlertDialog
44
45
import androidx.compose.material3.BottomAppBarDefaults
45
46
import androidx.compose.material3.ButtonDefaults
46
47
import androidx.compose.material3.Checkbox
47
48
import androidx.compose.material3.CircularProgressIndicator
49
+ import androidx.compose.material3.Icon
50
+ import androidx.compose.material3.IconButton
48
51
import androidx.compose.material3.MaterialTheme
49
52
import androidx.compose.material3.OutlinedButton
50
53
import androidx.compose.material3.OutlinedCard
@@ -70,7 +73,6 @@ import androidx.compose.ui.res.stringResource
70
73
import androidx.compose.ui.text.font.FontWeight
71
74
import androidx.compose.ui.text.style.TextDecoration
72
75
import androidx.compose.ui.unit.dp
73
- import androidx.documentfile.provider.DocumentFile
74
76
import dev.icerock.moko.mvvm.flow.compose.collectAsMutableState
75
77
import dev.zwander.installwithoptions.components.Footer
76
78
import dev.zwander.installwithoptions.data.DataModel
@@ -80,10 +82,10 @@ import dev.zwander.installwithoptions.data.rememberInstallOptions
80
82
import dev.zwander.installwithoptions.data.rememberMutableOptions
81
83
import dev.zwander.installwithoptions.ui.theme.InstallWithOptionsTheme
82
84
import dev.zwander.installwithoptions.util.ElevatedPermissionHandler
85
+ import dev.zwander.installwithoptions.util.handleIncomingUris
83
86
import dev.zwander.installwithoptions.util.plus
84
87
import dev.zwander.installwithoptions.util.rememberPackageInstaller
85
88
86
- @Suppress(" DEPRECATION" )
87
89
class MainActivity : AppCompatActivity () {
88
90
private val permissionHandler by lazy {
89
91
ElevatedPermissionHandler (
@@ -129,24 +131,13 @@ class MainActivity : AppCompatActivity() {
129
131
}
130
132
131
133
private fun checkIntentForPackage (intent : Intent ) {
132
- if (intent.type == " application/vnd.android.package-archive" ) {
133
- val selected = DataModel .selectedFiles.value.toMutableMap()
134
- val apkUri = intent.data ? : return
135
- val file = DocumentFile .fromSingleUri(this , apkUri) ? : return
136
-
137
- val fd = contentResolver.openAssetFileDescriptor(apkUri, " r" ) ? : return
138
- val apkFile = PackageParser .parseApkLite(fd.fileDescriptor, file.name, 0 )
139
-
140
- val packageList = selected[apkFile.packageName] ? : listOf ()
141
-
142
- selected[apkFile.packageName] = packageList + file
143
- fd.close()
144
-
145
- DataModel .selectedFiles.value = selected
134
+ intent.data?.let {
135
+ handleIncomingUris(listOf (it))
146
136
}
147
137
}
148
138
}
149
139
140
+ @OptIn(ExperimentalFoundationApi ::class )
150
141
@Composable
151
142
fun MainContent (modifier : Modifier = Modifier ) {
152
143
var selectedFiles by DataModel .selectedFiles.collectAsMutableState()
@@ -158,20 +149,7 @@ fun MainContent(modifier: Modifier = Modifier) {
158
149
val context = LocalContext .current
159
150
val fileSelector =
160
151
rememberLauncherForActivityResult(contract = ActivityResultContracts .OpenMultipleDocuments ()) { uris ->
161
- val selected = selectedFiles.toMutableMap()
162
-
163
- uris.forEach { uri ->
164
- val file = DocumentFile .fromSingleUri(context, uri) ? : return @forEach
165
- val fd = context.contentResolver.openAssetFileDescriptor(uri, " r" ) ? : return @forEach
166
- val apkFile = PackageParser .parseApkLite(fd.fileDescriptor, file.name, 0 )
167
-
168
- val packageList = selected[apkFile.packageName] ? : listOf ()
169
-
170
- selected[apkFile.packageName] = packageList + file
171
- fd.close()
172
- }
173
-
174
- selectedFiles = selected
152
+ context.handleIncomingUris(uris)
175
153
}
176
154
val options = (rememberInstallOptions() + rememberMutableOptions()).sortedBy {
177
155
context.resources.getString(it.labelResource)
@@ -207,7 +185,11 @@ fun MainContent(modifier: Modifier = Modifier) {
207
185
verticalAlignment = Alignment .CenterVertically ,
208
186
) {
209
187
OutlinedButton (
210
- onClick = { fileSelector.launch(arrayOf(" application/vnd.android.package-archive" )) },
188
+ onClick = {
189
+ fileSelector.launch(
190
+ arrayOf(" */*" ),
191
+ )
192
+ },
211
193
modifier = Modifier .weight(1f ),
212
194
) {
213
195
Box (
@@ -359,21 +341,66 @@ fun MainContent(modifier: Modifier = Modifier) {
359
341
LazyColumn (
360
342
modifier = Modifier .fillMaxWidth(),
361
343
) {
362
- items(items = selectedFiles.entries.flatMap { listOf (it.key) + it.value }) {
363
- if (it is String ) {
364
- Text (
365
- text = it,
366
- fontWeight = FontWeight .Bold ,
367
- textDecoration = TextDecoration .Underline ,
368
- modifier = Modifier .padding(top = 8 .dp, bottom = 0 .dp),
369
- )
344
+ selectedFiles.forEach { (pkg, files) ->
345
+ stickyHeader {
346
+ Row (
347
+ modifier = Modifier .fillMaxWidth()
348
+ .background(color = MaterialTheme .colorScheme.surfaceContainerHigh),
349
+ horizontalArrangement = Arrangement .SpaceBetween ,
350
+ verticalAlignment = Alignment .CenterVertically ,
351
+ ) {
352
+ Text (
353
+ text = pkg,
354
+ fontWeight = FontWeight .Bold ,
355
+ textDecoration = TextDecoration .Underline ,
356
+ )
357
+
358
+ IconButton (
359
+ onClick = {
360
+ selectedFiles - = pkg
361
+ },
362
+ ) {
363
+ Icon (
364
+ imageVector = Icons .Filled .Delete ,
365
+ contentDescription = stringResource(id = R .string.remove),
366
+ )
367
+ }
368
+ }
370
369
}
371
370
372
- if (it is DocumentFile ) {
373
- Text (
374
- text = it.name ? : it.uri.toString(),
375
- modifier = Modifier .padding(top = 0 .dp, bottom = 4 .dp),
376
- )
371
+ items(items = files) {
372
+ Row (
373
+ modifier = Modifier .fillMaxWidth()
374
+ .background(color = MaterialTheme .colorScheme.surfaceContainerHigh),
375
+ horizontalArrangement = Arrangement .SpaceBetween ,
376
+ verticalAlignment = Alignment .CenterVertically ,
377
+ ) {
378
+ Text (
379
+ text = it.name ? : it.uri.toString(),
380
+ modifier = Modifier .weight(1f ),
381
+ )
382
+
383
+ if (files.size > 1 ) {
384
+ IconButton (
385
+ onClick = {
386
+ selectedFiles = selectedFiles.toMutableMap().apply {
387
+ val newList = this [pkg]?.minus(it)
388
+
389
+ if (newList.isNullOrEmpty()) {
390
+ remove(pkg)
391
+ } else {
392
+ this [pkg] = newList
393
+ }
394
+ }
395
+ },
396
+ ) {
397
+ Icon (
398
+ imageVector = Icons .Filled .Delete ,
399
+ contentDescription = stringResource(id = R .string.remove),
400
+ )
401
+ }
402
+ }
403
+ }
377
404
}
378
405
}
379
406
}
0 commit comments