package net.tinsae.clocked import android.content.Context import android.net.Uri import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.app.AppCompatDelegate import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding 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.foundation.layout.safeDrawingPadding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.List import androidx.compose.material.icons.automirrored.filled.Logout import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState 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.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.modifier.modifierLocalConsumer import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.os.LocaleListCompat import androidx.lifecycle.lifecycleScope import io.github.jan.supabase.auth.auth import io.github.jan.supabase.auth.status.SessionStatus import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import net.tinsae.clocked.components.ShowLoadingScreen import net.tinsae.clocked.dashboard.DashboardScreen import net.tinsae.clocked.data.Locale import net.tinsae.clocked.data.LogRepository import net.tinsae.clocked.data.Theme import net.tinsae.clocked.history.HistoryScreen import net.tinsae.clocked.settings.SettingsScreen import net.tinsae.clocked.settings.SettingsViewModel import net.tinsae.clocked.ui.theme.ClockedTheme import net.tinsae.clocked.util.SupabaseClient import java.text.SimpleDateFormat import java.util.Date class MainActivity : ComponentActivity() { private val settingsViewModel: SettingsViewModel by viewModels() private val authViewModel: AuthViewModel by viewModels() private val createDocumentLauncher = registerForActivityResult( ActivityResultContracts.CreateDocument("text/csv") ) { uri: Uri? -> uri?.let { val content = settingsViewModel.uiState.value.pendingCsvContent if (content != null) { try { contentResolver.openOutputStream(it)?.use { outputStream -> outputStream.writer().use { writer -> writer.write(content) } } } catch (e: Exception) { Log.e("MainActivity", "Error writing to file", e) } } } // Always reset the state settingsViewModel.onExportHandled() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycleScope.launch { settingsViewModel.uiState .map { it.pendingCsvContent } .distinctUntilChanged() .collect { csvContent -> if (csvContent != null) { val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", java.util.Locale.US).format(Date()) createDocumentLauncher.launch("Clocked-Export-$timestamp.csv") } } } enableEdgeToEdge() setContent { // The single ViewModel instances from the Activity are passed down. AppEntry(settingsViewModel, authViewModel) } } } @Composable fun AppEntry(settingsViewModel: SettingsViewModel, authViewModel: AuthViewModel) { val settingsState by settingsViewModel.uiState.collectAsState() val sessionStatus by authViewModel.sessionStatus.collectAsState() /*LaunchedEffect(Unit) { SupabaseClient.client.auth.sessionStatus.collect { status -> if (status is SessionStatus.Authenticated && SupabaseClient.client.auth.currentSessionOrNull() != null ) { LogRepository.fetchLogs() } else { return@collect } } }*/ LaunchedEffect(sessionStatus) { if (sessionStatus is SessionStatus.Authenticated){ LogRepository.fetchLogs() } } val useDarkTheme = when (settingsState.theme) { Theme.LIGHT -> false Theme.DARK -> true Theme.SYSTEM -> isSystemInDarkTheme() } val context = LocalContext.current updateLocale(context, settingsState.locale) ClockedTheme(darkTheme = useDarkTheme) { when (sessionStatus) { is SessionStatus.Initializing -> { // Only show loading for the initial session check. ShowLoadingScreen() } is SessionStatus.Authenticated -> { // Just show the app. The ViewModels inside will manage their own loading UI. ClockedApp(settingsViewModel, authViewModel) } is SessionStatus.NotAuthenticated -> { LoginScreen(authViewModel) } else -> {} } } } fun updateLocale(context: Context, locale: Locale) { val localeTag = locale.tag.ifEmpty { null } val localeList = if (localeTag != null) { LocaleListCompat.forLanguageTags(localeTag) } else { LocaleListCompat.getEmptyLocaleList() } AppCompatDelegate.setApplicationLocales(localeList) } @Composable fun ClockedApp(settingsViewModel: SettingsViewModel, authViewModel: AuthViewModel) { var currentDestination by rememberSaveable { mutableStateOf(AppDestinations.HOME) } NavigationSuiteScaffold( navigationSuiteItems = { AppDestinations.entries.forEach { destination -> item( icon = { Icon( imageVector = destination.icon, contentDescription = stringResource(destination.label)) }, alwaysShowLabel = false, label = { Text(stringResource(destination.label)) }, selected = destination == currentDestination, onClick = { currentDestination = destination } ) } } ) { Scaffold( modifier = Modifier.fillMaxSize(), topBar = { Surface( color = colorScheme.surfaceContainer, shadowElevation = 2.dp, // Adds a subtle shadow to lift the bar modifier = Modifier.fillMaxWidth() // No padding needed here on the Surface itself ) { Row( // Apply safe area padding to the Row to push content down modifier = Modifier .fillMaxWidth() .padding(WindowInsets.safeDrawing.only(WindowInsetsSides.Top).asPaddingValues()), verticalAlignment = androidx.compose.ui.Alignment.CenterVertically ) { Text( modifier = Modifier.padding(start = 16.dp), // Vertical padding is handled by Row's alignment text = stringResource(R.string.app_name), style = typography.titleLarge, fontWeight = FontWeight.Bold ) Spacer(modifier = Modifier.weight(1f)) IconButton(onClick = { authViewModel.logout() }, modifier = Modifier.padding(10.dp)) { Icon( imageVector = Icons.AutoMirrored.Filled.Logout, contentDescription = "Logout" // Use string resource ) } } } }, ) { innerPadding -> val modifier = Modifier.fillMaxWidth().padding(innerPadding) when (currentDestination) { AppDestinations.HOME -> DashboardScreen( modifier = modifier) AppDestinations.HISTORY -> HistoryScreen(modifier = modifier) AppDestinations.SETTING -> SettingsScreen( modifier = Modifier, viewModel = settingsViewModel, authViewModel = authViewModel ) } } } } enum class AppDestinations( val label: Int, val icon: ImageVector, ) { HOME(R.string.nav_home, Icons.Default.Home), HISTORY(R.string.nav_history, Icons.AutoMirrored.Filled.List), SETTING(R.string.nav_settings, Icons.Default.Settings), } @Preview(showBackground = true) @Composable fun AppEntryPreview() { ClockedTheme { AppEntry(SettingsViewModel(), AuthViewModel()) } }