This commit is contained in:
Minecon724 2025-04-27 09:21:42 +02:00
commit e88efe5f2d
Signed by untrusted user who does not match committer: m724
GPG key ID: A02E6E67AB961189
5 changed files with 61 additions and 30 deletions

View file

@ -18,7 +18,6 @@ import eu.m724.pojavbackup.core.backup.Backup.BackupStatus
import eu.m724.pojavbackup.core.data.GameDataRepository import eu.m724.pojavbackup.core.data.GameDataRepository
import eu.m724.pojavbackup.core.datastore.SettingsRepository import eu.m724.pojavbackup.core.datastore.SettingsRepository
import eu.m724.pojavbackup.notification.NotificationChannels import eu.m724.pojavbackup.notification.NotificationChannels
import kotlinx.coroutines.delay
import org.apache.commons.compress.compressors.CompressorStreamFactory import org.apache.commons.compress.compressors.CompressorStreamFactory
import java.time.LocalDate import java.time.LocalDate
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
@ -50,8 +49,6 @@ class BackupWorker @AssistedInject constructor(
updateStatus("Initializing") updateStatus("Initializing")
delay(10000)
val settings = settingsRepository.getSettings() val settings = settingsRepository.getSettings()
val worldIds = settingsRepository.getIncludedWorldIds(settings.worldOrder) val worldIds = settingsRepository.getIncludedWorldIds(settings.worldOrder)

View file

@ -30,7 +30,9 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.core.content.ContextCompat.startActivity
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
@ -43,8 +45,8 @@ import eu.m724.pojavbackup.home.screen.HomeScreen
import eu.m724.pojavbackup.home.screen.dashboard.DashboardScreen import eu.m724.pojavbackup.home.screen.dashboard.DashboardScreen
import eu.m724.pojavbackup.home.screen.history.HistoryScreen import eu.m724.pojavbackup.home.screen.history.HistoryScreen
import eu.m724.pojavbackup.settings.SettingsActivity import eu.m724.pojavbackup.settings.SettingsActivity
import eu.m724.pojavbackup.setup.SetupActivity
import eu.m724.pojavbackup.ui.theme.PojavBackupTheme import eu.m724.pojavbackup.ui.theme.PojavBackupTheme
import kotlinx.coroutines.launch
@AndroidEntryPoint @AndroidEntryPoint
class HomeActivity : ComponentActivity() { class HomeActivity : ComponentActivity() {
@ -62,8 +64,8 @@ class HomeActivity : ComponentActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
viewModel.load( viewModel.load(
onSetupNeeded = { onSetupRequired = {
setupResult.launch(Intent(applicationContext, SetupActivity::class.java)) setupResult.launch(it)
} }
) )

View file

@ -1,9 +1,13 @@
package eu.m724.pojavbackup.home package eu.m724.pojavbackup.home
import android.Manifest
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.util.Log import android.util.Log
import androidx.core.content.ContextCompat
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@ -12,6 +16,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import eu.m724.pojavbackup.core.data.GameDataRepository import eu.m724.pojavbackup.core.data.GameDataRepository
import eu.m724.pojavbackup.core.datastore.SettingsRepository import eu.m724.pojavbackup.core.datastore.SettingsRepository
import eu.m724.pojavbackup.notification.NotificationChannels import eu.m724.pojavbackup.notification.NotificationChannels
import eu.m724.pojavbackup.setup.SetupActivity
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@ -38,14 +43,20 @@ class HomeViewModel @Inject constructor(
} }
fun load( fun load(
onSetupNeeded: () -> Unit onSetupRequired: (Intent) -> Unit
) { ) {
viewModelScope.launch { viewModelScope.launch {
val uri = settingsRepository.getSource() val uri = settingsRepository.getSource()
if (uri == null || !checkForStoragePermission(uri)) { val storagePermission = uri?.let { checkForStoragePermission(uri) } == true
// TODO there could be that only one or two permissions are missing val notificationPermission = checkForNotificationPermission()
onSetupNeeded()
if (!storagePermission || !notificationPermission) {
val intent = Intent(appContext, SetupActivity::class.java)
.putExtra("storagePermissionGranted", storagePermission)
.putExtra("notificationPermissionGranted", notificationPermission)
onSetupRequired(intent)
} else { } else {
_uiState.update { it.copy(loading = false) } _uiState.update { it.copy(loading = false) }
} }
@ -75,4 +86,9 @@ class HomeViewModel @Inject constructor(
return true return true
} }
private fun checkForNotificationPermission(): Boolean {
// TODO check if rejected
return ContextCompat.checkSelfPermission(appContext, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
}
} }

View file

@ -1,5 +1,6 @@
package eu.m724.pojavbackup.setup package eu.m724.pojavbackup.setup
import android.Manifest
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.widget.Toast import android.widget.Toast
@ -37,7 +38,7 @@ class SetupActivity : ComponentActivity() {
private val viewModel: SetupViewModel by viewModels() private val viewModel: SetupViewModel by viewModels()
private val openDocumentTree = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { private val openDocumentTree = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) {
viewModel.onOpenDocumentTree(applicationContext, it) { success -> viewModel.onOpenDocumentTree(it) { success ->
if (success) { if (success) {
onComplete() onComplete()
} else { } else {
@ -51,16 +52,18 @@ class SetupActivity : ComponentActivity() {
} }
} }
private val notificationGrant = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> private val notificationGrant = registerForActivityResult(ActivityResultContracts.RequestPermission()) {
if (isGranted) { viewModel.onNotificationGrant(it) { success ->
onComplete() if (success) {
} else { onComplete()
// TODO instead red text? } else {
Toast.makeText( // TODO instead red text?
applicationContext, Toast.makeText(
"This is not a PojavLauncher directory.", applicationContext,
Toast.LENGTH_SHORT "This is not a PojavLauncher directory.",
).show() Toast.LENGTH_SHORT
).show()
}
} }
} }
@ -95,7 +98,7 @@ class SetupActivity : ComponentActivity() {
openDocumentTree.launch(defaultUri) openDocumentTree.launch(defaultUri)
}, },
onNotificationPermissionGrantClick = { onNotificationPermissionGrantClick = {
openDocumentTree.launch(defaultUri) notificationGrant.launch(Manifest.permission.POST_NOTIFICATIONS)
} }
) )
} }
@ -134,7 +137,7 @@ fun SetupScreen(
GrantCard( GrantCard(
title = "Notification permission", title = "Notification permission",
description = "It's needed to notify you about backup status.", description = "It's needed to notify you about backup status.",
granted = uiState.storagePermissionGranted, granted = uiState.notificationPermissionGranted,
onClick = onNotificationPermissionGrantClick onClick = onNotificationPermissionGrantClick
) )
} }

