update: merged Events and Calendar to remove the duplication.

Feat: image preview added to both journal and notes.
This commit is contained in:
chindaronit
2025-12-20 17:36:09 +05:30
parent 21511cf928
commit c4510a1ec7
12 changed files with 288 additions and 306 deletions
@@ -26,10 +26,11 @@ import com.flux.data.model.NotesModel
import com.flux.data.model.SettingsModel
import com.flux.data.model.TodoModel
import com.flux.data.model.WorkspaceModel
import kotlinx.serialization.json.Json
@Database(
entities = [EventModel::class, LabelModel::class, EventInstanceModel::class, SettingsModel::class, NotesModel::class, HabitModel::class, HabitInstanceModel::class, WorkspaceModel::class, TodoModel::class, JournalModel::class],
version = 3,
version = 4,
exportSchema = false
)
@TypeConverters(Converter::class)
@@ -59,4 +60,72 @@ val MIGRATION_2_3 = object : Migration(2, 3) {
db.execSQL("ALTER TABLE HabitModel ADD COLUMN endDateTime INTEGER NOT NULL DEFAULT -1")
db.execSQL("ALTER TABLE EventModel ADD COLUMN endDateTime INTEGER NOT NULL DEFAULT -1")
}
}
}
val MIGRATION_3_4 = object : Migration(3, 4) {
override fun migrate(db: SupportSQLiteDatabase) {
// Add temp column
db.execSQL("""
ALTER TABLE WorkspaceModel
ADD COLUMN selectedSpaces_new TEXT NOT NULL DEFAULT '[]'
""")
val cursor = db.query(
"SELECT workspaceId, selectedSpaces FROM WorkspaceModel"
)
while (cursor.moveToNext()) {
val id = cursor.getString(0)
val raw = cursor.getString(1)
val spaces = Json.decodeFromString<List<Int>>(raw)
.toMutableSet()
// 1. Merge Calendar (4) → Events (3)
if (spaces.remove(4)) {
spaces.add(3)
}
// 2. Downgrade IDs above 4
val normalized = spaces.map {
if (it > 4) it - 1 else it
}.toSet()
val newJson = Json.encodeToString(normalized.toList())
db.execSQL(
"UPDATE WorkspaceModel SET selectedSpaces_new = ? WHERE workspaceId = ?",
arrayOf(newJson, id)
)
}
cursor.close()
// Recreate table (SQLite cannot drop columns)
db.execSQL("""
CREATE TABLE WorkspaceModel_new (
workspaceId TEXT NOT NULL PRIMARY KEY,
title TEXT NOT NULL,
description TEXT NOT NULL,
colorInd INTEGER NOT NULL,
cover TEXT NOT NULL,
icon INTEGER NOT NULL,
passKey TEXT NOT NULL,
isPinned INTEGER NOT NULL,
selectedSpaces TEXT NOT NULL
)
""")
db.execSQL("""
INSERT INTO WorkspaceModel_new
SELECT
workspaceId, title, description, colorInd,
cover, icon, passKey, isPinned, selectedSpaces_new
FROM WorkspaceModel
""")
db.execSQL("DROP TABLE WorkspaceModel")
db.execSQL("ALTER TABLE WorkspaceModel_new RENAME TO WorkspaceModel")
}
}
@@ -4,7 +4,6 @@ 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
@@ -44,9 +43,8 @@ fun getSpacesList(): List<Space> {
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)
Space(4, stringResource(R.string.Journal), Icons.Default.AutoStories),
Space(5, stringResource(R.string.Habits), Icons.Default.EventAvailable),
Space(6, stringResource(R.string.Analytics), Icons.Default.Analytics)
)
}
+2 -1
View File
@@ -15,6 +15,7 @@ import com.flux.data.dao.WorkspaceDao
import com.flux.data.database.FluxDatabase
import com.flux.data.database.MIGRATION_1_2
import com.flux.data.database.MIGRATION_2_3
import com.flux.data.database.MIGRATION_3_4
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -35,7 +36,7 @@ object DataModule {
FluxDatabase::class.java,
"FluxDatabase"
)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
.build()
@Singleton
@@ -204,7 +204,6 @@ val WorkspaceScreens =
states.notesState.allLabels.filter { it.workspaceId == workspaceId },
states.settings,
states.notesState.isNotesLoading,
states.eventState.isAllEventsLoading,
states.eventState.isDatedEventLoading,
states.todoState.isLoading,
states.journalState.isLoading,
@@ -12,7 +12,6 @@ import androidx.compose.material.icons.filled.PushPin
import androidx.compose.material.icons.filled.RemoveCircleOutline
import androidx.compose.material.icons.outlined.Analytics
import androidx.compose.material.icons.outlined.AutoStories
import androidx.compose.material.icons.outlined.CalendarMonth
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material.icons.outlined.Event
@@ -171,8 +170,8 @@ fun SpacesMenu(
}
if (selectedSpaces.contains(4)) {
DropdownMenuItem(
text = { Text(stringResource(R.string.Calendar)) },
leadingIcon = { Icon(Icons.Outlined.CalendarMonth, contentDescription = null) },
text = { Text(stringResource(R.string.Journal)) },
leadingIcon = { Icon(Icons.Outlined.AutoStories, contentDescription = null) },
onClick = {
onConfirm(4)
onDismiss()
@@ -181,8 +180,8 @@ fun SpacesMenu(
}
if (selectedSpaces.contains(5)) {
DropdownMenuItem(
text = { Text(stringResource(R.string.Journal)) },
leadingIcon = { Icon(Icons.Outlined.AutoStories, contentDescription = null) },
text = { Text(stringResource(R.string.Habits)) },
leadingIcon = { Icon(Icons.Outlined.EventAvailable, contentDescription = null) },
onClick = {
onConfirm(5)
onDismiss()
@@ -190,21 +189,11 @@ fun SpacesMenu(
)
}
if (selectedSpaces.contains(6)) {
DropdownMenuItem(
text = { Text(stringResource(R.string.Habits)) },
leadingIcon = { Icon(Icons.Outlined.EventAvailable, contentDescription = null) },
onClick = {
onConfirm(6)
onDismiss()
}
)
}
if (selectedSpaces.contains(7)) {
DropdownMenuItem(
text = { Text(stringResource(R.string.Analytics)) },
leadingIcon = { Icon(Icons.Outlined.Analytics, contentDescription = null) },
onClick = {
onConfirm(7)
onConfirm(6)
onDismiss()
}
)
@@ -4,6 +4,7 @@ import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
@@ -24,6 +25,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
@@ -34,6 +36,7 @@ import androidx.compose.material.icons.automirrored.outlined.FormatAlignLeft
import androidx.compose.material.icons.automirrored.outlined.FormatAlignRight
import androidx.compose.material.icons.automirrored.outlined.FormatListBulleted
import androidx.compose.material.icons.filled.AddPhotoAlternate
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Code
import androidx.compose.material.icons.filled.FormatStrikethrough
import androidx.compose.material.icons.filled.Highlight
@@ -56,12 +59,17 @@ import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
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.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.ParagraphStyle
import androidx.compose.ui.text.PlatformSpanStyle
@@ -74,6 +82,9 @@ import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import coil.compose.rememberAsyncImagePainter
import com.flux.R
import com.flux.data.model.LabelModel
import com.flux.data.model.NotesModel
@@ -107,6 +118,50 @@ fun NotesInputCard(
}
}
)
var previewImage by remember { mutableStateOf<String?>(null) }
if (previewImage != null) {
Dialog(
onDismissRequest = { previewImage = null },
properties = DialogProperties(
usePlatformDefaultWidth = false
)
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.95f))
) {
Image(
painter = rememberAsyncImagePainter(previewImage),
contentDescription = null,
modifier = Modifier
.fillMaxSize()
.padding(24.dp),
contentScale = ContentScale.Fit
)
IconButton(
onClick = { previewImage = null },
modifier = Modifier
.align(Alignment.TopEnd)
.padding(16.dp)
.size(48.dp)
.background(
color = Color.Black.copy(alpha = 0.6f),
shape = CircleShape
)
) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = "Close",
tint = Color.White
)
}
}
}
}
Column(
modifier = Modifier
@@ -114,7 +169,7 @@ fun NotesInputCard(
.padding(innerPadding)
.imePadding(),
) {
Carousel(images) { onRemoveImage(it) }
Carousel(images, {onRemoveImage(it)}) { previewImage=it }
TextField(
value = title,
onValueChange = onTitleChange,
@@ -149,7 +149,7 @@ fun JournalToolBar(navController: NavController, workspaceId: String) {
@RequiresApi(Build.VERSION_CODES.TIRAMISU, Build.VERSION_CODES.S)
@Composable
fun CalendarToolBar(navController: NavController, workspaceId: String, context: Context, selectedDate: Long, isMonthlyView: Boolean, onClick: (Boolean) -> Unit) {
fun EventToolBar(navController: NavController, workspaceId: String, context: Context, selectedDate: Long, isMonthlyView: Boolean, onClick: (Boolean) -> Unit) {
Row {
IconButton({ onClick(!isMonthlyView) }) {
Icon(
@@ -213,32 +213,6 @@ fun HabitToolBar(context: Context, onAddClick: () -> Unit) {
}) { Icon(Icons.Default.Add, null, tint = MaterialTheme.colorScheme.primary) }
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU, Build.VERSION_CODES.S)
@Composable
fun EventToolBar(context: Context, navController: NavController, workspaceId: String) {
IconButton({
if (!canScheduleReminder(context)) {
Toast.makeText(
context,
context.getText(R.string.Reminder_Permission),
Toast.LENGTH_SHORT
).show()
requestExactAlarmPermission(context)
}
if (!isNotificationPermissionGranted(context)) {
Toast.makeText(
context,
context.getText(R.string.Notification_Permission),
Toast.LENGTH_SHORT
).show()
openAppNotificationSettings(context)
}
if (canScheduleReminder(context) && isNotificationPermissionGranted(context)) {
navController.navigate(NavRoutes.NewEvent.withArgs(workspaceId, "", System.currentTimeMillis()))
}
}) { Icon(Icons.Default.Add, null, tint = MaterialTheme.colorScheme.primary) }
}
@Composable
fun NotesToolBar(
navController: NavController,
@@ -1,111 +0,0 @@
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<EventModel>,
allEventInstances: List<EventInstanceModel>,
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))
}
}
}
}
@@ -31,24 +31,24 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.flux.R
import com.flux.data.model.EventInstanceModel
import com.flux.data.model.EventModel
import com.flux.data.model.RecurrenceRule
import com.flux.data.model.occursOn
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.components.shapeManager
import com.flux.ui.events.TaskEvents
import com.flux.ui.state.Settings
import java.text.SimpleDateFormat
import java.time.Instant
import java.time.LocalDate
import java.time.YearMonth
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit
import java.util.Date
import java.util.Locale
@@ -58,128 +58,85 @@ fun LazyListScope.eventHomeItems(
radius: Int,
is24HourFormat: Boolean,
isLoading: Boolean,
allEvents: List<EventModel>,
allEventInstances: List<EventInstanceModel>,
workspaceId: String,
selectedMonth: YearMonth,
selectedDate: Long,
settings: Settings,
datedEvents: List<EventModel>,
allEventInstances: List<EventInstanceModel>,
onTaskEvents: (TaskEvents) -> Unit
) {
val today = LocalDate.now()
val endOfMonth = today.withDayOfMonth(today.lengthOfMonth())
val isMonthlyView = settings.data.isCalendarMonthlyView
// Todays events
val eventsToday = allEvents.filter { it.occursOn(today) }
// Upcoming (at least one occurrence before end of month, but not today)
val upcomingEvents = allEvents.filter { event ->
!event.occursOn(today) &&
(1..ChronoUnit.DAYS.between(today, endOfMonth)).any { offset ->
val date = today.plusDays(offset)
event.occursOn(date)
}
}.distinctBy { it.id }
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 {
if (eventsToday.isEmpty() && upcomingEvents.isEmpty()) {
item { EmptyEvents() }
} else {
// Section for today
if (eventsToday.isNotEmpty()) {
item {
Text(
text = stringResource(R.string.Today),
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(vertical = 8.dp)
)
}
}
item { Spacer(Modifier.height(24.dp)) }
items(eventsToday) { event ->
val instance = allEventInstances.find {
it.eventId == event.id && it.instanceDate == today.toEpochDay()
}
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 = instance == null,
title = event.title,
repeat = event.recurrence,
startDateTime = event.startDateTime,
onChangeStatus = {
onTaskEvents(
TaskEvents.ToggleStatus(
instance == null,
event.id,
workspaceId,
today.toEpochDay()
)
)
},
onClick = { navController.navigate(NavRoutes.EventDetails.withArgs(workspaceId, event.id, today.toEpochDay())) }
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))
}
}
// Section for upcoming
if (upcomingEvents.isNotEmpty()) {
item {
Text(
text = stringResource(R.string.Upcoming),
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(vertical = 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)) }
)
}
items(upcomingEvents) { event ->
// Find the *next occurrence date* after today
val nextDate = (1..ChronoUnit.DAYS.between(today, endOfMonth))
.map { today.plusDays(it) }
.firstOrNull { event.occursOn(it) }
if (nextDate != null) {
val epochDay = nextDate.toEpochDay()
// Find if an instance already exists for that date
val instance = allEventInstances.find {
it.eventId == event.id && it.instanceDate == epochDay
}
EventCard(
radius = radius,
is24HourFormat = is24HourFormat,
isPending = instance == null,
title = event.title,
repeat = event.recurrence,
startDateTime = event.startDateTime,
onChangeStatus = {
onTaskEvents(
TaskEvents.ToggleStatus(
instance == null,
event.id,
workspaceId,
epochDay
)
)
},
onClick = {
navController.navigate(
NavRoutes.EventDetails.withArgs(
workspaceId,
event.id,
epochDay
)
)
}
)
Spacer(Modifier.height(8.dp))
}
Spacer(Modifier.height(8.dp))
}
}
}
@@ -3,6 +3,9 @@ package com.flux.ui.screens.journal
import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -13,10 +16,13 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.DeleteOutline
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -47,8 +53,11 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.navigation.NavController
import coil.compose.AsyncImage
import coil.compose.rememberAsyncImagePainter
import com.flux.R
import com.flux.data.model.JournalModel
import com.flux.ui.components.DatePickerModal
@@ -71,9 +80,7 @@ fun EditJournal(
journal: JournalModel,
onJournalEvents: (JournalEvents) -> Unit
) {
val isToday =
LocalDate.now() == Instant.ofEpochMilli(journal.dateTime).atZone(ZoneId.systemDefault())
.toLocalDate()
val isToday = LocalDate.now() == Instant.ofEpochMilli(journal.dateTime).atZone(ZoneId.systemDefault()).toLocalDate()
val context = LocalContext.current
val richTextState = rememberRichTextState()
val interactionSource = remember { MutableInteractionSource() }
@@ -81,6 +88,7 @@ fun EditJournal(
val pickedImages = rememberSaveable { mutableStateListOf<String>().apply { addAll(journal.images) } }
var showDatePicker by rememberSaveable { mutableStateOf(false) }
var selectedDateTime by rememberSaveable { mutableLongStateOf(journal.dateTime) }
var previewImage by remember { mutableStateOf<String?>(null) }
val imagePickerLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.GetContent(),
@@ -118,6 +126,49 @@ fun EditJournal(
}, onDismiss = { showDatePicker = false })
}
if (previewImage != null) {
Dialog(
onDismissRequest = { previewImage = null },
properties = DialogProperties(
usePlatformDefaultWidth = false
)
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.95f))
) {
Image(
painter = rememberAsyncImagePainter(previewImage),
contentDescription = null,
modifier = Modifier
.fillMaxSize()
.padding(24.dp),
contentScale = ContentScale.Fit
)
IconButton(
onClick = { previewImage = null },
modifier = Modifier
.align(Alignment.TopEnd)
.padding(16.dp)
.size(48.dp)
.background(
color = Color.Black.copy(alpha = 0.6f),
shape = CircleShape
)
) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = "Close",
tint = Color.White
)
}
}
}
}
Scaffold(
containerColor = MaterialTheme.colorScheme.surfaceContainerLow,
topBar = {
@@ -169,8 +220,7 @@ fun EditJournal(
.padding(innerPadding)
.imePadding()
) {
Carousel(pickedImages) { pickedImages.remove(it) }
Carousel(pickedImages, {pickedImages.remove(it)}) { previewImage=it }
RichTextEditor(
state = richTextState,
interactionSource = interactionSource,
@@ -203,7 +253,7 @@ fun EditJournal(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Carousel(items: List<String>, onItemRemoved: (String) -> Unit) {
fun Carousel(items: List<String>, onItemRemoved: (String) -> Unit, onClick: (String)->Unit) {
if (items.isNotEmpty()) {
HorizontalMultiBrowseCarousel(
state = rememberCarouselState { items.count() },
@@ -219,6 +269,7 @@ fun Carousel(items: List<String>, onItemRemoved: (String) -> Unit) {
Box(
modifier = Modifier
.height(160.dp)
.clickable{onClick(item)}
.maskClip(MaterialTheme.shapes.extraLarge)
) {
AsyncImage(
@@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Lightbulb
import androidx.compose.material.icons.rounded.ChatBubble
import androidx.compose.material.icons.rounded.Email
import androidx.compose.material.icons.rounded.Feedback
@@ -41,10 +42,27 @@ fun Contact(navController: NavController, radius: Int) {
) {
item {
SettingOption(
title = stringResource(R.string.Contact_desc),
title = "Feature Request/Suggestion",
description = "Give your ideas on github discussion.",
icon = Icons.Default.Lightbulb,
radius = shapeManager(radius = radius, isFirst = true),
actionType = ActionType.LINK,
linkClicked = {
val intent = Intent(
Intent.ACTION_VIEW,
"https://github.com/chindaronit/Flux/discussions/61".toUri()
)
context.startActivity(intent)
}
)
}
item {
SettingOption(
title = "Bug Report",
description = stringResource(R.string.Contact_desc2),
icon = Icons.Rounded.Feedback,
radius = shapeManager(radius = radius, isBoth = true),
radius = shapeManager(radius = radius, isLast = true),
actionType = ActionType.LINK,
linkClicked = {
val intent = Intent(
@@ -50,7 +50,6 @@ import com.flux.data.model.getSpacesList
import com.flux.navigation.NavRoutes
import com.flux.other.icons
import com.flux.ui.components.AddNewSpacesBottomSheet
import com.flux.ui.components.CalendarToolBar
import com.flux.ui.components.ChangeIconBottomSheet
import com.flux.ui.components.DeleteAlert
import com.flux.ui.components.EventToolBar
@@ -72,7 +71,6 @@ import com.flux.ui.events.TaskEvents
import com.flux.ui.events.TodoEvents
import com.flux.ui.events.WorkspaceEvents
import com.flux.ui.screens.analytics.analyticsItems
import com.flux.ui.screens.calendar.calendarItems
import com.flux.ui.screens.events.eventHomeItems
import com.flux.ui.screens.habits.habitsHomeItems
import com.flux.ui.screens.journal.journalHomeItems
@@ -94,7 +92,6 @@ fun WorkspaceDetails(
allLabels: List<LabelModel>,
settings: Settings,
isNotesLoading: Boolean,
isAllEventsLoading: Boolean,
isDatedTaskLoading: Boolean,
isTodoLoading: Boolean,
isJournalEntriesLoading: Boolean,
@@ -312,8 +309,8 @@ fun WorkspaceDetails(
if (spacesList.find { it.id == selectedSpaceId.intValue }?.title == stringResource(R.string.To_Do)) {
TodoToolBar(navController, workspaceId)
}
if (spacesList.find { it.id == selectedSpaceId.intValue }?.title == stringResource(R.string.Calendar)) {
CalendarToolBar(
if (spacesList.find { it.id == selectedSpaceId.intValue }?.title == stringResource(R.string.Events)) {
EventToolBar(
navController,
workspaceId,
context,
@@ -330,9 +327,6 @@ fun WorkspaceDetails(
}
)
}
if (spacesList.find { it.id == selectedSpaceId.intValue }?.title == stringResource(R.string.Events)) {
EventToolBar(context, navController, workspaceId)
}
}
}
}
@@ -362,21 +356,6 @@ fun WorkspaceDetails(
onNotesEvents
)
}
if (spacesList.find { it.id == selectedSpaceId.intValue }?.title == context.getString(R.string.Calendar)) {
calendarItems(
navController,
radius,
is24HourFormat,
isDatedTaskLoading,
workspaceId,
selectedYearMonth,
selectedDate,
settings,
datedEvents,
allEventInstances,
onTaskEvents
)
}
if (spacesList.find { it.id == selectedSpaceId.intValue }?.title == context.getString(R.string.Journal)) {
journalHomeItems(navController, isJournalEntriesLoading, workspaceId, allEntries)
}
@@ -408,10 +387,13 @@ fun WorkspaceDetails(
navController,
radius,
is24HourFormat,
isAllEventsLoading,
allEvents,
allEventInstances,
isDatedTaskLoading,
workspaceId,
selectedYearMonth,
selectedDate,
settings,
datedEvents,
allEventInstances,
onTaskEvents
)
}