This commit is contained in:
Minecon724 2025-04-20 07:51:21 +02:00
commit 34af1f025f
Signed by untrusted user who does not match committer: m724
GPG key ID: A02E6E67AB961189
14 changed files with 322 additions and 91 deletions

View file

@ -1,6 +1,7 @@
package eu.m724.pojavbackup.core.datastore package eu.m724.pojavbackup.core.datastore
import androidx.datastore.core.DataStore import androidx.datastore.core.DataStore
import eu.m724.pojavbackup.proto.BackupDestination
import eu.m724.pojavbackup.proto.Settings import eu.m724.pojavbackup.proto.Settings
import eu.m724.pojavbackup.proto.WorldOrder import eu.m724.pojavbackup.proto.WorldOrder
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -28,4 +29,26 @@ class SettingsRepository @Inject constructor(
.build() .build()
} }
} }
/* Destination */
suspend fun getDestinations(): List<BackupDestination> {
return getSettings().destinationsList
}
suspend fun addDestination(destination: BackupDestination) {
dataStore.updateData {
it.toBuilder()
.addDestinations(destination)
.build()
}
}
suspend fun removeDestination(index: Int) {
dataStore.updateData {
it.toBuilder()
.removeDestinations(index)
.build()
}
}
} }

View file

@ -5,15 +5,14 @@ import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@ -29,7 +28,6 @@ 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.net.toUri
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavDestination.Companion.hierarchy
@ -50,18 +48,20 @@ import eu.m724.pojavbackup.ui.theme.PojavBackupTheme
class HomeActivity : ComponentActivity() { class HomeActivity : ComponentActivity() {
private val viewModel: HomeViewModel by viewModels() private val viewModel: HomeViewModel by viewModels()
private val setupResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == 0) {
// TODO success
} else {
// TODO failure
}
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
viewModel.load( viewModel.load(
onSetupNeeded = { onSetupNeeded = {
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> setupResult.launch(Intent(applicationContext, SetupActivity::class.java))
if (result.resultCode == 0) {
// TODO success
} else {
// TODO failure
}
}.launch(Intent(applicationContext, SetupActivity::class.java))
} }
) )
@ -122,10 +122,14 @@ fun HomeScaffold(
startDestination = HomeScreen.Dashboard, startDestination = HomeScreen.Dashboard,
modifier = Modifier.padding(innerPadding), modifier = Modifier.padding(innerPadding),
enterTransition = { enterTransition = {
fadeIn() + slideInHorizontally(initialOffsetX = { it / 10 }) fadeIn(
animationSpec = tween(100)
) + slideInHorizontally(initialOffsetX = { it / 10 })
}, },
exitTransition = { exitTransition = {
fadeOut() + slideOutHorizontally(targetOffsetX = { -it / 10 }) fadeOut(
animationSpec = tween(100)
) + slideOutHorizontally(targetOffsetX = { -it / 15 })
} }
) { ) {
composable<HomeScreen.Dashboard> { composable<HomeScreen.Dashboard> {

View file

@ -1,6 +1,5 @@
package eu.m724.pojavbackup.home.screen.history package eu.m724.pojavbackup.home.screen.history
import android.system.Os.stat
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -10,9 +9,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CardElevation
import androidx.compose.material3.ElevatedCard import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -23,7 +20,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.m724.pojavbackup.R import eu.m724.pojavbackup.R
import eu.m724.pojavbackup.core.BackupStatus import eu.m724.pojavbackup.core.BackupStatus
@ -31,7 +27,6 @@ import eu.m724.pojavbackup.home.screen.ScreenColumn
import java.time.ZonedDateTime import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle import java.time.format.FormatStyle
import java.util.Locale
@Composable @Composable
fun HistoryScreen() { fun HistoryScreen() {
@ -101,6 +96,7 @@ fun BackupCard(
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp), .padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {

View file

@ -4,6 +4,9 @@ import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideInHorizontally
@ -11,11 +14,14 @@ import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -30,17 +36,26 @@ import dagger.hilt.android.AndroidEntryPoint
import eu.m724.pojavbackup.R import eu.m724.pojavbackup.R
import eu.m724.pojavbackup.settings.screen.SettingsScreen import eu.m724.pojavbackup.settings.screen.SettingsScreen
import eu.m724.pojavbackup.settings.screen.content.ContentScreen import eu.m724.pojavbackup.settings.screen.content.ContentScreen
import eu.m724.pojavbackup.settings.screen.destination.DestinationScreen
import eu.m724.pojavbackup.settings.screen.options.OptionsScreen import eu.m724.pojavbackup.settings.screen.options.OptionsScreen
import eu.m724.pojavbackup.ui.theme.PojavBackupTheme import eu.m724.pojavbackup.ui.theme.PojavBackupTheme
@AndroidEntryPoint @AndroidEntryPoint
class SettingsActivity : ComponentActivity() { class SettingsActivity : ComponentActivity() {
private val viewModel: SettingsViewModel by viewModels()
private val openDocumentTree = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) {
viewModel.onOpenDocumentTree(it)
}
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val startPage = when (intent.getStringExtra("settingsPage")) { val startPage = when (intent.getStringExtra("settingsPage")) {
"options" -> SettingsScreen.Options "options" -> SettingsScreen.Options
"content" -> SettingsScreen.Content "content" -> SettingsScreen.Content
"destination" -> SettingsScreen.Destination
else -> SettingsScreen.Options else -> SettingsScreen.Options
} }
@ -51,6 +66,25 @@ class SettingsActivity : ComponentActivity() {
PojavBackupTheme { PojavBackupTheme {
Scaffold( Scaffold(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBar(
title = {
Text("Settings")
},
navigationIcon = {
IconButton(
onClick = {
finish()
}
) {
Icon(
painter = painterResource(R.drawable.baseline_arrow_back_24),
contentDescription = "Exit Settings"
)
}
}
)
},
bottomBar = { bottomBar = {
NavigationBar { NavigationBar {
ScreenNavigationBarItem( ScreenNavigationBarItem(
@ -65,6 +99,12 @@ class SettingsActivity : ComponentActivity() {
route = SettingsScreen.Content, route = SettingsScreen.Content,
iconResourceId = R.drawable.baseline_folder_copy_24 iconResourceId = R.drawable.baseline_folder_copy_24
) )
ScreenNavigationBarItem(
navController = navController,
label = "Destination",
route = SettingsScreen.Destination,
iconResourceId = R.drawable.baseline_cloud_24
)
} }
} }
) { innerPadding -> ) { innerPadding ->
@ -73,10 +113,14 @@ class SettingsActivity : ComponentActivity() {
startDestination = startPage, startDestination = startPage,
modifier = Modifier.padding(innerPadding), modifier = Modifier.padding(innerPadding),
enterTransition = { enterTransition = {
fadeIn() + slideInHorizontally(initialOffsetX = { it / 10 }) fadeIn(
animationSpec = tween(100)
) + slideInHorizontally(initialOffsetX = { it / 10 })
}, },
exitTransition = { exitTransition = {
fadeOut() + slideOutHorizontally(targetOffsetX = { -it / 10 }) fadeOut(
animationSpec = tween(100)
) + slideOutHorizontally(targetOffsetX = { -it / 15 })
} }
) { ) {
composable<SettingsScreen.Options> { composable<SettingsScreen.Options> {
@ -85,12 +129,19 @@ class SettingsActivity : ComponentActivity() {
composable<SettingsScreen.Content> { composable<SettingsScreen.Content> {
ContentScreen(navController) ContentScreen(navController)
} }
composable<SettingsScreen.Destination> {
DestinationScreen(navController, onAddDestination = { onAddDestination() })
}
// Add more destinations similarly. // Add more destinations similarly.
} }
} }
} }
} }
} }
fun onAddDestination() {
openDocumentTree.launch(null)
}
} }
// TODO those functions are reused in Home // TODO those functions are reused in Home
@ -107,6 +158,7 @@ fun RowScope.ScreenNavigationBarItem(
selected = selected, selected = selected,
onClick = { onClick = {
if (!selected) { if (!selected) {
navController.popBackStack() // this makes back exit settings
navController.navigate(route) navController.navigate(route)
} }
}, },

View file

@ -0,0 +1,42 @@
package eu.m724.pojavbackup.settings
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import eu.m724.pojavbackup.core.datastore.SettingsRepository
import eu.m724.pojavbackup.proto.BackupDestination
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class SettingsViewModel @Inject constructor(
@ApplicationContext private val appContext: Context,
private val settingsRepository: SettingsRepository
) : ViewModel() {
private val TAG = javaClass.name
fun onOpenDocumentTree(uri: Uri?) {
if (uri != null) {
Log.i(TAG, "Got URI: $uri")
appContext.contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
val destination = BackupDestination.newBuilder()
.setLabel("label")
.setUri(uri.toString())
.build()
viewModelScope.launch {
settingsRepository.addDestination(destination)
}
}
}
}

View file

@ -13,6 +13,7 @@ import kotlinx.serialization.Serializable
@Serializable sealed interface SettingsScreen { @Serializable sealed interface SettingsScreen {
@Serializable data object Options : SettingsScreen @Serializable data object Options : SettingsScreen
@Serializable data object Content : SettingsScreen @Serializable data object Content : SettingsScreen
@Serializable data object Destination : SettingsScreen
} }
@Composable @Composable

View file

@ -12,13 +12,11 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CardElevation import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@ -55,41 +53,42 @@ fun ContentScreen(
val worlds by viewModel.worlds.collectAsStateWithLifecycle() val worlds by viewModel.worlds.collectAsStateWithLifecycle()
LaunchedEffect(Unit) {
viewModel.listWorlds()
}
ScreenColumn { ScreenColumn {
val state = rememberReorderableLazyListState(onMove = { from, to -> if (worlds.size <= 1) { // separator
viewModel.moveWorld(from.index, to.index) Text(
}) text = "No worlds available!"
)
} else {
val state = rememberReorderableLazyListState(onMove = { from, to ->
viewModel.moveWorld(from.index, to.index)
})
LazyColumn( LazyColumn(
modifier = Modifier modifier = Modifier
.reorderable(state) .reorderable(state)
.detectReorderAfterLongPress(state), .detectReorderAfterLongPress(state),
state = state.listState, state = state.listState,
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
items( items(
items = worlds, items = worlds,
key = { it.id } key = { it.id }
) { world -> ) { world ->
ReorderableItem(state, key = world.id) { isDragging -> ReorderableItem(state, key = world.id) { isDragging ->
if (!world.id.isEmpty()) { if (!world.id.isEmpty()) {
WorldInfoCard( WorldInfoCard(
bitmap = world.icon, bitmap = world.icon,
id = world.id, id = world.id,
displayName = world.displayName, displayName = world.displayName,
lastPlayed = world.lastPlayed, lastPlayed = world.lastPlayed
elevation = if (isDragging) CardDefaults.elevatedCardElevation() else CardDefaults.cardElevation() )
) } else {
} else { Text(
Text( text = "↑ Worlds above this line will be backed up ↑",
text = "↑ Worlds above this line will be backed up ↑", modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp), textAlign = TextAlign.Center
textAlign = TextAlign.Center )
) }
} }
} }
} }
@ -118,10 +117,7 @@ fun WorldInfoCard(
iconSize: Dp = 64.dp, // Control icon size here iconSize: Dp = 64.dp, // Control icon size here
id: String, id: String,
displayName: String, displayName: String,
lastPlayed: ZonedDateTime, lastPlayed: ZonedDateTime
elevation: CardElevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
internalPadding: Dp = 16.dp,
spacingBetweenIconAndText: Dp = 16.dp
) { ) {
// Formatter for the timestamp - remember caches the formatter across recompositions // Formatter for the timestamp - remember caches the formatter across recompositions
val formatter = remember { val formatter = remember {
@ -131,15 +127,14 @@ fun WorldInfoCard(
lastPlayed.format(formatter) lastPlayed.format(formatter)
} }
Card( ElevatedCard(
modifier = modifier modifier = modifier
.padding(horizontal = 8.dp, vertical = 4.dp) .padding(horizontal = 8.dp, vertical = 4.dp)
.width(300.dp), .width(300.dp)
elevation = elevation
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
.padding(internalPadding) .padding(horizontal = 16.dp, vertical = 8.dp)
.fillMaxWidth(), .fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
@ -163,20 +158,17 @@ fun WorldInfoCard(
contentScale = ContentScale.Crop // Crop is usually best for fixed aspect ratio contentScale = ContentScale.Crop // Crop is usually best for fixed aspect ratio
) )
Spacer(modifier = Modifier.width(spacingBetweenIconAndText)) Spacer(modifier = Modifier.width(16.dp))
// --- Text Column --- // --- Text Column ---
Column( Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(4.dp) verticalArrangement = Arrangement.spacedBy(4.dp)
) { ) {
Text( Text(
text = displayName, text = displayName,
style = MaterialTheme.typography.titleLarge style = MaterialTheme.typography.titleLarge
) )
Spacer(
modifier = Modifier.width(5.dp)
)
Text( Text(
text = "Last played $formattedTimestamp", // Use formatted timestamp text = "Last played $formattedTimestamp", // Use formatted timestamp
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,

View file

@ -10,13 +10,11 @@ import eu.m724.pojavbackup.core.World
import eu.m724.pojavbackup.core.WorldRepository import eu.m724.pojavbackup.core.WorldRepository
import eu.m724.pojavbackup.core.datastore.SettingsRepository import eu.m724.pojavbackup.core.datastore.SettingsRepository
import eu.m724.pojavbackup.proto.WorldOrder import eu.m724.pojavbackup.proto.WorldOrder
import kotlinx.coroutines.Dispatchers
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
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.time.Instant import java.time.Instant
import java.time.ZoneOffset import java.time.ZoneOffset
import javax.inject.Inject import javax.inject.Inject
@ -30,32 +28,16 @@ class ContentScreenViewModel @Inject constructor(
private val _worlds = MutableStateFlow<List<World>>(emptyList()) private val _worlds = MutableStateFlow<List<World>>(emptyList())
val worlds: StateFlow<List<World>> = _worlds.asStateFlow() val worlds: StateFlow<List<World>> = _worlds.asStateFlow()
fun listWorlds() { init {
viewModelScope.launch { viewModelScope.launch {
withContext(Dispatchers.IO) { settingsRepository.getSettingsFlow().collect { settings ->
// TODO load order val worlds = settings.worldOrder.worldIdsList.map {
val worldOrder = settingsRepository.getSettings().worldOrder
val worlds = worldOrder.worldIdsList.map {
// TODO mark deleted worlds better // TODO mark deleted worlds better
worldRepository.getWorld(it) ?: World(it, "Deleted world", Instant.EPOCH.atZone(ZoneOffset.UTC), null) worldRepository.getWorld(it) ?: World(it, "Deleted world", Instant.EPOCH.atZone(ZoneOffset.UTC), null)
}.toMutableList() }.toMutableList()
worlds.add(worldOrder.separatorIndex, World.SEPARATOR) worlds.add(settings.worldOrder.separatorIndex, World.SEPARATOR)
_worlds.update { worlds } // TODO same for extras
}
}
}
fun listExtras() {
viewModelScope.launch {
withContext(Dispatchers.IO) {
// TODO load order
val worldOrder = settingsRepository.getSettings().worldOrder
val worlds = worldOrder.worldIdsList.map {
// TODO mark deleted worlds better
worldRepository.getWorld(it) ?: World(it, "Deleted world", Instant.EPOCH.atZone(ZoneOffset.UTC), null)
}.toMutableList()
worlds.add(worldOrder.separatorIndex, World.SEPARATOR)
_worlds.update { worlds } _worlds.update { worlds }
} }

View file

@ -0,0 +1,83 @@
package eu.m724.pojavbackup.settings.screen.destination
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import eu.m724.pojavbackup.R
import eu.m724.pojavbackup.settings.screen.ScreenColumn
@Composable
fun DestinationScreen(
navController: NavController,
onAddDestination: () -> Unit
) {
val viewModel: DestinationScreenViewModel = hiltViewModel()
val destinations by viewModel.destinations.collectAsStateWithLifecycle()
ScreenColumn {
Column(
modifier = Modifier.fillMaxWidth().fillMaxHeight(0.5f), // TODO make room for more destinations
horizontalAlignment = Alignment.CenterHorizontally
) {
if (destinations.isEmpty()) {
Text("There are no destinations.")
} else {
LazyColumn(
horizontalAlignment = Alignment.CenterHorizontally
) {
items(
items = destinations
) { destination ->
Card { // this is TODO
Text(destination.label)
Text(destination.uri)
}
}
}
}
}
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(
onClick = onAddDestination
) {
Icon(
painter = painterResource(R.drawable.baseline_add_24),
contentDescription = null
)
Spacer(
modifier = Modifier.width(10.dp)
)
Text(
text = "Add destination"
)
}
}
}
}

View file

@ -0,0 +1,34 @@
package eu.m724.pojavbackup.settings.screen.destination
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import eu.m724.pojavbackup.core.datastore.SettingsRepository
import eu.m724.pojavbackup.proto.BackupDestination
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class DestinationScreenViewModel @Inject constructor(
@ApplicationContext private val appContext: Context,
private val settingsRepository: SettingsRepository
) : ViewModel() {
private val _destinations = MutableStateFlow<List<BackupDestination>>(emptyList())
val destinations: StateFlow<List<BackupDestination>> = _destinations.asStateFlow()
init {
viewModelScope.launch {
settingsRepository.getSettingsFlow().collect { settings ->
_destinations.update {
settings.destinationsList
}
}
}
}
}

View file

@ -8,7 +8,14 @@ message WorldOrder {
int32 separatorIndex = 2; int32 separatorIndex = 2;
} }
message BackupDestination {
string label = 1;
string uri = 2; // TODO
}
message Settings { message Settings {
WorldOrder worldOrder = 1; WorldOrder worldOrder = 1;
repeated string extraPaths = 2; repeated string extraPaths = 2;
repeated BackupDestination destinations = 3;
} }

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96z"/>
</vector>