package net.tinsae.clocked import android.content.Context import android.net.Uri import android.os.Bundle import android.util.Log import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size 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.Scaffold 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.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.core.os.LocaleListCompat import androidx.lifecycle.lifecycleScope 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.biometric.GlobalAuthenticator 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 java.text.SimpleDateFormat import java.util.Date class MainActivity : AppCompatActivity() { 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) GlobalAuthenticator() } } } @Composable fun AppEntry(settingsViewModel: SettingsViewModel, authViewModel: AuthViewModel) { val settingsState by settingsViewModel.uiState.collectAsState() val sessionStatus by authViewModel.sessionStatus.collectAsState() // Trigger fetching of logs when the session status changes. 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) } else -> { LoginScreen(authViewModel) } } } } 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( modifier = Modifier.fillMaxSize(), navigationSuiteItems = { AppDestinations.entries.forEach { destination -> item( icon = { Icon( imageVector = destination.icon, contentDescription = stringResource(destination.label), modifier = Modifier.padding(1.dp).size(24.dp) ) }, alwaysShowLabel = false, label = { Text(stringResource(destination.label)) }, selected = destination == currentDestination, onClick = { currentDestination = destination } ) } } ) { Scaffold( modifier = Modifier.fillMaxSize(), /*topBar = { TopBar( appName = stringResource(id = R.string.app_name, currentDestination.label), modifier = Modifier.padding(horizontal = 16.dp), isForContainer = true, actions = { IconButton( onClick = { authViewModel.logout() }, modifier = Modifier.padding(10.dp), ) { Icon(imageVector = Icons.AutoMirrored.Filled.Logout, contentDescription = stringResource(R.string.logout)) } } ) }*/ ) { 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 ) AppDestinations.LOGOUT -> { authViewModel.logout() } } } } } 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), LOGOUT(R.string.logout, Icons.AutoMirrored.Filled.Logout) }