From 3a08f3b0467e9161f657a39ca847a56f28a1d065 Mon Sep 17 00:00:00 2001 From: chindaronit Date: Sat, 22 Nov 2025 17:29:39 +0530 Subject: [PATCH] Normalize line endings to LF --- .gitattributes | 1 + .../main/java/com/flux/data/dao/JournalDao.kt | 70 +- .../com/flux/data/model/WorkspaceModel.kt | 102 +- .../java/com/flux/navigation/NavRoutes.kt | 492 ++--- .../java/com/flux/ui/components/Dialog.kt | 1656 ++++++++--------- .../java/com/flux/ui/components/SearchBar.kt | 380 ++-- .../com/flux/ui/screens/calendar/Calendar.kt | 220 +-- gradlew.bat | 188 +- 8 files changed, 1555 insertions(+), 1554 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..94f480d --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf \ No newline at end of file diff --git a/app/src/main/java/com/flux/data/dao/JournalDao.kt b/app/src/main/java/com/flux/data/dao/JournalDao.kt index 165b8af..97286ae 100644 --- a/app/src/main/java/com/flux/data/dao/JournalDao.kt +++ b/app/src/main/java/com/flux/data/dao/JournalDao.kt @@ -1,36 +1,36 @@ -package com.flux.data.dao - -import androidx.room.Dao -import androidx.room.Delete -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import com.flux.data.model.JournalModel -import kotlinx.coroutines.flow.Flow - -@Dao -interface JournalDao { - @Query("SELECT EXISTS(SELECT 1 FROM JournalModel WHERE journalId = :journalId)") - suspend fun exists(journalId: String): Boolean - - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun upsertEntry(entry: JournalModel) - - @Delete - suspend fun deleteEntry(entry: JournalModel) - - @Query("Delete FROM JournalModel where workspaceId = :workspaceId") - suspend fun deleteAllWorkspaceEntries(workspaceId: String) - - @Query( - """ - SELECT * FROM JournalModel - WHERE workspaceId = :workspaceId - ORDER BY dateTime DESC - """ - ) - fun loadAllEntries(workspaceId: String): Flow> - - @Query("SELECT * FROM JournalModel") - suspend fun loadAllEntries(): List +package com.flux.data.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import com.flux.data.model.JournalModel +import kotlinx.coroutines.flow.Flow + +@Dao +interface JournalDao { + @Query("SELECT EXISTS(SELECT 1 FROM JournalModel WHERE journalId = :journalId)") + suspend fun exists(journalId: String): Boolean + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun upsertEntry(entry: JournalModel) + + @Delete + suspend fun deleteEntry(entry: JournalModel) + + @Query("Delete FROM JournalModel where workspaceId = :workspaceId") + suspend fun deleteAllWorkspaceEntries(workspaceId: String) + + @Query( + """ + SELECT * FROM JournalModel + WHERE workspaceId = :workspaceId + ORDER BY dateTime DESC + """ + ) + fun loadAllEntries(workspaceId: String): Flow> + + @Query("SELECT * FROM JournalModel") + suspend fun loadAllEntries(): List } \ No newline at end of file diff --git a/app/src/main/java/com/flux/data/model/WorkspaceModel.kt b/app/src/main/java/com/flux/data/model/WorkspaceModel.kt index 5677e0e..1efb2c4 100644 --- a/app/src/main/java/com/flux/data/model/WorkspaceModel.kt +++ b/app/src/main/java/com/flux/data/model/WorkspaceModel.kt @@ -1,52 +1,52 @@ -package com.flux.data.model - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.Notes -import androidx.compose.material.icons.filled.Analytics -import androidx.compose.material.icons.filled.AutoStories -import androidx.compose.material.icons.filled.CalendarMonth -import androidx.compose.material.icons.filled.Event -import androidx.compose.material.icons.filled.EventAvailable -import androidx.compose.material.icons.filled.TaskAlt -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.res.stringResource -import androidx.room.Entity -import androidx.room.PrimaryKey -import java.util.UUID -import com.flux.R -import kotlinx.serialization.Serializable - -@Serializable -@Entity -data class WorkspaceModel( - @PrimaryKey - val workspaceId: String = UUID.randomUUID().toString(), - val title: String = "", - val description: String = "", - val colorInd: Int = 0, - val cover: String = "", - val icon: Int = 48, - val passKey: String = "", - val isPinned: Boolean = false, - val selectedSpaces: List = emptyList() -) - -data class Space( - val id: Int, - val title: String, - val icon: ImageVector -) - -@Composable -fun getSpacesList(): List { - return listOf( - Space(1, stringResource(R.string.Notes), Icons.AutoMirrored.Default.Notes), - Space(2, stringResource(R.string.To_Do), Icons.Default.TaskAlt), - Space(3, stringResource(R.string.Events), Icons.Default.Event), - Space(4, stringResource(R.string.Calendar), Icons.Default.CalendarMonth), - Space(5, stringResource(R.string.Journal), Icons.Default.AutoStories), - Space(6, stringResource(R.string.Habits), Icons.Default.EventAvailable), - Space(7, stringResource(R.string.Analytics), Icons.Default.Analytics) - ) +package com.flux.data.model + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.Notes +import androidx.compose.material.icons.filled.Analytics +import androidx.compose.material.icons.filled.AutoStories +import androidx.compose.material.icons.filled.CalendarMonth +import androidx.compose.material.icons.filled.Event +import androidx.compose.material.icons.filled.EventAvailable +import androidx.compose.material.icons.filled.TaskAlt +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.util.UUID +import com.flux.R +import kotlinx.serialization.Serializable + +@Serializable +@Entity +data class WorkspaceModel( + @PrimaryKey + val workspaceId: String = UUID.randomUUID().toString(), + val title: String = "", + val description: String = "", + val colorInd: Int = 0, + val cover: String = "", + val icon: Int = 48, + val passKey: String = "", + val isPinned: Boolean = false, + val selectedSpaces: List = emptyList() +) + +data class Space( + val id: Int, + val title: String, + val icon: ImageVector +) + +@Composable +fun getSpacesList(): List { + return listOf( + Space(1, stringResource(R.string.Notes), Icons.AutoMirrored.Default.Notes), + Space(2, stringResource(R.string.To_Do), Icons.Default.TaskAlt), + Space(3, stringResource(R.string.Events), Icons.Default.Event), + Space(4, stringResource(R.string.Calendar), Icons.Default.CalendarMonth), + Space(5, stringResource(R.string.Journal), Icons.Default.AutoStories), + Space(6, stringResource(R.string.Habits), Icons.Default.EventAvailable), + Space(7, stringResource(R.string.Analytics), Icons.Default.Analytics) + ) } \ No newline at end of file diff --git a/app/src/main/java/com/flux/navigation/NavRoutes.kt b/app/src/main/java/com/flux/navigation/NavRoutes.kt index 228529d..6160363 100644 --- a/app/src/main/java/com/flux/navigation/NavRoutes.kt +++ b/app/src/main/java/com/flux/navigation/NavRoutes.kt @@ -1,246 +1,246 @@ -package com.flux.navigation - -import android.os.Build -import androidx.annotation.RequiresApi -import androidx.compose.material3.SnackbarHostState -import androidx.compose.runtime.Composable -import androidx.navigation.NavController -import com.flux.data.model.EventModel -import com.flux.data.model.HabitModel -import com.flux.data.model.JournalModel -import com.flux.data.model.NotesModel -import com.flux.data.model.TodoModel -import com.flux.ui.screens.auth.AuthScreen -import com.flux.ui.screens.events.EventDetails -import com.flux.ui.screens.events.NewEvent -import com.flux.ui.screens.habits.HabitDetails -import com.flux.ui.screens.habits.NewHabit -import com.flux.ui.screens.journal.EditJournal -import com.flux.ui.screens.notes.EditLabels -import com.flux.ui.screens.notes.NoteDetails -import com.flux.ui.screens.settings.About -import com.flux.ui.screens.settings.Backup -import com.flux.ui.screens.settings.Contact -import com.flux.ui.screens.settings.Customize -import com.flux.ui.screens.settings.Languages -import com.flux.ui.screens.settings.Privacy -import com.flux.ui.screens.settings.Settings -import com.flux.ui.screens.todo.TodoDetail -import com.flux.ui.screens.workspaces.WorkSpaces -import com.flux.ui.screens.workspaces.WorkspaceDetails -import com.flux.ui.state.States -import com.flux.ui.viewModel.ViewModels - -sealed class NavRoutes(val route: String) { - data object AuthScreen : NavRoutes("biometric") // auth screen - data object Workspace : NavRoutes("workspace") // workspaces - data object WorkspaceHome : NavRoutes("workspace/details") - data object EditLabels : NavRoutes("workspace/labels/edit") //Labels - data object NoteDetails : NavRoutes("workspace/note/details") // Notes - data object HabitDetails : NavRoutes("workspace/habit/details") // Habit detail - data object NewHabit : NavRoutes("workspace/habit/new") // new habit - data object EventDetails : NavRoutes("workspace/event/details") // event detail - data object TodoDetail : NavRoutes("workspace/todo/details") // TodoList - data object EditJournal : NavRoutes("workspace/journal/edit") // Journal - data object NewEvent : NavRoutes("workspace/event/edit") // new event - - // Settings - data object Settings : NavRoutes("settings") - data object Privacy : NavRoutes("settings/privacy") - data object Customize : NavRoutes("settings/customize") - data object Languages : NavRoutes("settings/language") - data object About : NavRoutes("settings/about") - data object Contact : NavRoutes("settings/contact") - data object Backup : NavRoutes("setting/backup") - - fun withArgs(vararg args: Any): String { - return buildString { - append(route) - args.forEach { arg -> - append("/${arg}") - } - } - } -} - -val AuthScreen = - mapOf Unit>( - NavRoutes.AuthScreen.route to { navController, states -> - AuthScreen(navController, states.settings.data.isBiometricEnabled) - } - ) - -val NotesScreens = - mapOf Unit>( - NavRoutes.NoteDetails.route + "/{workspaceId}" + "/{notesId}" to { navController, notesId, workspaceId, states, viewModel -> - NoteDetails( - navController, - workspaceId, - states.notesState.allNotes.find { it.notesId == notesId } - ?: NotesModel(workspaceId = workspaceId), - states.notesState.allLabels.filter { it.workspaceId == workspaceId }, - viewModel.notesViewModel::onEvent - ) - } - ) - -val HabitScreens = - mapOf Unit>( - NavRoutes.HabitDetails.route + "/{workspaceId}" + "/{habitId}" to { navController, habitId, workspaceId, states, viewModel -> - HabitDetails( - navController, - states.settings.data.cornerRadius, - workspaceId, - states.habitState.allHabits.first { it.id == habitId }, - states.habitState.allInstances.filter { it.habitId == habitId }, - viewModel.habitViewModel::onEvent - ) - }, - NavRoutes.NewHabit.route + "/{workspaceId}" + "/{habitId}" to { navController, habitId, workspaceId, states, viewModel -> - NewHabit( - navController, - states.habitState.allHabits.find { it.id == habitId } ?: HabitModel(workspaceId=workspaceId), - states.settings, - viewModel.habitViewModel::onEvent - ) - } - ) - -val TodoScreens = - mapOf Unit>( - NavRoutes.TodoDetail.route + "/{workspaceId}" + "/{listId}" to { navController, listId, workspaceId, states, viewModel -> - TodoDetail( - navController, - states.todoState.allLists.find { it.id == listId } - ?: TodoModel(workspaceId = workspaceId), - workspaceId, - viewModel.todoViewModel::onEvent - ) - } - ) - -val JournalScreens = - mapOf Unit>( - NavRoutes.EditJournal.route + "/{workspaceId}" + "/{journalId}" to { navController, journalId, workspaceId, states, viewModel -> - EditJournal( - navController, - states.journalState.allEntries.find { it.journalId == journalId } ?: - JournalModel(workspaceId = workspaceId), - viewModel.journalViewModel::onEvent - ) - } - ) - -@RequiresApi(Build.VERSION_CODES.TIRAMISU) -val SettingsScreens = - mapOf Unit>( - NavRoutes.Settings.route to { navController, _, states, viewModels -> - Settings(navController, states.settings) - }, - NavRoutes.Privacy.route to { navController, _, states, viewModels -> - Privacy(navController, states.settings, viewModels.settingsViewModel::onEvent) - }, - NavRoutes.About.route to { navController, _, states, _ -> - About(navController, states.settings.data.cornerRadius) - }, - NavRoutes.Languages.route to { navController, _, states, _ -> - Languages(navController, states.settings) - }, - NavRoutes.Customize.route to { navController, _, states, viewModels -> - Customize(navController, states.settings, viewModels.settingsViewModel::onEvent) - }, - NavRoutes.Contact.route to { navController, _, states, _ -> - Contact(navController, states.settings.data.cornerRadius) - }, - NavRoutes.Backup.route to { navController, _, states, viewModels -> - Backup(navController, states.settings.data.cornerRadius, viewModels.backupViewModel) - } - ) - -val EventScreens = - mapOf Unit>( - NavRoutes.EventDetails.route + "/{workspaceId}" + "/{eventId}" + "/{instanceDate}" to { navController, states, viewModels, eventId, workspaceId, instanceDate, _ -> - EventDetails( - navController, - workspaceId, - states.eventState.allEvent.find { it.id == eventId } ?: EventModel(workspaceId = workspaceId), - states.eventState.allEventInstances.find { it.eventId == eventId && it.instanceDate == instanceDate } == null, - instanceDate, - states.settings, - viewModels.eventViewModel::onEvent - ) - }, - NavRoutes.NewEvent.route + "/{workspaceId}" + "/{eventId}" + "/{eventDate}" to { navController, states, viewModels, eventId, workspaceId, _, eventDate -> - NewEvent( - navController, - states.eventState.allEvent.find { it.id == eventId } ?: EventModel(workspaceId = workspaceId, startDateTime = eventDate), - states.settings, - viewModels.eventViewModel::onEvent - ) - } - ) - -@RequiresApi(Build.VERSION_CODES.TIRAMISU) -val WorkspaceScreens = - mapOf Unit>( - NavRoutes.Workspace.route to { navController, snackbarHostState, states, viewModels, _ -> - WorkSpaces( - snackbarHostState, - navController, - states.settings.data.workspaceGridColumns, - states.settings.data.cornerRadius, - states.workspaceState.allSpaces, - viewModels.notesViewModel::onEvent, - viewModels.eventViewModel::onEvent, - viewModels.habitViewModel::onEvent, - viewModels.todoViewModel::onEvent, - viewModels.workspaceViewModel::onEvent, - viewModels.journalViewModel::onEvent - ) - }, - NavRoutes.WorkspaceHome.route + "/{workspaceId}" to { navController, snackbarHostState, states, viewModels, workspaceId -> - WorkspaceDetails( - navController, - states.notesState.allLabels.filter { it.workspaceId == workspaceId }, - states.settings, - states.notesState.isNotesLoading, - states.eventState.isAllEventsLoading, - states.eventState.isDatedEventLoading, - states.todoState.isLoading, - states.journalState.isLoading, - states.habitState.isLoading, - states.workspaceState.allSpaces.first { it.workspaceId == workspaceId }, - states.eventState.allEvent, - states.notesState.allNotes.filter { it.workspaceId == workspaceId }, - states.notesState.selectedNotes, - states.eventState.selectedYearMonth, - states.eventState.selectedDate, - states.eventState.datedEvents, - states.habitState.allHabits, - states.todoState.allLists, - states.journalState.allEntries, - states.habitState.allInstances, - states.eventState.allEventInstances, - viewModels.workspaceViewModel::onEvent, - viewModels.notesViewModel::onEvent, - viewModels.eventViewModel::onEvent, - viewModels.habitViewModel::onEvent, - viewModels.todoViewModel::onEvent, - viewModels.journalViewModel::onEvent, - viewModels.settingsViewModel::onEvent - ) - } - ) - -val LabelScreens = - mapOf Unit>( - NavRoutes.EditLabels.route + "/{workspaceId}" to { navController, states, viewModels, workspaceId -> - EditLabels( - navController, - states.notesState.isLabelsLoading, - workspaceId, - states.notesState.allLabels, - viewModels.notesViewModel::onEvent - ) - } - ) +package com.flux.navigation + +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.material3.SnackbarHostState +import androidx.compose.runtime.Composable +import androidx.navigation.NavController +import com.flux.data.model.EventModel +import com.flux.data.model.HabitModel +import com.flux.data.model.JournalModel +import com.flux.data.model.NotesModel +import com.flux.data.model.TodoModel +import com.flux.ui.screens.auth.AuthScreen +import com.flux.ui.screens.events.EventDetails +import com.flux.ui.screens.events.NewEvent +import com.flux.ui.screens.habits.HabitDetails +import com.flux.ui.screens.habits.NewHabit +import com.flux.ui.screens.journal.EditJournal +import com.flux.ui.screens.notes.EditLabels +import com.flux.ui.screens.notes.NoteDetails +import com.flux.ui.screens.settings.About +import com.flux.ui.screens.settings.Backup +import com.flux.ui.screens.settings.Contact +import com.flux.ui.screens.settings.Customize +import com.flux.ui.screens.settings.Languages +import com.flux.ui.screens.settings.Privacy +import com.flux.ui.screens.settings.Settings +import com.flux.ui.screens.todo.TodoDetail +import com.flux.ui.screens.workspaces.WorkSpaces +import com.flux.ui.screens.workspaces.WorkspaceDetails +import com.flux.ui.state.States +import com.flux.ui.viewModel.ViewModels + +sealed class NavRoutes(val route: String) { + data object AuthScreen : NavRoutes("biometric") // auth screen + data object Workspace : NavRoutes("workspace") // workspaces + data object WorkspaceHome : NavRoutes("workspace/details") + data object EditLabels : NavRoutes("workspace/labels/edit") //Labels + data object NoteDetails : NavRoutes("workspace/note/details") // Notes + data object HabitDetails : NavRoutes("workspace/habit/details") // Habit detail + data object NewHabit : NavRoutes("workspace/habit/new") // new habit + data object EventDetails : NavRoutes("workspace/event/details") // event detail + data object TodoDetail : NavRoutes("workspace/todo/details") // TodoList + data object EditJournal : NavRoutes("workspace/journal/edit") // Journal + data object NewEvent : NavRoutes("workspace/event/edit") // new event + + // Settings + data object Settings : NavRoutes("settings") + data object Privacy : NavRoutes("settings/privacy") + data object Customize : NavRoutes("settings/customize") + data object Languages : NavRoutes("settings/language") + data object About : NavRoutes("settings/about") + data object Contact : NavRoutes("settings/contact") + data object Backup : NavRoutes("setting/backup") + + fun withArgs(vararg args: Any): String { + return buildString { + append(route) + args.forEach { arg -> + append("/${arg}") + } + } + } +} + +val AuthScreen = + mapOf Unit>( + NavRoutes.AuthScreen.route to { navController, states -> + AuthScreen(navController, states.settings.data.isBiometricEnabled) + } + ) + +val NotesScreens = + mapOf Unit>( + NavRoutes.NoteDetails.route + "/{workspaceId}" + "/{notesId}" to { navController, notesId, workspaceId, states, viewModel -> + NoteDetails( + navController, + workspaceId, + states.notesState.allNotes.find { it.notesId == notesId } + ?: NotesModel(workspaceId = workspaceId), + states.notesState.allLabels.filter { it.workspaceId == workspaceId }, + viewModel.notesViewModel::onEvent + ) + } + ) + +val HabitScreens = + mapOf Unit>( + NavRoutes.HabitDetails.route + "/{workspaceId}" + "/{habitId}" to { navController, habitId, workspaceId, states, viewModel -> + HabitDetails( + navController, + states.settings.data.cornerRadius, + workspaceId, + states.habitState.allHabits.first { it.id == habitId }, + states.habitState.allInstances.filter { it.habitId == habitId }, + viewModel.habitViewModel::onEvent + ) + }, + NavRoutes.NewHabit.route + "/{workspaceId}" + "/{habitId}" to { navController, habitId, workspaceId, states, viewModel -> + NewHabit( + navController, + states.habitState.allHabits.find { it.id == habitId } ?: HabitModel(workspaceId=workspaceId), + states.settings, + viewModel.habitViewModel::onEvent + ) + } + ) + +val TodoScreens = + mapOf Unit>( + NavRoutes.TodoDetail.route + "/{workspaceId}" + "/{listId}" to { navController, listId, workspaceId, states, viewModel -> + TodoDetail( + navController, + states.todoState.allLists.find { it.id == listId } + ?: TodoModel(workspaceId = workspaceId), + workspaceId, + viewModel.todoViewModel::onEvent + ) + } + ) + +val JournalScreens = + mapOf Unit>( + NavRoutes.EditJournal.route + "/{workspaceId}" + "/{journalId}" to { navController, journalId, workspaceId, states, viewModel -> + EditJournal( + navController, + states.journalState.allEntries.find { it.journalId == journalId } ?: + JournalModel(workspaceId = workspaceId), + viewModel.journalViewModel::onEvent + ) + } + ) + +@RequiresApi(Build.VERSION_CODES.TIRAMISU) +val SettingsScreens = + mapOf Unit>( + NavRoutes.Settings.route to { navController, _, states, viewModels -> + Settings(navController, states.settings) + }, + NavRoutes.Privacy.route to { navController, _, states, viewModels -> + Privacy(navController, states.settings, viewModels.settingsViewModel::onEvent) + }, + NavRoutes.About.route to { navController, _, states, _ -> + About(navController, states.settings.data.cornerRadius) + }, + NavRoutes.Languages.route to { navController, _, states, _ -> + Languages(navController, states.settings) + }, + NavRoutes.Customize.route to { navController, _, states, viewModels -> + Customize(navController, states.settings, viewModels.settingsViewModel::onEvent) + }, + NavRoutes.Contact.route to { navController, _, states, _ -> + Contact(navController, states.settings.data.cornerRadius) + }, + NavRoutes.Backup.route to { navController, _, states, viewModels -> + Backup(navController, states.settings.data.cornerRadius, viewModels.backupViewModel) + } + ) + +val EventScreens = + mapOf Unit>( + NavRoutes.EventDetails.route + "/{workspaceId}" + "/{eventId}" + "/{instanceDate}" to { navController, states, viewModels, eventId, workspaceId, instanceDate, _ -> + EventDetails( + navController, + workspaceId, + states.eventState.allEvent.find { it.id == eventId } ?: EventModel(workspaceId = workspaceId), + states.eventState.allEventInstances.find { it.eventId == eventId && it.instanceDate == instanceDate } == null, + instanceDate, + states.settings, + viewModels.eventViewModel::onEvent + ) + }, + NavRoutes.NewEvent.route + "/{workspaceId}" + "/{eventId}" + "/{eventDate}" to { navController, states, viewModels, eventId, workspaceId, _, eventDate -> + NewEvent( + navController, + states.eventState.allEvent.find { it.id == eventId } ?: EventModel(workspaceId = workspaceId, startDateTime = eventDate), + states.settings, + viewModels.eventViewModel::onEvent + ) + } + ) + +@RequiresApi(Build.VERSION_CODES.TIRAMISU) +val WorkspaceScreens = + mapOf Unit>( + NavRoutes.Workspace.route to { navController, snackbarHostState, states, viewModels, _ -> + WorkSpaces( + snackbarHostState, + navController, + states.settings.data.workspaceGridColumns, + states.settings.data.cornerRadius, + states.workspaceState.allSpaces, + viewModels.notesViewModel::onEvent, + viewModels.eventViewModel::onEvent, + viewModels.habitViewModel::onEvent, + viewModels.todoViewModel::onEvent, + viewModels.workspaceViewModel::onEvent, + viewModels.journalViewModel::onEvent + ) + }, + NavRoutes.WorkspaceHome.route + "/{workspaceId}" to { navController, snackbarHostState, states, viewModels, workspaceId -> + WorkspaceDetails( + navController, + states.notesState.allLabels.filter { it.workspaceId == workspaceId }, + states.settings, + states.notesState.isNotesLoading, + states.eventState.isAllEventsLoading, + states.eventState.isDatedEventLoading, + states.todoState.isLoading, + states.journalState.isLoading, + states.habitState.isLoading, + states.workspaceState.allSpaces.first { it.workspaceId == workspaceId }, + states.eventState.allEvent, + states.notesState.allNotes.filter { it.workspaceId == workspaceId }, + states.notesState.selectedNotes, + states.eventState.selectedYearMonth, + states.eventState.selectedDate, + states.eventState.datedEvents, + states.habitState.allHabits, + states.todoState.allLists, + states.journalState.allEntries, + states.habitState.allInstances, + states.eventState.allEventInstances, + viewModels.workspaceViewModel::onEvent, + viewModels.notesViewModel::onEvent, + viewModels.eventViewModel::onEvent, + viewModels.habitViewModel::onEvent, + viewModels.todoViewModel::onEvent, + viewModels.journalViewModel::onEvent, + viewModels.settingsViewModel::onEvent + ) + } + ) + +val LabelScreens = + mapOf Unit>( + NavRoutes.EditLabels.route + "/{workspaceId}" to { navController, states, viewModels, workspaceId -> + EditLabels( + navController, + states.notesState.isLabelsLoading, + workspaceId, + states.notesState.allLabels, + viewModels.notesViewModel::onEvent + ) + } + ) diff --git a/app/src/main/java/com/flux/ui/components/Dialog.kt b/app/src/main/java/com/flux/ui/components/Dialog.kt index fc40c97..c89a73d 100644 --- a/app/src/main/java/com/flux/ui/components/Dialog.kt +++ b/app/src/main/java/com/flux/ui/components/Dialog.kt @@ -1,829 +1,829 @@ -package com.flux.ui.components - -import android.content.Context -import android.content.Intent -import android.widget.Toast -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.FlowRow -import androidx.compose.foundation.layout.IntrinsicSize -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.selection.selectable -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.Label -import androidx.compose.material.icons.automirrored.outlined.LabelImportant -import androidx.compose.material.icons.filled.AccessTime -import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.DeleteOutline -import androidx.compose.material.icons.filled.Edit -import androidx.compose.material.icons.filled.EditCalendar -import androidx.compose.material.icons.filled.FilePresent -import androidx.compose.material.icons.filled.FontDownload -import androidx.compose.material.icons.filled.NewLabel -import androidx.compose.material.icons.filled.TextFields -import androidx.compose.material.icons.outlined.NotificationsActive -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.Checkbox -import androidx.compose.material3.DatePicker -import androidx.compose.material3.DatePickerDialog -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.RadioButton -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.material3.TimeInput -import androidx.compose.material3.TimePicker -import androidx.compose.material3.rememberDatePickerState -import androidx.compose.material3.rememberTimePickerState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog -import androidx.compose.ui.window.DialogProperties -import androidx.core.content.FileProvider -import com.flux.R -import com.flux.data.model.LabelModel -import com.flux.ui.theme.FONTS -import com.mohamedrejeb.richeditor.model.RichTextState -import java.io.File -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Date -import java.util.Locale - -@Composable -fun AddLabelDialog( - initialValue: String, - onConfirmation: (String) -> Unit, - onDismissRequest: () -> Unit -) { - var label by remember { mutableStateOf(initialValue) } - - AlertDialog( - icon = { - CircleWrapper(MaterialTheme.colorScheme.primary) { - val icon = if (initialValue.isBlank()) Icons.Default.Add else Icons.Default.Edit - Icon( - icon, - contentDescription = "Add/Edit Icon", - tint = MaterialTheme.colorScheme.onPrimary - ) - } - }, - title = { - Text( - text = if (initialValue.isBlank()) stringResource(R.string.Add_Label) else stringResource( - R.string.Edit_Label - ) - ) - }, - text = { - OutlinedTextField( - value = label, onValueChange = { label = it }, singleLine = true, - keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions( - onDone = { - onConfirmation(label) - onDismissRequest() - } - )) - }, - onDismissRequest = { onDismissRequest() }, - confirmButton = { - TextButton( - onClick = { - onConfirmation(label) - onDismissRequest() - }, - colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.primary, - contentColor = MaterialTheme.colorScheme.onPrimary - ) - ) { Text(stringResource(R.string.Confirm)) } - }, - dismissButton = { TextButton(onClick = { onDismissRequest() }) { Text(stringResource(R.string.Dismiss)) } } - ) -} - -@Composable -fun SelectLabelDialog( - currNoteLabels: List, - labels: List, - onAddLabel: () -> Unit, - onConfirmation: (List) -> Unit, - onDismissRequest: () -> Unit -) { - val selectedLabel = remember { - mutableStateListOf().apply { - addAll(currNoteLabels) - } - } - - AlertDialog( - icon = { - CircleWrapper(MaterialTheme.colorScheme.primary) { - Icon( - Icons.AutoMirrored.Filled.Label, - contentDescription = "Label Icon", - tint = MaterialTheme.colorScheme.onPrimary - ) - } - }, - title = { Text(text = stringResource(R.string.Select_Label)) }, - text = { - LabelCheckBoxList( - selectedLabel, - labels, - onChecked = { selectedLabel.add(it) }, - onAddLabel = { - onAddLabel() - onDismissRequest() - }, - onUnChecked = { selectedLabel.remove(it) }) - }, - onDismissRequest = { onDismissRequest() }, - confirmButton = { - TextButton( - onClick = { - onConfirmation(selectedLabel) - onDismissRequest() - }, - colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.primary, - contentColor = MaterialTheme.colorScheme.onPrimary - ) - ) { - Text(stringResource(R.string.Confirm)) - } - }, - dismissButton = { TextButton(onClick = { onDismissRequest() }) { Text(stringResource(R.string.Dismiss)) } } - ) -} - -@Composable -fun LabelCheckBoxList( - checkedLabel: List, - labels: List, - onChecked: (LabelModel) -> Unit, - onUnChecked: (LabelModel) -> Unit, - onAddLabel: () -> Unit -) { - - LazyColumn( - modifier = Modifier.heightIn(max = 250.dp) - ) { - item { - Card( - Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(50)) - .padding(vertical = 2.dp) - .clickable(onClick = onAddLabel), - shape = RoundedCornerShape(50) - ) { - Row( - Modifier - .fillMaxWidth() - .padding(vertical = 6.dp, horizontal = 8.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - CircleWrapper(color = MaterialTheme.colorScheme.primary) { - Icon( - Icons.Default.NewLabel, - null, - tint = MaterialTheme.colorScheme.onPrimary - ) - } - Text( - text = stringResource(R.string.Add_Label), - fontWeight = FontWeight.Bold - ) - } - IconButton(onAddLabel) { } - } - } - } - items(labels) { label -> - val isChecked = checkedLabel.contains(label) - Card( - Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(50)) - .padding(vertical = 2.dp) - .clickable { - if (isChecked) { - onUnChecked(label) - } else { - onChecked(label) - } - }, - shape = RoundedCornerShape(50) - ) { - Row( - Modifier - .padding(vertical = 6.dp, horizontal = 8.dp) - .fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - CircleWrapper(color = MaterialTheme.colorScheme.primary) { - Icon( - Icons.AutoMirrored.Outlined.LabelImportant, - null, - tint = MaterialTheme.colorScheme.onPrimary - ) - } - Text( - text = label.value, - fontWeight = FontWeight.Bold, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - modifier = Modifier.width(150.dp) - ) - } - Checkbox(checked = isChecked, onCheckedChange = { - if (isChecked) { - onUnChecked(label) - } else { - onChecked(label) - } - }) - } - } - } - } -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun DatePickerModal( - onDateSelected: (Long?) -> Unit, - onDismiss: () -> Unit -) { - val datePickerState = rememberDatePickerState() - - DatePickerDialog( - onDismissRequest = onDismiss, - confirmButton = { - TextButton(onClick = { - onDateSelected(datePickerState.selectedDateMillis) - onDismiss() - }) { - Text(stringResource(R.string.Set)) - } - }, - dismissButton = { - TextButton(onClick = onDismiss) { - Text(stringResource(R.string.Cancel)) - } - } - ) { - DatePicker(state = datePickerState) - } -} - -fun convertMillisToDate(millis: Long): String { - val formatter = SimpleDateFormat("dd MMM yyyy", Locale.getDefault()) - return formatter.format(Date(millis)) -} - -fun convertMillisToTime(millis: Long, is24Hour: Boolean = false): String { - val pattern = if (is24Hour) "HH:mm" else "hh:mm a" - val formatter = SimpleDateFormat(pattern, Locale.getDefault()) - return formatter.format(Date(millis)) -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun TimePicker( - initialTime: Long, - is24Hour: Boolean = false, - onConfirm: (Long) -> Unit, - onDismiss: () -> Unit, -) { - val currentTime = Calendar.getInstance().apply { timeInMillis = initialTime } - val timePickerState = rememberTimePickerState( - initialHour = currentTime.get(Calendar.HOUR_OF_DAY), - initialMinute = currentTime.get(Calendar.MINUTE), - is24Hour = is24Hour, - ) - var showDial by remember { mutableStateOf(true) } - val toggleIcon = if (showDial) { Icons.Filled.EditCalendar } else { Icons.Filled.AccessTime } - - TimePickerDialog( - onDismiss = onDismiss, - onConfirm = { - val calendar = Calendar.getInstance().apply { - timeInMillis = initialTime - set(Calendar.HOUR_OF_DAY, timePickerState.hour) - set(Calendar.MINUTE, timePickerState.minute) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - } - onConfirm(calendar.timeInMillis) - onDismiss() - }, - toggle = { - IconButton(onClick = { showDial = !showDial }) { - Icon( - imageVector = toggleIcon, - contentDescription = "Time picker type toggle", - ) - } - }, - ) { - if (showDial) { - TimePicker(timePickerState) - } else { - TimeInput(timePickerState) - } - } -} - -// Remove the onDismiss() call from TimePickerDialog's onConfirm button -@Composable -fun TimePickerDialog( - title: String = stringResource(R.string.Select_Time), - onDismiss: () -> Unit, - onConfirm: () -> Unit, - toggle: @Composable () -> Unit = {}, - content: @Composable () -> Unit, -) { - Dialog( - onDismissRequest = onDismiss, - properties = DialogProperties(usePlatformDefaultWidth = false), - ) { - Surface( - shape = MaterialTheme.shapes.extraLarge, - tonalElevation = 6.dp, - modifier = - Modifier - .width(IntrinsicSize.Min) - .height(IntrinsicSize.Min) - .background( - shape = MaterialTheme.shapes.extraLarge, - color = MaterialTheme.colorScheme.surface - ), - ) { - Column( - modifier = Modifier.padding(24.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text( - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 20.dp), - text = title, - style = MaterialTheme.typography.labelMedium - ) - content() - Row( - modifier = Modifier - .height(40.dp) - .fillMaxWidth() - ) { - toggle() - Spacer(modifier = Modifier.weight(1f)) - TextButton(onClick = onDismiss) { Text(stringResource(R.string.Cancel)) } - TextButton(onClick = onConfirm) { Text(stringResource(R.string.Set)) } - } - } - } - } -} - -@Composable -fun EventNotificationDialog( - currentOffset: Long, - onChange: (Long) -> Unit, - onCustomClick: () -> Unit, - onDismissRequest: () -> Unit -) { - val options = listOf( - 0L to stringResource(R.string.On_Time), - 5L to stringResource(R.string.five_minutes_before), - 30L to stringResource(R.string.thirty_minutes_before) - ) - - // Convert to minutes for comparison - val currentMinutes = currentOffset / 1000 / 60 - - Dialog(onDismissRequest = onDismissRequest) { - Card(Modifier.fillMaxWidth()) { - Column( - Modifier.fillMaxWidth().padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - CircleWrapper(MaterialTheme.colorScheme.primary) { - Icon( - Icons.Outlined.NotificationsActive, - contentDescription = "Notification Icon", - tint = MaterialTheme.colorScheme.onPrimary - ) - } - - Text( - stringResource(R.string.Add_Notification), - style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.SemiBold) - ) - Spacer(Modifier.height(16.dp)) - options.forEach { (minutesBefore, label) -> - Row( - modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(8.dp)) - .clickable { - onChange(minutesBefore * 60 * 1000) - onDismissRequest() - } - .padding(8.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text(label) - RadioButton( - selected = currentMinutes == minutesBefore, - onClick = { - onChange(minutesBefore * 60 * 1000) - onDismissRequest() - } - ) - } - } - - Row( - modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(8.dp)) - .clickable { - onCustomClick() - onDismissRequest() - } - .padding(8.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text(stringResource(R.string.Custom)) - RadioButton( - selected = options.none { it.first == currentMinutes }, - onClick = { - onCustomClick() - onDismissRequest() - } - ) - } - } - } - } -} - -@Composable -fun SetPasskeyDialog(onConfirmRequest: (String) -> Unit, onDismissRequest: () -> Unit) { - var passKey by remember { mutableStateOf("") } - - Dialog(onDismissRequest) { - Card(shape = RoundedCornerShape(16.dp)) { - Column( - Modifier - .fillMaxWidth() - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Text( - stringResource(R.string.enterPasskey), - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.SemiBold, - color = MaterialTheme.colorScheme.primary - ) - - OutlinedTextField( - value = passKey, - singleLine = true, - onValueChange = { passKey = it }, - modifier = Modifier.fillMaxWidth(), - keyboardOptions = KeyboardOptions.Default.copy( - imeAction = ImeAction.Done - ), - keyboardActions = KeyboardActions( - onDone = { - onConfirmRequest(passKey) - onDismissRequest() - } - ) - ) - - Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { - TextButton(onDismissRequest) { Text(stringResource(R.string.Dismiss)) } - TextButton( - onClick = { - onConfirmRequest(passKey) - onDismissRequest() - }, - colors = ButtonDefaults.buttonColors() - ) { Text(stringResource(R.string.Confirm)) } - } - } - } - } -} - -@Composable -fun CustomNotificationDialog( - onDismissRequest: () -> Unit, - onConfirm: (offsetMillis: Long) -> Unit -) { - val timeUnits = listOf( - stringResource(R.string.minutes), - stringResource(R.string.hours), - stringResource(R.string.days) - ) - var selectedUnit by remember { mutableStateOf(timeUnits[0]) } - var amountText by remember { mutableStateOf("1") } - val amount = amountText.toIntOrNull()?.coerceAtLeast(1) ?: 1 - - val offsetMillis = when (selectedUnit) { - stringResource(R.string.minutes) -> amount * 60_000L - stringResource(R.string.hours) -> amount * 60 * 60_000L - stringResource(R.string.days) -> amount * 24 * 60 * 60_000L - else -> 0L - } - - Dialog(onDismissRequest = onDismissRequest) { - Card(Modifier.fillMaxWidth()) { - Column(Modifier.padding(16.dp)) { - Text( - stringResource(R.string.Custom_Notification), - style = MaterialTheme.typography.titleLarge - ) - Spacer(Modifier.height(8.dp)) - - timeUnits.forEach { unit -> - Row( - Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(8.dp)) - .selectable( - selected = selectedUnit == unit, - onClick = { selectedUnit = unit } - ) - .padding(2.dp), - verticalAlignment = Alignment.CenterVertically - ) { - RadioButton( - selected = selectedUnit == unit, - onClick = { selectedUnit = unit } - ) - Text(unit, modifier = Modifier.padding(start = 8.dp)) - } - } - Spacer(Modifier.height(16.dp)) - OutlinedTextField( - value = amountText, - onValueChange = { if (it.all { c -> c.isDigit() }) amountText = it }, - label = { Text(stringResource(R.string.Amount)) }, - singleLine = true, - keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Number - ), - modifier = Modifier.fillMaxWidth() - ) - - Spacer(Modifier.height(16.dp)) - Row( - Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End - ) { - TextButton(onClick = onDismissRequest) { Text(stringResource(R.string.Cancel)) } - Spacer(Modifier.width(8.dp)) - Button(onClick = { - onConfirm(offsetMillis) - onDismissRequest() - }) { - Text(stringResource(R.string.Set)) - } - } - } - } - } -} - -@Composable -fun DeleteAlert( - onDismissRequest: () -> Unit, - onConfirmation: () -> Unit, - dialogTitle: String = stringResource(R.string.deleteDialogTitle), - dialogText: String = stringResource(R.string.deleteDialogText), - icon: ImageVector = Icons.Default.DeleteOutline, -) { - AlertDialog( - icon = { - Icon( - icon, - contentDescription = "Delete Icon", - tint = MaterialTheme.colorScheme.error - ) - }, - title = { Text(text = dialogTitle) }, - text = { Text(text = dialogText) }, - onDismissRequest = { onDismissRequest() }, - confirmButton = { TextButton(onClick = { onConfirmation() }) { Text(stringResource(R.string.Confirm)) } }, - dismissButton = { TextButton(onClick = { onDismissRequest() }) { Text(stringResource(R.string.Dismiss)) } } - ) -} - -@Composable -fun ExportNoteDialog( - noteTitle: String, - richTextState: RichTextState, - onDismiss: () -> Unit -) { - val context = LocalContext.current - val fileName = noteTitle.trim().replace("\\s+".toRegex(), "_") - - Dialog(onDismissRequest = onDismiss) { - Card( - shape = RoundedCornerShape(16.dp), - elevation = CardDefaults.cardElevation(8.dp), - modifier = Modifier.fillMaxWidth().padding(16.dp) - ) { - Column(modifier = Modifier.padding(16.dp)) { - Text( - text = stringResource(R.string.Export_As), - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.primary, - modifier = Modifier.padding(bottom = 16.dp) - ) - - // Text Export - Card( - onClick = { - val content = richTextState.annotatedString.text - context.shareTextFile(fileName, content) - }, - modifier = Modifier.fillMaxWidth() - ) { - Row(modifier = Modifier.padding(16.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) { - Icon(Icons.Default.TextFields, null) - Text( stringResource(R.string.Plain_Text)) - } - } - - // Markdown Export - Card( - onClick = { - val markdownContent = richTextState.toMarkdown() - context.shareMarkdownFile(fileName, markdownContent) - onDismiss() - }, - modifier = Modifier.fillMaxWidth() - ) { - Row(modifier = Modifier.padding(16.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) { - Icon(Icons.Default.FilePresent, null) - Text( stringResource(R.string.Markdown)) - } - } - } - } - } -} - -@Composable -fun FontDialog( - selectedFont: Int, - onSelectFont: (Int)->Unit, - onDismissRequest: () -> Unit -){ - Dialog(onDismissRequest = onDismissRequest) { - Card( - Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors(MaterialTheme.colorScheme.surfaceContainerLow) - ) { - Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { - Icon(Icons.Default.FontDownload, null, modifier = Modifier.size(36.dp)) - Text(stringResource(R.string.Fonts), style = MaterialTheme.typography.titleLarge) - FlowRow( - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - FONTS.forEachIndexed { index, font -> - val containerColor = - if (selectedFont == index) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surfaceContainerHigh - val contentColor = - if (selectedFont == index) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurface - Card( - onClick = { - onSelectFont(index) - onDismissRequest() - }, - shape = - if (selectedFont == index) RoundedCornerShape(50) - else RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors( - containerColor = containerColor, - contentColor = contentColor - ) - ) { - Text(font, modifier = Modifier.padding(8.dp)) - } - } - } - } - } - } -} - -fun Context.shareTextFile(fileName: String, content: String) { - try { - val file = File(cacheDir, "$fileName.txt") - file.writeText(content) - - val uri = FileProvider.getUriForFile( - this, - "$packageName.fileprovider", - file - ) - - val shareIntent = Intent(Intent.ACTION_SEND).apply { - type = "text/plain" - putExtra(Intent.EXTRA_STREAM, uri) - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - } - - startActivity(Intent.createChooser(shareIntent, getString(R.string.Share_txt))) - } catch (e: Exception) { - e.printStackTrace() - Toast.makeText(this, getString(R.string.Error_Share_txt), Toast.LENGTH_SHORT).show() - } -} - -fun Context.shareMarkdownFile(fileName: String, markdownContent: String) { - try { - // Create a temporary .md file in cache - val file = File(cacheDir, "$fileName.md") - file.writeText(markdownContent) - - // Get URI using FileProvider - val uri = FileProvider.getUriForFile( - this, - "$packageName.fileprovider", - file - ) - - // Share intent - val shareIntent = Intent(Intent.ACTION_SEND).apply { - type = "text/markdown" - putExtra(Intent.EXTRA_STREAM, uri) - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - } - - startActivity(Intent.createChooser(shareIntent, getString(R.string.Share_md))) - } catch (e: Exception) { - e.printStackTrace() - Toast.makeText(this, getString(R.string.Error_Share_md), Toast.LENGTH_SHORT).show() - } +package com.flux.ui.components + +import android.content.Context +import android.content.Intent +import android.widget.Toast +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.selection.selectable +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.Label +import androidx.compose.material.icons.automirrored.outlined.LabelImportant +import androidx.compose.material.icons.filled.AccessTime +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.DeleteOutline +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.icons.filled.EditCalendar +import androidx.compose.material.icons.filled.FilePresent +import androidx.compose.material.icons.filled.FontDownload +import androidx.compose.material.icons.filled.NewLabel +import androidx.compose.material.icons.filled.TextFields +import androidx.compose.material.icons.outlined.NotificationsActive +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Checkbox +import androidx.compose.material3.DatePicker +import androidx.compose.material3.DatePickerDialog +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.RadioButton +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TimeInput +import androidx.compose.material3.TimePicker +import androidx.compose.material3.rememberDatePickerState +import androidx.compose.material3.rememberTimePickerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import androidx.core.content.FileProvider +import com.flux.R +import com.flux.data.model.LabelModel +import com.flux.ui.theme.FONTS +import com.mohamedrejeb.richeditor.model.RichTextState +import java.io.File +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Date +import java.util.Locale + +@Composable +fun AddLabelDialog( + initialValue: String, + onConfirmation: (String) -> Unit, + onDismissRequest: () -> Unit +) { + var label by remember { mutableStateOf(initialValue) } + + AlertDialog( + icon = { + CircleWrapper(MaterialTheme.colorScheme.primary) { + val icon = if (initialValue.isBlank()) Icons.Default.Add else Icons.Default.Edit + Icon( + icon, + contentDescription = "Add/Edit Icon", + tint = MaterialTheme.colorScheme.onPrimary + ) + } + }, + title = { + Text( + text = if (initialValue.isBlank()) stringResource(R.string.Add_Label) else stringResource( + R.string.Edit_Label + ) + ) + }, + text = { + OutlinedTextField( + value = label, onValueChange = { label = it }, singleLine = true, + keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions( + onDone = { + onConfirmation(label) + onDismissRequest() + } + )) + }, + onDismissRequest = { onDismissRequest() }, + confirmButton = { + TextButton( + onClick = { + onConfirmation(label) + onDismissRequest() + }, + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary + ) + ) { Text(stringResource(R.string.Confirm)) } + }, + dismissButton = { TextButton(onClick = { onDismissRequest() }) { Text(stringResource(R.string.Dismiss)) } } + ) +} + +@Composable +fun SelectLabelDialog( + currNoteLabels: List, + labels: List, + onAddLabel: () -> Unit, + onConfirmation: (List) -> Unit, + onDismissRequest: () -> Unit +) { + val selectedLabel = remember { + mutableStateListOf().apply { + addAll(currNoteLabels) + } + } + + AlertDialog( + icon = { + CircleWrapper(MaterialTheme.colorScheme.primary) { + Icon( + Icons.AutoMirrored.Filled.Label, + contentDescription = "Label Icon", + tint = MaterialTheme.colorScheme.onPrimary + ) + } + }, + title = { Text(text = stringResource(R.string.Select_Label)) }, + text = { + LabelCheckBoxList( + selectedLabel, + labels, + onChecked = { selectedLabel.add(it) }, + onAddLabel = { + onAddLabel() + onDismissRequest() + }, + onUnChecked = { selectedLabel.remove(it) }) + }, + onDismissRequest = { onDismissRequest() }, + confirmButton = { + TextButton( + onClick = { + onConfirmation(selectedLabel) + onDismissRequest() + }, + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary + ) + ) { + Text(stringResource(R.string.Confirm)) + } + }, + dismissButton = { TextButton(onClick = { onDismissRequest() }) { Text(stringResource(R.string.Dismiss)) } } + ) +} + +@Composable +fun LabelCheckBoxList( + checkedLabel: List, + labels: List, + onChecked: (LabelModel) -> Unit, + onUnChecked: (LabelModel) -> Unit, + onAddLabel: () -> Unit +) { + + LazyColumn( + modifier = Modifier.heightIn(max = 250.dp) + ) { + item { + Card( + Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(50)) + .padding(vertical = 2.dp) + .clickable(onClick = onAddLabel), + shape = RoundedCornerShape(50) + ) { + Row( + Modifier + .fillMaxWidth() + .padding(vertical = 6.dp, horizontal = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + CircleWrapper(color = MaterialTheme.colorScheme.primary) { + Icon( + Icons.Default.NewLabel, + null, + tint = MaterialTheme.colorScheme.onPrimary + ) + } + Text( + text = stringResource(R.string.Add_Label), + fontWeight = FontWeight.Bold + ) + } + IconButton(onAddLabel) { } + } + } + } + items(labels) { label -> + val isChecked = checkedLabel.contains(label) + Card( + Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(50)) + .padding(vertical = 2.dp) + .clickable { + if (isChecked) { + onUnChecked(label) + } else { + onChecked(label) + } + }, + shape = RoundedCornerShape(50) + ) { + Row( + Modifier + .padding(vertical = 6.dp, horizontal = 8.dp) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + CircleWrapper(color = MaterialTheme.colorScheme.primary) { + Icon( + Icons.AutoMirrored.Outlined.LabelImportant, + null, + tint = MaterialTheme.colorScheme.onPrimary + ) + } + Text( + text = label.value, + fontWeight = FontWeight.Bold, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + modifier = Modifier.width(150.dp) + ) + } + Checkbox(checked = isChecked, onCheckedChange = { + if (isChecked) { + onUnChecked(label) + } else { + onChecked(label) + } + }) + } + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DatePickerModal( + onDateSelected: (Long?) -> Unit, + onDismiss: () -> Unit +) { + val datePickerState = rememberDatePickerState() + + DatePickerDialog( + onDismissRequest = onDismiss, + confirmButton = { + TextButton(onClick = { + onDateSelected(datePickerState.selectedDateMillis) + onDismiss() + }) { + Text(stringResource(R.string.Set)) + } + }, + dismissButton = { + TextButton(onClick = onDismiss) { + Text(stringResource(R.string.Cancel)) + } + } + ) { + DatePicker(state = datePickerState) + } +} + +fun convertMillisToDate(millis: Long): String { + val formatter = SimpleDateFormat("dd MMM yyyy", Locale.getDefault()) + return formatter.format(Date(millis)) +} + +fun convertMillisToTime(millis: Long, is24Hour: Boolean = false): String { + val pattern = if (is24Hour) "HH:mm" else "hh:mm a" + val formatter = SimpleDateFormat(pattern, Locale.getDefault()) + return formatter.format(Date(millis)) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun TimePicker( + initialTime: Long, + is24Hour: Boolean = false, + onConfirm: (Long) -> Unit, + onDismiss: () -> Unit, +) { + val currentTime = Calendar.getInstance().apply { timeInMillis = initialTime } + val timePickerState = rememberTimePickerState( + initialHour = currentTime.get(Calendar.HOUR_OF_DAY), + initialMinute = currentTime.get(Calendar.MINUTE), + is24Hour = is24Hour, + ) + var showDial by remember { mutableStateOf(true) } + val toggleIcon = if (showDial) { Icons.Filled.EditCalendar } else { Icons.Filled.AccessTime } + + TimePickerDialog( + onDismiss = onDismiss, + onConfirm = { + val calendar = Calendar.getInstance().apply { + timeInMillis = initialTime + set(Calendar.HOUR_OF_DAY, timePickerState.hour) + set(Calendar.MINUTE, timePickerState.minute) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + } + onConfirm(calendar.timeInMillis) + onDismiss() + }, + toggle = { + IconButton(onClick = { showDial = !showDial }) { + Icon( + imageVector = toggleIcon, + contentDescription = "Time picker type toggle", + ) + } + }, + ) { + if (showDial) { + TimePicker(timePickerState) + } else { + TimeInput(timePickerState) + } + } +} + +// Remove the onDismiss() call from TimePickerDialog's onConfirm button +@Composable +fun TimePickerDialog( + title: String = stringResource(R.string.Select_Time), + onDismiss: () -> Unit, + onConfirm: () -> Unit, + toggle: @Composable () -> Unit = {}, + content: @Composable () -> Unit, +) { + Dialog( + onDismissRequest = onDismiss, + properties = DialogProperties(usePlatformDefaultWidth = false), + ) { + Surface( + shape = MaterialTheme.shapes.extraLarge, + tonalElevation = 6.dp, + modifier = + Modifier + .width(IntrinsicSize.Min) + .height(IntrinsicSize.Min) + .background( + shape = MaterialTheme.shapes.extraLarge, + color = MaterialTheme.colorScheme.surface + ), + ) { + Column( + modifier = Modifier.padding(24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 20.dp), + text = title, + style = MaterialTheme.typography.labelMedium + ) + content() + Row( + modifier = Modifier + .height(40.dp) + .fillMaxWidth() + ) { + toggle() + Spacer(modifier = Modifier.weight(1f)) + TextButton(onClick = onDismiss) { Text(stringResource(R.string.Cancel)) } + TextButton(onClick = onConfirm) { Text(stringResource(R.string.Set)) } + } + } + } + } +} + +@Composable +fun EventNotificationDialog( + currentOffset: Long, + onChange: (Long) -> Unit, + onCustomClick: () -> Unit, + onDismissRequest: () -> Unit +) { + val options = listOf( + 0L to stringResource(R.string.On_Time), + 5L to stringResource(R.string.five_minutes_before), + 30L to stringResource(R.string.thirty_minutes_before) + ) + + // Convert to minutes for comparison + val currentMinutes = currentOffset / 1000 / 60 + + Dialog(onDismissRequest = onDismissRequest) { + Card(Modifier.fillMaxWidth()) { + Column( + Modifier.fillMaxWidth().padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + CircleWrapper(MaterialTheme.colorScheme.primary) { + Icon( + Icons.Outlined.NotificationsActive, + contentDescription = "Notification Icon", + tint = MaterialTheme.colorScheme.onPrimary + ) + } + + Text( + stringResource(R.string.Add_Notification), + style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.SemiBold) + ) + Spacer(Modifier.height(16.dp)) + options.forEach { (minutesBefore, label) -> + Row( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(8.dp)) + .clickable { + onChange(minutesBefore * 60 * 1000) + onDismissRequest() + } + .padding(8.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text(label) + RadioButton( + selected = currentMinutes == minutesBefore, + onClick = { + onChange(minutesBefore * 60 * 1000) + onDismissRequest() + } + ) + } + } + + Row( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(8.dp)) + .clickable { + onCustomClick() + onDismissRequest() + } + .padding(8.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text(stringResource(R.string.Custom)) + RadioButton( + selected = options.none { it.first == currentMinutes }, + onClick = { + onCustomClick() + onDismissRequest() + } + ) + } + } + } + } +} + +@Composable +fun SetPasskeyDialog(onConfirmRequest: (String) -> Unit, onDismissRequest: () -> Unit) { + var passKey by remember { mutableStateOf("") } + + Dialog(onDismissRequest) { + Card(shape = RoundedCornerShape(16.dp)) { + Column( + Modifier + .fillMaxWidth() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + stringResource(R.string.enterPasskey), + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.primary + ) + + OutlinedTextField( + value = passKey, + singleLine = true, + onValueChange = { passKey = it }, + modifier = Modifier.fillMaxWidth(), + keyboardOptions = KeyboardOptions.Default.copy( + imeAction = ImeAction.Done + ), + keyboardActions = KeyboardActions( + onDone = { + onConfirmRequest(passKey) + onDismissRequest() + } + ) + ) + + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { + TextButton(onDismissRequest) { Text(stringResource(R.string.Dismiss)) } + TextButton( + onClick = { + onConfirmRequest(passKey) + onDismissRequest() + }, + colors = ButtonDefaults.buttonColors() + ) { Text(stringResource(R.string.Confirm)) } + } + } + } + } +} + +@Composable +fun CustomNotificationDialog( + onDismissRequest: () -> Unit, + onConfirm: (offsetMillis: Long) -> Unit +) { + val timeUnits = listOf( + stringResource(R.string.minutes), + stringResource(R.string.hours), + stringResource(R.string.days) + ) + var selectedUnit by remember { mutableStateOf(timeUnits[0]) } + var amountText by remember { mutableStateOf("1") } + val amount = amountText.toIntOrNull()?.coerceAtLeast(1) ?: 1 + + val offsetMillis = when (selectedUnit) { + stringResource(R.string.minutes) -> amount * 60_000L + stringResource(R.string.hours) -> amount * 60 * 60_000L + stringResource(R.string.days) -> amount * 24 * 60 * 60_000L + else -> 0L + } + + Dialog(onDismissRequest = onDismissRequest) { + Card(Modifier.fillMaxWidth()) { + Column(Modifier.padding(16.dp)) { + Text( + stringResource(R.string.Custom_Notification), + style = MaterialTheme.typography.titleLarge + ) + Spacer(Modifier.height(8.dp)) + + timeUnits.forEach { unit -> + Row( + Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(8.dp)) + .selectable( + selected = selectedUnit == unit, + onClick = { selectedUnit = unit } + ) + .padding(2.dp), + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton( + selected = selectedUnit == unit, + onClick = { selectedUnit = unit } + ) + Text(unit, modifier = Modifier.padding(start = 8.dp)) + } + } + Spacer(Modifier.height(16.dp)) + OutlinedTextField( + value = amountText, + onValueChange = { if (it.all { c -> c.isDigit() }) amountText = it }, + label = { Text(stringResource(R.string.Amount)) }, + singleLine = true, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Number + ), + modifier = Modifier.fillMaxWidth() + ) + + Spacer(Modifier.height(16.dp)) + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End + ) { + TextButton(onClick = onDismissRequest) { Text(stringResource(R.string.Cancel)) } + Spacer(Modifier.width(8.dp)) + Button(onClick = { + onConfirm(offsetMillis) + onDismissRequest() + }) { + Text(stringResource(R.string.Set)) + } + } + } + } + } +} + +@Composable +fun DeleteAlert( + onDismissRequest: () -> Unit, + onConfirmation: () -> Unit, + dialogTitle: String = stringResource(R.string.deleteDialogTitle), + dialogText: String = stringResource(R.string.deleteDialogText), + icon: ImageVector = Icons.Default.DeleteOutline, +) { + AlertDialog( + icon = { + Icon( + icon, + contentDescription = "Delete Icon", + tint = MaterialTheme.colorScheme.error + ) + }, + title = { Text(text = dialogTitle) }, + text = { Text(text = dialogText) }, + onDismissRequest = { onDismissRequest() }, + confirmButton = { TextButton(onClick = { onConfirmation() }) { Text(stringResource(R.string.Confirm)) } }, + dismissButton = { TextButton(onClick = { onDismissRequest() }) { Text(stringResource(R.string.Dismiss)) } } + ) +} + +@Composable +fun ExportNoteDialog( + noteTitle: String, + richTextState: RichTextState, + onDismiss: () -> Unit +) { + val context = LocalContext.current + val fileName = noteTitle.trim().replace("\\s+".toRegex(), "_") + + Dialog(onDismissRequest = onDismiss) { + Card( + shape = RoundedCornerShape(16.dp), + elevation = CardDefaults.cardElevation(8.dp), + modifier = Modifier.fillMaxWidth().padding(16.dp) + ) { + Column(modifier = Modifier.padding(16.dp)) { + Text( + text = stringResource(R.string.Export_As), + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(bottom = 16.dp) + ) + + // Text Export + Card( + onClick = { + val content = richTextState.annotatedString.text + context.shareTextFile(fileName, content) + }, + modifier = Modifier.fillMaxWidth() + ) { + Row(modifier = Modifier.padding(16.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) { + Icon(Icons.Default.TextFields, null) + Text( stringResource(R.string.Plain_Text)) + } + } + + // Markdown Export + Card( + onClick = { + val markdownContent = richTextState.toMarkdown() + context.shareMarkdownFile(fileName, markdownContent) + onDismiss() + }, + modifier = Modifier.fillMaxWidth() + ) { + Row(modifier = Modifier.padding(16.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) { + Icon(Icons.Default.FilePresent, null) + Text( stringResource(R.string.Markdown)) + } + } + } + } + } +} + +@Composable +fun FontDialog( + selectedFont: Int, + onSelectFont: (Int)->Unit, + onDismissRequest: () -> Unit +){ + Dialog(onDismissRequest = onDismissRequest) { + Card( + Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(MaterialTheme.colorScheme.surfaceContainerLow) + ) { + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + Icon(Icons.Default.FontDownload, null, modifier = Modifier.size(36.dp)) + Text(stringResource(R.string.Fonts), style = MaterialTheme.typography.titleLarge) + FlowRow( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + FONTS.forEachIndexed { index, font -> + val containerColor = + if (selectedFont == index) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surfaceContainerHigh + val contentColor = + if (selectedFont == index) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurface + Card( + onClick = { + onSelectFont(index) + onDismissRequest() + }, + shape = + if (selectedFont == index) RoundedCornerShape(50) + else RoundedCornerShape(8.dp), + colors = CardDefaults.cardColors( + containerColor = containerColor, + contentColor = contentColor + ) + ) { + Text(font, modifier = Modifier.padding(8.dp)) + } + } + } + } + } + } +} + +fun Context.shareTextFile(fileName: String, content: String) { + try { + val file = File(cacheDir, "$fileName.txt") + file.writeText(content) + + val uri = FileProvider.getUriForFile( + this, + "$packageName.fileprovider", + file + ) + + val shareIntent = Intent(Intent.ACTION_SEND).apply { + type = "text/plain" + putExtra(Intent.EXTRA_STREAM, uri) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } + + startActivity(Intent.createChooser(shareIntent, getString(R.string.Share_txt))) + } catch (e: Exception) { + e.printStackTrace() + Toast.makeText(this, getString(R.string.Error_Share_txt), Toast.LENGTH_SHORT).show() + } +} + +fun Context.shareMarkdownFile(fileName: String, markdownContent: String) { + try { + // Create a temporary .md file in cache + val file = File(cacheDir, "$fileName.md") + file.writeText(markdownContent) + + // Get URI using FileProvider + val uri = FileProvider.getUriForFile( + this, + "$packageName.fileprovider", + file + ) + + // Share intent + val shareIntent = Intent(Intent.ACTION_SEND).apply { + type = "text/markdown" + putExtra(Intent.EXTRA_STREAM, uri) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } + + startActivity(Intent.createChooser(shareIntent, getString(R.string.Share_md))) + } catch (e: Exception) { + e.printStackTrace() + Toast.makeText(this, getString(R.string.Error_Share_md), Toast.LENGTH_SHORT).show() + } } \ No newline at end of file diff --git a/app/src/main/java/com/flux/ui/components/SearchBar.kt b/app/src/main/java/com/flux/ui/components/SearchBar.kt index 25bbe2f..33d4613 100644 --- a/app/src/main/java/com/flux/ui/components/SearchBar.kt +++ b/app/src/main/java/com/flux/ui/components/SearchBar.kt @@ -1,191 +1,191 @@ -package com.flux.ui.components - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.BasicTextField -import androidx.compose.foundation.text.input.TextFieldState -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Close -import androidx.compose.material.icons.filled.Search -import androidx.compose.material.icons.rounded.Search -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.SearchBar -import androidx.compose.material3.SearchBarDefaults -import androidx.compose.material3.Text -import androidx.compose.material3.surfaceColorAtElevation -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.draw.clip -import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.isTraversalGroup -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.traversalIndex -import androidx.compose.ui.unit.dp -import com.flux.R - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun WorkspaceSearchBar( - textFieldState: TextFieldState, - onSearch: (String) -> Unit, - onSettingsClicked: () -> Unit, - onCloseClicked: () -> Unit, -) { - val query = textFieldState.text.toString() - var expanded by rememberSaveable { mutableStateOf(false) } - val keyboardController = LocalSoftwareKeyboardController.current - - Box( - modifier = Modifier - .fillMaxWidth() - .semantics { isTraversalGroup = true }) { - SearchBar( - modifier = Modifier - .align(Alignment.TopCenter) - .wrapContentHeight() - .semantics { traversalIndex = 0f }, - inputField = { - WorkspaceSearchInputField( - query, - onQueryChange = { - textFieldState.edit { replace(0, length, it) } - onSearch(it) - if (it.isBlank()) { - expanded = false - } - }, - onSearch = { - keyboardController?.hide() - onSearch(query) - }, - onSearchClosed = { - textFieldState.edit { replace(0, length, "") } - onCloseClicked() - onSearch("") - expanded = false - }, - onSettingsClicked = onSettingsClicked - ) - }, - colors = SearchBarDefaults.colors( - containerColor = if (expanded) MaterialTheme.colorScheme.surfaceContainerLow else MaterialTheme.colorScheme.surfaceColorAtElevation( - 6.dp - ) - ), - expanded = expanded, - onExpandedChange = { }, - ) {} - } -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun WorkspaceSearchInputField( - query: String, - onQueryChange: (String) -> Unit, - onSearch: (String) -> Unit, - onSearchClosed: () -> Unit, - onSettingsClicked: () -> Unit -) { - SearchBarDefaults.InputField( - query = query, - onQueryChange = onQueryChange, - onSearch = onSearch, - expanded = false, - onExpandedChange = { }, - placeholder = { Text(stringResource(R.string.Search_Workspaces)) }, - leadingIcon = { Icon(Icons.Rounded.Search, "Search") }, - trailingIcon = { - Row { - if (query.isNotBlank()) CloseButton(onSearchClosed) - SettingsButton(onSettingsClicked) - } - } - ) -} - -@Composable -fun NotesSearchBar( - query: String, - onQueryChange: (String) -> Unit, - onCloseClicked: () -> Unit, - modifier: Modifier = Modifier, - placeholder: String = "Search..." -) { - Box( - modifier = modifier - .height(40.dp) - .clip(RoundedCornerShape(50)) - .background(MaterialTheme.colorScheme.surfaceColorAtElevation(6.dp)) - .padding(horizontal = 12.dp) - ) { - Row( - modifier = Modifier.fillMaxSize(), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = Icons.Default.Search, - contentDescription = "Search", - tint = MaterialTheme.colorScheme.primary - ) - - Spacer(Modifier.width(6.dp)) - - BasicTextField( - value = query, - onValueChange = onQueryChange, - singleLine = true, - textStyle = MaterialTheme.typography.bodySmall.copy( - color = MaterialTheme.colorScheme.onSurface - ), - modifier = Modifier - .weight(1f) - .padding(vertical = 2.dp), // keeps text centered vertically - decorationBox = { innerTextField -> - if (query.isEmpty()) { - Text( - text = placeholder, - style = MaterialTheme.typography.bodySmall.copy( - color = MaterialTheme.colorScheme.primary - ) - ) - } - innerTextField() - } - ) - - IconButton( - onClick = { - onCloseClicked() - onQueryChange("") - }, - modifier = Modifier.size(24.dp) - ) { - Icon( - imageVector = Icons.Default.Close, - contentDescription = "Clear", - tint = MaterialTheme.colorScheme.primary - ) - } - } - } +package com.flux.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.input.TextFieldState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Search +import androidx.compose.material.icons.rounded.Search +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SearchBar +import androidx.compose.material3.SearchBarDefaults +import androidx.compose.material3.Text +import androidx.compose.material3.surfaceColorAtElevation +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.draw.clip +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.isTraversalGroup +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.traversalIndex +import androidx.compose.ui.unit.dp +import com.flux.R + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun WorkspaceSearchBar( + textFieldState: TextFieldState, + onSearch: (String) -> Unit, + onSettingsClicked: () -> Unit, + onCloseClicked: () -> Unit, +) { + val query = textFieldState.text.toString() + var expanded by rememberSaveable { mutableStateOf(false) } + val keyboardController = LocalSoftwareKeyboardController.current + + Box( + modifier = Modifier + .fillMaxWidth() + .semantics { isTraversalGroup = true }) { + SearchBar( + modifier = Modifier + .align(Alignment.TopCenter) + .wrapContentHeight() + .semantics { traversalIndex = 0f }, + inputField = { + WorkspaceSearchInputField( + query, + onQueryChange = { + textFieldState.edit { replace(0, length, it) } + onSearch(it) + if (it.isBlank()) { + expanded = false + } + }, + onSearch = { + keyboardController?.hide() + onSearch(query) + }, + onSearchClosed = { + textFieldState.edit { replace(0, length, "") } + onCloseClicked() + onSearch("") + expanded = false + }, + onSettingsClicked = onSettingsClicked + ) + }, + colors = SearchBarDefaults.colors( + containerColor = if (expanded) MaterialTheme.colorScheme.surfaceContainerLow else MaterialTheme.colorScheme.surfaceColorAtElevation( + 6.dp + ) + ), + expanded = expanded, + onExpandedChange = { }, + ) {} + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun WorkspaceSearchInputField( + query: String, + onQueryChange: (String) -> Unit, + onSearch: (String) -> Unit, + onSearchClosed: () -> Unit, + onSettingsClicked: () -> Unit +) { + SearchBarDefaults.InputField( + query = query, + onQueryChange = onQueryChange, + onSearch = onSearch, + expanded = false, + onExpandedChange = { }, + placeholder = { Text(stringResource(R.string.Search_Workspaces)) }, + leadingIcon = { Icon(Icons.Rounded.Search, "Search") }, + trailingIcon = { + Row { + if (query.isNotBlank()) CloseButton(onSearchClosed) + SettingsButton(onSettingsClicked) + } + } + ) +} + +@Composable +fun NotesSearchBar( + query: String, + onQueryChange: (String) -> Unit, + onCloseClicked: () -> Unit, + modifier: Modifier = Modifier, + placeholder: String = "Search..." +) { + Box( + modifier = modifier + .height(40.dp) + .clip(RoundedCornerShape(50)) + .background(MaterialTheme.colorScheme.surfaceColorAtElevation(6.dp)) + .padding(horizontal = 12.dp) + ) { + Row( + modifier = Modifier.fillMaxSize(), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Default.Search, + contentDescription = "Search", + tint = MaterialTheme.colorScheme.primary + ) + + Spacer(Modifier.width(6.dp)) + + BasicTextField( + value = query, + onValueChange = onQueryChange, + singleLine = true, + textStyle = MaterialTheme.typography.bodySmall.copy( + color = MaterialTheme.colorScheme.onSurface + ), + modifier = Modifier + .weight(1f) + .padding(vertical = 2.dp), // keeps text centered vertically + decorationBox = { innerTextField -> + if (query.isEmpty()) { + Text( + text = placeholder, + style = MaterialTheme.typography.bodySmall.copy( + color = MaterialTheme.colorScheme.primary + ) + ) + } + innerTextField() + } + ) + + IconButton( + onClick = { + onCloseClicked() + onQueryChange("") + }, + modifier = Modifier.size(24.dp) + ) { + Icon( + imageVector = Icons.Default.Close, + contentDescription = "Clear", + tint = MaterialTheme.colorScheme.primary + ) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/flux/ui/screens/calendar/Calendar.kt b/app/src/main/java/com/flux/ui/screens/calendar/Calendar.kt index c35dd24..dc6b047 100644 --- a/app/src/main/java/com/flux/ui/screens/calendar/Calendar.kt +++ b/app/src/main/java/com/flux/ui/screens/calendar/Calendar.kt @@ -1,111 +1,111 @@ -package com.flux.ui.screens.calendar - -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.foundation.lazy.items -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import com.flux.data.model.EventInstanceModel -import com.flux.data.model.EventModel -import com.flux.navigation.Loader -import com.flux.navigation.NavRoutes -import com.flux.ui.components.DailyViewCalendar -import com.flux.ui.components.MonthlyViewCalendar -import com.flux.ui.events.TaskEvents -import com.flux.ui.screens.events.EmptyEvents -import com.flux.ui.screens.events.EventCard -import com.flux.ui.state.Settings -import java.time.YearMonth - -@OptIn(ExperimentalMaterial3Api::class) -fun LazyListScope.calendarItems( - navController: NavController, - radius: Int, - is24HourFormat: Boolean, - isLoading: Boolean, - workspaceId: String, - selectedMonth: YearMonth, - selectedDate: Long, - settings: Settings, - datedEvents: List, - allEventInstances: List, - onTaskEvents: (TaskEvents) -> Unit -) { - val isMonthlyView = settings.data.isCalendarMonthlyView - - if (isMonthlyView) { - item { - MonthlyViewCalendar( - selectedMonth, selectedDate, - onMonthChange = { - onTaskEvents(TaskEvents.ChangeMonth(it)) - }, - onDateChange = { - onTaskEvents(TaskEvents.LoadDateTask(workspaceId, it)) - onTaskEvents(TaskEvents.ChangeDate(it)) - }) - } - } else { - item { - DailyViewCalendar( - selectedMonth, - selectedDate, - onDateChange = { - onTaskEvents(TaskEvents.LoadDateTask(workspaceId, it)) - onTaskEvents(TaskEvents.ChangeDate(it)) - }) - } - } - - if (isLoading) { - item { Loader() } - } else if (datedEvents.isEmpty()) { - item { EmptyEvents() } - } else { - item { Spacer(Modifier.height(24.dp)) } - - val pendingTasks = datedEvents.filter { task -> - val instance = allEventInstances.find { it.eventId == task.id && it.instanceDate == selectedDate } - instance == null - } - - val completedTasks = datedEvents.filter { task -> - val instance = allEventInstances.find { it.eventId == task.id && it.instanceDate == selectedDate } - instance != null - } - - if (pendingTasks.isNotEmpty()) { - items(pendingTasks) { task -> - EventCard( - radius = radius, - is24HourFormat = is24HourFormat, - isPending = true, - title = task.title, - repeat = task.recurrence, - startDateTime = task.startDateTime, - onChangeStatus = { onTaskEvents(TaskEvents.ToggleStatus(true, task.id, workspaceId, selectedDate)) }, - onClick = { navController.navigate(NavRoutes.EventDetails.withArgs(workspaceId, task.id, selectedDate)) } - ) - Spacer(Modifier.height(8.dp)) - } - } - if (completedTasks.isNotEmpty()) { - items(completedTasks) { task -> - EventCard( - radius = radius, - is24HourFormat = is24HourFormat, - isPending = false, - title = task.title, - repeat = task.recurrence, - startDateTime = task.startDateTime, - onChangeStatus = { onTaskEvents(TaskEvents.ToggleStatus(false, task.id, workspaceId, selectedDate)) }, - onClick = { navController.navigate(NavRoutes.EventDetails.withArgs(workspaceId, task.id, selectedDate)) } - ) - Spacer(Modifier.height(8.dp)) - } - } - } +package com.flux.ui.screens.calendar + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import com.flux.data.model.EventInstanceModel +import com.flux.data.model.EventModel +import com.flux.navigation.Loader +import com.flux.navigation.NavRoutes +import com.flux.ui.components.DailyViewCalendar +import com.flux.ui.components.MonthlyViewCalendar +import com.flux.ui.events.TaskEvents +import com.flux.ui.screens.events.EmptyEvents +import com.flux.ui.screens.events.EventCard +import com.flux.ui.state.Settings +import java.time.YearMonth + +@OptIn(ExperimentalMaterial3Api::class) +fun LazyListScope.calendarItems( + navController: NavController, + radius: Int, + is24HourFormat: Boolean, + isLoading: Boolean, + workspaceId: String, + selectedMonth: YearMonth, + selectedDate: Long, + settings: Settings, + datedEvents: List, + allEventInstances: List, + onTaskEvents: (TaskEvents) -> Unit +) { + val isMonthlyView = settings.data.isCalendarMonthlyView + + if (isMonthlyView) { + item { + MonthlyViewCalendar( + selectedMonth, selectedDate, + onMonthChange = { + onTaskEvents(TaskEvents.ChangeMonth(it)) + }, + onDateChange = { + onTaskEvents(TaskEvents.LoadDateTask(workspaceId, it)) + onTaskEvents(TaskEvents.ChangeDate(it)) + }) + } + } else { + item { + DailyViewCalendar( + selectedMonth, + selectedDate, + onDateChange = { + onTaskEvents(TaskEvents.LoadDateTask(workspaceId, it)) + onTaskEvents(TaskEvents.ChangeDate(it)) + }) + } + } + + if (isLoading) { + item { Loader() } + } else if (datedEvents.isEmpty()) { + item { EmptyEvents() } + } else { + item { Spacer(Modifier.height(24.dp)) } + + val pendingTasks = datedEvents.filter { task -> + val instance = allEventInstances.find { it.eventId == task.id && it.instanceDate == selectedDate } + instance == null + } + + val completedTasks = datedEvents.filter { task -> + val instance = allEventInstances.find { it.eventId == task.id && it.instanceDate == selectedDate } + instance != null + } + + if (pendingTasks.isNotEmpty()) { + items(pendingTasks) { task -> + EventCard( + radius = radius, + is24HourFormat = is24HourFormat, + isPending = true, + title = task.title, + repeat = task.recurrence, + startDateTime = task.startDateTime, + onChangeStatus = { onTaskEvents(TaskEvents.ToggleStatus(true, task.id, workspaceId, selectedDate)) }, + onClick = { navController.navigate(NavRoutes.EventDetails.withArgs(workspaceId, task.id, selectedDate)) } + ) + Spacer(Modifier.height(8.dp)) + } + } + if (completedTasks.isNotEmpty()) { + items(completedTasks) { task -> + EventCard( + radius = radius, + is24HourFormat = is24HourFormat, + isPending = false, + title = task.title, + repeat = task.recurrence, + startDateTime = task.startDateTime, + onChangeStatus = { onTaskEvents(TaskEvents.ToggleStatus(false, task.id, workspaceId, selectedDate)) }, + onClick = { navController.navigate(NavRoutes.EventDetails.withArgs(workspaceId, task.id, selectedDate)) } + ) + Spacer(Modifier.height(8.dp)) + } + } + } } \ No newline at end of file diff --git a/gradlew.bat b/gradlew.bat index 5eed7ee..db3a6ac 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,94 +1,94 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH= - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega