Update
This commit is contained in:
parent
d1a60b9a6f
commit
e90d0f21d1
22 changed files with 586 additions and 21 deletions
3
.idea/misc.xml
generated
3
.idea/misc.xml
generated
|
@ -1,6 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
|
|
@ -2,6 +2,7 @@ plugins {
|
|||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
}
|
||||
|
||||
android {
|
||||
|
@ -54,6 +55,7 @@ dependencies {
|
|||
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||
implementation(libs.knbt)
|
||||
implementation(libs.reorderable)
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Required to check which pojavlauncher is installed -->
|
||||
<queries>
|
||||
<package android:name="net.kdt.pojavlaunch" />
|
||||
<package android:name="net.kdt.pojavlaunch.debug" />
|
||||
</queries>
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
|
@ -12,6 +18,11 @@
|
|||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.PojavBackup"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".home.HomeActivity"
|
||||
android:exported="false"
|
||||
android:label="@string/title_activity_home"
|
||||
android:theme="@style/Theme.PojavBackup" />
|
||||
<activity
|
||||
android:name=".setup.MainActivity"
|
||||
android:exported="true"
|
||||
|
@ -25,7 +36,7 @@
|
|||
</activity>
|
||||
|
||||
<provider
|
||||
android:name="eu.m724.pojavbackup.MyCloudProvider"
|
||||
android:name=".MyCloudProvider"
|
||||
android:authorities="eu.m724.pojavbackup"
|
||||
android:exported="true"
|
||||
android:grantUriPermissions="true"
|
||||
|
@ -36,10 +47,4 @@
|
|||
</provider>
|
||||
</application>
|
||||
|
||||
<!-- Required to check which pojavlauncher is installed -->
|
||||
<queries>
|
||||
<package android:name="net.kdt.pojavlaunch" />
|
||||
<package android:name="net.kdt.pojavlaunch.debug" />
|
||||
</queries>
|
||||
|
||||
</manifest>
|
10
app/src/main/java/eu/m724/pojavbackup/core/Backup.kt
Normal file
10
app/src/main/java/eu/m724/pojavbackup/core/Backup.kt
Normal file
|
@ -0,0 +1,10 @@
|
|||
package eu.m724.pojavbackup.core
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
data class Backup(
|
||||
val timestamp: Instant,
|
||||
val status: BackupStatus
|
||||
) {
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package eu.m724.pojavbackup.core
|
||||
|
||||
enum class BackupStatus {
|
||||
SUCCESS,
|
||||
FAILURE,
|
||||
ONGOING,
|
||||
ABORTED
|
||||
}
|
29
app/src/main/java/eu/m724/pojavbackup/core/DataLoader.kt
Normal file
29
app/src/main/java/eu/m724/pojavbackup/core/DataLoader.kt
Normal file
|
@ -0,0 +1,29 @@
|
|||
package eu.m724.pojavbackup.core
|
||||
|
||||
import android.content.ContentResolver
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
|
||||
class DataLoader(
|
||||
private val contentResolver: ContentResolver,
|
||||
private val dataDirectory: DocumentFile
|
||||
) {
|
||||
|
||||
fun listWorlds(): List<World> {
|
||||
return WorldDetector(contentResolver, getSavesDirectory())
|
||||
.listWorlds()
|
||||
}
|
||||
|
||||
fun listWorlds(consumer: (World) -> Unit) {
|
||||
WorldDetector(contentResolver, getSavesDirectory())
|
||||
.listWorlds(consumer)
|
||||
}
|
||||
|
||||
fun getWorld(id: String): World? {
|
||||
return WorldDetector(contentResolver, getSavesDirectory())
|
||||
.getWorld(id)
|
||||
}
|
||||
|
||||
fun getSavesDirectory(): DocumentFile {
|
||||
return dataDirectory.findFile(".minecraft")!!.findFile("saves")!!
|
||||
}
|
||||
}
|
150
app/src/main/java/eu/m724/pojavbackup/home/HomeActivity.kt
Normal file
150
app/src/main/java/eu/m724/pojavbackup/home/HomeActivity.kt
Normal file
|
@ -0,0 +1,150 @@
|
|||
package eu.m724.pojavbackup.home
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.Scaffold
|
||||
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.navigation.NavController
|
||||
import androidx.navigation.NavDestination.Companion.hierarchy
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import eu.m724.pojavbackup.R
|
||||
import eu.m724.pojavbackup.home.screen.Screen
|
||||
import eu.m724.pojavbackup.home.screen.dashboard.DashboardScreen
|
||||
import eu.m724.pojavbackup.home.screen.history.HistoryScreen
|
||||
import eu.m724.pojavbackup.ui.theme.PojavBackupTheme
|
||||
|
||||
class HomeActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
val navController = rememberNavController()
|
||||
|
||||
PojavBackupTheme {
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
bottomBar = {
|
||||
NavigationBar {
|
||||
ScreenNavigationBarItem(
|
||||
navController = navController,
|
||||
label = "Dashboard",
|
||||
route = Screen.Dashboard,
|
||||
iconResourceId = R.drawable.baseline_home_24
|
||||
)
|
||||
ScreenNavigationBarItem(
|
||||
navController = navController,
|
||||
label = "History",
|
||||
route = Screen.History,
|
||||
iconResourceId = R.drawable.baseline_history_24
|
||||
)
|
||||
}
|
||||
}
|
||||
) { innerPadding ->
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = Screen.Dashboard,
|
||||
modifier = Modifier.padding(innerPadding),
|
||||
enterTransition = {
|
||||
fadeIn() + slideInHorizontally(initialOffsetX = { it / 10 })
|
||||
},
|
||||
exitTransition = {
|
||||
fadeOut() + slideOutHorizontally(targetOffsetX = { -it / 10 })
|
||||
}
|
||||
) {
|
||||
composable<Screen.Dashboard> {
|
||||
DashboardScreen(navController)
|
||||
}
|
||||
composable<Screen.History> {
|
||||
HistoryScreen()
|
||||
}
|
||||
// Add more destinations similarly.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RowScope.ScreenNavigationBarItem(
|
||||
navController: NavController,
|
||||
label: String,
|
||||
route: Screen,
|
||||
iconResourceId: Int
|
||||
) {
|
||||
val selected = isSelected(navController, route)
|
||||
|
||||
NavigationBarItem(
|
||||
selected = selected,
|
||||
onClick = {
|
||||
if (!selected) {
|
||||
navController.navigate(route)
|
||||
}
|
||||
},
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterResource(iconResourceId),
|
||||
contentDescription = label
|
||||
)
|
||||
},
|
||||
label = {
|
||||
Text(label)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun isSelected(
|
||||
navController: NavController,
|
||||
route: Screen
|
||||
): Boolean {
|
||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||
val currentDestination = navBackStackEntry?.destination
|
||||
|
||||
// Check if the current destination's hierarchy contains a destination
|
||||
// whose route definition matches the target 'route' object.
|
||||
|
||||
// For @Serializable object routes, Navigation Compose typically uses a stable
|
||||
// route string derived from the object's definition. We compare against that.
|
||||
return currentDestination?.hierarchy?.any { destination ->
|
||||
// Check if the destination in the hierarchy corresponds to the
|
||||
// target 'route' object passed into this function.
|
||||
// For @Serializable objects/classes used as routes, comparing
|
||||
// the destination's 'route' property is the standard way.
|
||||
destination.route == navController.graph.findNode(route)?.route
|
||||
// Explanation:
|
||||
// 1. `navController.graph.findNode(route)`: Finds the NavDestination node
|
||||
// within the navigation graph that corresponds to your @Serializable 'route' object.
|
||||
// (Requires NavController knows about this route, typically added via `composable(typeMap = ...)`)
|
||||
// 2. `?.route`: Gets the unique route string pattern associated with that node.
|
||||
// 3. `destination.route`: Gets the route string pattern of the current destination being checked in the hierarchy.
|
||||
// 4. `==`: Compares if they are the same route.
|
||||
|
||||
} == true // If currentDestination is null, it's not selected, return false.
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
package eu.m724.pojavbackup.home
|
||||
|
||||
class HomeViewModel {
|
||||
}
|
29
app/src/main/java/eu/m724/pojavbackup/home/screen/Screen.kt
Normal file
29
app/src/main/java/eu/m724/pojavbackup/home/screen/Screen.kt
Normal file
|
@ -0,0 +1,29 @@
|
|||
package eu.m724.pojavbackup.home.screen
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable sealed interface Screen {
|
||||
@Serializable data object Dashboard : Screen
|
||||
@Serializable data object History : Screen
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ScreenColumn(
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable (ColumnScope.() -> Unit)
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxSize().padding(top = 50.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
content = content
|
||||
)
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
package eu.m724.pojavbackup.home.screen.dashboard
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.ElevatedCard
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavController
|
||||
import eu.m724.pojavbackup.R
|
||||
import eu.m724.pojavbackup.home.screen.Screen
|
||||
import eu.m724.pojavbackup.home.screen.ScreenColumn
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun DashboardScreen(
|
||||
navController: NavController
|
||||
) {
|
||||
ScreenColumn {
|
||||
FlowRow(
|
||||
maxItemsInEachRow = 3
|
||||
) {
|
||||
DashboardCard(
|
||||
title = "Worlds included",
|
||||
value = "1",
|
||||
iconResourceId = R.drawable.baseline_mosque_24,
|
||||
onClick = {
|
||||
|
||||
}
|
||||
)
|
||||
|
||||
DashboardCard(
|
||||
title = "Health",
|
||||
value = "Good",
|
||||
iconResourceId = R.drawable.baseline_heart_broken_24
|
||||
)
|
||||
|
||||
DashboardCard(
|
||||
title = "Last backup",
|
||||
value = "1d ago",
|
||||
iconResourceId = R.drawable.baseline_access_time_filled_24,
|
||||
onClick = {
|
||||
navController.navigate(Screen.History)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DashboardCard(
|
||||
title: String,
|
||||
value: Any,
|
||||
iconResourceId: Int? = null,
|
||||
onClick: (() -> Unit)? = null
|
||||
) {
|
||||
// TODO or card? or with card colors?
|
||||
ElevatedCard(
|
||||
modifier = Modifier.padding(5.dp).width(180.dp)
|
||||
.clickable(
|
||||
enabled = onClick != null,
|
||||
onClick = onClick ?: {}
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(
|
||||
horizontal = 20.dp,
|
||||
vertical = 10.dp
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = title
|
||||
)
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
if (iconResourceId != null) {
|
||||
Icon(
|
||||
painter = painterResource(iconResourceId),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.padding(start = 2.dp, end = 6.dp).size(26.dp),
|
||||
tint = MaterialTheme.colorScheme.secondary
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = value.toString(),
|
||||
fontSize = 26.sp,
|
||||
fontWeight = FontWeight.Light
|
||||
)
|
||||
|
||||
Spacer(
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
if (onClick != null) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.baseline_arrow_forward_ios_24),
|
||||
contentDescription = "Go to $title",
|
||||
modifier = Modifier.padding(end = 2.dp).size(12.dp),
|
||||
tint = MaterialTheme.colorScheme.secondary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
package eu.m724.pojavbackup.home.screen.history
|
||||
|
||||
import android.system.Os.stat
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.CardElevation
|
||||
import androidx.compose.material3.ElevatedCard
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.m724.pojavbackup.R
|
||||
import eu.m724.pojavbackup.core.BackupStatus
|
||||
import eu.m724.pojavbackup.home.screen.ScreenColumn
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.FormatStyle
|
||||
import java.util.Locale
|
||||
|
||||
@Composable
|
||||
fun HistoryScreen() {
|
||||
ScreenColumn {
|
||||
Column {
|
||||
BackupCard(
|
||||
status = BackupStatus.ONGOING,
|
||||
dateTime = ZonedDateTime.now().minusDays(28)
|
||||
)
|
||||
BackupCard(
|
||||
status = BackupStatus.SUCCESS,
|
||||
dateTime = ZonedDateTime.now().minusDays(7)
|
||||
)
|
||||
BackupCard(
|
||||
status = BackupStatus.FAILURE,
|
||||
dateTime = ZonedDateTime.now().minusDays(14)
|
||||
)
|
||||
BackupCard(
|
||||
status = BackupStatus.ABORTED,
|
||||
dateTime = ZonedDateTime.now().minusDays(21)
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Composable Card that displays a square icon (Bitmap or default drawable) on the left
|
||||
* and text information (ID, Display Name, formatted Timestamp) on the right.
|
||||
*
|
||||
* @param modifier Optional Modifier for the Card.
|
||||
* @param status Backup status
|
||||
* @param dateTime The ZonedDateTime timestamp to display, formatted by locale.
|
||||
*/
|
||||
@Composable
|
||||
fun BackupCard(
|
||||
modifier: Modifier = Modifier,
|
||||
status: BackupStatus,
|
||||
dateTime: ZonedDateTime
|
||||
) {
|
||||
// Formatter for the timestamp - remember caches the formatter across recompositions
|
||||
val formatter = remember {
|
||||
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM) // Adjust FormatStyle as needed (SHORT, MEDIUM, LONG, FULL)
|
||||
}
|
||||
val formattedTimestamp = remember(dateTime, formatter) { // Only reformat when timestamp or formatter changes
|
||||
dateTime.format(formatter)
|
||||
}
|
||||
|
||||
val cardColors = when (status) {
|
||||
BackupStatus.SUCCESS -> CardDefaults.cardColors()
|
||||
BackupStatus.FAILURE -> CardDefaults.cardColors().copy(
|
||||
containerColor = MaterialTheme.colorScheme.errorContainer
|
||||
)
|
||||
BackupStatus.ONGOING -> CardDefaults.cardColors().copy(
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
||||
)
|
||||
BackupStatus.ABORTED -> CardDefaults.cardColors().copy(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainer
|
||||
)
|
||||
}
|
||||
|
||||
ElevatedCard(
|
||||
modifier = modifier
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||
.width(300.dp),
|
||||
colors = cardColors
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.baseline_history_24),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(64.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
.clip(CardDefaults.shape), // TODO match corner radius
|
||||
contentScale = ContentScale.Crop // Crop is usually best for fixed aspect ratio
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
|
||||
// --- Text Column ---
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = status.toString().lowercase().replaceFirstChar { it.uppercase()},
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
Spacer(
|
||||
modifier = Modifier.width(5.dp)
|
||||
)
|
||||
Text(
|
||||
text = "$formattedTimestamp", // Use formatted timestamp
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,10 @@
|
|||
package eu.m724.pojavbackup.setup
|
||||
|
||||
import android.R.attr.end
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
|
@ -47,23 +50,41 @@ import androidx.compose.ui.unit.sp
|
|||
import eu.m724.pojavbackup.ui.theme.PojavBackupTheme
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.compose.NavHost
|
||||
import eu.m724.pojavbackup.R
|
||||
import eu.m724.pojavbackup.home.HomeActivity
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.burnoutcrew.reorderable.ReorderableItem
|
||||
import org.burnoutcrew.reorderable.detectReorder
|
||||
import org.burnoutcrew.reorderable.detectReorderAfterLongPress
|
||||
import org.burnoutcrew.reorderable.rememberReorderableLazyListState
|
||||
import org.burnoutcrew.reorderable.reorderable
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.FormatStyle
|
||||
import kotlin.jvm.javaClass
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
private val viewModel: SetupViewModel by viewModels()
|
||||
|
||||
private val openDocumentTree = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) {
|
||||
viewModel.onOpenDocumentTree(applicationContext, it)
|
||||
viewModel.onOpenDocumentTree(applicationContext, it, { success ->
|
||||
if (success) {
|
||||
onComplete()
|
||||
} else {
|
||||
// TODO instead red text?
|
||||
Toast.makeText(applicationContext, "This is not a PojavLauncher directory.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun onComplete() {
|
||||
startActivity(Intent(applicationContext, HomeActivity::class.java))
|
||||
finishActivity(0)
|
||||
}
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
|
@ -77,6 +98,10 @@ class MainActivity : ComponentActivity() {
|
|||
uri
|
||||
)
|
||||
|
||||
if (hasPermission) {
|
||||
onComplete()
|
||||
}
|
||||
|
||||
setContent {
|
||||
PojavBackupTheme {
|
||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||
|
@ -104,7 +129,7 @@ fun SetupScreen(
|
|||
val worldsState by viewModel.worlds.collectAsStateWithLifecycle()
|
||||
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
modifier = modifier.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
|
@ -154,7 +179,7 @@ fun SetupScreen(
|
|||
}
|
||||
|
||||
// TODO this is for testing world loading only
|
||||
val state = rememberReorderableLazyListState(onMove = { from, to ->
|
||||
/*val state = rememberReorderableLazyListState(onMove = { from, to ->
|
||||
viewModel.moveWorld(from.index, to.index)
|
||||
})
|
||||
|
||||
|
@ -186,7 +211,7 @@ fun SetupScreen(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ class SetupViewModel : ViewModel() {
|
|||
val worlds: StateFlow<List<World>> = _worlds.asStateFlow()
|
||||
|
||||
// TODO we could make the check call separate and not pass context here
|
||||
fun onOpenDocumentTree(context: Context, uri: Uri?) {
|
||||
fun onOpenDocumentTree(context: Context, uri: Uri?, result: (Boolean) -> Unit) {
|
||||
if (uri != null) {
|
||||
Log.i(TAG, "Got URI: $uri")
|
||||
|
||||
|
@ -47,6 +47,7 @@ class SetupViewModel : ViewModel() {
|
|||
)
|
||||
|
||||
val hasPermission = checkForStoragePermission(context, uri)
|
||||
result(hasPermission)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM15.29,16.71L11,12.41V7h2v4.59l3.71,3.71L15.29,16.71z"/>
|
||||
|
||||
</vector>
|
|
@ -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="M6.23,20.23l1.77,1.77l10,-10l-10,-10l-1.77,1.77l8.23,8.23z"/>
|
||||
|
||||
</vector>
|
5
app/src/main/res/drawable/baseline_heart_broken_24.xml
Normal file
5
app/src/main/res/drawable/baseline_heart_broken_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="M16.5,3c-0.96,0 -1.9,0.25 -2.73,0.69L12,9h3l-3,10l1,-9h-3l1.54,-5.39C10.47,3.61 9.01,3 7.5,3C4.42,3 2,5.42 2,8.5c0,4.13 4.16,7.18 10,12.5c5.47,-4.94 10,-8.26 10,-12.5C22,5.42 19.58,3 16.5,3z"/>
|
||||
|
||||
</vector>
|
5
app/src/main/res/drawable/baseline_history_24.xml
Normal file
5
app/src/main/res/drawable/baseline_history_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="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z"/>
|
||||
|
||||
</vector>
|
5
app/src/main/res/drawable/baseline_home_24.xml
Normal file
5
app/src/main/res/drawable/baseline_home_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="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/>
|
||||
|
||||
</vector>
|
7
app/src/main/res/drawable/baseline_mosque_24.xml
Normal file
7
app/src/main/res/drawable/baseline_mosque_24.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<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="M7,8h10c0.29,0 0.57,0.06 0.84,0.13C17.93,7.8 18,7.46 18,7.09c0,-1.31 -0.65,-2.53 -1.74,-3.25L12,1L7.74,3.84C6.65,4.56 6,5.78 6,7.09C6,7.46 6.07,7.8 6.16,8.13C6.43,8.06 6.71,8 7,8z"/>
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M24,7c0,-1.1 -2,-3 -2,-3s-2,1.9 -2,3c0,0.74 0.4,1.38 1,1.72V13h-2v-2c0,-1.1 -0.9,-2 -2,-2H7c-1.1,0 -2,0.9 -2,2v2H3V8.72C3.6,8.38 4,7.74 4,7c0,-1.1 -2,-3 -2,-3S0,5.9 0,7c0,0.74 0.4,1.38 1,1.72V21h9v-4c0,-1.1 0.9,-2 2,-2s2,0.9 2,2v4h9V8.72C23.6,8.38 24,7.74 24,7z"/>
|
||||
|
||||
</vector>
|
|
@ -1,4 +1,5 @@
|
|||
<resources>
|
||||
<string name="app_name">PojavBackup</string>
|
||||
<string name="title_activity_main">MainActivity</string>
|
||||
<string name="title_activity_home">HomeActivity</string>
|
||||
</resources>
|
|
@ -3,4 +3,5 @@ plugins {
|
|||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.kotlin.android) apply false
|
||||
alias(libs.plugins.kotlin.compose) apply false
|
||||
alias(libs.plugins.kotlin.serialization) apply false
|
||||
}
|
|
@ -1,18 +1,19 @@
|
|||
[versions]
|
||||
agp = "8.9.1"
|
||||
kotlin = "2.0.21"
|
||||
coreKtx = "1.10.1"
|
||||
coreKtx = "1.16.0"
|
||||
junit = "4.13.2"
|
||||
junitVersion = "1.1.5"
|
||||
espressoCore = "3.5.1"
|
||||
appcompat = "1.6.1"
|
||||
material = "1.10.0"
|
||||
lifecycleRuntimeKtx = "2.6.1"
|
||||
activityCompose = "1.8.0"
|
||||
composeBom = "2024.09.00"
|
||||
junitVersion = "1.2.1"
|
||||
espressoCore = "3.6.1"
|
||||
appcompat = "1.7.0"
|
||||
material = "1.12.0"
|
||||
lifecycleRuntimeKtx = "2.8.7"
|
||||
activityCompose = "1.10.1"
|
||||
composeBom = "2025.04.00"
|
||||
lifecycleViewmodelCompose = "2.8.7"
|
||||
knbt = "0.11.8"
|
||||
reorderable = "0.9.6"
|
||||
navigation = "2.8.9"
|
||||
|
||||
[libraries]
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
|
@ -34,9 +35,11 @@ androidx-material3 = { group = "androidx.compose.material3", name = "material3"
|
|||
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" }
|
||||
knbt = { group = "net.benwoodworth.knbt", name = "knbt", version.ref = "knbt" }
|
||||
reorderable = { group = "org.burnoutcrew.composereorderable", name = "reorderable", version.ref = "reorderable" }
|
||||
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin"}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue