Update
This commit is contained in:
parent
068d672eee
commit
34af1f025f
14 changed files with 322 additions and 91 deletions
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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> {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
5
app/src/main/res/drawable/baseline_add_24.xml
Normal file
5
app/src/main/res/drawable/baseline_add_24.xml
Normal 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>
|
||||||
5
app/src/main/res/drawable/baseline_arrow_back_24.xml
Normal file
5
app/src/main/res/drawable/baseline_arrow_back_24.xml
Normal 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>
|
||||||
5
app/src/main/res/drawable/baseline_cloud_24.xml
Normal file
5
app/src/main/res/drawable/baseline_cloud_24.xml
Normal 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>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue