Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ local.properties
/app/release/
/app/alpha/
/.kotlin/
/gradle/*.properties
2 changes: 2 additions & 0 deletions app/src/main/kotlin/com/looker/droidify/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.looker.droidify.installer.InstallManager
import com.looker.droidify.installer.model.installFrom
import com.looker.droidify.ui.appDetail.AppDetailFragment
import com.looker.droidify.ui.favourites.FavouritesFragment
import com.looker.droidify.ui.history.AppHistoryFragment
import com.looker.droidify.ui.repository.EditRepositoryFragment
import com.looker.droidify.ui.repository.RepositoriesFragment
import com.looker.droidify.ui.repository.RepositoryFragment
Expand Down Expand Up @@ -307,6 +308,7 @@ class MainActivity : AppCompatActivity() {
tabsFragment.activateSearch(query)
}

fun navigateAppHistory() = pushFragment(AppHistoryFragment())
fun navigateFavourites() = pushFragment(FavouritesFragment())
fun navigateProduct(packageName: String, repoAddress: String? = null) =
pushFragment(AppDetailFragment(packageName, repoAddress))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.looker.droidify.compose.settings

data class ImportExportOptions(
val repositories: Boolean = false,
)
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.pluralStringResource
Expand All @@ -30,6 +32,7 @@ import com.looker.droidify.compose.settings.SettingsViewModel.Companion.cleanUpI
import com.looker.droidify.compose.settings.SettingsViewModel.Companion.localeCodesList
import com.looker.droidify.compose.settings.components.ActionSettingItem
import com.looker.droidify.compose.settings.components.CustomButtonsSettingItem
import com.looker.droidify.compose.settings.components.ImportExportDialog
import com.looker.droidify.compose.settings.components.SelectionSettingItem
import com.looker.droidify.compose.settings.components.SettingHeader
import com.looker.droidify.compose.settings.components.SwitchSettingItem
Expand Down Expand Up @@ -68,33 +71,56 @@ fun SettingsScreen(
val customButtons by viewModel.customButtons.collectAsStateWithLifecycle()
val isBackgroundAllowed by viewModel.isBackgroundAllowed.collectAsStateWithLifecycle()

var showImportDialog by remember { mutableStateOf(false) }
var showExportDialog by remember { mutableStateOf(false) }
var importOptions by remember { mutableStateOf(ImportExportOptions()) }
var exportOptions by remember { mutableStateOf(ImportExportOptions()) }
var pendingExportOptions by remember { mutableStateOf<ImportExportOptions?>(null) }
var pendingImportOptions by remember { mutableStateOf<ImportExportOptions?>(null) }

LaunchedEffect(Unit) {
viewModel.updateBackgroundAccessState(context.isIgnoreBatteryEnabled())
}

val exportReposLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.CreateDocument(BACKUP_MIME_TYPE),
) { uri -> uri?.let { viewModel.exportRepos(it) } }

val exportSettingsLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.CreateDocument(BACKUP_MIME_TYPE),
) { uri -> uri?.let { viewModel.exportSettings(it) } }
) { uri ->
if (uri != null) {
viewModel.exportSettings(uri)
pendingExportOptions?.let { options ->
if (options.repositories) {
exportReposLauncher.launch(REPO_BACKUP_NAME)
}
pendingExportOptions = null
}
}
}

val importSettingsLauncher = rememberLauncherForActivityResult(
val importReposLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocument(),
) { uri ->
if (uri != null) {
viewModel.importSettings(uri)
viewModel.importRepos(uri)
} else {
viewModel.showSnackbar(R.string.file_format_error_DESC)
}
}

val exportReposLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.CreateDocument(BACKUP_MIME_TYPE),
) { uri -> uri?.let { viewModel.exportRepos(it) } }

val importReposLauncher = rememberLauncherForActivityResult(
val importSettingsLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocument(),
) { uri ->
if (uri != null) {
viewModel.importRepos(uri)
viewModel.importSettings(uri)
pendingImportOptions?.let { options ->
if (options.repositories) {
importReposLauncher.launch(arrayOf(BACKUP_MIME_TYPE))
}
pendingImportOptions = null
}
} else {
viewModel.showSnackbar(R.string.file_format_error_DESC)
}
Expand Down Expand Up @@ -328,33 +354,17 @@ fun SettingsScreen(

item {
ActionSettingItem(
title = stringResource(R.string.import_settings_title),
description = stringResource(R.string.import_settings_DESC),
onClick = { importSettingsLauncher.launch(arrayOf(BACKUP_MIME_TYPE)) },
)
}

item {
ActionSettingItem(
title = stringResource(R.string.export_settings_title),
description = stringResource(R.string.export_settings_DESC),
onClick = { exportSettingsLauncher.launch(SETTINGS_BACKUP_NAME) },
)
}

item {
ActionSettingItem(
title = stringResource(R.string.import_repos_title),
description = stringResource(R.string.import_repos_DESC),
onClick = { importReposLauncher.launch(arrayOf(BACKUP_MIME_TYPE)) },
title = stringResource(R.string.import_data_title),
description = stringResource(R.string.import_data_DESC),
onClick = { showImportDialog = true },
)
}

item {
ActionSettingItem(
title = stringResource(R.string.export_repos_title),
description = stringResource(R.string.export_repos_DESC),
onClick = { exportReposLauncher.launch(REPO_BACKUP_NAME) },
title = stringResource(R.string.export_data_title),
description = stringResource(R.string.export_data_DESC),
onClick = { showExportDialog = true },
)
}

Expand Down Expand Up @@ -390,6 +400,34 @@ fun SettingsScreen(
}
}
}

if (showImportDialog) {
ImportExportDialog(
options = importOptions,
onOptionsChange = { importOptions = it },
onConfirm = {
showImportDialog = false
pendingImportOptions = importOptions
importSettingsLauncher.launch(arrayOf(BACKUP_MIME_TYPE))
},
onDismiss = { showImportDialog = false },
isImport = true,
)
}

if (showExportDialog) {
ImportExportDialog(
options = exportOptions,
onOptionsChange = { exportOptions = it },
onConfirm = {
showExportDialog = false
pendingExportOptions = exportOptions
exportSettingsLauncher.launch(SETTINGS_BACKUP_NAME)
},
onDismiss = { showExportDialog = false },
isImport = false,
)
}
}

@Composable
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package com.looker.droidify.compose.settings.components

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Checkbox
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.looker.droidify.R
import com.looker.droidify.compose.settings.ImportExportOptions

@Composable
fun ImportExportDialog(
options: ImportExportOptions,
onOptionsChange: (ImportExportOptions) -> Unit,
onConfirm: () -> Unit,
onDismiss: () -> Unit,
isImport: Boolean = true,
) {
AlertDialog(
onDismissRequest = onDismiss,
title = {
Text(
text = stringResource(
if (isImport) R.string.import_data_title else R.string.export_data_title,
),
style = MaterialTheme.typography.headlineSmall,
)
},
text = {
Column {
CheckboxItem(label = stringResource(R.string.checkbox_settings))

CheckboxItem(label = stringResource(R.string.checkbox_favourites))

CheckboxItem(label = stringResource(R.string.checkbox_history))

CheckboxItem(
label = stringResource(R.string.checkbox_repos),
checked = options.repositories,
enabled = true,
onCheckedChange = {
onOptionsChange(options.copy(repositories = it))
},
)
}
},
confirmButton = {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
OutlinedButton(onClick = onDismiss) {
Text(text = stringResource(android.R.string.cancel))
}
TextButton(onClick = onConfirm) {
Text(
text = stringResource(
if (isImport) R.string.import_selected else R.string.export_selected,
),
)
}
}
},
)
}

@Composable
private fun CheckboxItem(
label: String,
checked: Boolean = true,
enabled: Boolean = false,
onCheckedChange: ((Boolean) -> Unit)? = null,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Checkbox(
checked = checked,
enabled = enabled,
onCheckedChange = onCheckedChange,
)
Text(
text = label,
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.padding(start = 8.dp),
color = if (enabled) {
MaterialTheme.colorScheme.onSurface
} else {
MaterialTheme.colorScheme.onSurfaceVariant
},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@ class PreferenceSettingsRepository(
val importedSettings = exporter.import(target)
val updatedFavorites = importedSettings.favouriteApps +
getInitial().favouriteApps
val updatedSettings = importedSettings.copy(favouriteApps = updatedFavorites)
val updatedHistory = importedSettings.installedAppsHistory +
getInitial().installedAppsHistory
val updatedSettings = importedSettings.copy(
favouriteApps = updatedFavorites,
installedAppsHistory = updatedHistory
)
dataStore.edit {
it.setting(updatedSettings)
}
Expand Down Expand Up @@ -165,6 +170,15 @@ class PreferenceSettingsRepository(
}
}

override suspend fun addToHistory(packageName: String) {
dataStore.edit { preference ->
val currentSet = preference[INSTALLED_APPS_HISTORY] ?: emptySet()
val newSet = currentSet.toMutableSet()
newSet.add(packageName)
preference[INSTALLED_APPS_HISTORY] = newSet
}
}

override suspend fun setRepoEnabled(repoId: Int, enabled: Boolean) {
dataStore.edit { preference ->
val currentSet = preference[ENABLED_REPO_IDS] ?: emptySet()
Expand Down Expand Up @@ -242,6 +256,7 @@ class PreferenceSettingsRepository(
val lastRbLogFetch = preferences[LAST_RB_FETCH]
val lastModifiedDownloadStats = preferences[LAST_MODIFIED_DS]?.takeIf { it > 0L }
val favouriteApps = preferences[FAVOURITE_APPS] ?: emptySet()
val installedAppsHistory = preferences[INSTALLED_APPS_HISTORY] ?: emptySet()
val homeScreenSwiping = preferences[HOME_SCREEN_SWIPING] ?: true
val enabledRepoIds =
preferences[ENABLED_REPO_IDS]?.mapNotNull { it.toIntOrNull() }?.toSet() ?: emptySet()
Expand All @@ -268,6 +283,7 @@ class PreferenceSettingsRepository(
lastRbLogFetch = lastRbLogFetch,
lastModifiedDownloadStats = lastModifiedDownloadStats,
favouriteApps = favouriteApps,
installedAppsHistory = installedAppsHistory,
homeScreenSwiping = homeScreenSwiping,
enabledRepoIds = enabledRepoIds,
deleteApkOnInstall = deleteApkOnInstall,
Expand Down Expand Up @@ -297,6 +313,7 @@ class PreferenceSettingsRepository(
val LAST_RB_FETCH = longPreferencesKey("key_last_rb_logs_fetch_time")
val LAST_MODIFIED_DS = longPreferencesKey("key_last_modified_download_stats")
val FAVOURITE_APPS = stringSetPreferencesKey("key_favourite_apps")
val INSTALLED_APPS_HISTORY = stringSetPreferencesKey("key_installed_apps_history")
val HOME_SCREEN_SWIPING = booleanPreferencesKey("key_home_swiping")
val DELETE_APK_ON_INSTALL = booleanPreferencesKey("key_delete_apk_on_install")
val DOWNLOAD_STATISTICS_ENABLED = booleanPreferencesKey("key_download_statistics_enabled")
Expand Down Expand Up @@ -359,6 +376,7 @@ class PreferenceSettingsRepository(
settings.lastRbLogFetch?.let { set(LAST_RB_FETCH, it) }
settings.lastModifiedDownloadStats?.let { set(LAST_MODIFIED_DS, it) }
set(FAVOURITE_APPS, settings.favouriteApps)
set(INSTALLED_APPS_HISTORY, settings.installedAppsHistory)
set(HOME_SCREEN_SWIPING, settings.homeScreenSwiping)
set(ENABLED_REPO_IDS, settings.enabledRepoIds.map { it.toString() }.toSet())
set(DELETE_APK_ON_INSTALL, settings.deleteApkOnInstall)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ data class Settings(
val lastRbLogFetch: Long? = null,
val lastModifiedDownloadStats: Long? = null,
val favouriteApps: Set<String> = emptySet(),
val installedAppsHistory: Set<String> = emptySet(),
val homeScreenSwiping: Boolean = true,
val enabledRepoIds: Set<Int> = emptySet(),
val deleteApkOnInstall: Boolean = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ interface SettingsRepository {

suspend fun toggleFavourites(packageName: String)

suspend fun addToHistory(packageName: String)

suspend fun setRepoEnabled(repoId: Int, enabled: Boolean)

fun getEnabledRepoIds(): Flow<Set<Int>>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import com.looker.droidify.utility.extension.combine
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
Expand Down Expand Up @@ -123,6 +122,7 @@ class AppDetailViewModel @Inject constructor(

fun installPackage(packageName: String, fileName: String) {
viewModelScope.launch {
settingsRepository.addToHistory(packageName)
installer install (packageName installFrom fileName)
}
}
Expand Down
Loading