A prototype

This commit is contained in:
2025-12-25 13:22:26 +01:00
commit 2f4820028f
59 changed files with 2344 additions and 0 deletions

View File

@@ -0,0 +1,170 @@
package net.tinsae.clocked
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.List
import androidx.compose.material.icons.filled.AccountBox
import androidx.compose.material.icons.filled.Home
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewScreenSizes
import androidx.compose.ui.unit.dp
import net.tinsae.clocked.dashboard.DashboardScreen
import net.tinsae.clocked.history.HistoryScreen
import net.tinsae.clocked.ui.theme.ClockedTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
ClockedTheme {
// The root of the app now decides whether to show Login or Main content
AppEntry()
}
}
}
}
@Composable
fun AppEntry() {
// For now, we'll fake authentication. In a real app, this would come from a ViewModel or repository.
var isAuthenticated by rememberSaveable { mutableStateOf(true) } // Start as not authenticated
if (isAuthenticated) {
// If authenticated, show the main app UI
ClockedApp()
} else {
// If not, show the Login screen
LoginScreen(onLoginSuccess = { })
}
}
@PreviewScreenSizes
@Composable
fun ClockedApp() {
var currentDestination by rememberSaveable { mutableStateOf(AppDestinations.HOME) }
Scaffold(
modifier = Modifier.fillMaxSize(), // Removed background for clarity
topBar = {
Box(
modifier = Modifier
.fillMaxWidth()
// Apply padding ONLY for the top safe area (status bar)
.padding(WindowInsets.safeDrawing.only(WindowInsetsSides.Top).asPaddingValues())
.background(MaterialTheme.colorScheme.surface),
contentAlignment = Alignment.CenterStart
) {
Text(
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp),
text = stringResource(R.string.app_name),
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold
)
}
},
) { innerPadding -> // This padding from the parent Scaffold accounts for the TopBar.
//val layoutDirection = LocalLayoutDirection.current
val contentPadding = PaddingValues(
// Respect the horizontal padding for gestures, etc.
//start = innerPadding.calculateStartPadding(layoutDirection),
//end = innerPadding.calculateEndPadding(layoutDirection),
// Respect the top padding to stay below the top bar.
top = innerPadding.calculateTopPadding(),
// CRITICAL: Ignore the bottom padding from the parent Scaffold.
bottom = 0.dp
)
NavigationSuiteScaffold(
// Apply our custom-calculated padding.
modifier = Modifier.padding(contentPadding),
navigationSuiteItems = {
AppDestinations.entries.forEach {
item(
icon = { Icon(it.icon, contentDescription = it.label) },
label = { Text(it.label) },
selected = it == currentDestination,
onClick = { currentDestination = it }
)
}
}
) {
// The router now shows the correct screen based on the destination.
when (currentDestination) {
AppDestinations.HOME -> DashboardScreen(modifier = Modifier.fillMaxSize())
AppDestinations.HISTORY -> HistoryScreen(modifier = Modifier.fillMaxSize())
AppDestinations.PROFILE -> Greeting(name = "Profile", modifier = Modifier.fillMaxSize())
}
}
}
}
enum class AppDestinations(
val label: String,
val icon: ImageVector,
) {
HOME("Home", Icons.Default.Home),
HISTORY("History", Icons.AutoMirrored.Filled.List),
PROFILE("Profile", Icons.Default.AccountBox),
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Scaffold(modifier = modifier) { innerPadding ->
Text(
text = "Hello $name!",
modifier = Modifier.padding(innerPadding)
)
}
}
/*
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
ClockedTheme {
Greeting("Android")
}
}
*/
// --- PREVIEW THE ACTUAL APP ENTRY POINT ---
@Preview(showBackground = true)
@Composable
fun AppEntryPreview() {
ClockedTheme {
// This preview will correctly show the LoginScreen because isAuthenticated starts as false.
AppEntry()
}
}