diff --git a/.gitignore b/.gitignore index 8421a2fd4..7332aacb2 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ local.properties /app/release/ /app/alpha/ /.kotlin/ +/gradle/*.properties \ No newline at end of file diff --git a/app/src/main/kotlin/com/looker/droidify/MainActivity.kt b/app/src/main/kotlin/com/looker/droidify/MainActivity.kt index 3da2b1833..f33324486 100644 --- a/app/src/main/kotlin/com/looker/droidify/MainActivity.kt +++ b/app/src/main/kotlin/com/looker/droidify/MainActivity.kt @@ -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 @@ -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)) diff --git a/app/src/main/kotlin/com/looker/droidify/compose/settings/ImportExportOptions.kt b/app/src/main/kotlin/com/looker/droidify/compose/settings/ImportExportOptions.kt new file mode 100644 index 000000000..6dc89dfda --- /dev/null +++ b/app/src/main/kotlin/com/looker/droidify/compose/settings/ImportExportOptions.kt @@ -0,0 +1,5 @@ +package com.looker.droidify.compose.settings + +data class ImportExportOptions( + val repositories: Boolean = false, +) diff --git a/app/src/main/kotlin/com/looker/droidify/compose/settings/SettingsScreen.kt b/app/src/main/kotlin/com/looker/droidify/compose/settings/SettingsScreen.kt index 54c7c0330..df30882ac 100644 --- a/app/src/main/kotlin/com/looker/droidify/compose/settings/SettingsScreen.kt +++ b/app/src/main/kotlin/com/looker/droidify/compose/settings/SettingsScreen.kt @@ -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 @@ -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 @@ -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(null) } + var pendingImportOptions by remember { mutableStateOf(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) } @@ -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 }, ) } @@ -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 diff --git a/app/src/main/kotlin/com/looker/droidify/compose/settings/components/ImportExportDialog.kt b/app/src/main/kotlin/com/looker/droidify/compose/settings/components/ImportExportDialog.kt new file mode 100644 index 000000000..7d57b2bb9 --- /dev/null +++ b/app/src/main/kotlin/com/looker/droidify/compose/settings/components/ImportExportDialog.kt @@ -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 + }, + ) + } +} diff --git a/app/src/main/kotlin/com/looker/droidify/datastore/PreferenceSettingsRepository.kt b/app/src/main/kotlin/com/looker/droidify/datastore/PreferenceSettingsRepository.kt index 303204cb8..acebb8178 100644 --- a/app/src/main/kotlin/com/looker/droidify/datastore/PreferenceSettingsRepository.kt +++ b/app/src/main/kotlin/com/looker/droidify/datastore/PreferenceSettingsRepository.kt @@ -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) } @@ -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() @@ -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() @@ -268,6 +283,7 @@ class PreferenceSettingsRepository( lastRbLogFetch = lastRbLogFetch, lastModifiedDownloadStats = lastModifiedDownloadStats, favouriteApps = favouriteApps, + installedAppsHistory = installedAppsHistory, homeScreenSwiping = homeScreenSwiping, enabledRepoIds = enabledRepoIds, deleteApkOnInstall = deleteApkOnInstall, @@ -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") @@ -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) diff --git a/app/src/main/kotlin/com/looker/droidify/datastore/Settings.kt b/app/src/main/kotlin/com/looker/droidify/datastore/Settings.kt index 1c4903ae8..0a7a5b96d 100644 --- a/app/src/main/kotlin/com/looker/droidify/datastore/Settings.kt +++ b/app/src/main/kotlin/com/looker/droidify/datastore/Settings.kt @@ -44,6 +44,7 @@ data class Settings( val lastRbLogFetch: Long? = null, val lastModifiedDownloadStats: Long? = null, val favouriteApps: Set = emptySet(), + val installedAppsHistory: Set = emptySet(), val homeScreenSwiping: Boolean = true, val enabledRepoIds: Set = emptySet(), val deleteApkOnInstall: Boolean = false, diff --git a/app/src/main/kotlin/com/looker/droidify/datastore/SettingsRepository.kt b/app/src/main/kotlin/com/looker/droidify/datastore/SettingsRepository.kt index 850b02cd0..7135d5922 100644 --- a/app/src/main/kotlin/com/looker/droidify/datastore/SettingsRepository.kt +++ b/app/src/main/kotlin/com/looker/droidify/datastore/SettingsRepository.kt @@ -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> diff --git a/app/src/main/kotlin/com/looker/droidify/ui/appDetail/AppDetailViewModel.kt b/app/src/main/kotlin/com/looker/droidify/ui/appDetail/AppDetailViewModel.kt index 532fa87bd..6c0209cf4 100644 --- a/app/src/main/kotlin/com/looker/droidify/ui/appDetail/AppDetailViewModel.kt +++ b/app/src/main/kotlin/com/looker/droidify/ui/appDetail/AppDetailViewModel.kt @@ -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 @@ -123,6 +122,7 @@ class AppDetailViewModel @Inject constructor( fun installPackage(packageName: String, fileName: String) { viewModelScope.launch { + settingsRepository.addToHistory(packageName) installer install (packageName installFrom fileName) } } diff --git a/app/src/main/kotlin/com/looker/droidify/ui/history/AppHistoryFragment.kt b/app/src/main/kotlin/com/looker/droidify/ui/history/AppHistoryFragment.kt new file mode 100644 index 000000000..11acc8787 --- /dev/null +++ b/app/src/main/kotlin/com/looker/droidify/ui/history/AppHistoryFragment.kt @@ -0,0 +1,81 @@ +package com.looker.droidify.ui.history + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.flowWithLifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.looker.droidify.database.Database +import com.looker.droidify.R +import com.looker.droidify.ui.ScreenFragment +import com.looker.droidify.utility.common.extension.systemBarsPadding +import com.looker.droidify.utility.extension.mainActivity +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + +@AndroidEntryPoint +class AppHistoryFragment : ScreenFragment() { + + private val viewModel: AppHistoryViewModel by viewModels() + + private lateinit var recyclerView: RecyclerView + private lateinit var recyclerViewAdapter: AppHistoryFragmentAdapter + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + super.onCreateView(inflater, container, savedInstanceState) + val view = fragmentBinding.root.apply { + val content = fragmentBinding.fragmentContent + content.addView( + RecyclerView(content.context).apply { + id = android.R.id.list + layoutManager = LinearLayoutManager(context) + isVerticalScrollBarEnabled = false + setHasFixedSize(true) + recyclerViewAdapter = + AppHistoryFragmentAdapter( + onProductClick = { mainActivity.navigateProduct(it) } + ) + this.adapter = recyclerViewAdapter + systemBarsPadding(includeFab = false) + recyclerView = this + } + ) + } + viewLifecycleOwner.lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.CREATED) { + launch { + viewModel.historyApps.collect { apps -> + recyclerViewAdapter.apps = apps + } + } + launch { + Database.RepositoryAdapter + .getAllStream() + .flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.RESUMED) + .collectLatest { repositories -> + recyclerViewAdapter.repositories = repositories.associateBy { it.id } + } + } + } + } + + toolbar.title = getString(R.string.app_history) + return view + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + mainActivity.onToolbarCreated(toolbar) + } +} diff --git a/app/src/main/kotlin/com/looker/droidify/ui/history/AppHistoryFragmentAdapter.kt b/app/src/main/kotlin/com/looker/droidify/ui/history/AppHistoryFragmentAdapter.kt new file mode 100644 index 000000000..76005ed70 --- /dev/null +++ b/app/src/main/kotlin/com/looker/droidify/ui/history/AppHistoryFragmentAdapter.kt @@ -0,0 +1,102 @@ +package com.looker.droidify.ui.history + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import coil3.load +import com.looker.droidify.databinding.ProductItemBinding +import com.looker.droidify.model.ProductItem +import com.looker.droidify.model.Repository +import com.looker.droidify.utility.common.extension.authentication +import com.looker.droidify.utility.common.extension.corneredBackground +import com.looker.droidify.utility.common.extension.dp +import com.looker.droidify.utility.common.extension.getColorFromAttr +import com.looker.droidify.utility.common.nullIfEmpty +import com.google.android.material.R as MaterialR + +class AppHistoryFragmentAdapter( + private val onProductClick: (String) -> Unit +) : RecyclerView.Adapter() { + + class ViewHolder(binding: ProductItemBinding) : RecyclerView.ViewHolder(binding.root) { + val icon = binding.icon + val name = binding.name + val summary = binding.summary + val version = binding.status + } + + var apps: List = emptyList() + set(value) { + field = value + notifyDataSetChanged() + } + + var repositories: Map = emptyMap() + set(value) { + field = value + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = + ViewHolder( + ProductItemBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ).apply { + itemView.setOnClickListener { + if (apps.isNotEmpty()) { + onProductClick(apps[absoluteAdapterPosition].packageName) + } + } + } + + override fun getItemCount(): Int = apps.size + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val item = apps[position] + val context = holder.itemView.context + val installedVersion = item.installedVersion.nullIfEmpty() + + val repository: Repository? = repositories[item.repositoryId] + holder.name.text = item.name + holder.summary.isVisible = item.summary.isNotEmpty() + holder.summary.text = item.summary + + if (repository != null) { + val iconUrl = item.icon(holder.icon, repository) + holder.icon.load(iconUrl) { + authentication(repository.authentication) + } + } + + holder.version.apply { + text = installedVersion ?: item.version + val isInstalled = installedVersion != null + when { + item.canUpdate -> { + backgroundTintList = + context.getColorFromAttr(MaterialR.attr.colorSecondaryContainer) + setTextColor(context.getColorFromAttr(MaterialR.attr.colorOnSecondaryContainer)) + } + + isInstalled -> { + backgroundTintList = + context.getColorFromAttr(MaterialR.attr.colorPrimaryContainer) + setTextColor(context.getColorFromAttr(MaterialR.attr.colorOnPrimaryContainer)) + } + + else -> { + setPadding(0, 0, 0, 0) + setTextColor(context.getColorFromAttr(MaterialR.attr.colorOnBackground)) + background = null + return@apply + } + } + background = context.corneredBackground + 6.dp.let { setPadding(it, it, it, it) } + } + } +} diff --git a/app/src/main/kotlin/com/looker/droidify/ui/history/AppHistoryViewModel.kt b/app/src/main/kotlin/com/looker/droidify/ui/history/AppHistoryViewModel.kt new file mode 100644 index 000000000..a6e5bc8e3 --- /dev/null +++ b/app/src/main/kotlin/com/looker/droidify/ui/history/AppHistoryViewModel.kt @@ -0,0 +1,34 @@ +package com.looker.droidify.ui.history + +import androidx.lifecycle.ViewModel +import com.looker.droidify.database.Database +import com.looker.droidify.datastore.SettingsRepository +import com.looker.droidify.datastore.get +import com.looker.droidify.model.ProductItem +import com.looker.droidify.utility.common.extension.asStateFlow +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +@HiltViewModel +class AppHistoryViewModel @Inject constructor( + settingsRepository: SettingsRepository, +) : ViewModel() { + + val historyApps: StateFlow> = + settingsRepository + .get { installedAppsHistory } + .map { history -> + history.mapNotNull { app -> + val products = Database.ProductAdapter.get(app, null) + val product = products.firstOrNull() ?: return@mapNotNull null + val installed = Database.InstalledAdapter.get(app, null) + + product.item().apply { + this.installedVersion = installed?.version.orEmpty() + this.canUpdate = product.canUpdate(installed) + } + } + }.asStateFlow(emptyList()) +} diff --git a/app/src/main/kotlin/com/looker/droidify/ui/tabsFragment/TabsFragment.kt b/app/src/main/kotlin/com/looker/droidify/ui/tabsFragment/TabsFragment.kt index 9349df8d1..44a148729 100644 --- a/app/src/main/kotlin/com/looker/droidify/ui/tabsFragment/TabsFragment.kt +++ b/app/src/main/kotlin/com/looker/droidify/ui/tabsFragment/TabsFragment.kt @@ -87,6 +87,7 @@ class TabsFragment : ScreenFragment() { val sectionIcon = view.sectionIcon } + private var appHistoryItem: MenuItem? = null private var favouritesItem: MenuItem? = null private var searchMenuItem: MenuItem? = null private var sortOrderMenu: Pair>? = null @@ -230,6 +231,13 @@ class TabsFragment : ScreenFragment() { Pair(menu.item, menuItems) } + appHistoryItem = add(1, 0, 0, stringRes.app_history) + .setIcon(toolbar.context.getMutatedIcon(R.drawable.ic_history)) + .setOnMenuItemClickListener { + view.post { mainActivity.navigateAppHistory() } + true + } + favouritesItem = add(1, 0, 0, stringRes.favourites) .setIcon(toolbar.context.getMutatedIcon(R.drawable.ic_favourite_checked)) .setOnMenuItemClickListener { @@ -405,6 +413,7 @@ class TabsFragment : ScreenFragment() { override fun onDestroyView() { super.onDestroyView() + appHistoryItem = null favouritesItem = null searchMenuItem = null sortOrderMenu = null diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a937bdc53..43a3fc012 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -57,16 +57,19 @@ Shizuku/Sui isn\'t installed Backup and restore - Import favourites and settings - Import settings and favourites from a file - Export favourites and settings - Export settings and favourites to a file - Import repositories - Import repositories from a file - Export repositories - Export repositories to a file + Import data + Select what data to import from a file + Export data + Select what data to export to a file + Import selected + Export selected + Settings + Favourites + App History + Repositories Enable repository Favourites + App History Invalid file format Fingerprint Force cleanup