Skip to content

Commit 17a61aa

Browse files
feat: multiple formats
1 parent d003d8b commit 17a61aa

File tree

13 files changed

+701
-345
lines changed

13 files changed

+701
-345
lines changed

app/src/main/java/org/androidlabs/applistbackup/BackupService.kt

Lines changed: 333 additions & 270 deletions
Large diffs are not rendered by default.

app/src/main/java/org/androidlabs/applistbackup/data/BackupAppInfo.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package org.androidlabs.applistbackup.data
22

33
import android.content.Context
44
import org.androidlabs.applistbackup.R
5+
import org.androidlabs.applistbackup.data.BackupAppInfo.entries
56

67
enum class BackupAppInfo(val value: String) {
78
Package("Package"),

app/src/main/java/org/androidlabs/applistbackup/data/BackupFormat.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.androidlabs.applistbackup.data
22

3+
import org.androidlabs.applistbackup.data.BackupFormat.entries
4+
35
enum class BackupFormat(
46
val value: String,
57
private val extension: String,
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.androidlabs.applistbackup.data
2+
3+
data class BackupFormatResult(
4+
val format: BackupFormat,
5+
val file: BackupRawFile?,
6+
val exception: Exception?
7+
) {
8+
fun isSuccess(): Boolean {
9+
return file != null
10+
}
11+
}

app/src/main/java/org/androidlabs/applistbackup/faq/InstructionsActivity.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ fun BackupInstructionsScreen(
122122
verticalArrangement = Arrangement.spacedBy(8.dp)
123123
) {
124124
instructions.forEachIndexed { index, instruction ->
125-
val isExpanded = expandedStates[index] ?: false
125+
val isExpanded = expandedStates[index] == true
126126
InstructionRow(
127127
instruction = instruction,
128128
isExpanded = isExpanded,
@@ -133,7 +133,7 @@ fun BackupInstructionsScreen(
133133
}
134134

135135
val intentIndex = instructions.count()
136-
val isIntentExpanded = expandedStates[intentIndex] ?: false
136+
val isIntentExpanded = expandedStates[intentIndex] == true
137137

138138
InstructionsIntent(
139139
appName = appName,

app/src/main/java/org/androidlabs/applistbackup/faq/InstructionsIntent.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ fun InstructionsIntent(
7878
BackupFormat.entries.forEach {
7979
ValueRow(title = "Extra (${it.value}):", value = "format:${it.value}")
8080
}
81+
82+
Text(
83+
text = stringResource(
84+
R.string.multiple_formats,
85+
"format:${BackupFormat.HTML.value},${BackupFormat.CSV.value}"
86+
)
87+
)
8188
}
8289

8390
Text(text = stringResource(R.string.intent_integration_description_2))
@@ -91,6 +98,13 @@ fun InstructionsIntent(
9198
value = "--es format ${it.value}"
9299
)
93100
}
101+
102+
Text(
103+
text = stringResource(
104+
R.string.multiple_formats,
105+
"--es format ${BackupFormat.HTML.value},${BackupFormat.CSV.value}"
106+
)
107+
)
94108
}
95109

96110
Text(
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package org.androidlabs.applistbackup.settings
2+
3+
import androidx.compose.foundation.layout.Arrangement
4+
import androidx.compose.foundation.layout.Box
5+
import androidx.compose.foundation.layout.Row
6+
import androidx.compose.foundation.layout.fillMaxWidth
7+
import androidx.compose.foundation.layout.padding
8+
import androidx.compose.material3.Button
9+
import androidx.compose.material3.Checkbox
10+
import androidx.compose.material3.DropdownMenu
11+
import androidx.compose.material3.DropdownMenuItem
12+
import androidx.compose.material3.HorizontalDivider
13+
import androidx.compose.material3.MaterialTheme
14+
import androidx.compose.material3.Text
15+
import androidx.compose.material3.TextButton
16+
import androidx.compose.runtime.Composable
17+
import androidx.compose.runtime.getValue
18+
import androidx.compose.runtime.mutableStateOf
19+
import androidx.compose.runtime.remember
20+
import androidx.compose.runtime.setValue
21+
import androidx.compose.ui.Alignment
22+
import androidx.compose.ui.Modifier
23+
import androidx.compose.ui.res.stringResource
24+
import androidx.compose.ui.unit.dp
25+
import org.androidlabs.applistbackup.R
26+
import org.androidlabs.applistbackup.data.BackupFormat
27+
28+
@Composable
29+
fun MultiFormatSelector(
30+
selectedFormats: Set<BackupFormat>,
31+
onFormatsChanged: (Set<BackupFormat>) -> Unit
32+
) {
33+
var expanded by remember { mutableStateOf(false) }
34+
var tempSelectedFormats by remember(selectedFormats) {
35+
mutableStateOf(selectedFormats)
36+
}
37+
var showError by remember { mutableStateOf(false) }
38+
39+
Box {
40+
Button(
41+
onClick = {
42+
expanded = true
43+
showError = false
44+
tempSelectedFormats = selectedFormats.toSet()
45+
}
46+
) {
47+
Text(
48+
text = stringResource(R.string.change)
49+
)
50+
}
51+
52+
DropdownMenu(
53+
expanded = expanded,
54+
onDismissRequest = { expanded = false }
55+
) {
56+
if (showError) {
57+
Box(
58+
modifier = Modifier
59+
.fillMaxWidth()
60+
.padding(horizontal = 16.dp, vertical = 8.dp),
61+
contentAlignment = Alignment.Center
62+
) {
63+
Text(
64+
text = stringResource(R.string.no_formats_error),
65+
color = MaterialTheme.colorScheme.error,
66+
style = MaterialTheme.typography.bodyMedium
67+
)
68+
}
69+
HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp))
70+
}
71+
72+
BackupFormat.entries.forEach { format ->
73+
val isSelected = tempSelectedFormats.contains(format)
74+
75+
DropdownMenuItem(
76+
text = { Text(format.value) },
77+
leadingIcon = {
78+
Checkbox(
79+
checked = isSelected,
80+
onCheckedChange = null
81+
)
82+
},
83+
onClick = {
84+
val updatedFormats = tempSelectedFormats.toMutableSet()
85+
if (isSelected) {
86+
updatedFormats.remove(format)
87+
} else {
88+
updatedFormats.add(format)
89+
}
90+
tempSelectedFormats = updatedFormats
91+
}
92+
)
93+
}
94+
95+
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
96+
97+
Row(
98+
modifier = Modifier
99+
.fillMaxWidth()
100+
.padding(8.dp),
101+
horizontalArrangement = Arrangement.SpaceEvenly
102+
) {
103+
TextButton(
104+
onClick = { expanded = false }
105+
) {
106+
Text("Cancel")
107+
}
108+
109+
Button(
110+
onClick = {
111+
if (tempSelectedFormats.isNotEmpty()) {
112+
onFormatsChanged(tempSelectedFormats)
113+
expanded = false
114+
} else {
115+
showError = true
116+
}
117+
}
118+
) {
119+
Text("Apply")
120+
}
121+
}
122+
}
123+
}
124+
}

app/src/main/java/org/androidlabs/applistbackup/settings/Settings.kt

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,42 @@ import android.app.Service.MODE_PRIVATE
44
import android.content.Context
55
import android.content.SharedPreferences
66
import android.net.Uri
7+
import androidx.core.content.edit
8+
import androidx.core.net.toUri
79
import org.androidlabs.applistbackup.data.BackupAppInfo
810
import org.androidlabs.applistbackup.data.BackupFormat
911

1012
object Settings {
1113
private const val PREFERENCES_FILE: String = "preferences"
1214
private const val KEY_BACKUP_URI: String = "backup_uri"
13-
private const val KEY_BACKUP_FORMAT: String = "backup_format"
15+
private const val KEY_BACKUP_FORMATS: String = "backup_formats"
1416
private const val KEY_BACKUP_EXCLUDE_DATA: String = "backup_exclude_data"
1517

1618
fun getBackupUri(context: Context): Uri? {
1719
val sharedPreferences = context.getSharedPreferences(PREFERENCES_FILE, MODE_PRIVATE)
18-
val uriString = sharedPreferences.getString(KEY_BACKUP_URI, null)
19-
return if (uriString != null) Uri.parse(uriString) else null
20+
return sharedPreferences.getString(KEY_BACKUP_URI, null)?.toUri()
2021
}
2122

2223
fun setBackupUri(context: Context, uri: Uri) {
2324
val sharedPreferences = context.getSharedPreferences(PREFERENCES_FILE, MODE_PRIVATE)
24-
val editor = sharedPreferences.edit()
25-
editor.putString(KEY_BACKUP_URI, uri.toString())
26-
editor.apply()
25+
sharedPreferences.edit {
26+
putString(KEY_BACKUP_URI, uri.toString())
27+
}
2728
}
2829

29-
fun getBackupFormat(context: Context): BackupFormat {
30+
fun getBackupFormats(context: Context): Set<BackupFormat> {
3031
val sharedPreferences = context.getSharedPreferences(PREFERENCES_FILE, MODE_PRIVATE)
31-
val formatString = sharedPreferences.getString(KEY_BACKUP_FORMAT, null)
32-
return if (formatString != null) BackupFormat.fromString(formatString) else BackupFormat.HTML
32+
val formatString = sharedPreferences.getString(KEY_BACKUP_FORMATS, null)
33+
return formatString?.split(",")?.map { BackupFormat.fromString(it) }?.toSet() ?: setOf(
34+
BackupFormat.HTML
35+
)
3336
}
3437

35-
fun setBackupFormat(context: Context, format: BackupFormat) {
38+
fun setBackupFormats(context: Context, formats: Set<BackupFormat>) {
3639
val sharedPreferences = context.getSharedPreferences(PREFERENCES_FILE, MODE_PRIVATE)
37-
val editor = sharedPreferences.edit()
38-
editor.putString(KEY_BACKUP_FORMAT, format.value)
39-
editor.apply()
40+
sharedPreferences.edit {
41+
putString(KEY_BACKUP_FORMATS, formats.joinToString(",") { it.value })
42+
}
4043
}
4144

4245
fun getBackupExcludeData(context: Context): List<BackupAppInfo> {
@@ -50,9 +53,9 @@ object Settings {
5053

5154
fun setBackupExcludeData(context: Context, list: List<BackupAppInfo>) {
5255
val sharedPreferences = context.getSharedPreferences(PREFERENCES_FILE, MODE_PRIVATE)
53-
val editor = sharedPreferences.edit()
54-
editor.putString(KEY_BACKUP_EXCLUDE_DATA, list.map { it.value }.joinToString(","))
55-
editor.apply()
56+
sharedPreferences.edit {
57+
putString(KEY_BACKUP_EXCLUDE_DATA, list.joinToString(",") { it.value })
58+
}
5659
}
5760

5861
fun observeBackupUri(

app/src/main/java/org/androidlabs/applistbackup/settings/SettingsFragment.kt

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import androidx.activity.result.ActivityResultLauncher
1515
import androidx.activity.result.contract.ActivityResultContracts
1616
import androidx.compose.foundation.Image
1717
import androidx.compose.foundation.layout.Column
18-
import androidx.compose.foundation.layout.width
1918
import androidx.compose.foundation.rememberScrollState
2019
import androidx.compose.foundation.verticalScroll
2120
import androidx.compose.material.icons.Icons
@@ -34,7 +33,6 @@ import androidx.compose.ui.platform.ComposeView
3433
import androidx.compose.ui.platform.LocalContext
3534
import androidx.compose.ui.res.painterResource
3635
import androidx.compose.ui.res.stringResource
37-
import androidx.compose.ui.unit.dp
3836
import androidx.compose.ui.unit.sp
3937
import androidx.core.content.ContextCompat.checkSelfPermission
4038
import androidx.core.net.toUri
@@ -47,7 +45,6 @@ import org.androidlabs.applistbackup.docs.DocsViewerActivity
4745
import org.androidlabs.applistbackup.faq.InstructionsActivity
4846
import org.androidlabs.applistbackup.settings.data.BackupDataActivity
4947
import org.androidlabs.applistbackup.settings.tvpicker.TvFolderPickerActivity
50-
import org.androidlabs.applistbackup.ui.DropdownInput
5148
import org.androidlabs.applistbackup.utils.Utils.isTV
5249
import java.io.File
5350

@@ -211,7 +208,7 @@ private fun SettingsScreen(
211208
onBackupDataSettings: () -> Unit,
212209
) {
213210
val backupUri = viewModel.backupUri.observeAsState()
214-
val backupFormat = viewModel.backupFormat.observeAsState(initial = BackupFormat.HTML)
211+
val backupFormat = viewModel.backupFormats.observeAsState(initial = setOf(BackupFormat.HTML))
215212

216213
val localContext = LocalContext.current
217214

@@ -259,11 +256,17 @@ private fun SettingsScreen(
259256
)
260257
},
261258
rightView = {
262-
DropdownInput(
263-
entries = BackupFormat.entries.map { it.value },
264-
value = backupFormat.value.value,
265-
onChange = { viewModel.saveBackupFormat(BackupFormat.fromString(it)) },
266-
modifier = Modifier.width(128.dp)
259+
MultiFormatSelector(
260+
selectedFormats = backupFormat.value,
261+
onFormatsChanged = {
262+
viewModel.saveBackupFormats(it)
263+
}
264+
)
265+
},
266+
footerView = {
267+
Text(
268+
text = backupFormat.value.joinToString(", "),
269+
fontSize = 12.sp,
267270
)
268271
}
269272
)

app/src/main/java/org/androidlabs/applistbackup/settings/SettingsViewModel.kt

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,23 @@ import org.androidlabs.applistbackup.data.BackupFormat
1212

1313
class SettingsViewModel(application: Application) : AndroidViewModel(application) {
1414
private val _backupUri: MutableLiveData<Uri?> = MutableLiveData(loadBackupUri())
15-
private val _backupFormat: MutableLiveData<BackupFormat> = MutableLiveData(loadBackupFormat())
15+
private val _backupFormat: MutableLiveData<Set<BackupFormat>> =
16+
MutableLiveData(loadBackupFormats())
1617

1718
val backupUri: LiveData<Uri?> get() = _backupUri
18-
val backupFormat: LiveData<BackupFormat> get() = _backupFormat
19+
val backupFormats: LiveData<Set<BackupFormat>> get() = _backupFormat
1920

2021
private fun loadBackupUri(): Uri? {
2122
return Settings.getBackupUri(getApplication())
2223
}
2324

24-
private fun loadBackupFormat(): BackupFormat {
25-
return Settings.getBackupFormat(getApplication())
25+
private fun loadBackupFormats(): Set<BackupFormat> {
26+
return Settings.getBackupFormats(getApplication())
2627
}
2728

2829
fun refresh() {
2930
_backupUri.postValue(loadBackupUri())
30-
_backupFormat.postValue(loadBackupFormat())
31+
_backupFormat.postValue(loadBackupFormats())
3132
}
3233

3334
fun saveBackupUri(uri: Uri) {
@@ -37,9 +38,9 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
3738
}
3839
}
3940

40-
fun saveBackupFormat(format: BackupFormat) {
41+
fun saveBackupFormats(format: Set<BackupFormat>) {
4142
viewModelScope.launch(Dispatchers.IO) {
42-
Settings.setBackupFormat(getApplication(), format)
43+
Settings.setBackupFormats(getApplication(), format)
4344
_backupFormat.postValue(format)
4445
}
4546
}

0 commit comments

Comments
 (0)