View file

@ -2,7 +2,6 @@ package eu.m724.pojavbackup.setup
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.PackageManager.NameNotFoundException import android.content.pm.PackageManager.NameNotFoundException
import android.net.Uri import android.net.Uri
import android.util.Log import android.util.Log
@ -10,6 +9,7 @@ import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import eu.m724.pojavbackup.core.datastore.SettingsRepository import eu.m724.pojavbackup.core.datastore.SettingsRepository
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -20,6 +20,7 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class SetupViewModel @Inject constructor( class SetupViewModel @Inject constructor(
@ApplicationContext private val appContext: Context,
private val settingsRepository: SettingsRepository private val settingsRepository: SettingsRepository
) : ViewModel() { ) : ViewModel() {
private val TAG: String = javaClass.name private val TAG: String = javaClass.name
@ -51,16 +52,16 @@ class SetupViewModel @Inject constructor(
} }
// TODO we could make the check call separate and not pass context here // TODO we could make the check call separate and not pass context here
fun onOpenDocumentTree(context: Context, uri: Uri?, result: (Boolean) -> Unit) { fun onOpenDocumentTree(uri: Uri?, result: (Boolean) -> Unit) {
if (uri != null) { if (uri != null) {
Log.i(TAG, "Got URI: $uri") Log.i(TAG, "Got URI: $uri")
context.contentResolver.takePersistableUriPermission( appContext.contentResolver.takePersistableUriPermission(
uri, uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION
) )
val hasPermission = checkForStoragePermission(context, uri) val hasPermission = checkForStoragePermission(uri)
viewModelScope.launch { viewModelScope.launch {
if (hasPermission) { if (hasPermission) {
@ -72,11 +73,21 @@ class SetupViewModel @Inject constructor(
} }
} }
fun checkForStoragePermission(context: Context, uri: Uri): Boolean { fun onNotificationGrant(isGranted: Boolean, result: (Boolean) -> Unit) {
if (isGranted) {
_uiState.update {
it.copy(notificationPermissionGranted = true)
}
}
result(isGranted)
}
fun checkForStoragePermission(uri: Uri): Boolean {
Log.i(TAG, "Checking for storage permission...") Log.i(TAG, "Checking for storage permission...")
// TODO Is this the right way? This isn't in https://developer.android.com/training/data-storage/shared/documents-files // TODO Is this the right way? This isn't in https://developer.android.com/training/data-storage/shared/documents-files
val directory = DocumentFile.fromTreeUri(context, uri) val directory = DocumentFile.fromTreeUri(appContext, uri)
if (directory == null || !directory.isDirectory) { if (directory == null || !directory.isDirectory) {
Log.i(TAG, "No permission or not a directory") Log.i(TAG, "No permission or not a directory")
@ -100,7 +111,9 @@ class SetupViewModel @Inject constructor(
return true return true
} }
fun detectInstalledLauncherPackage(packageManager: PackageManager): List<String> { fun detectInstalledLauncherPackage(): List<String> {
val packageManager = appContext.packageManager
return POJAV_PACKAGES.filter { return POJAV_PACKAGES.filter {
try { try {
packageManager.getPackageInfo(it, 0) packageManager.getPackageInfo(it, 0)