diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 3f30e666b..41acf1505 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -1,7 +1,7 @@ plugins { kotlin("android") id("com.android.application") - id("org.jetbrains.kotlin.plugin.compose") version "2.3.20" + id("org.jetbrains.kotlin.plugin.compose") version "2.3.21" } android { @@ -13,8 +13,8 @@ android { applicationId = "me.timeto.app" minSdk = 26 targetSdk = 36 - versionCode = 613 - versionName = "2026.04.23" + versionCode = 614 + versionName = "2026.06.12" } buildTypes { @@ -68,6 +68,6 @@ dependencies { implementation(project(":shared")) implementation("androidx.core:core:1.18.0") implementation("androidx.activity:activity-compose:1.13.0") - implementation("androidx.compose.material:material:1.11.0") + implementation("androidx.compose.material:material:1.11.1") implementation("com.google.android.material:material:1.13.0") } diff --git a/android/src/main/java/me/timeto/app/ui/SwipeToAction.kt b/android/src/main/java/me/timeto/app/ui/SwipeToAction.kt index 1631311b4..f9ce25ec7 100644 --- a/android/src/main/java/me/timeto/app/ui/SwipeToAction.kt +++ b/android/src/main/java/me/timeto/app/ui/SwipeToAction.kt @@ -1,26 +1,14 @@ package me.timeto.app.ui -import androidx.compose.animation.animateColorAsState -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* -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.platform.LocalViewConfiguration import androidx.compose.ui.platform.ViewConfiguration -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import kotlinx.coroutines.launch import me.timeto.app.Haptic -import me.timeto.app.ui.navigation.LocalNavigationFs import kotlin.math.absoluteValue @OptIn(ExperimentalMaterialApi::class) @@ -29,7 +17,7 @@ fun SwipeToAction( isStartOrEnd: MutableState, modifier: Modifier = Modifier, ignoreOneAction: MutableState = remember { mutableStateOf(false) }, - startView: @Composable () -> Unit, + startView: @Composable (DismissState) -> Unit, endView: @Composable (DismissState) -> Unit, onStart: () -> Boolean, // false - restart state onEnd: () -> Boolean, // false - restart state @@ -109,7 +97,7 @@ fun SwipeToAction( }, background = { when (state.dismissDirection) { - DismissDirection.StartToEnd -> startView() + DismissDirection.StartToEnd -> startView(state) DismissDirection.EndToStart -> endView(state) null -> {} } @@ -119,102 +107,6 @@ fun SwipeToAction( } } -@Composable -fun SwipeToAction__StartView( - text: String, - bgColor: Color, -) { - val bgAnimate = animateColorAsState(bgColor) - Box( - modifier = Modifier - .fillMaxSize() - .background(bgAnimate.value) - ) { - Text( - text, - modifier = Modifier - .padding(start = 19.dp, bottom = 1.dp) - .align(Alignment.CenterStart), - color = c.white, - fontSize = 15.sp - ) - } -} - -@OptIn(ExperimentalMaterialApi::class) -@Composable -fun SwipeToAction__DeleteView( - state: DismissState, - note: String, - deletionConfirmationNote: String? = null, - onDelete: () -> Unit, -) { - val scope = rememberCoroutineScope() - - val navigationFs = LocalNavigationFs.current - - Row( - modifier = Modifier - .fillMaxHeight() - .background(c.red), - verticalAlignment = Alignment.CenterVertically - ) { - - Text( - text = note, - color = c.white, - fontWeight = FontWeight.W300, - fontSize = 13.sp, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier - .padding(start = 16.dp, end = 8.dp) - .weight(1f) - ) - - Text( - "Cancel", - color = c.white, - modifier = Modifier - .padding(end = 8.dp) - .clip(squircleShape) - .clickable { - // С launchEx can be canceled - scope.launch { - state.reset() - } - } - .padding(horizontal = 10.dp, vertical = 4.dp) - .alpha(0.9f), - fontSize = 15.sp, - ) - - Text( - text = "Delete", - color = c.red, - modifier = Modifier - .padding(end = 10.dp) - .clip(squircleShape) - .background(c.white) - .clickable { - if (deletionConfirmationNote != null) { - navigationFs.confirmation( - message = deletionConfirmationNote, - buttonText = "Delete", - onConfirm = { - onDelete() - }, - ) - } else { - onDelete() - } - } - .padding(start = 10.dp, end = 10.dp, top = 4.dp, bottom = 5.dp), - fontWeight = FontWeight.Bold, - fontSize = 15.sp - ) - } -} private class ActionsContainer( val onStart: () -> Boolean, diff --git a/android/src/main/java/me/timeto/app/ui/activity_form/ActivityFormFs.kt b/android/src/main/java/me/timeto/app/ui/activity_form/ActivityFormFs.kt index 8f4979b05..de339719a 100644 --- a/android/src/main/java/me/timeto/app/ui/activity_form/ActivityFormFs.kt +++ b/android/src/main/java/me/timeto/app/ui/activity_form/ActivityFormFs.kt @@ -7,9 +7,11 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import me.timeto.app.toColor @@ -20,15 +22,15 @@ import me.timeto.app.ui.c import me.timeto.app.ui.checklists.ChecklistsPickerFs import me.timeto.app.ui.color_picker.ColorPickerFs import me.timeto.app.ui.daytime_picker.DaytimePickerSheet -import me.timeto.app.ui.emoji.EmojiPickerFs import me.timeto.app.ui.form.FormHeader import me.timeto.app.ui.form.FormInput import me.timeto.app.ui.form.FormSwitch import me.timeto.app.ui.form.button.FormButton import me.timeto.app.ui.form.button.FormButtonArrowView -import me.timeto.app.ui.form.button.FormButtonEmoji +import me.timeto.app.ui.form.button.FormButtonSymbol import me.timeto.app.ui.form.button.FormButtonView import me.timeto.app.ui.form.padding.FormPaddingBottom +import me.timeto.app.ui.form.padding.FormPaddingHeaderSection import me.timeto.app.ui.form.padding.FormPaddingSectionHeader import me.timeto.app.ui.form.padding.FormPaddingSectionSection import me.timeto.app.ui.form.padding.FormPaddingTop @@ -41,7 +43,10 @@ import me.timeto.app.ui.navigation.picker.NavigationPickerItem import me.timeto.app.ui.rememberVm import me.timeto.app.ui.roundedShape import me.timeto.app.ui.shortcuts.ShortcutsPickerFs +import me.timeto.app.ui.symbol.SymbolPickerFs import me.timeto.app.ui.timer.TimerSheet +import me.timeto.shared.ColorRgba +import me.timeto.shared.Symbol import me.timeto.shared.db.ActivityDb import me.timeto.shared.vm.activity_form.ActivityFormVm @@ -59,6 +64,11 @@ fun ActivityFormFs( ) } + val colorRgba: ColorRgba = + state.colorRgba + val color: Color = + remember(colorRgba) { colorRgba.toColor() } + Screen( modifier = Modifier .imePadding(), @@ -106,47 +116,11 @@ fun ActivityFormFs( vm.setName(newName) }, isFirst = true, - isLast = false, + isLast = true, isAutoFocus = activityDb == null, imeAction = ImeAction.Done, ) - fun showEmojiPicker() { - navigationFs.push { - EmojiPickerFs( - onDone = { emoji -> - vm.setEmoji(emoji) - }, - ) - } - } - - val emoji: String? = state.emoji - if (emoji == null) { - FormButton( - title = state.emojiTitle, - isFirst = false, - isLast = true, - note = "Not Selected", - noteColor = c.red, - withArrow = true, - onClick = { - showEmojiPicker() - }, - ) - } else { - FormButtonEmoji( - title = state.emojiTitle, - emoji = emoji, - withArrow = true, - isFirst = false, - isLast = true, - onClick = { - showEmojiPicker() - }, - ) - } - FormPaddingSectionHeader() FormHeader(state.goalHeader) @@ -242,27 +216,11 @@ fun ActivityFormFs( }, ) - FormPaddingSectionSection() + FormPaddingSectionHeader() - FormButton( - title = state.periodTitle, - isFirst = true, - isLast = true, - note = state.periodNote, - withArrow = true, - onClick = { - navigationFs.push { - ActivityFormPeriodFs( - initActivityDbPeriod = state.period, - onDone = { newPeriod -> - vm.setPeriod(newPeriod = newPeriod) - }, - ) - } - }, - ) + FormHeader("TIMER") - FormPaddingSectionSection() + FormPaddingHeaderSection() FormButton( title = state.timerTypeTitle, @@ -334,9 +292,27 @@ fun ActivityFormFs( FormPaddingSectionSection() FormButton( - title = state.parentActivityTitle, + title = state.periodTitle, isFirst = true, - isLast = true, + isLast = false, + note = state.periodNote, + withArrow = true, + onClick = { + navigationFs.push { + ActivityFormPeriodFs( + initActivityDbPeriod = state.period, + onDone = { newPeriod -> + vm.setPeriod(newPeriod = newPeriod) + }, + ) + } + }, + ) + + FormButton( + title = state.parentActivityTitle, + isFirst = false, + isLast = false, note = state.parentActivityUi?.title ?: "None", withArrow = true, onClick = { @@ -353,13 +329,48 @@ fun ActivityFormFs( }, ) - FormPaddingSectionSection() + fun showSymbolPicker() { + navigationFs.push { + SymbolPickerFs( + onPick = { symbol -> + vm.setSymbol(symbol) + }, + ) + } + } + + val symbol: Symbol? = state.symbol + if (symbol == null) { + FormButton( + title = state.iconTitle, + isFirst = false, + isLast = false, + note = "Not Selected", + noteColor = c.red, + withArrow = true, + onClick = { + showSymbolPicker() + }, + ) + } else { + FormButtonSymbol( + title = state.iconTitle, + symbol = symbol, + color = c.secondaryText, + withArrow = true, + isFirst = false, + isLast = false, + onClick = { + showSymbolPicker() + }, + ) + } FormButtonView( title = state.colorTitle, titleColor = null, - isFirst = true, - isLast = true, + isFirst = false, + isLast = false, modifier = Modifier, rightView = { HStack( @@ -370,7 +381,7 @@ fun ActivityFormFs( .padding(end = 8.dp) .size(28.dp) .clip(roundedShape) - .background(state.colorRgba.toColor()), + .background(color), ) FormButtonArrowView() } @@ -389,11 +400,9 @@ fun ActivityFormFs( onLongClick = null, ) - FormPaddingSectionSection() - FormButton( title = state.pomodoroTitle, - isFirst = true, + isFirst = false, isLast = false, note = state.pomodoroNote, withArrow = true, diff --git a/android/src/main/java/me/timeto/app/ui/calendar/CalendarDayView.kt b/android/src/main/java/me/timeto/app/ui/calendar/CalendarDayView.kt index 9f5ef5cb1..923091307 100644 --- a/android/src/main/java/me/timeto/app/ui/calendar/CalendarDayView.kt +++ b/android/src/main/java/me/timeto/app/ui/calendar/CalendarDayView.kt @@ -27,7 +27,7 @@ import me.timeto.app.ui.SpacerW1 import me.timeto.app.ui.calendar.list.CalendarListItemView import me.timeto.app.ui.events.EventFormFs import me.timeto.app.ui.navigation.LocalNavigationFs -import me.timeto.app.ui.tasks.tab.repeatings.TasksTabRepeatingsItemView +import me.timeto.app.ui.repeatings.list.RepeatingsListItemView import me.timeto.shared.vm.calendar.CalendarDayVm @Composable @@ -106,7 +106,7 @@ fun CalendarDayView( is CalendarDayVm.ItemUi.RepeatingUi -> { key("repeating_${itemUi.repeatingsListRepeatingUi.repeatingDb.id}") { - TasksTabRepeatingsItemView( + RepeatingsListItemView( repeatingUi = itemUi.repeatingsListRepeatingUi, withTopDivider = !isFirst, modifier = Modifier diff --git a/android/src/main/java/me/timeto/app/ui/calendar/CalendarTabsView.kt b/android/src/main/java/me/timeto/app/ui/calendar/CalendarTabsView.kt index b99b3d027..1ca6233d2 100644 --- a/android/src/main/java/me/timeto/app/ui/calendar/CalendarTabsView.kt +++ b/android/src/main/java/me/timeto/app/ui/calendar/CalendarTabsView.kt @@ -5,6 +5,7 @@ import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.material.Divider import androidx.compose.material.Text @@ -22,16 +23,12 @@ import me.timeto.app.ui.H_PADDING import me.timeto.app.MainActivity import me.timeto.app.ui.VStack import me.timeto.app.ui.c -import me.timeto.app.goldenRatioUp import me.timeto.app.ui.halfDpCeil import me.timeto.app.ui.onePx import me.timeto.app.ui.squircleShape import me.timeto.app.ui.calendar.list.CalendarListView -import me.timeto.app.ui.tasks.tab.TasksTabView__PADDING_END private val menuTopPadding: Dp = 8.dp -private val menuBottomPadding: Dp = - menuTopPadding.goldenRatioUp().goldenRatioUp() @Composable fun CalendarTabsView() { @@ -60,14 +57,12 @@ fun CalendarTabsView() { ) } - Divider( - modifier = Modifier - .padding(start = H_PADDING, end = TasksTabView__PADDING_END), - ) + Divider() HStack( modifier = Modifier - .padding(top = menuTopPadding, bottom = menuBottomPadding), + .padding(top = menuTopPadding) + .navigationBarsPadding(), ) { ModeButton( diff --git a/android/src/main/java/me/timeto/app/ui/calendar/CalendarView.kt b/android/src/main/java/me/timeto/app/ui/calendar/CalendarView.kt index 023988c81..3c4159a3e 100644 --- a/android/src/main/java/me/timeto/app/ui/calendar/CalendarView.kt +++ b/android/src/main/java/me/timeto/app/ui/calendar/CalendarView.kt @@ -20,13 +20,11 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import me.timeto.app.ui.HStack -import me.timeto.app.ui.H_PADDING import me.timeto.app.ui.VStack import me.timeto.app.ui.c import me.timeto.app.ui.rememberVm import me.timeto.app.ui.Divider import me.timeto.app.ui.SpacerW1 -import me.timeto.app.ui.tasks.tab.TasksTabView__PADDING_END import me.timeto.shared.vm.calendar.CalendarVm @Composable @@ -43,8 +41,7 @@ fun CalendarView( } VStack( - modifier = modifier - .padding(start = H_PADDING, end = TasksTabView__PADDING_END), + modifier = modifier, ) { VStack { @@ -162,7 +159,7 @@ fun CalendarView( ) { if (isDaySelected) { CalendarDayView( - unixDay = selectedDayLocal!!.unixDay, + unixDay = selectedDayLocal.unixDay, ) } } diff --git a/android/src/main/java/me/timeto/app/ui/calendar/list/CalendarListView.kt b/android/src/main/java/me/timeto/app/ui/calendar/list/CalendarListView.kt index 5742d513f..47cd95f79 100644 --- a/android/src/main/java/me/timeto/app/ui/calendar/list/CalendarListView.kt +++ b/android/src/main/java/me/timeto/app/ui/calendar/list/CalendarListView.kt @@ -5,11 +5,10 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import me.timeto.app.ui.H_PADDING_HALF import me.timeto.app.ui.rememberVm import me.timeto.app.ui.squircleShape -import me.timeto.app.ui.tasks.tab.TasksTabView__LIST_SECTION_PADDING -import me.timeto.app.ui.tasks.tab.TasksTabView__PADDING_END import me.timeto.shared.vm.calendar.CalendarListVm @Composable @@ -25,8 +24,7 @@ fun CalendarListView( modifier = modifier, reverseLayout = true, contentPadding = PaddingValues( - end = TasksTabView__PADDING_END - H_PADDING_HALF, - top = TasksTabView__LIST_SECTION_PADDING, + top = 20.dp, ), ) { diff --git a/android/src/main/java/me/timeto/app/ui/colors.kt b/android/src/main/java/me/timeto/app/ui/colors.kt index b090c0a6b..ef610478b 100644 --- a/android/src/main/java/me/timeto/app/ui/colors.kt +++ b/android/src/main/java/me/timeto/app/ui/colors.kt @@ -50,6 +50,4 @@ object c { val mainTabsMenuPrimary = MainTabsVm.menuPrimaryColorDark.toColor() val mainTabsMenuSecondary = MainTabsVm.menuSecondaryColorDark.toColor() - - val tasksDropFocused = Palette.green.dark.toColor() } diff --git a/android/src/main/java/me/timeto/app/ui/doc/DocFs.kt b/android/src/main/java/me/timeto/app/ui/doc/DocFs.kt new file mode 100644 index 000000000..96aad06c4 --- /dev/null +++ b/android/src/main/java/me/timeto/app/ui/doc/DocFs.kt @@ -0,0 +1,1690 @@ +package me.timeto.app.ui.doc + +import androidx.activity.compose.BackHandler +import androidx.annotation.DrawableRes +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.LinkAnnotation +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.withLink +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import me.timeto.app.R +import me.timeto.app.askAQuestion +import me.timeto.app.ui.H_PADDING +import me.timeto.app.ui.Screen +import me.timeto.app.ui.SquircleShape +import me.timeto.app.ui.ZStack +import me.timeto.app.ui.c +import me.timeto.app.ui.header.Header +import me.timeto.app.ui.header.HeaderCancelButton +import me.timeto.app.ui.header.Header__titleFontSize +import me.timeto.app.ui.header.Header__titleFontWeight +import me.timeto.app.ui.navigation.LocalNavigationLayer +import me.timeto.app.ui.rememberVm +import me.timeto.app.ui.roundedShape +import me.timeto.shared.vm.doc.DocVm + +private val pTextLineHeight = 23.sp + +@Composable +fun DocFs( + forceRead: Boolean, +) { + + val navigationLayer = LocalNavigationLayer.current + val scrollState = rememberLazyListState() + + val (vm, state) = rememberVm { + DocVm() + } + + Screen { + + Header( + title = "How to Use the App", + scrollState = scrollState, + actionButton = null, + cancelButton = if (forceRead) null else { + HeaderCancelButton( + text = "Close", + onClick = { + navigationLayer.close() + }, + ) + }, + ) + + BackHandler(forceRead) { + } + + LazyColumn( + state = scrollState, + modifier = Modifier + .weight(1f), + ) { + + if (forceRead) { + item { + + ForceTextView( + text = "I force you to read this guide because without it, you will not understand how to use the app.", + modifier = Modifier + .padding(top = 4.dp), + ) + + ForceTextView( + text = "Please DO NOT SKIP this! It will help you get started and begin improving your life.", + ) + + ForceTextView( + text = "Good luck!", + ) + + SeparatorView() + } + } + + item { + + PTextView( + buildAnnotatedString { + append("I built this app to manage my productivity. Here, I will ") + appendGreenSemiBold("SHARE") + append(" my productivity system and how I use the app.") + } + ) + + PTextView( + buildAnnotatedString { + append("My system ") + appendRedSemiBold("IS NOT") + append(" about time tracking, ") + appendRedSemiBold("IS NOT") + append(" about getting nice activity charts, ") + appendRedSemiBold("IS NOT") + append(" about reducing wasted time.") + } + ) + + PTextView( + buildAnnotatedString { + append("My system ") + appendGreenSemiBold("IS ALL ABOUT") + append(" achieving my ") + appendGreenSemiBold("REAL-LIFE") + append(" goals.") + } + ) + + PTextView( + buildAnnotatedString { + append("For example, ") + appendRedSemiBold("I DO NOT") + append(" care how much time I waste, but ") + appendGreenSemiBold("I CARE") + append(" if I read a book every day, ") + appendGreenSemiBold("I CARE") + append(" if I exercise every day, ") + appendGreenSemiBold("I CARE") + append(" if I don't forget anything, ") + appendGreenSemiBold("I CARE") + append(" if I constantly follow my long-term goals.") + } + ) + + PTextView( + buildAnnotatedString { + append("Now I will show ") + appendGreenSemiBold("MY PERSONAL") + append(" app setup with ") + appendGreenSemiBold("REAL-LIFE") + append(" scenarios.") + } + ) + + PTextView( + buildAnnotatedString { + appendBlueSemiBold("IMPORTANT!") + append(" Life is hard, life is tricky. No way to have a perfect app or system.") + append(" Some solutions seem strange, but they work, they help me achieve my ") + appendGreenSemiBold("REAL-LIFE") + append(" goals.") + } + ) + } + + item { + + HeaderView("Activities") + + PTextView( + buildAnnotatedString { + append("The first thing you have to do is ") + appendGreenSemiBold("SET UP ACTIVITIES.") + } + ) + + PTextView( + buildAnnotatedString { + append("This is how ") + appendGreenSemiBold("MY ACTIVITIES") + append(" look in the morning, right after I wake up:") + } + ) + + ScreenshotView(R.drawable.doc_activities_morning) + } + + item { + + PTextView( + buildAnnotatedString { + append("During the day, I have to turn it into this:") + } + ) + + ScreenshotView(R.drawable.doc_activities_evening) + } + + item { + PTextView( + buildAnnotatedString { + append("I ") + appendGreenSemiBold("ONLY") + append(" create activities to follow my ") + appendGreenSemiBold("REAL-LIFE") + append(" goals. I ") + appendRedSemiBold("DO NOT") + append(" create activities just to track, like commute, eating, etc.") + } + ) + + PTextView( + buildAnnotatedString { + append("Every activity has ") + appendGreenSemiBold("PRACTICAL") + append(" value. Now I'll show how I set up and use each activity.") + } + ) + } + + item { + + HeaderView("Morning") + + PTextView( + buildAnnotatedString { + append("Right after waking up, I tap the ") + appendGreenSemiBold("Morning") + append(" activity. This is what I see:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_morning_start, + fraction = 0.7f, + innerPadding = 4.dp, + ) + } + + item { + + PTextView( + buildAnnotatedString { + append("There are two important things: ") + appendGreenSemiBold("TIMER") + append(" and ") + appendGreenSemiBold("CHECKLIST.") + } + ) + + PTextView( + buildAnnotatedString { + appendGreenSemiBold("TIMER") + append(" helps me limit my morning routine time. I set 2 hours, it's enough to do everything smoothly, but I don't have to spend more time.") + } + ) + + PTextView( + buildAnnotatedString { + appendGreenSemiBold("CHECKLIST") + append(" helps me make sure I don't forget anything. I'm just doing step by step.") + } + ) + + PTextView( + buildAnnotatedString { + append("Once I finish the checklist, ") + appendGreenSemiBold("Morning") + append(" will be marked as complete:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_morning_completed, + fraction = 0.7f, + innerPadding = 4.dp, + ) + } + + item { + + PTextView( + buildAnnotatedString { + append("To make ") + appendGreenSemiBold("Morning") + append(" works this way, you have to set up two options:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_morning_form, + fraction = 0.7f, + ) + } + + item { + + HeaderView("Workout") + + PTextView( + buildAnnotatedString { + appendGreenSemiBold("Workout") + append(" works absolutely ") + appendGreenSemiBold("DIFFERENT.") + append(" Just after tapping ") + appendGreenSemiBold("Workout,") + append(" I see this:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_workout_start, + fraction = 0.7f, + innerPadding = 4.dp, + ) + } + + item { + + PTextView( + buildAnnotatedString { + append("Two differences:") + appendGreenSemiBold("\n1. Workout") + append(" marked ") + appendGreenSemiBold("AS COMPLETED") + append(" even if a checklist ") + appendRedSemiBold("IS NOT") + append(" completed;") + appendGreenSemiBold("\n2.") + append(" Instead of timer ") + appendRedSemiBold("(COUNT DOWN)") + append(" we see a stopwatch ") + appendGreenSemiBold("(COUNT UP FROM 00:00).") + } + ) + + PTextView( + buildAnnotatedString { + append("Why it works this way? As I said, I focus on ") + appendGreenSemiBold("PRACTICAL") + append(" value. I exercise to stay healthy. ") + append("I have to find a way to exercise ") + appendGreenSemiBold("EVERY DAY.") + } + ) + + PTextView( + buildAnnotatedString { + append("We know, the most difficult thing is getting started. ") + append("I just tap ") + appendGreenSemiBold("Workout") + append(" (feels like I've done the first step), then commute to the place, do my workout, come back, take a shower, and have dinner.") + } + ) + + PTextView( + buildAnnotatedString { + append("Usually, it takes up to 4 hours. ") + appendRedSemiBold("I DO NOT") + append(" care about tracking every single step, but ") + appendGreenSemiBold("I CARE") + append(" I do workout every day.") + } + ) + + PTextView( + buildAnnotatedString { + appendRedSemiBold("I DO NOT FORCE MYSELF") + append(" completing checklists, setting timer, etc.") + append(" Only this way works best for me for ") + appendGreenSemiBold("Workout.") + } + ) + } + + item { + + PTextView( + buildAnnotatedString { + append("Let's see the ") + appendGreenSemiBold("Workout's") + append(" settings:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_workout_form, + fraction = 0.7f, + ) + } + + item { + + HeaderView("Small Tasks") + + PTextView( + buildAnnotatedString { + append("We all have plenty of non-urgent tasks that we constantly postpone.") + append(" It could be personal matters, housework, etc.") + append(" Every day, ") + appendGreenSemiBold("I FORCE MYSELF") + append(" to spend 30 minutes for that.") + } + ) + + PTextView( + buildAnnotatedString { + append("I just tap ") + appendGreenSemiBold("Small Tasks") + append(" and do these tasks.") + append(" After 30 minutes, the activity will be marked as complete.") + } + ) + + ScreenshotView( + resId = R.drawable.doc_small_tasks_progress, + fraction = 0.7f, + innerPadding = 4.dp, + ) + } + + item { + + PTextView( + buildAnnotatedString { + append("Settings:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_small_tasks_form, + fraction = 0.7f, + ) + } + + item { + + HeaderView("timeto.me") + + PTextView( + buildAnnotatedString { + append("As a ") + appendGreenSemiBold("timeto.me") + append(" developer, I dedicate all the time I can to the project.") + append(" Here's how I manage that.") + } + ) + + PTextView( + buildAnnotatedString { + append("Tapping the ") + appendGreenSemiBold("timeto.me,") + append(" I see this:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_timetome_start, + fraction = 0.7f, + innerPadding = 4.dp, + ) + } + + item { + + PTextView( + buildAnnotatedString { + append("We see") + appendGreenSemiBold(" TIMER, CHECKLIST,") + append(" and ") + appendGreenSemiBold("TASKS.") + } + ) + + PTextView( + buildAnnotatedString { + append("I will talk about ") + appendGreenSemiBold("TASKS") + append(" later. Let's see the ") + appendGreenSemiBold("CHECKLIST") + append(" and ") + appendGreenSemiBold("TIMER") + append(" for now.") + } + ) + + PTextView( + buildAnnotatedString { + appendGreenSemiBold("CHECKLIST.") + append(" Every day, I start by answering user questions.") + append(" Then mark the checklist ") + appendGreenSemiBold("AS COMPLETED.") + append(" And only then start working.") + } + ) + + PTextView( + buildAnnotatedString { + append("It may seem ") + appendRedSemiBold("ILLOGICAL") + append(" that I mark ") + appendGreenSemiBold("timeto.me") + append(" as completed and only then start working on it.") + append(" But it works in ") + appendGreenSemiBold("REAL-LIFE.") + } + ) + + PTextView( + buildAnnotatedString { + append("It works because, I don't know how much work I'll be able to get done today,") + append(" and it's really frustrating that one of the activities will always remain uncompleted.") + } + ) + + PTextView( + buildAnnotatedString { + append("I don't forget the essential tasks thanks the checklist, then I work whatever hours I can.") + } + ) + + PTextView( + buildAnnotatedString { + append("This way, I can constantly follow my long-term plans as a ") + appendGreenSemiBold("timeto.me") + append(" developer, without overwhelming by \"Task Management\" rituals.") + } + ) + } + + item { + + PTextView( + buildAnnotatedString { + appendGreenSemiBold("TIMER.") + append(" I use a ") + appendGreenSemiBold("POMODORO-LIKE") + append(" technique. I set the timer for 45 minutes, then take a break, and set the timer again.") + } + ) + + PTextView( + buildAnnotatedString { + append("After the timer ends, it turns red and display the overdue time:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_timetome_overdue, + fraction = 0.7f, + innerPadding = 4.dp, + ) + } + + item { + + PTextView( + buildAnnotatedString { + append("It ") + appendRedSemiBold("DOES NOT") + append(" mean I'm taking a break immediately.") + append(" Sometimes I want to continue working. ") + appendGreenSemiBold("KEEP IN MIND:") + append(" the most important is ") + appendGreenSemiBold("PRACTICAL VALUE.") + } + ) + + PTextView( + buildAnnotatedString { + append("You can tap the timer to start a ") + appendGreenSemiBold("BREAK") + append(" timer:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_timetome_break, + fraction = 0.7f, + innerPadding = 4.dp, + ) + } + + item { + + PTextView( + buildAnnotatedString { + append("But ") + appendGreenSemiBold("HONESTLY,") + append(" I don't use this feature. After the break, I just tap ") + appendGreenSemiBold("timeto.me") + append(" again to start a new 45 min timer.") + } + ) + + PTextView( + buildAnnotatedString { + append("Settings:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_timetome_form, + fraction = 0.7f, + ) + } + + item { + + HeaderView("Option1") + + PTextView( + buildAnnotatedString { + withLink(LinkAnnotation.Url(url = "https://option1.io")) { + appendBlueSemiBold("option1.io") + } + append(" is also my personal project.") + append(" Here, I'm building a pragmatic window manager for macOS.") + } + ) + + PTextView( + buildAnnotatedString { + append("Settings are the same as for ") + appendGreenSemiBold("timeto.me.") + } + ) + } + + item { + + HeaderView("Work") + + PTextView( + buildAnnotatedString { + appendGreenSemiBold("Work") + append(" is a special case.") + append(" Working as a developer, I have to track my working hours, there are a set of features for that.") + } + ) + + PTextView( + buildAnnotatedString { + append("Let's see the settings:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_work_form, + fraction = 0.7f, + ) + } + + item { + + PTextView( + buildAnnotatedString { + appendGreenSemiBold("Total Stopwatch") + append(" makes the timer display ") + appendGreenSemiBold("TOTAL TIME") + append(" spent on work ") + appendGreenSemiBold("FOR TODAY.") + } + ) + + PTextView( + buildAnnotatedString { + append("In other words, it's a regular stopwatch ") + appendGreenSemiBold("(COUNT UP),") + append(" but it ") + appendRedSemiBold("DOES NOT") + append(" start from ") + appendRedSemiBold("00:00,") + append(" it continues for the activity.") + } + ) + + PTextView( + buildAnnotatedString { + append("This way, I can always see how much time I've spent on work today.") + } + ) + + PTextView( + buildAnnotatedString { + append("Another feature is adding notes to the current task:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_work_note, + fraction = 0.7f, + innerPadding = 4.dp, + ) + } + + item { + + PTextView( + buildAnnotatedString { + append("Tap the edit icon to make a note about the task you're working on.") + } + ) + + PTextView( + buildAnnotatedString { + append("Then, in the history, you can see how much time you spent on each task:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_work_history, + fraction = 0.7f, + innerPadding = 4.dp, + ) + } + + item { + + PTextView( + buildAnnotatedString { + append("If you're a ") + appendGreenSemiBold("NIGHT OWL") + append(" and work after ") + appendGreenSemiBold("12:00 AM,") + append(" you can set a ") + appendGreenSemiBold("DAY START TIME") + append(" setting, to track the working time right.") + append(" I suggest set it for 2 hours before you wake up to refresh activities while you sleep.") + } + ) + } + + item { + + HeaderView("Reading") + + PTextView( + buildAnnotatedString { + append("I don't know how it works, but I see that constant reading ") + appendGreenSemiBold("MAKES PEOPLE BETTER.") + } + ) + + PTextView( + buildAnnotatedString { + append("Some people set a goal to read for a hour a day. It ") + appendRedSemiBold("DOES NOT") + append(" work for me.") + } + ) + + PTextView( + buildAnnotatedString { + append("I like to read a fixed number of chapters per day. In the book I'm reading now, I read ") + appendGreenSemiBold("FIVE") + append(" chapters a day.") + } + ) + + ScreenshotView( + resId = R.drawable.doc_reading_progress, + fraction = 0.7f, + innerPadding = 4.dp, + ) + + PTextView( + buildAnnotatedString { + append("Technically, it works like a counter. In practice, I tap ") + appendGreenSemiBold("Reading") + append(" and start reading. Then I count how many chapters I've read.") + } + ) + + PTextView( + buildAnnotatedString { + append("Settings:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_reading_form, + fraction = 0.7f, + ) + + PTextView( + buildAnnotatedString { + append("Timer ") + appendRedSemiBold("DOES NOT") + append(" matter.") + } + ) + } + + item { + + HeaderView("Music") + + PTextView( + buildAnnotatedString { + appendGreenSemiBold("Music") + append(" is my hobby. I try to play the piano twice a day.") + } + ) + + ScreenshotView( + resId = R.drawable.doc_music_progress, + fraction = 0.7f, + innerPadding = 4.dp, + ) + + PTextView( + buildAnnotatedString { + append("Settings are very similar to ") + appendGreenSemiBold("Reading:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_music_form, + fraction = 0.7f, + ) + + PTextView( + buildAnnotatedString { + append("Timer ") + appendRedSemiBold("DOES NOT") + append(" matter.") + } + ) + } + + item { + + HeaderView("Free Time") + + PTextView( + buildAnnotatedString { + append("I use ") + appendGreenSemiBold("Free Time") + append(" for activities I don't need to track, like eating, walking, meeting, etc.") + } + ) + + PTextView( + buildAnnotatedString { + append("I would like to highlight ") + appendGreenSemiBold("THREE") + append(" key points:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_free_time_start, + fraction = 0.7f, + innerPadding = 4.dp, + ) + } + + item { + + PTextView( + buildAnnotatedString { + appendGreenSemiBold("1. ALWAYS COMPLETED.") + append(" No sense in setting goals.") + } + ) + + PTextView( + buildAnnotatedString { + appendGreenSemiBold("2. STOPWATCH (COUNT UP FROM 00:00).") + append(" Helps me control the time I spend on different tasks.") + append(" Sometimes it's useful to notice that I spend too much time on something.") + } + ) + + PTextView( + buildAnnotatedString { + appendGreenSemiBold("3. NESTED CHECKLISTS.") + append(" The ") + appendGreenSemiBold("Free Time") + append(" checklist contains all sorts of things.") + append(" For example, the ") + appendGreenSemiBold("Shopping") + append(" item contains a nested checklist with a list of goods I have to buy.") + } + ) + + PTextView( + buildAnnotatedString { + append("Settings:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_free_time_form, + fraction = 0.7f, + ) + } + + item { + + HeaderView("Sleep") + + PTextView( + buildAnnotatedString { + appendGreenSemiBold("Sleep") + append(" is another special case:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_sleep_start, + fraction = 0.7f, + innerPadding = 4.dp, + ) + } + + item { + + PTextView( + buildAnnotatedString { + appendGreenSemiBold("1. ALWAYS COMPLETED.") + append(" Since during the day I try to mark all activities as completed, it’s really frustrating that one of them will always remain uncompleted.") + } + ) + + PTextView( + buildAnnotatedString { + append("That is why, despite the checklist, it's better when ") + appendGreenSemiBold("Sleep") + append(" is always completed.") + } + ) + + PTextView( + buildAnnotatedString { + appendGreenSemiBold("2. STOPWATCH (COUNT UP FROM 00:00).") + append(" Some people prefer to set a timer for sleep, for example, for 7 hours.") + append(" That doesn't work for me.") + } + ) + + PTextView( + buildAnnotatedString { + append("I sleep as much as I feel I need to today.") + append(" I prefer to use a stopwatch and check in the morning how long I slept.") + } + ) + + PTextView( + buildAnnotatedString { + append("Settings:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_sleep_form, + fraction = 0.7f, + ) + } + + item { + + HeaderView("Conclusion") + + PTextView( + buildAnnotatedString { + append("That is all the activities I use.") + append(" By default, the app comes with almost the same activities and settings.") + appendGreenSemiBold(" You can use this setup.") + } + ) + + PTextView( + buildAnnotatedString { + append("I want to give you ") + appendGreenSemiBold("THREE TIPS") + append(" dealing with activities that are extremely important to me: ") + appendGreenSemiBold("PROCRASTINATION, ") + appendGreenSemiBold("PRIORITIES,") + append(" and ") + appendGreenSemiBold("FLEXIBILITY.") + } + ) + } + + item { + + HeaderView("Procrastination") + + PTextView( + buildAnnotatedString { + append("All of us have been there - when it's crystal clear what we should do, but we just don't do it.") + } + ) + + PTextView( + buildAnnotatedString { + append("I solve it simply: I open the app, see uncompleted activity, and tap on it without thinking.") + } + ) + + PTextView( + buildAnnotatedString { + appendRedSemiBold("DO NOT THINK!") + appendGreenSemiBold(" JUST TAP THE ACTIVITY IMMEDIATELY!") + } + ) + + PTextView( + buildAnnotatedString { + appendBlueSemiBold("ONCE AGAIN:") + appendGreenSemiBold(" OPEN THE APP AND TAP ON UNCOMPLETED ACTIVITY WITHOUT THINKING!") + } + ) + + PTextView( + buildAnnotatedString { + append("The most difficult is to get started.") + append(" Tapping the activity feels like you've made the first step.") + append(" If you start thinking, you will continue procrastinating.") + } + ) + + PTextView( + buildAnnotatedString { + append("It always works for me. For example, I open the app, see an uncompleted ") + appendGreenSemiBold("Piano,") + append(" tap it immediately, make some tea, and start practicing.") + } + ) + + PTextView( + buildAnnotatedString { + append("I hope you get the idea. Just tap ") + appendGreenSemiBold("WITHOUT THINKING.") + } + ) + + PTextView( + buildAnnotatedString { + appendBlueSemiBold("IMPORTANT:") + append(" You should trust your activities. Only important things should be here. Otherwise, you will be overwhelmed and fail.") + } + ) + } + + item { + + HeaderView("Priorities") + + PTextView( + buildAnnotatedString { + append("We usually start the day with the most urgent tasks and ") + appendRedSemiBold("SACRIFICE") + append(" long-term goals, because long-term goals are not usually urgent.") + } + ) + + PTextView( + buildAnnotatedString { + appendGreenSemiBold("NO MATTER") + append(" what happens, I try to start my day focusing only on what really ") + appendGreenSemiBold("MATTERS TO ME.") + } + ) + + PTextView( + buildAnnotatedString { + append("My perfect day: after the morning routine, I read, then practice the piano, then work on my personal projects, and only then I get to work.") + } + ) + + PTextView( + buildAnnotatedString { + append("Only this way I'm able to keep developing this app for years.") + } + ) + + PTextView( + buildAnnotatedString { + append("It's very difficult, but you have to remember what is really ") + appendGreenSemiBold("IMPORTANT TO YOU.") + } + ) + } + + item { + + HeaderView("Flexibility") + + PTextView( + buildAnnotatedString { + append("Unexpected things happen every day. We have to accept this fact.") + append(" It's ") + appendGreenSemiBold("ABSOLUTELY OKAY") + append(" if we can't do some activity today.") + } + ) + + PTextView( + buildAnnotatedString { + append("For example, I have a meeting today, so I don't have time to ") + appendGreenSemiBold("Workout.") + append(" It's absolutely okay. ") + appendGreenSemiBold("I JUST MARK WORKOUT AS COMPLETED") + append(" and move to other activities.") + } + ) + + PTextView( + buildAnnotatedString { + append("It may seem strange that I mark ") + appendGreenSemiBold("Workout") + append(" as completed even if ") + appendRedSemiBold("I HAVEN'T") + append(" done it, but I just don't want to get distracted by uncompleted activity.") + } + ) + + PTextView( + buildAnnotatedString { + appendBlueSemiBold("NOTE: ") + append("I have the same activities for every day, even if ") + appendRedSemiBold("I DON'T") + append(" need ") + appendGreenSemiBold("Work") + append(" activity on weekends.") + append(" There is an option to hide activities on selected days, but ") + appendGreenSemiBold("HONESTLY,") + append(" I don't use it.") + append(" On weekends, every morning, while planning my day, I just mark ") + appendGreenSemiBold("Work") + append(" as complete and focus on the remaining activities.") + } + ) + + PTextView( + buildAnnotatedString { + appendBlueSemiBold("KEEP IN MIND:") + append(" the most important is ") + appendGreenSemiBold("REAL-LIFE") + append(" and ") + appendGreenSemiBold("PRACTICAL VALUE.") + } + ) + } + + item { + + HeaderView("DO NOT RUSH") + + PTextView( + buildAnnotatedString { + append("I believe we do more when we don't rush.") + } + ) + + PTextView( + buildAnnotatedString { + append("In the ") + appendGreenSemiBold("Procrastination") + append(" section, I suggest to start an activity without thinking, it's right, but it doesn't mean you have to immediately jump into action.") + } + ) + + PTextView( + buildAnnotatedString { + append("For example, I open the app, see an uncompleted ") + appendGreenSemiBold("Reading,") + append(" and tap it immediately.") + append(" Then I go to the park with a book and start reading there.") + } + ) + + PTextView( + buildAnnotatedString { + appendGreenSemiBold("Stay calm and start slowly.") + } + ) + } + + item { + + SeparatorView() + + PTextView( + buildAnnotatedString { + append("That's all for ") + appendGreenSemiBold("Activities.") + append(" Let's move to other features.") + } + ) + } + + item { + + HeaderView("Timer") + + PTextView( + buildAnnotatedString { + append("You may notice that every screenshot has a timer.") + } + ) + + PTextView( + buildAnnotatedString { + append("Timer is running ") + appendGreenSemiBold("ALL THE TIME.") + append(" There is ") + appendRedSemiBold("NO STOP OPTION!") + append(" To stop the current activity, you have to start the next one.") + } + ) + + PTextView( + buildAnnotatedString { + append("This way I always remember what I have to do. Also, it provides 24/7 data on how long everything takes:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_timer_summary, + fraction = 0.7f, + innerPadding = 4.dp, + ) + } + + item { + + HeaderView("Tasks") + + PTextView( + buildAnnotatedString { + appendGreenSemiBold("TASKS") + append(" is a big part of the app.") + append(" Let's create a task:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_tasks_field, + fraction = 0.7f, + innerPadding = 4.dp, + ) + + PTextView( + buildAnnotatedString { + append("You can select an activity for this task.") + append(" But ") + appendGreenSemiBold("HONESTLY,") + append(" I always keep the default ") + appendGreenSemiBold("Free Time:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_tasks_form, + fraction = 0.7f, + ) + + PTextView( + buildAnnotatedString { + appendGreenSemiBold("Nice!") + append(" Now you will not forget to buy fruits:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_tasks_example1, + fraction = 0.7f, + innerPadding = 4.dp, + ) + + PTextView( + buildAnnotatedString { + append("You can tap it to start a stopwatch:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_tasks_started, + fraction = 0.7f, + innerPadding = 4.dp, + ) + + PTextView( + buildAnnotatedString { + append("But ") + appendGreenSemiBold("HONESTLY,") + append(" I newer tap on tasks.") + append(" I prefer to complete the task first, then just delete it by swiping left:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_tasks_delete, + fraction = 0.7f, + innerPadding = 4.dp, + ) + } + + item { + + HeaderView("Task Folders") + + ScreenshotView( + resId = R.drawable.doc_folders_example, + fraction = 0.7f, + innerPadding = 4.dp, + ) + + PTextView( + buildAnnotatedString { + appendGreenSemiBold("TODAY:") + append(" Tasks you need to do today.") + } + ) + + PTextView( + buildAnnotatedString { + appendGreenSemiBold("TOMORROW:") + append(" Tasks that will be moved to ") + appendGreenSemiBold("TODAY") + append(" folder tomorrow.") + } + ) + + PTextView( + buildAnnotatedString { + append("Let's schedule a call with Ann for tomorrow.") + append(" Just tap the folder and add the task:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_folders_tomorrow, + fraction = 0.7f, + innerPadding = 4.dp, + ) + + PTextView( + buildAnnotatedString { + append("If you want to move it to another folder, like ") + appendGreenSemiBold("TODAY,") + append(" swipe right and tap the folder you need:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_folders_swipe, + fraction = 0.7f, + innerPadding = 4.dp, + ) + } + + item { + + PTextView( + buildAnnotatedString { + appendGreenSemiBold("CUSTOM FOLDERS:") + append(" I use a few folders:") + append("\n- tasks and ideas for ") + appendGreenSemiBold("timeto.me;") + append("\n- tasks and ideas for ") + appendGreenSemiBold("Option1;") + append("\n- interesting quotes from ") + appendGreenSemiBold("books") + append(" I've read.") + append("\nNote that the last one is not actually \"tasks\", but it’s very convenient to store them this way.") + } + ) + + PTextView( + buildAnnotatedString { + append("Sometimes I create temporary folders. For example, while I was writing this guide, I created a folder to store the ideas.") + } + ) + } + + item { + + HeaderView("Conclusion") + + PTextView( + buildAnnotatedString { + append("I want to give you ") + appendGreenSemiBold("TWO TIPS") + append(" dealing with ") + appendGreenSemiBold("TASKS") + append(" that are extremely important to me:") + } + ) + + PTextView( + buildAnnotatedString { + appendRedSemiBold("NEVER KEEP ANYTHING IN MIND!") + append(" As soon as a task or idea comes to mind, leave it to the list.") + append(" We get really tired when we try to keep everything in mind. ") + appendGreenSemiBold("TRY TO EXPERIENCE") + append(" the feeling when you don't need to remember anything. ") + appendGreenSemiBold("EVERYTHING") + append(" in the task list.") + } + ) + + PTextView( + buildAnnotatedString { + appendGreenSemiBold("ADD NEW TASKS ONLY TO THE TOMORROW FOLDER.") + append(" If I add this to ") + appendGreenSemiBold("TODAY,") + append(" it breaks my plans and overwhelms me.") + } + ) + } + + item { + + HeaderView("Repeating Tasks") + + PTextView( + buildAnnotatedString { + append("There are many repeating tasks or events that we have to remember.") + append(" Like birthdays, recurring payments, special dates, etc.") + } + ) + + PTextView( + buildAnnotatedString { + append("I have about 30, and have no idea how to keep them all in mind. ") + appendGreenSemiBold("EVERYTHING IN THE APP.") + } + ) + + PTextView( + buildAnnotatedString { + append("You can create any kind of repeating task:") + append("\n- Every Day;") + append("\n- Every N Days;") + append("\n- Days of the Week;") + append("\n- Days of the Month;") + append("\n- Days of the Year.") + } + ) + + PTextView( + buildAnnotatedString { + append("Let's create a birthday reminder:") + } + ) + + ScreenshotView( + resId = R.drawable.doc_repeating_form_1, + fraction = 0.7f, + ) + } + + item { + + PTextView( + buildAnnotatedString { + append("Now, on ") + appendGreenSemiBold("MARCH 30,") + append(" this task will appear in ") + appendGreenSemiBold("TODAY") + append(" folder, so you can't miss it.") + } + ) + + PTextView( + buildAnnotatedString { + append("One more example: paying for internet service at the ") + appendGreenSemiBold("END OF THE MONTH.") + } + ) + + ScreenshotView( + resId = R.drawable.doc_repeating_form_2, + fraction = 0.7f, + ) + + PTextView( + buildAnnotatedString { + append("Also, these tasks will appear on the ") + appendGreenSemiBold("CALENDAR.") + } + ) + } + + item { + + HeaderView("Calendar") + + ScreenshotView( + resId = R.drawable.doc_calendar_button, + fraction = 0.7f, + innerPadding = 4.dp, + ) + + PTextView( + buildAnnotatedString { + append("A regular calendar where you can schedule tasks. As I mentioned, repeating tasks are also here.") + } + ) + + ScreenshotView( + resId = R.drawable.doc_calendar_screen, + fraction = 0.7f, + innerPadding = 4.dp, + ) + } + + item { + + SeparatorView() + + HeaderView("Let's Go") + + PTextView( + buildAnnotatedString { + append("I hope my app will lead you to what matters to you the most in your life.") + }, + ) + + PTextView( + buildAnnotatedString { + append("If you have any questions, please feel free to ask.") + }, + ) + + Text( + text = "Ask a Question", + modifier = Modifier + .padding(start = H_PADDING - 1.dp) + .clip(roundedShape) + .background(c.blue) + .padding(horizontal = 12.dp, vertical = 4.dp) + .clickable { + askAQuestion(state.askQuestionSubject) + }, + color = c.white, + fontWeight = FontWeight.SemiBold, + ) + + Text( + text = "Go to the App", + modifier = Modifier + .padding(start = H_PADDING - 1.dp, top = 12.dp) + .clip(roundedShape) + .background(c.blue) + .padding(horizontal = 12.dp, vertical = 4.dp) + .clickable { + vm.onRead() + navigationLayer.close() + }, + color = c.white, + fontWeight = FontWeight.SemiBold, + ) + + PTextView( + buildAnnotatedString { + append("Best regards,\n") + withLink(LinkAnnotation.Url(url = "https://github.com/Medvedev91")) { + appendBlueSemiBold("Ivan") + } + }, + modifier = Modifier + .padding(top = 40.dp, bottom = 20.dp), + ) + } + + item { + ZStack(Modifier.navigationBarsPadding()) + } + } + } +} + +private fun AnnotatedString.Builder.appendRedSemiBold(text: String) { + withStyle(style = SpanStyle(fontWeight = FontWeight.SemiBold, color = c.red)) { + append(text) + } +} + +private fun AnnotatedString.Builder.appendGreenSemiBold(text: String) { + withStyle(style = SpanStyle(fontWeight = FontWeight.SemiBold, color = c.green)) { + append(text) + } +} + +private fun AnnotatedString.Builder.appendBlueSemiBold(text: String) { + withStyle(style = SpanStyle(fontWeight = FontWeight.SemiBold, color = c.blue)) { + append(text) + } +} + +@Composable +private fun HeaderView( + text: String, +) { + Text( + text = text, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = H_PADDING) + .padding(top = 48.dp, bottom = 8.dp), + color = c.text, + fontWeight = Header__titleFontWeight, + fontSize = Header__titleFontSize, + ) +} + +@Composable +private fun SeparatorView() { + ZStack( + modifier = Modifier + .padding(horizontal = H_PADDING, vertical = 20.dp) + .fillMaxWidth() + .height(1.dp) + .background(c.divider), + ) +} + +@Composable +private fun PTextView( + text: AnnotatedString, + modifier: Modifier = Modifier, +) { + Text( + text = text, + modifier = modifier + .fillMaxWidth() + .padding(horizontal = H_PADDING) + .padding(vertical = 8.dp), + color = c.text, + lineHeight = pTextLineHeight, + fontWeight = FontWeight.Normal, + ) +} + +@Composable +private fun ForceTextView( + text: String, + modifier: Modifier = Modifier, +) { + Text( + text = text, + modifier = modifier + .padding(horizontal = H_PADDING, vertical = 8.dp), + fontSize = 20.sp, + fontWeight = FontWeight.SemiBold, + color = c.blue, + ) +} + +/// + +private val screenshotBorderColor: Color = c.gray5 +private val screenshotSliderShape = SquircleShape(24.dp) + +@Composable +private fun ScreenshotView( + @DrawableRes resId: Int, + fraction: Float = 1f, + innerPadding: Dp = 0.dp, +) { + Image( + painter = painterResource(id = resId), + modifier = Modifier + .padding(horizontal = 8.dp, vertical = 8.dp) + .fillMaxWidth(fraction = fraction) + .clip(screenshotSliderShape) + .border(1.dp, screenshotBorderColor, shape = screenshotSliderShape) + .padding(innerPadding), + contentDescription = "Screenshot", + contentScale = ContentScale.Fit, + ) +} diff --git a/android/src/main/java/me/timeto/app/ui/form/button/FormButtonSybol.kt b/android/src/main/java/me/timeto/app/ui/form/button/FormButtonSybol.kt new file mode 100644 index 000000000..c20df4561 --- /dev/null +++ b/android/src/main/java/me/timeto/app/ui/form/button/FormButtonSybol.kt @@ -0,0 +1,57 @@ +package me.timeto.app.ui.form.button + +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment.Companion.CenterVertically +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import me.timeto.app.ui.HStack +import me.timeto.app.ui.c +import me.timeto.app.ui.symbol.SymbolView +import me.timeto.shared.Symbol + +@Composable +fun FormButtonSymbol( + title: String, + symbol: Symbol, + color: Color, + withArrow: Boolean, + isFirst: Boolean, + isLast: Boolean, + onClick: () -> Unit, +) { + FormButtonView( + title = title, + titleColor = null, + isFirst = isFirst, + isLast = isLast, + modifier = Modifier, + rightView = { + HStack( + verticalAlignment = CenterVertically, + ) { + + SymbolView( + symbol = symbol, + color = color, + letterSize = 23.sp, + iconSize = 22.dp, + emojiSize = 20.sp, + modifier = Modifier + .padding(end = if (withArrow) 7.dp else 10.dp), + ) + + if (withArrow) { + FormButtonArrowView() + } + } + }, + onClick = { + onClick() + }, + onLongClick = null, + ) +} diff --git a/android/src/main/java/me/timeto/app/ui/home/HomeReadmeView.kt b/android/src/main/java/me/timeto/app/ui/home/HomeReadmeView.kt index 1a33010a6..cca52d959 100644 --- a/android/src/main/java/me/timeto/app/ui/home/HomeReadmeView.kt +++ b/android/src/main/java/me/timeto/app/ui/home/HomeReadmeView.kt @@ -17,19 +17,19 @@ import androidx.compose.ui.unit.dp import me.timeto.app.ui.SquircleShape import me.timeto.app.ui.VStack import me.timeto.app.ui.c +import me.timeto.app.ui.doc.DocFs import me.timeto.app.ui.navigation.LocalNavigationFs -import me.timeto.app.ui.readme.Readme2Fs import me.timeto.app.ui.roundedShape private val shape = SquircleShape(12.dp) private val bottomMargin: Dp = (HomeScreen__itemHeight - HomeScreen__itemCircleHeight) / 2 +// todo remove after update July 2026 @Composable fun HomeReadmeView( title: String, buttonText: String, - onButtonClick: () -> Unit, ) { val navigationFs = LocalNavigationFs.current @@ -62,9 +62,10 @@ fun HomeReadmeView( .clip(roundedShape) .background(c.white) .clickable { - onButtonClick() navigationFs.push { - Readme2Fs() + DocFs( + forceRead = true, + ) } } .padding(horizontal = 10.dp), diff --git a/android/src/main/java/me/timeto/app/ui/home/HomeScreen.kt b/android/src/main/java/me/timeto/app/ui/home/HomeScreen.kt index 50e1b0a87..5236bea85 100644 --- a/android/src/main/java/me/timeto/app/ui/home/HomeScreen.kt +++ b/android/src/main/java/me/timeto/app/ui/home/HomeScreen.kt @@ -1,7 +1,7 @@ package me.timeto.app.ui.home import android.os.Build -import androidx.activity.compose.LocalActivity +import androidx.activity.compose.BackHandler import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.PaddingValues @@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -19,7 +20,6 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.zIndex -import me.timeto.app.MainActivity import me.timeto.app.ui.VStack import me.timeto.app.ui.c import me.timeto.app.ui.pxToDp @@ -28,8 +28,11 @@ import me.timeto.app.ui.roundedShape import me.timeto.app.ui.checklists.ChecklistView import me.timeto.app.ui.Padding import me.timeto.app.ui.SpacerW1 +import me.timeto.app.ui.doc.DocFs import me.timeto.app.ui.donations.DonationsFs import me.timeto.app.ui.home.buttons.HomeButtonsView +import me.timeto.app.ui.home.tasks.HomeTasksBarView +import me.timeto.app.ui.home.tasks.HomeTasksView import me.timeto.app.ui.navigation.LocalNavigationFs import me.timeto.app.ui.privacy.PrivacyFs import me.timeto.app.ui.whats_new.WhatsNewFs @@ -50,66 +53,83 @@ val HomeScreen__barTextLineHeight = 18.sp fun HomeScreen() { val navigationFs = LocalNavigationFs.current - val mainActivity = LocalActivity.current as MainActivity val (vm, state) = rememberVm { HomeVm() } + val isToday: Boolean = + state.taskFolderUi.taskFolderDb.isToday + + BackHandler(!isToday) { + vm.setTodayTaskFolder() + } + + val forceOpenDoc = state.forceOpenDoc + LaunchedEffect(forceOpenDoc) { + if (forceOpenDoc) { + navigationFs.push { + DocFs(forceRead = true) + } + } + } + val checklistDb = state.checklistDb VStack( modifier = Modifier .fillMaxSize() - .background(c.black) - .padding(top = mainActivity.statusBarHeightDp), + .background(c.black), horizontalAlignment = Alignment.CenterHorizontally, ) { - HomeTimerView( - vm = vm, - state = state, - ) - - HomeExtraTriggersView( - extraTriggers = state.extraTriggers, - modifier = Modifier.padding(top = 10.dp), - contentPadding = PaddingValues(horizontal = 50.dp), - ) - - val privacyMessage = state.privacyMessage - if (privacyMessage != null) { - MessageButton( - title = privacyMessage, - onClick = { - navigationFs.push { - PrivacyFs(toForceChoice = true) - } - }, - ) - } + if (isToday) { - val whatsNewMessage = state.whatsNewMessage - if (whatsNewMessage != null) { - MessageButton( - title = whatsNewMessage, - onClick = { - navigationFs.push { - WhatsNewFs() - } - }, + HomeTimerView( + vm = vm, + state = state, ) - } - val donationsMessage = state.donationsMessage - if (donationsMessage != null) { - MessageButton( - title = donationsMessage, - onClick = { - navigationFs.push { - DonationsFs() - } - }, + HomeExtraTriggersView( + extraTriggers = state.extraTriggers, + modifier = Modifier.padding(top = 10.dp), + contentPadding = PaddingValues(horizontal = 50.dp), ) + + val privacyMessage = state.privacyMessage + if (privacyMessage != null) { + MessageButton( + title = privacyMessage, + onClick = { + navigationFs.push { + PrivacyFs(toForceChoice = true) + } + }, + ) + } + + val whatsNewMessage = state.whatsNewMessage + if (whatsNewMessage != null) { + MessageButton( + title = whatsNewMessage, + onClick = { + navigationFs.push { + WhatsNewFs() + } + }, + ) + } + + val donationsMessage = state.donationsMessage + if (donationsMessage != null) { + MessageButton( + title = donationsMessage, + onClick = { + navigationFs.push { + DonationsFs() + } + }, + ) + } } VStack( @@ -140,10 +160,10 @@ fun HomeScreen() { val checklistScrollState = rememberLazyListState() - val isMainListItemsExists = state.mainListItemsUi.isNotEmpty() + val isMainListItemsExists = state.homeTasksItemsUi.isNotEmpty() val listSizes = state.listsSizes - if (checklistDb != null) { + if (checklistDb != null && isToday) { ChecklistView( checklistDb = checklistDb, modifier = Modifier @@ -157,12 +177,14 @@ fun HomeScreen() { ) } - if (isMainListItemsExists) { + if (isMainListItemsExists || !isToday) { HomeTasksView( - homeVm = vm, homeState = state, - modifier = Modifier - .height(listSizes.mainTasks.dp), + modifier = + if (!isToday) + Modifier.weight(1f) + else + Modifier.height(listSizes.mainTasks.dp) ) } @@ -177,13 +199,10 @@ fun HomeScreen() { HomeNotificationsView(notificationsPermissionUi) } - if (state.showReadme) { + if (state.showDocBanner) { HomeReadmeView( title = state.readmeTitle, buttonText = state.readmeButtonText, - onButtonClick = { - vm.onReadmeOpen() - }, ) } @@ -194,6 +213,13 @@ fun HomeScreen() { ) } + HomeTasksBarView( + tasksBarUi = state.tasksBarUi, + changeTaskFolder = { taskFolderUi -> + vm.updateTaskFolder(taskFolderUi) + }, + ) + HomeButtonsView() Padding(vertical = 8.dp) diff --git a/android/src/main/java/me/timeto/app/ui/home/HomeTasksView.kt b/android/src/main/java/me/timeto/app/ui/home/HomeTasksView.kt deleted file mode 100644 index b95619dfe..000000000 --- a/android/src/main/java/me/timeto/app/ui/home/HomeTasksView.kt +++ /dev/null @@ -1,315 +0,0 @@ -package me.timeto.app.ui.home - -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.fillMaxHeight -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.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material.Icon -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import me.timeto.app.ui.HStack -import me.timeto.app.R -import me.timeto.app.ui.ZStack -import me.timeto.app.ui.c -import me.timeto.app.ui.onePx -import me.timeto.app.ui.roundedShape -import me.timeto.app.ui.navigation.LocalNavigationFs -import me.timeto.app.ui.task_form.TaskFormFs -import me.timeto.app.ui.tasks.TaskTimerFs -import me.timeto.shared.TextFeatures -import me.timeto.shared.vm.home.HomeVm -import me.timeto.shared.vm.task_form.TaskFormStrategy - -private val mainTaskInnerHPadding: Dp = 7.dp -private val mainTaskOuterHPadding: Dp = HomeScreen__hPadding - mainTaskInnerHPadding - -@Composable -fun HomeTasksView( - homeVm: HomeVm, - homeState: HomeVm.State, - modifier: Modifier, -) { - val scrollState = rememberLazyListState() - - LazyColumn( - modifier = modifier - .fillMaxWidth(), - state = scrollState, - reverseLayout = true, - ) { - - items( - items = homeState.mainListItemsUi, - key = { it.id }, - ) { itemUi -> - when (itemUi) { - is HomeVm.MainListItemUi.MainTaskUi -> TaskView( - taskListUi = itemUi, - showOnHomeActivity = - !homeState.onHomeActivity && (itemUi.taskUi.taskDb.folder_id == homeState.activityTaskFolderDb?.id), - ) - is HomeVm.MainListItemUi.TaskFolderBarUi -> TaskFolderBarView( - barUi = itemUi, - onHomeActivity = homeState.onHomeActivity, - toggleOnHomeActivity = { - homeVm.toggleOnHomeActivity() - }, - ) - } - } - } -} - -@Composable -private fun TaskView( - taskListUi: HomeVm.MainListItemUi.MainTaskUi, - showOnHomeActivity: Boolean, -) { - val navigationFs = LocalNavigationFs.current - - HStack( - modifier = Modifier - .height(HomeScreen__itemHeight) - .fillMaxWidth() - .padding(horizontal = mainTaskOuterHPadding) - .clip(roundedShape) - .clickable { - val taskDb = taskListUi.taskUi.taskDb - taskDb.startIntervalForUi( - ifJustStarted = {}, - ifTimerNeeded = { - navigationFs.push { - TaskTimerFs( - taskDb = taskDb, - ) - } - }, - ) - } - .padding(horizontal = mainTaskInnerHPadding), - verticalAlignment = Alignment.CenterVertically, - ) { - - val timeUi = taskListUi.timeUi - if (timeUi != null) { - val bgColor: Color = when (timeUi.status) { - TextFeatures.TimeData.STATUS.IN -> c.homeFg - TextFeatures.TimeData.STATUS.SOON -> c.blue - TextFeatures.TimeData.STATUS.OVERDUE -> c.red - } - HStack( - modifier = Modifier - .padding(end = if (taskListUi.taskUi.tf.paused != null) 9.dp else HomeScreen__itemCircleMarginTrailing) - .height(HomeScreen__itemCircleHeight) - .clip(roundedShape) - .background(bgColor) - .padding(horizontal = HomeScreen__itemCircleHPadding), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = timeUi.text, - modifier = Modifier - .padding(top = onePx), - fontWeight = HomeScreen__itemCircleFontWeight, - fontSize = HomeScreen__itemCircleFontSize, - lineHeight = 18.sp, - color = c.white, - ) - } - } - - if (taskListUi.taskUi.tf.paused != null) { - ZStack( - modifier = Modifier - .padding(end = 8.dp) - .size(HomeScreen__itemCircleHeight) - .clip(roundedShape) - .background(c.green), - contentAlignment = Alignment.Center, - ) { - Icon( - painter = painterResource(id = R.drawable.sf_pause_medium_black), - contentDescription = "Paused Task", - tint = c.white, - modifier = Modifier - .size(10.dp), - ) - } - } - - Text( - text = taskListUi.text, - modifier = Modifier - .padding(end = 4.dp) - .weight(1f), - fontSize = HomeScreen__primaryFontSize, - color = c.white, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - - if (timeUi != null) { - val noteColor: Color = when (timeUi.status) { - TextFeatures.TimeData.STATUS.IN -> c.secondaryText - TextFeatures.TimeData.STATUS.SOON -> c.blue - TextFeatures.TimeData.STATUS.OVERDUE -> c.red - } - Text( - text = timeUi.note, - fontSize = HomeScreen__primaryFontSize, - color = noteColor, - ) - } - - if (showOnHomeActivity) { - ZStack( - modifier = Modifier - .padding(start = 8.dp, end = 1.dp) - .clip(roundedShape) - .clickable { - taskListUi.toggleOnHomeActivity() - }, - contentAlignment = Alignment.Center, - ) { - Icon( - painter = painterResource(R.drawable.sf_house_medium_semibold), - contentDescription = "New Task", - tint = if (taskListUi.taskUi.taskDb.onHomeActivity) c.secondaryText else c.homeFg, - modifier = Modifier - .size(23.dp), - ) - } - } - } -} - -@Composable -private fun TaskFolderBarView( - barUi: HomeVm.MainListItemUi.TaskFolderBarUi, - onHomeActivity: Boolean, - toggleOnHomeActivity: () -> Unit, -) { - val navigationFs = LocalNavigationFs.current - - HStack( - modifier = Modifier - .height(HomeScreen__itemHeight) - .fillMaxWidth() - .padding(start = mainTaskOuterHPadding), - verticalAlignment = Alignment.CenterVertically, - ) { - - HStack( - modifier = Modifier - .clip(roundedShape) - .fillMaxHeight() - .weight(1f) - .clickable { - navigationFs.push { - TaskFormFs( - strategy = TaskFormStrategy.NewTask( - taskFolderDb = barUi.taskFolderDb, - ) - ) - } - } - .padding(start = mainTaskInnerHPadding), - verticalAlignment = Alignment.CenterVertically, - ) { - - ZStack( - modifier = Modifier - .size(HomeScreen__itemCircleHeight) - .clip(roundedShape) - .background(c.blue), - contentAlignment = Alignment.Center, - ) { - Icon( - painter = painterResource(R.drawable.sf_plus_medium_bold), - contentDescription = "New Task", - tint = c.black, - modifier = Modifier - .size(10.dp), - ) - } - - Text( - text = barUi.addButtonText, - modifier = Modifier - .padding(start = 8.dp), - color = c.blue, - fontSize = HomeScreen__primaryFontSize, - maxLines = 1, - ) - } - - ZStack( - modifier = Modifier - .padding(end = 3.dp) - .size(size = HomeScreen__itemHeight) - .clip(roundedShape) - .clickable { - toggleOnHomeActivity() - }, - contentAlignment = Alignment.Center, - ) { - Icon( - painter = painterResource(R.drawable.sf_house_medium_semibold), - contentDescription = "New Task", - tint = if (onHomeActivity) c.secondaryText else c.homeFg, - modifier = Modifier - .size(23.dp), - ) - } - - if (barUi.todayTasksCount > 0) { - ZStack( - modifier = Modifier - .padding(start = 5.dp, end = 8.dp) - .size(HomeScreen__itemCircleHeight) - .clip(roundedShape) - .clickable { - barUi.toggleCollapseToday() - } - .border(2.dp, c.secondaryText, roundedShape), - contentAlignment = Alignment.Center, - ) { - if (barUi.isCollapsed) { - Text( - text = barUi.todayTasksCount.toString(), - modifier = Modifier - .padding(start = 1.dp), - color = c.secondaryText, - fontSize = HomeScreen__itemCircleFontSize, - fontWeight = HomeScreen__itemCircleFontWeight, - lineHeight = 20.sp, - ) - } else { - Icon( - painterResource(id = R.drawable.sf_chevron_down_medium_bold), - contentDescription = "Today Tasks", - tint = c.secondaryText, - modifier = Modifier - .size(10.dp), - ) - } - } - } - } -} diff --git a/android/src/main/java/me/timeto/app/ui/home/HomeTimerView.kt b/android/src/main/java/me/timeto/app/ui/home/HomeTimerView.kt index bd929a149..021f8595e 100644 --- a/android/src/main/java/me/timeto/app/ui/home/HomeTimerView.kt +++ b/android/src/main/java/me/timeto/app/ui/home/HomeTimerView.kt @@ -1,5 +1,6 @@ package me.timeto.app.ui.home +import androidx.activity.compose.LocalActivity import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.Spring @@ -28,6 +29,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import me.timeto.app.MainActivity import me.timeto.app.ui.HStack import me.timeto.app.ui.H_PADDING import me.timeto.app.R @@ -39,12 +41,11 @@ import me.timeto.app.ui.squircleShape import me.timeto.app.ui.timerFont import me.timeto.app.toColor import me.timeto.app.ui.daytime_picker.DaytimePickerSheet +import me.timeto.app.ui.doc.DocFs import me.timeto.app.ui.history.form.HistoryFormFs import me.timeto.app.ui.navigation.LocalNavigationFs -import me.timeto.app.ui.readme.ReadmeFs import me.timeto.app.ui.timer.TimerSheet import me.timeto.shared.vm.home.HomeVm -import me.timeto.shared.vm.readme.ReadmeVm @Composable fun HomeTimerView( @@ -53,6 +54,7 @@ fun HomeTimerView( ) { val navigationFs = LocalNavigationFs.current + val mainActivity = LocalActivity.current as MainActivity val noteColor = animateColorAsState(state.timerStateUi.noteColor.toColor()).value val timerColor = animateColorAsState(state.timerStateUi.timerColor.toColor()).value @@ -61,6 +63,8 @@ fun HomeTimerView( ).value VStack( + modifier = Modifier + .padding(top = mainActivity.statusBarHeightDp), horizontalAlignment = Alignment.CenterHorizontally, ) { @@ -230,8 +234,8 @@ fun HomeTimerView( color = timerColor, onClick = { navigationFs.push { - ReadmeFs( - defaultItem = ReadmeVm.DefaultItem.pomodoro, + DocFs( + forceRead = false, ) } }, diff --git a/android/src/main/java/me/timeto/app/ui/home/buttons/HomeButtonActivityView.kt b/android/src/main/java/me/timeto/app/ui/home/buttons/HomeButtonActivityView.kt index 95c96a1d6..e5532e8a2 100644 --- a/android/src/main/java/me/timeto/app/ui/home/buttons/HomeButtonActivityView.kt +++ b/android/src/main/java/me/timeto/app/ui/home/buttons/HomeButtonActivityView.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.Icon @@ -33,13 +34,16 @@ import me.timeto.app.ui.home.HomeScreen__itemCircleFontWeight import me.timeto.app.ui.home.HomeScreen__itemCircleHPadding import me.timeto.app.ui.home.HomeScreen__itemCircleHeight import me.timeto.app.ui.home.HomeScreen__itemHeight -import me.timeto.app.ui.home.settings.HomeSettingsButtonsFs import me.timeto.app.ui.navigation.LocalNavigationFs import me.timeto.app.ui.navigation.picker.NavigationPickerItem import me.timeto.app.ui.roundedShape +import me.timeto.app.ui.symbol.SymbolView +import me.timeto.app.ui.task_form.TaskFormFs import me.timeto.app.ui.timer.TimerSheet import me.timeto.shared.DaytimeUi +import me.timeto.shared.Symbol import me.timeto.shared.vm.home.buttons.HomeButtonType +import me.timeto.shared.vm.task_form.TaskFormStrategy @OptIn(ExperimentalFoundationApi::class) @Composable @@ -150,9 +154,19 @@ fun HomeButtonActivityView( activity.startRestOfGoal() } - ContextPickerItemType.HomeScreenSettings -> { + is ContextPickerItemType.NewTaskToday -> { navigationFs.push { - HomeSettingsButtonsFs() + TaskFormFs( + strategy = pickerItem.item.newTaskFormStrategy, + ) + } + } + + is ContextPickerItemType.NewTaskTomorrow -> { + navigationFs.push { + TaskFormFs( + strategy = pickerItem.item.newTaskFormStrategy, + ) } } } @@ -180,15 +194,32 @@ fun HomeButtonActivityView( .fillMaxSize(), ) { - Text( - text = activity.activityDb.emoji, + val symbol: Symbol = remember(activity.activityDb) { + activity.activityDb.symbolOrDefault() + } + + SymbolView( + symbol = symbol, + color = c.white, + letterSize = HomeScreen__itemCircleFontSize, + iconSize = 15.dp, + emojiSize = HomeScreen__itemCircleFontSize, modifier = Modifier - .padding(start = HomeScreen__itemCircleHPadding) + .padding( + start = when (symbol) { + is Symbol.Letter -> 7.dp + is Symbol.Icon -> 6.dp + is Symbol.Emoji -> HomeScreen__itemCircleHPadding + }, + ) + .offset( + y = when (symbol) { + is Symbol.Letter, + is Symbol.Emoji -> (-1).dp + is Symbol.Icon -> 0.dp + }, + ) .align(Alignment.CenterStart), - color = c.white, - fontSize = HomeScreen__itemCircleFontSize, - fontWeight = HomeScreen__itemCircleFontWeight, - lineHeight = HomeScreen__barTextLineHeight, ) ZStack( @@ -279,13 +310,23 @@ private fun buildContextPickerItems( ) ) } + list.add( NavigationPickerItem( - title = "Home Screen Settings", + title = "New Task Today", isSelected = false, - item = ContextPickerItemType.HomeScreenSettings, + item = ContextPickerItemType.NewTaskToday(activity.newTaskTodayFormStrategy), ) ) + + list.add( + NavigationPickerItem( + title = "New Task Tomorrow", + isSelected = false, + item = ContextPickerItemType.NewTaskTomorrow(activity.newTaskTomorrowFormStrategy), + ) + ) + return list } @@ -301,9 +342,16 @@ private sealed class ContextPickerItemType { val childActivityUi: HomeButtonType.Activity.ChildActivityUi, ) : ContextPickerItemType() + data class NewTaskToday( + val newTaskFormStrategy: TaskFormStrategy.NewTask, + ) : ContextPickerItemType() + + data class NewTaskTomorrow( + val newTaskFormStrategy: TaskFormStrategy.NewTask, + ) : ContextPickerItemType() + object UntilTime : ContextPickerItemType() object RestOfGoal : ContextPickerItemType() - object HomeScreenSettings : ContextPickerItemType() } @Composable diff --git a/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTaskStaEndView.kt b/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTaskStaEndView.kt new file mode 100644 index 000000000..e9f3e6fbf --- /dev/null +++ b/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTaskStaEndView.kt @@ -0,0 +1,93 @@ +package me.timeto.app.ui.home.tasks + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material.DismissState +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp +import kotlinx.coroutines.launch +import me.timeto.app.ui.HStack +import me.timeto.app.ui.SpacerW1 +import me.timeto.app.ui.ZStack +import me.timeto.app.ui.c +import me.timeto.app.ui.home.HomeScreen__barTextLineHeight +import me.timeto.app.ui.home.HomeScreen__itemCircleFontWeight +import me.timeto.app.ui.home.HomeScreen__itemHeight +import me.timeto.app.ui.home.HomeScreen__primaryFontSize +import me.timeto.app.ui.roundedShape + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun HomeTaskStaEndView( + state: DismissState, + onMoveToTimer: () -> Unit, + onDelete: () -> Unit, +) { + val scope = rememberCoroutineScope() + + HStack( + modifier = Modifier + .fillMaxHeight() + .background(c.red), + verticalAlignment = Alignment.CenterVertically, + ) { + + Text( + text = "Move to Timer", + color = c.white, + modifier = Modifier + .padding(end = 8.dp) + .clip(roundedShape) + .clickable { + onMoveToTimer() + } + .padding(horizontal = 8.dp, vertical = 4.dp), + ) + + SpacerW1() + + Text( + text = "Cancel", + color = c.white, + modifier = Modifier + .padding(end = 8.dp) + .clip(roundedShape) + .clickable { + scope.launch { + state.reset() + } + } + .padding(horizontal = 8.dp, vertical = 4.dp), + ) + + ZStack( + modifier = Modifier + .padding(end = 7.dp) + .height(HomeScreen__itemHeight - 8.dp) + .clip(roundedShape) + .background(c.white) + .padding(horizontal = 10.dp) + .clickable { + onDelete() + }, + contentAlignment = Alignment.Center, + ) { + Text( + text = "Delete", + color = c.red, + fontSize = HomeScreen__primaryFontSize, + fontWeight = HomeScreen__itemCircleFontWeight, + lineHeight = HomeScreen__barTextLineHeight, + ) + } + } +} diff --git a/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTaskStaStartView.kt b/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTaskStaStartView.kt new file mode 100644 index 000000000..bc3add2f6 --- /dev/null +++ b/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTaskStaStartView.kt @@ -0,0 +1,129 @@ +package me.timeto.app.ui.home.tasks + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import me.timeto.app.R +import me.timeto.app.ui.HStack +import me.timeto.app.ui.ZStack +import me.timeto.app.ui.c +import me.timeto.app.ui.events.EventFormFs +import me.timeto.app.ui.home.HomeScreen__itemCircleHeight +import me.timeto.app.ui.navigation.LocalNavigationFs +import me.timeto.app.ui.roundedShape +import me.timeto.app.ui.task_form.TaskFormFs +import me.timeto.shared.vm.home.tasks.HomeTasksItemUi + +// STA - Swipe to Action +@Composable +fun HomeTaskStaStartView( + homeTaskUi: HomeTasksItemUi.HomeTaskUi, + resetSta: (() -> Unit) -> Unit, +) { + val scope = rememberCoroutineScope() + val navigationFs = LocalNavigationFs.current + + HStack( + modifier = Modifier + .fillMaxSize() + .background(c.blue), + verticalAlignment = Alignment.CenterVertically, + ) { + + ZStack( + modifier = Modifier + .padding(start = 8.dp) + .size(HomeScreen__itemCircleHeight) + .clip(roundedShape) + .background(c.white) + .clickable { + resetSta({}) + }, + contentAlignment = Alignment.Center, + ) { + Icon( + painterResource(id = R.drawable.sf_xmark_small_medium), + contentDescription = "Close", + tint = c.blue, + modifier = Modifier + .size(10.dp), + ) + } + + ZStack( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .padding(end = 8.dp) + .clip(roundedShape) + .clickable { + scope.launch { + delay(200L) + resetSta({}) + } + navigationFs.push { + TaskFormFs(strategy = homeTaskUi.editStrategy) + } + }, + contentAlignment = Alignment.CenterStart, + ) { + + Text( + text = "Edit..", + modifier = Modifier + .padding(start = 8.dp), + color = c.white, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + + homeTaskUi.staTaskFoldersUi.forEach { staFolderUi -> + HomeTasksFolderButton( + taskFolderUi = staFolderUi.taskFolderUi, + color = if (staFolderUi.isSelected) c.white else c.secondaryText, + modifier = Modifier, + onClick = { + resetSta { + staFolderUi.onTap() + } + }, + ) + } + + HomeTasksCalendarButton( + color = c.secondaryText, + onClick = { + scope.launch { + delay(200L) + resetSta({}) + } + navigationFs.push { + EventFormFs( + initEventDb = null, + initText = homeTaskUi.taskUi.taskDb.text, + initTime = null, + onDone = { + homeTaskUi.taskUi.delete() + }, + ) + } + }, + ) + } +} diff --git a/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTaskView.kt b/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTaskView.kt new file mode 100644 index 000000000..7324fad44 --- /dev/null +++ b/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTaskView.kt @@ -0,0 +1,251 @@ +package me.timeto.app.ui.home.tasks + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import kotlinx.coroutines.launch +import me.timeto.app.Haptic +import me.timeto.app.R +import me.timeto.app.toColor +import me.timeto.app.ui.HStack +import me.timeto.app.ui.SwipeToAction +import me.timeto.app.ui.ZStack +import me.timeto.app.ui.c +import me.timeto.app.ui.home.HomeScreen__itemCircleFontSize +import me.timeto.app.ui.home.HomeScreen__itemCircleFontWeight +import me.timeto.app.ui.home.HomeScreen__itemCircleHPadding +import me.timeto.app.ui.home.HomeScreen__itemCircleHeight +import me.timeto.app.ui.home.HomeScreen__itemCircleMarginTrailing +import me.timeto.app.ui.home.HomeScreen__itemHeight +import me.timeto.app.ui.home.HomeScreen__primaryFontSize +import me.timeto.app.ui.navigation.LocalNavigationFs +import me.timeto.app.ui.onePx +import me.timeto.app.ui.roundedShape +import me.timeto.app.ui.symbol.SymbolView +import me.timeto.app.ui.task_timer.TaskTimerFs +import me.timeto.shared.ActivityUi +import me.timeto.shared.Symbol +import me.timeto.shared.TextFeatures +import me.timeto.shared.db.TaskDb +import me.timeto.shared.vm.home.HomeVm +import me.timeto.shared.vm.home.tasks.HomeTasksItemUi + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun HomeTaskView( + homeTaskUi: HomeTasksItemUi.HomeTaskUi, + homeState: HomeVm.State, +) { + + val navigationFs = LocalNavigationFs.current + + val isEditOrDelete = remember { mutableStateOf(null) } + val stateOffsetAbsDp = remember { mutableStateOf(0.dp) } + + val scope = rememberCoroutineScope() + + val taskDb: TaskDb = + homeTaskUi.taskUi.taskDb + + SwipeToAction( + isStartOrEnd = isEditOrDelete, + modifier = Modifier + .height(HomeScreen__itemHeight), + startView = { state -> + HomeTaskStaStartView( + homeTaskUi = homeTaskUi, + resetSta = { onComplete -> + scope.launch { + state.reset() + onComplete() + } + }, + ) + }, + endView = { state -> + HomeTaskStaEndView( + state = state, + onMoveToTimer = { + Haptic.long() + homeTaskUi.taskUi.moveToTimer() + }, + onDelete = { + Haptic.long() + homeTaskUi.taskUi.delete() + }, + ) + }, + onStart = { + true + }, + onEnd = { + true + }, + stateOffsetAbsDp = stateOffsetAbsDp, + content = { + HStack( + modifier = Modifier + .height(HomeScreen__itemHeight) + .fillMaxWidth() + .background(c.black) + .padding(horizontal = homeTasksOuterHPadding) + .clip(roundedShape) + .clickable { + taskDb.startIntervalForUi( + ifJustStarted = {}, + ifTimerNeeded = { + navigationFs.push { + TaskTimerFs( + taskDb = taskDb, + ) + } + }, + ) + } + .padding(start = homeTasksInnerHPadding), + verticalAlignment = Alignment.CenterVertically, + ) { + + val timeUi = homeTaskUi.timeUi + if (timeUi != null) { + val bgColor: Color = when (timeUi.status) { + TextFeatures.TimeData.STATUS.IN -> c.homeFg + TextFeatures.TimeData.STATUS.SOON -> c.blue + TextFeatures.TimeData.STATUS.OVERDUE -> c.red + } + HStack( + modifier = Modifier + .padding(end = if (homeTaskUi.taskUi.tf.paused != null) 9.dp else HomeScreen__itemCircleMarginTrailing) + .height(HomeScreen__itemCircleHeight) + .clip(roundedShape) + .background(bgColor) + .padding(horizontal = HomeScreen__itemCircleHPadding), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = timeUi.text, + modifier = Modifier + .padding(top = onePx), + fontWeight = HomeScreen__itemCircleFontWeight, + fontSize = HomeScreen__itemCircleFontSize, + lineHeight = 18.sp, + color = c.white, + ) + } + } + + if (homeTaskUi.taskUi.tf.paused != null) { + ZStack( + modifier = Modifier + .padding(end = 8.dp) + .size(HomeScreen__itemCircleHeight) + .clip(roundedShape) + .background(c.green), + contentAlignment = Alignment.Center, + ) { + Icon( + painter = painterResource(id = R.drawable.sf_pause_medium_black), + contentDescription = "Paused Task", + tint = c.white, + modifier = Modifier + .size(10.dp), + ) + } + } + + val activityUi: ActivityUi? = + homeTaskUi.taskUi.activityUi + if (activityUi != null) { + val symbol: Symbol = + activityUi.symbol + ZStack( + modifier = Modifier + .width(20.dp), + ) { + val offsetX: Dp = remember(symbol) { + when (symbol) { + is Symbol.Letter -> 0.dp + is Symbol.Icon -> (-3).dp + is Symbol.Emoji -> (-3).dp + } + } + SymbolView( + symbol = symbol, + color = remember(activityUi.colorRgba) { + activityUi.colorRgba.toColor() + }, + letterSize = HomeScreen__primaryFontSize, + iconSize = 17.dp, + emojiSize = HomeScreen__itemCircleFontSize, + modifier = Modifier + .offset(x = offsetX), + ) + } + } + + Text( + text = homeTaskUi.text, + modifier = Modifier + .padding(end = 4.dp) + .weight(1f), + fontSize = HomeScreen__primaryFontSize, + color = c.white, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + + if (timeUi != null) { + val noteColor: Color = when (timeUi.status) { + TextFeatures.TimeData.STATUS.IN -> c.secondaryText + TextFeatures.TimeData.STATUS.SOON -> c.blue + TextFeatures.TimeData.STATUS.OVERDUE -> c.red + } + Text( + text = timeUi.note, + modifier = Modifier + .padding(end = homeTasksInnerHPadding), + fontSize = HomeScreen__primaryFontSize, + color = noteColor, + ) + } + + if ((homeState.taskFolderUi.activityDb != null) && + (taskDb.isToday || taskDb.isTomorrow) + ) { + HomeTasksFolderButton( + taskFolderUi = homeTaskUi.taskUi.taskFolderUi, + color = if (taskDb.isToday) c.orange else c.indigo, + modifier = Modifier + .padding(end = 1.dp), + onClick = { + homeTaskUi.taskUi.updateTaskFolder( + taskFolderDb = homeState.taskFolderUi.taskFolderDb, + ) + }, + ) + } + } + }, + ) +} diff --git a/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTasksBarView.kt b/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTasksBarView.kt new file mode 100644 index 000000000..3be4045f3 --- /dev/null +++ b/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTasksBarView.kt @@ -0,0 +1,91 @@ +package me.timeto.app.ui.home.tasks + +import android.view.MotionEvent +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.motionEventSpy +import androidx.compose.ui.unit.dp +import me.timeto.app.toColor +import me.timeto.app.ui.HStack +import me.timeto.app.ui.Screen +import me.timeto.app.ui.navigation.LocalNavigationFs +import me.timeto.app.ui.task_form.TaskFormFs +import me.timeto.shared.vm.home.tasks.HomeTasksBarUi +import me.timeto.app.ui.c +import me.timeto.app.ui.calendar.CalendarTabsView +import me.timeto.app.ui.home.HomeScreen__itemHeight +import me.timeto.app.ui.home.HomeScreen__primaryFontSize +import me.timeto.shared.TaskFolderUi + +@Composable +fun HomeTasksBarView( + tasksBarUi: HomeTasksBarUi, + changeTaskFolder: (TaskFolderUi) -> Unit, +) { + val navigationFs = LocalNavigationFs.current + + HStack( + modifier = Modifier + .height(HomeScreen__itemHeight) + .fillMaxWidth() + .padding(start = homeTasksOuterHPadding), + verticalAlignment = Alignment.CenterVertically, + ) { + + HStack( + modifier = Modifier + .fillMaxHeight() + .weight(1f) + .padding(end = 8.dp) + .motionEventSpy { event -> + if (event.action == MotionEvent.ACTION_DOWN) { + navigationFs.push { + TaskFormFs( + strategy = tasksBarUi.addTaskStrategy, + ) + } + } + } + .padding(start = homeTasksInnerHPadding), + verticalAlignment = Alignment.CenterVertically, + ) { + + Text( + text = "Task..", + color = c.secondaryText, + fontSize = HomeScreen__primaryFontSize, + ) + } + + tasksBarUi.taskFoldersUi.forEach { taskFolderUi -> + HomeTasksFolderButton( + taskFolderUi = taskFolderUi, + color = when { + taskFolderUi.taskFolderDb.id != tasksBarUi.taskFolderDb.id -> c.gray2 + else -> taskFolderUi.colorRgba.toColor() + }, + modifier = Modifier, + onClick = { + changeTaskFolder(taskFolderUi) + }, + ) + } + + HomeTasksCalendarButton( + color = c.gray2, + onClick = { + navigationFs.push { + Screen { + CalendarTabsView() + } + } + }, + ) + } +} diff --git a/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTasksCalendarButton.kt b/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTasksCalendarButton.kt new file mode 100644 index 000000000..db1cc3e33 --- /dev/null +++ b/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTasksCalendarButton.kt @@ -0,0 +1,34 @@ +package me.timeto.app.ui.home.tasks + +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import me.timeto.app.R + +@Composable +fun HomeTasksCalendarButton( + color: Color, + onClick: () -> Unit, +) { + HomeTasksIconButton( + onClick = { + onClick() + }, + modifier = Modifier + .padding(end = 2.dp), + content = { + Icon( + painter = painterResource(R.drawable.sf_calendar_medium_light), + contentDescription = "New Task", + tint = color, + modifier = Modifier + .size(21.dp), + ) + }, + ) +} diff --git a/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTasksFolderButton.kt b/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTasksFolderButton.kt new file mode 100644 index 000000000..1a6ac774d --- /dev/null +++ b/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTasksFolderButton.kt @@ -0,0 +1,88 @@ +package me.timeto.app.ui.home.tasks + +import androidx.compose.animation.core.Animatable +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.size +import androidx.compose.material.Icon +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.scale +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import me.timeto.app.R +import me.timeto.app.ui.ZStack +import me.timeto.app.ui.home.HomeScreen__itemHeight +import me.timeto.app.ui.roundedShape +import me.timeto.app.ui.symbol.SymbolView +import me.timeto.shared.TaskFolderUi +import me.timeto.shared.vm.home.tasks.homeTasksBarFolderAnimateFlow + +private val iconSize = 24.dp +private val letterSize = 23.sp + +@Composable +fun HomeTasksFolderButton( + taskFolderUi: TaskFolderUi, + color: Color, + modifier: Modifier, + onClick: () -> Unit, +) { + val scaleAnimation = remember { Animatable(1f) } + + LaunchedEffect(Unit) { + homeTasksBarFolderAnimateFlow.collect { folderId -> + if (folderId == taskFolderUi.taskFolderDb.id) { + scaleAnimation.animateTo(1.40f) + scaleAnimation.animateTo(1f) + scaleAnimation.animateTo(1.25f) + scaleAnimation.animateTo(1f) + } + } + } + + ZStack( + modifier = modifier + .size(HomeScreen__itemHeight) + .clip(roundedShape) + .clickable { + onClick() + }, + contentAlignment = Alignment.Center, + ) { + if (taskFolderUi.taskFolderDb.isToday) { + Icon( + painterResource(id = R.drawable.ms_wb_sunny_fill), + contentDescription = "Today", + tint = color, + modifier = Modifier + .size(iconSize) + .scale(scaleAnimation.value), + ) + } else if (taskFolderUi.taskFolderDb.isTomorrow) { + Icon( + painterResource(id = R.drawable.ms_dark_mode_fill), + contentDescription = "Tomorrow", + tint = color, + modifier = Modifier + .size(iconSize) + .scale(scaleAnimation.value), + ) + } else { + SymbolView( + symbol = taskFolderUi.symbol, + color = color, + letterSize = letterSize, + iconSize = iconSize, + emojiSize = letterSize, + modifier = Modifier + .scale(scaleAnimation.value), + ) + } + } +} diff --git a/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTasksIconButton.kt b/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTasksIconButton.kt new file mode 100644 index 000000000..5f2fcc986 --- /dev/null +++ b/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTasksIconButton.kt @@ -0,0 +1,30 @@ +package me.timeto.app.ui.home.tasks + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import me.timeto.app.ui.ZStack +import me.timeto.app.ui.home.HomeScreen__itemHeight +import me.timeto.app.ui.roundedShape + +@Composable +fun HomeTasksIconButton( + onClick: () -> Unit, + modifier: Modifier, + content: @Composable () -> Unit, +) { + ZStack( + modifier = modifier + .size(HomeScreen__itemHeight) + .clip(roundedShape) + .clickable { + onClick() + }, + contentAlignment = Alignment.Center, + ) { + content() + } +} diff --git a/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTasksTomorrowView.kt b/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTasksTomorrowView.kt new file mode 100644 index 000000000..7141c0cb6 --- /dev/null +++ b/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTasksTomorrowView.kt @@ -0,0 +1,130 @@ +package me.timeto.app.ui.home.tasks + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxHeight +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.material.Icon +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import me.timeto.app.R +import me.timeto.app.ui.HStack +import me.timeto.app.ui.ZStack +import me.timeto.app.ui.c +import me.timeto.app.ui.home.HomeScreen__hPadding +import me.timeto.app.ui.home.HomeScreen__itemCircleFontSize +import me.timeto.app.ui.home.HomeScreen__itemCircleFontWeight +import me.timeto.app.ui.home.HomeScreen__itemCircleHPadding +import me.timeto.app.ui.home.HomeScreen__itemCircleHeight +import me.timeto.app.ui.home.HomeScreen__itemCircleMarginTrailing +import me.timeto.app.ui.home.HomeScreen__itemHeight +import me.timeto.app.ui.home.HomeScreen__primaryFontSize +import me.timeto.app.ui.onePx +import me.timeto.app.ui.roundedShape +import me.timeto.shared.vm.home.tasks.HomeTasksItemUi + +@Composable +fun HomeTasksTomorrowView( + tomorrowUi: HomeTasksItemUi.HomeTomorrowItemUi, +) { + HStack( + modifier = Modifier + .height(HomeScreen__itemHeight) + .fillMaxWidth() + .background(c.black) + .padding(horizontal = HomeScreen__hPadding), + verticalAlignment = Alignment.CenterVertically, + ) { + + TypeIcon(tomorrowUi.type) + + val timeUi = tomorrowUi.timeUi + if (timeUi != null) { + HStack( + modifier = Modifier + .padding(end = HomeScreen__itemCircleMarginTrailing) + .height(HomeScreen__itemCircleHeight) + .clip(roundedShape) + .background(c.blue) + .padding(horizontal = HomeScreen__itemCircleHPadding), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = timeUi.text, + modifier = Modifier + .padding(top = onePx), + fontWeight = HomeScreen__itemCircleFontWeight, + fontSize = HomeScreen__itemCircleFontSize, + lineHeight = 18.sp, + color = c.white, + ) + } + } + + Text( + text = tomorrowUi.text, + modifier = Modifier + .padding(end = 4.dp) + .weight(1f), + fontSize = HomeScreen__primaryFontSize, + color = c.white, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } +} + +@Composable +private fun TypeIcon( + type: HomeTasksItemUi.HomeTomorrowItemUi.TomorrowType, +) { + ZStack( + modifier = Modifier + .fillMaxHeight() + .padding( + end = when (type) { + HomeTasksItemUi.HomeTomorrowItemUi.TomorrowType.repeating -> + 9.dp + HomeTasksItemUi.HomeTomorrowItemUi.TomorrowType.calendar -> + 8.dp + } + ), + contentAlignment = Alignment.Center, + ) { + Icon( + painter = painterResource( + when (type) { + HomeTasksItemUi.HomeTomorrowItemUi.TomorrowType.repeating -> + R.drawable.sf_repeat_medium_semibold + HomeTasksItemUi.HomeTomorrowItemUi.TomorrowType.calendar -> + R.drawable.sf_calendar_medium_regular + } + ), + contentDescription = when (type) { + HomeTasksItemUi.HomeTomorrowItemUi.TomorrowType.repeating -> + "Repeating Task" + HomeTasksItemUi.HomeTomorrowItemUi.TomorrowType.calendar -> + "Calendar Event" + }, + modifier = Modifier + .size( + when (type) { + HomeTasksItemUi.HomeTomorrowItemUi.TomorrowType.repeating -> + 19.dp + HomeTasksItemUi.HomeTomorrowItemUi.TomorrowType.calendar -> + 20.dp + } + ), + tint = c.blue, + ) + } +} diff --git a/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTasksView.kt b/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTasksView.kt new file mode 100644 index 000000000..1f77cbc0b --- /dev/null +++ b/android/src/main/java/me/timeto/app/ui/home/tasks/HomeTasksView.kt @@ -0,0 +1,67 @@ +package me.timeto.app.ui.home.tasks + +import androidx.activity.compose.LocalActivity +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import me.timeto.app.MainActivity +import me.timeto.app.ui.c +import me.timeto.app.ui.header.Header__titleFontSize +import me.timeto.app.ui.header.Header__titleFontWeight +import me.timeto.app.ui.home.HomeScreen__hPadding +import me.timeto.shared.vm.home.HomeVm +import me.timeto.shared.vm.home.tasks.HomeTasksItemUi + +@Composable +fun HomeTasksView( + homeState: HomeVm.State, + modifier: Modifier, +) { + + val scrollState = rememberLazyListState() + val mainActivity = LocalActivity.current as MainActivity + + if (!homeState.taskFolderUi.taskFolderDb.isToday) { + Text( + text = homeState.taskFolderUi.taskFolderDb.name, + modifier = Modifier + .padding( + top = mainActivity.statusBarHeightDp, + start = HomeScreen__hPadding, + bottom = 8.dp, + ), + fontSize = Header__titleFontSize, + fontWeight = Header__titleFontWeight, + color = c.text, + ) + } + + LazyColumn( + modifier = modifier + .fillMaxWidth(), + state = scrollState, + reverseLayout = true, + ) { + + items( + items = homeState.homeTasksItemsUi, + key = { it.id }, + ) { itemUi -> + when (itemUi) { + is HomeTasksItemUi.HomeTaskUi -> HomeTaskView( + homeTaskUi = itemUi, + homeState = homeState, + ) + is HomeTasksItemUi.HomeTomorrowItemUi -> HomeTasksTomorrowView( + tomorrowUi = itemUi, + ) + } + } + } +} diff --git a/android/src/main/java/me/timeto/app/ui/home/tasks/homeTasksInnerHPadding.kt b/android/src/main/java/me/timeto/app/ui/home/tasks/homeTasksInnerHPadding.kt new file mode 100644 index 000000000..764085ea1 --- /dev/null +++ b/android/src/main/java/me/timeto/app/ui/home/tasks/homeTasksInnerHPadding.kt @@ -0,0 +1,7 @@ +package me.timeto.app.ui.home.tasks + +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +val homeTasksInnerHPadding: Dp = + 7.dp diff --git a/android/src/main/java/me/timeto/app/ui/home/tasks/homeTasksOuterHPadding.kt b/android/src/main/java/me/timeto/app/ui/home/tasks/homeTasksOuterHPadding.kt new file mode 100644 index 000000000..8710e3db1 --- /dev/null +++ b/android/src/main/java/me/timeto/app/ui/home/tasks/homeTasksOuterHPadding.kt @@ -0,0 +1,7 @@ +package me.timeto.app.ui.home.tasks + +import androidx.compose.ui.unit.Dp +import me.timeto.app.ui.home.HomeScreen__hPadding + +val homeTasksOuterHPadding: Dp = + HomeScreen__hPadding - homeTasksInnerHPadding diff --git a/android/src/main/java/me/timeto/app/ui/main/MainScreen.kt b/android/src/main/java/me/timeto/app/ui/main/MainScreen.kt index ca8974868..7a0de466e 100644 --- a/android/src/main/java/me/timeto/app/ui/main/MainScreen.kt +++ b/android/src/main/java/me/timeto/app/ui/main/MainScreen.kt @@ -10,7 +10,6 @@ import me.timeto.app.ui.activity.ActivityScreen import me.timeto.app.ui.home.HomeScreen import me.timeto.app.ui.navigation.NavigationScreen import me.timeto.app.ui.settings.SettingsScreen -import me.timeto.app.ui.tasks.tab.TasksTabView @Composable fun MainScreen() { @@ -40,15 +39,6 @@ fun MainScreen() { ) } } - MainTabEnum.tasks -> { - NavigationScreen { - TasksTabView( - onClose = { - tab.value = MainTabEnum.home - }, - ) - } - } MainTabEnum.settings -> { NavigationScreen { SettingsScreen( diff --git a/android/src/main/java/me/timeto/app/ui/main/MainTabEnum.kt b/android/src/main/java/me/timeto/app/ui/main/MainTabEnum.kt index 6ac0ce2a5..a9eb82367 100644 --- a/android/src/main/java/me/timeto/app/ui/main/MainTabEnum.kt +++ b/android/src/main/java/me/timeto/app/ui/main/MainTabEnum.kt @@ -3,6 +3,5 @@ package me.timeto.app.ui.main enum class MainTabEnum { home, activities, - tasks, settings, } diff --git a/android/src/main/java/me/timeto/app/ui/main/MainTabsView.kt b/android/src/main/java/me/timeto/app/ui/main/MainTabsView.kt index 633460678..8db6b25be 100644 --- a/android/src/main/java/me/timeto/app/ui/main/MainTabsView.kt +++ b/android/src/main/java/me/timeto/app/ui/main/MainTabsView.kt @@ -39,6 +39,7 @@ import me.timeto.app.ui.onePx import me.timeto.app.ui.rememberVm import me.timeto.app.ui.squircleShape import me.timeto.app.ui.timerFont +import me.timeto.shared.vm.home.HomeVm import me.timeto.shared.vm.main.MainTabsVm val MainTabsView__height = 56.dp @@ -107,11 +108,10 @@ fun MainTabsView( .fillMaxHeight() .clip(squircleShape) .motionEventSpy { event -> - if (event.action == MotionEvent.ACTION_DOWN) - onTabChanged( - if (tab == MainTabEnum.home) MainTabEnum.tasks - else MainTabEnum.home - ) + if (event.action == MotionEvent.ACTION_DOWN) { + onTabChanged(MainTabEnum.home) + HomeVm.showStartScreen() + } }, verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, diff --git a/android/src/main/java/me/timeto/app/ui/readme/Readme2Fs.kt b/android/src/main/java/me/timeto/app/ui/readme/Readme2Fs.kt deleted file mode 100644 index a8c8642c7..000000000 --- a/android/src/main/java/me/timeto/app/ui/readme/Readme2Fs.kt +++ /dev/null @@ -1,162 +0,0 @@ -package me.timeto.app.ui.readme - -import androidx.annotation.DrawableRes -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextDecoration -import androidx.compose.ui.text.withStyle -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import me.timeto.app.R -import me.timeto.app.ui.HStack -import me.timeto.app.ui.H_PADDING -import me.timeto.app.ui.SquircleShape -import me.timeto.app.ui.VStack -import me.timeto.app.ui.c -import me.timeto.app.ui.header.Header - -private val pTextLineHeight = 23.sp - -@Composable -fun Readme2Fs() { - - VStack( - modifier = Modifier - .background(c.bg), - ) { - - val scrollState = rememberScrollState() - - Header( - title = "How to Use the App", - scrollState = scrollState, - actionButton = null, - cancelButton = null, - ) - - VStack( - modifier = Modifier - .verticalScroll(state = scrollState) - .weight(1f), - ) { - - PTextView( - buildAnnotatedString { - append("The main feature of this app is ") - withStyle(style = SpanStyle(textDecoration = TextDecoration.Underline)) { - append("goals") - } - append(":") - } - ) - - ScreenshotView(resId = R.drawable.readme_goals) - - PTextView( - buildAnnotatedString { - append("Tap a goal to start a timer with the remaining time for that goal:") - } - ) - - ScreenshotView(resId = R.drawable.readme_timer) - - PTextView( - buildAnnotatedString { - append("Timer is running ") - withStyle(style = SpanStyle(textDecoration = TextDecoration.Underline)) { - append("all the time") - } - append(", 24/7, even for sleep or breakfast. ") - withStyle(style = SpanStyle(textDecoration = TextDecoration.Underline)) { - append("No stop option!") - } - append(" To stop the current goal, you have to start the next one.") - } - ) - - PTextView( - buildAnnotatedString { - append("You can add a checklist for goals. Useful for morning/evening routines, work, exercises, etc. Like this:") - } - ) - - ScreenshotView(resId = R.drawable.readme_checklist) - - PTextView( - buildAnnotatedString { - append("This way I control my time and don't forget anything.") - } - ) - - PTextView( - buildAnnotatedString { - append("Try to adapt it to your life.") - } - ) - - PTextView( - buildAnnotatedString { - append("Best regards,\nIvan") - } - ) - - HStack( - modifier = Modifier - .navigationBarsPadding() - .padding(bottom = 16.dp) - ) {} - } - } -} - -@Composable -private fun PTextView( - text: AnnotatedString, -) { - Text( - text = text, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = H_PADDING) - .padding(vertical = 8.dp), - color = c.text, - lineHeight = pTextLineHeight, - fontWeight = FontWeight.Normal, - ) -} - -private val screenshotBorderColor = c.gray5 -private val screenshotSliderShape = SquircleShape(24.dp) - -@Composable -private fun ScreenshotView( - @DrawableRes resId: Int -) { - Image( - painter = painterResource(resId), - modifier = Modifier - .padding(horizontal = 8.dp, vertical = 8.dp) - .fillMaxHeight() - .clip(screenshotSliderShape) - .border(1.dp, screenshotBorderColor, shape = screenshotSliderShape), - contentDescription = "Screenshot", - contentScale = ContentScale.Fit, - ) -} diff --git a/android/src/main/java/me/timeto/app/ui/readme/ReadmeFs.kt b/android/src/main/java/me/timeto/app/ui/readme/ReadmeFs.kt deleted file mode 100644 index 96c84cf80..000000000 --- a/android/src/main/java/me/timeto/app/ui/readme/ReadmeFs.kt +++ /dev/null @@ -1,468 +0,0 @@ -package me.timeto.app.ui.readme - -import androidx.compose.animation.animateColorAsState -import androidx.compose.foundation.* -import androidx.compose.foundation.layout.* -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import me.timeto.app.* -import me.timeto.app.R -import me.timeto.app.askAQuestion -import me.timeto.app.ui.Divider -import me.timeto.app.ui.HStack -import me.timeto.app.ui.H_PADDING -import me.timeto.app.ui.Padding -import me.timeto.app.ui.SquircleShape -import me.timeto.app.ui.VStack -import me.timeto.app.ui.c -import me.timeto.app.ui.form.button.FormButton -import me.timeto.app.ui.halfDpCeil -import me.timeto.app.ui.header.Header -import me.timeto.app.ui.header.HeaderCancelButton -import me.timeto.app.ui.header.Header__titleFontSize -import me.timeto.app.ui.header.Header__titleFontWeight -import me.timeto.app.ui.navigation.LocalNavigationFs -import me.timeto.app.ui.navigation.LocalNavigationLayer -import me.timeto.app.ui.navigation.Navigation -import me.timeto.app.ui.rememberVm -import me.timeto.app.ui.roundedShape -import me.timeto.app.ui.squircleShape -import me.timeto.shared.vm.readme.ReadmeVm -import me.timeto.shared.vm.readme.ReadmeVm.DefaultItem - -private val imagesHBetween = 4.dp -private val imagesHBlock = H_PADDING - imagesHBetween -private val imagesShape = SquircleShape(10.dp) - -private val pTextLineHeight = 23.sp - -@Composable -fun ReadmeFs( - defaultItem: DefaultItem = DefaultItem.basics, -) { - val navigationLayer = LocalNavigationLayer.current - - val (vm, state) = rememberVm { - ReadmeVm(defaultItem = defaultItem) - } - - VStack( - modifier = Modifier - .background(c.bg), - ) { - - val scrollState = rememberScrollState() - - Header( - title = state.title, - scrollState = scrollState, - actionButton = null, - cancelButton = HeaderCancelButton( - text = "Close", - onClick = { - navigationLayer.close() - }, - ), - ) - - LaunchedEffect(state.tabUi.id) { - scrollState.scrollTo(0) - } - - VStack( - modifier = Modifier - .verticalScroll(state = scrollState) - .weight(1f), - ) { - - val paragraphs = state.tabUi.paragraphs - - paragraphs.forEachIndexed { idx, paragraph -> - - val prevP: ReadmeVm.Paragraph? = - if (idx == 0) null else paragraphs[idx - 1] - - when (paragraph) { - - is ReadmeVm.Paragraph.Title -> PTitleView(paragraph.text, prevP) - - is ReadmeVm.Paragraph.Text -> PTextView(paragraph.text, prevP) - - is ReadmeVm.Paragraph.TextHighlight -> PTextHighlightView(paragraph.text, prevP) - - is ReadmeVm.Paragraph.AskAQuestion -> { - - FormButton( - title = paragraph.title, - isFirst = true, - isLast = true, - modifier = Modifier - .padding(top = 20.dp), - onClick = { - askAQuestion(subject = paragraph.subject) - }, - ) - } - - is ReadmeVm.Paragraph.TimerTypical -> { - ImagePreviewsView( - R.drawable.readme_timer_1, - ) - } - - is ReadmeVm.Paragraph.TimerMyActivities -> { - ImagePreviewsView( - R.drawable.readme_activities_1, - ) - } - - is ReadmeVm.Paragraph.TimerCharts -> { - ImagePreviewsView( - R.drawable.readme_chart_1, - R.drawable.readme_chart_2, - R.drawable.readme_chart_3, - ) - } - - is ReadmeVm.Paragraph.TimerPractice1 -> { - ImagePreviewsView( - R.drawable.readme_timer_practice_1, - R.drawable.readme_timer_practice_2, - R.drawable.readme_timer_practice_3, - R.drawable.readme_timer_practice_4, - ) - } - - is ReadmeVm.Paragraph.TimerPractice2 -> { - ImagePreviewsView( - R.drawable.readme_timer_practice_5, - R.drawable.readme_chart_2, - R.drawable.readme_chart_3, - ) - } - - is ReadmeVm.Paragraph.RepeatingsMy -> { - ImagePreviewsView( - R.drawable.readme_repeatings_1, - ) - } - - - is ReadmeVm.Paragraph.RepeatingsToday -> { - ImagePreviewsView( - R.drawable.readme_repeatings_2, - ) - } - - - is ReadmeVm.Paragraph.RepeatingsPractice1 -> { - ImagePreviewsView( - R.drawable.readme_repeating_practice_1, - R.drawable.readme_repeating_practice_2, - R.drawable.readme_repeating_practice_3, - ) - } - - - is ReadmeVm.Paragraph.RepeatingsPractice2 -> { - ImagePreviewsView( - R.drawable.readme_repeating_practice_4, - R.drawable.readme_repeating_practice_5, - ) - } - - is ReadmeVm.Paragraph.ChecklistsExamples -> { - ImagePreviewsView( - R.drawable.readme_checklists_1, - R.drawable.readme_checklists_2, - R.drawable.readme_checklists_3, - ) - } - - is ReadmeVm.Paragraph.ChecklistsPractice1 -> { - ImagePreviewsView( - R.drawable.readme_checklists_practice_1, - R.drawable.readme_checklists_practice_2, - R.drawable.readme_checklists_practice_3, - R.drawable.readme_checklists_practice_4, - R.drawable.readme_checklists_practice_5, - R.drawable.readme_checklists_practice_6, - R.drawable.readme_checklists_practice_7, - ) - } - - is ReadmeVm.Paragraph.ChecklistsPractice2 -> { - ImagePreviewsView( - R.drawable.readme_checklists_practice_8, - R.drawable.readme_checklists_practice_9, - ) - } - - is ReadmeVm.Paragraph.PomodoroExamples -> { - ImagePreviewsView( - R.drawable.readme_pomodoro_1, - R.drawable.readme_pomodoro_2, - R.drawable.readme_pomodoro_3, - ) - } - - is ReadmeVm.Paragraph.GoalsExamples -> { - ImagePreviewsView( - R.drawable.readme_goals_1, - ) - } - - is ReadmeVm.Paragraph.CalendarExamples -> { - ImagePreviewsView( - R.drawable.readme_calendar_1, - R.drawable.readme_calendar_2, - ) - } - } - } - - Padding(vertical = 26.dp) - } - - Divider(Modifier.padding(horizontal = H_PADDING)) - - HStack( - modifier = Modifier - .padding(top = 9.dp, bottom = 4.dp) - .padding(horizontal = H_PADDING) - .navigationBarsPadding(), - ) { - - state.tabsUi.forEach { tabUi -> - TabBarItemView( - title = tabUi.title, - isActive = state.tabUi.id == tabUi.id, - onClick = { - vm.setTabUi(tabUi) - }, - ) - Padding(horizontal = 6.dp) - } - } - } -} - -/// - -private val tabBarItemViewShape = SquircleShape(10.dp) - -@Composable -private fun TabBarItemView( - title: String, - isActive: Boolean, - onClick: () -> Unit, -) { - val bgColorAnimate = - animateColorAsState(if (isActive) c.blue else c.transparent) - Text( - text = title, - modifier = Modifier - .clip(tabBarItemViewShape) - .background(bgColorAnimate.value) - .clickable { - onClick() - } - .padding(horizontal = 8.dp) - .padding(top = 1.dp, bottom = halfDpCeil), - color = if (isActive) c.white else c.text, - fontSize = 14.sp, - fontWeight = if (isActive) FontWeight.Medium else FontWeight.Normal, - ) -} - -/// - -@Composable -private fun PTitleView( - text: String, - prevP: ReadmeVm.Paragraph?, -) { - val paddingTop: Dp = when { - prevP == null -> 14.dp - prevP.isSlider -> 36.dp - prevP is ReadmeVm.Paragraph.Text -> 38.dp - else -> throw Exception() - } - Text( - text = text, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = H_PADDING) - .padding(top = paddingTop), - color = c.text, - fontWeight = Header__titleFontWeight, - fontSize = Header__titleFontSize, - ) -} - -@Composable -private fun PTextView( - text: String, - prevP: ReadmeVm.Paragraph?, -) { - val paddingTop: Dp = when { - prevP == null -> 13.dp - prevP.isSlider -> 10.dp - prevP is ReadmeVm.Paragraph.Title -> 15.dp - prevP is ReadmeVm.Paragraph.Text -> 12.dp // Text height * 3 - prevP is ReadmeVm.Paragraph.TextHighlight -> 18.dp // Equals to paragraph padding - else -> throw Exception() - } - Text( - text = text, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = H_PADDING) - .padding(top = paddingTop), - color = c.text, - lineHeight = pTextLineHeight, - fontWeight = FontWeight.Normal, - ) -} - -@Composable -private fun PTextHighlightView( - text: String, - prevP: ReadmeVm.Paragraph?, -) { - val paddingTop: Dp = when { - prevP == null -> throw Exception() - prevP.isSlider -> 18.dp - prevP is ReadmeVm.Paragraph.Text -> 20.dp - else -> throw Exception() - } - Text( - text = text, - modifier = Modifier - .fillMaxWidth() - .padding(top = paddingTop) - .padding(horizontal = H_PADDING - 2.dp) - .clip(squircleShape) - .background(c.blue) - .padding(horizontal = 14.dp) - .padding(top = 11.dp, bottom = 11.dp), - color = c.text, - lineHeight = pTextLineHeight, - fontWeight = FontWeight.Normal, - ) -} - -private val imageBorderColor = c.gray5 -private val imageSliderShape = SquircleShape(24.dp) - -@Composable -private fun ImagePreviewsView( - vararg resIds: Int, -) { - - val navigationFs = LocalNavigationFs.current - val scrollState = rememberScrollState() - - HStack( - modifier = Modifier - .padding(top = 20.dp, bottom = 8.dp) - .horizontalScroll(scrollState), - ) { - - Padding(horizontal = imagesHBlock) - - resIds.forEach { resId -> - - Image( - painter = painterResource(resId), - modifier = Modifier - .height(250.dp) - .padding(horizontal = imagesHBetween) - .clip(imagesShape) - .border(1.dp, imageBorderColor, shape = imagesShape) - .clickable { - showImagesSlider( - navigationFs = navigationFs, - resIds = resIds, - ) - }, - contentDescription = "Screenshot", - contentScale = ContentScale.Fit, - ) - } - - Padding(horizontal = imagesHBlock) - } -} - -private fun showImagesSlider( - navigationFs: Navigation, - vararg resIds: Int, -) { - navigationFs.push { - val navigationLayer = LocalNavigationLayer.current - - VStack( - modifier = Modifier - .background(c.bg) - .fillMaxSize() - .padding(top = (LocalContext.current as MainActivity).statusBarHeightDp + 2.dp), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - - val scrollState = rememberScrollState() - HStack( - modifier = Modifier - .weight(1f) - .horizontalScroll(scrollState), - ) { - - Padding(horizontal = 8.dp) - - resIds.forEach { resId -> - Image( - painter = painterResource(resId), - modifier = Modifier - .padding(horizontal = 8.dp) - .fillMaxHeight() - .clip(imageSliderShape) - .border(1.dp, imageBorderColor, shape = imageSliderShape), - contentDescription = "Screenshot", - contentScale = ContentScale.FillHeight, - ) - } - - Padding(horizontal = 8.dp) - } - - Text( - text = "Close", - modifier = Modifier - .padding(end = 16.dp) - .padding(vertical = 8.dp) - .align(Alignment.End) - .navigationBarsPadding() - .clip(roundedShape) - .clickable { - navigationLayer.close() - } - .padding(horizontal = 14.dp) - .padding(top = 6.dp, bottom = 7.dp), - color = c.secondaryText, - fontSize = 15.sp, - fontWeight = FontWeight.Light, - ) - } - } -} diff --git a/android/src/main/java/me/timeto/app/ui/repeatings/form/RepeatingFormFs.kt b/android/src/main/java/me/timeto/app/ui/repeatings/form/RepeatingFormFs.kt index 94c71ed64..b6ed7a1f5 100644 --- a/android/src/main/java/me/timeto/app/ui/repeatings/form/RepeatingFormFs.kt +++ b/android/src/main/java/me/timeto/app/ui/repeatings/form/RepeatingFormFs.kt @@ -101,7 +101,7 @@ fun RepeatingFormFs( FormButton( title = state.periodTitle, isFirst = true, - isLast = false, + isLast = true, note = state.periodNote, noteColor = if (state.period == null) c.red else c.secondaryText, withArrow = true, @@ -117,9 +117,11 @@ fun RepeatingFormFs( }, ) + FormPaddingSectionSection() + FormButton( title = state.daytimeTitle, - isFirst = false, + isFirst = true, isLast = true, note = state.daytimeNote, noteColor = if (state.daytimeUi == null) c.red else c.secondaryText, diff --git a/android/src/main/java/me/timeto/app/ui/repeatings/list/RepeatingsListFs.kt b/android/src/main/java/me/timeto/app/ui/repeatings/list/RepeatingsListFs.kt new file mode 100644 index 000000000..c289b56fb --- /dev/null +++ b/android/src/main/java/me/timeto/app/ui/repeatings/list/RepeatingsListFs.kt @@ -0,0 +1,88 @@ +package me.timeto.app.ui.repeatings.list + +import androidx.activity.compose.LocalActivity +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import me.timeto.app.MainActivity +import me.timeto.app.ui.H_PADDING +import me.timeto.app.ui.H_PADDING_HALF +import me.timeto.app.ui.Screen +import me.timeto.app.ui.c +import me.timeto.app.ui.navigation.LocalNavigationFs +import me.timeto.app.ui.rememberVm +import me.timeto.app.ui.repeatings.form.RepeatingFormFs +import me.timeto.app.ui.squircleShape +import me.timeto.shared.vm.repeatings.list.RepeatingsListVm + +@Composable +fun RepeatingsListFs() { + + val navigationFs = LocalNavigationFs.current + val mainActivity = LocalActivity.current as MainActivity + + val (_, state) = rememberVm { + RepeatingsListVm() + } + + Screen { + + LazyColumn( + reverseLayout = true, + modifier = Modifier + .padding(top = mainActivity.statusBarHeightDp) + .navigationBarsPadding() + .fillMaxHeight(), + ) { + + item { + + Text( + text = "New Repeating Task", + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = H_PADDING) + .padding(top = 20.dp) + .clip(squircleShape) + .background(c.blue) + .clickable { + navigationFs.push { + RepeatingFormFs( + initRepeatingDb = null, + ) + } + } + .padding(vertical = 10.dp), + color = c.white, + fontWeight = FontWeight.SemiBold, + textAlign = TextAlign.Center, + ) + } + + val repeatingsUi = state.repeatingsUi + repeatingsUi.forEachIndexed { idx, repeatingUi -> + item { + RepeatingsListItemView( + repeatingUi = repeatingUi, + // Remember that the list is reversed + withTopDivider = (idx != repeatingsUi.size - 1), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = H_PADDING_HALF), + ) + } + } + } + } +} diff --git a/android/src/main/java/me/timeto/app/ui/tasks/tab/repeatings/TasksTabRepeatingsItemView.kt b/android/src/main/java/me/timeto/app/ui/repeatings/list/RepeatingsListItemView.kt similarity index 92% rename from android/src/main/java/me/timeto/app/ui/tasks/tab/repeatings/TasksTabRepeatingsItemView.kt rename to android/src/main/java/me/timeto/app/ui/repeatings/list/RepeatingsListItemView.kt index 2d3e5f983..271954075 100644 --- a/android/src/main/java/me/timeto/app/ui/tasks/tab/repeatings/TasksTabRepeatingsItemView.kt +++ b/android/src/main/java/me/timeto/app/ui/repeatings/list/RepeatingsListItemView.kt @@ -1,4 +1,4 @@ -package me.timeto.app.ui.tasks.tab.repeatings +package me.timeto.app.ui.repeatings.list import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -27,11 +27,11 @@ import me.timeto.app.ui.c import me.timeto.app.ui.navigation.LocalNavigationFs import me.timeto.app.ui.repeatings.form.RepeatingFormFs import me.timeto.app.ui.squircleShape -import me.timeto.shared.vm.tasks.tab.repeatings.TasksTabRepeatingsVm +import me.timeto.shared.vm.repeatings.list.RepeatingsListVm @Composable -fun TasksTabRepeatingsItemView( - repeatingUi: TasksTabRepeatingsVm.RepeatingUi, +fun RepeatingsListItemView( + repeatingUi: RepeatingsListVm.RepeatingUi, withTopDivider: Boolean, modifier: Modifier, ) { @@ -54,7 +54,7 @@ fun TasksTabRepeatingsItemView( } } .padding(vertical = 10.dp) - .padding(start = H_PADDING_HALF), + .padding(horizontal = H_PADDING_HALF), ) { HStack( @@ -112,6 +112,6 @@ fun TasksTabRepeatingsItemView( } if (withTopDivider) - Divider(Modifier.padding(start = H_PADDING_HALF)) + Divider(Modifier.padding(horizontal = H_PADDING_HALF)) } } diff --git a/android/src/main/java/me/timeto/app/ui/settings/SettingsScreen.kt b/android/src/main/java/me/timeto/app/ui/settings/SettingsScreen.kt index fe9ba2532..7791db9d1 100644 --- a/android/src/main/java/me/timeto/app/ui/settings/SettingsScreen.kt +++ b/android/src/main/java/me/timeto/app/ui/settings/SettingsScreen.kt @@ -40,6 +40,7 @@ import me.timeto.app.ui.checklists.form.ChecklistFormFs import me.timeto.app.ui.checklists.form.ChecklistFormItemsFs import me.timeto.app.ui.checklists.ChecklistScreen import me.timeto.app.ui.daytime_picker.DaytimePickerSheet +import me.timeto.app.ui.doc.DocFs import me.timeto.app.ui.donations.DonationsFs import me.timeto.app.ui.form.button.FormButton import me.timeto.app.ui.form.FormHeader @@ -47,7 +48,6 @@ import me.timeto.app.ui.form.padding.FormPaddingTop import me.timeto.app.ui.form.padding.FormPaddingHeaderSection import me.timeto.app.ui.form.padding.FormPaddingSectionHeader import me.timeto.app.ui.form.padding.FormPaddingSectionSection -import me.timeto.app.ui.form.FormSwitch import me.timeto.app.ui.form.button.FormButtonEmoji import me.timeto.app.ui.form.button.FormButtonView import me.timeto.app.ui.header.Header @@ -59,11 +59,11 @@ import me.timeto.app.ui.navigation.picker.NavigationPickerItem import me.timeto.app.ui.notes.NoteFormFs import me.timeto.app.ui.notes.NoteFs import me.timeto.app.ui.privacy.PrivacyFs -import me.timeto.app.ui.readme.Readme2Fs import me.timeto.app.ui.rememberVm +import me.timeto.app.ui.repeatings.list.RepeatingsListFs import me.timeto.app.ui.roundedShape import me.timeto.app.ui.shortcuts.ShortcutFormFs -import me.timeto.app.ui.tasks.folders.TaskFoldersFormFs +import me.timeto.app.ui.task_folders.TaskFoldersFormFs import me.timeto.app.ui.timer.TimerSheet import me.timeto.shared.* import me.timeto.shared.backups.Backup @@ -71,6 +71,7 @@ import me.timeto.shared.vm.settings.SettingsVm import me.timeto.shared.performUi import java.io.BufferedReader import java.io.InputStreamReader +import kotlin.time.Duration.Companion.seconds private const val persistentNotificationText = "Persistent Notification" @@ -182,8 +183,9 @@ fun SettingsScreen( withArrow = true, onClick = { navigationFs.push { - // ReadmeFs() - Readme2Fs() + DocFs( + forceRead = false, + ) } }, ) @@ -202,13 +204,31 @@ fun SettingsScreen( ) } + // + // Repeatings + + item { + FormPaddingSectionSection() + FormButton( + title = "Repeating Tasks", + isFirst = true, + isLast = true, + withArrow = true, + onClick = { + navigationFs.push { + RepeatingsListFs() + } + }, + ) + } + // // Goals item { FormPaddingSectionHeader() FormHeader( - title = "GOALS", + title = "ACTIVITIES", ) FormPaddingHeaderSection() } @@ -314,7 +334,7 @@ fun SettingsScreen( } FormButton( - title = "New Goal", + title = "New Activity", titleColor = c.blue, isFirst = false, isLast = false, @@ -335,7 +355,7 @@ fun SettingsScreen( HomeSettingsButtonsFs() } scope.launch { - delay(1_000) + delay(1.seconds) onClose() } }, @@ -573,7 +593,7 @@ fun SettingsScreen( FormButton( title = "Day Start", isFirst = false, - isLast = false, + isLast = true, note = state.dayStartNote, withArrow = true, onClick = { @@ -585,16 +605,6 @@ fun SettingsScreen( } }, ) - - FormSwitch( - title = state.todayOnHomeScreenText, - isEnabled = state.todayOnHomeScreen, - isFirst = false, - isLast = true, - onChange = { newValue -> - vm.setTodayOnHomeScreen(isOn = newValue) - }, - ) } // diff --git a/android/src/main/java/me/timeto/app/ui/symbol/SymbolEmojiView.kt b/android/src/main/java/me/timeto/app/ui/symbol/SymbolEmojiView.kt new file mode 100644 index 000000000..7876b1ddc --- /dev/null +++ b/android/src/main/java/me/timeto/app/ui/symbol/SymbolEmojiView.kt @@ -0,0 +1,23 @@ +package me.timeto.app.ui.symbol + +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.TextUnit +import me.timeto.shared.Symbol + +@Composable +fun SymbolEmojiView( + emoji: Symbol.Emoji, + size: TextUnit, + modifier: Modifier, +) { + Text( + text = emoji.emoji, + modifier = modifier, + fontWeight = FontWeight.SemiBold, + fontSize = size, + ) +} diff --git a/android/src/main/java/me/timeto/app/ui/symbol/SymbolIconView.kt b/android/src/main/java/me/timeto/app/ui/symbol/SymbolIconView.kt new file mode 100644 index 000000000..50ac5e573 --- /dev/null +++ b/android/src/main/java/me/timeto/app/ui/symbol/SymbolIconView.kt @@ -0,0 +1,57 @@ +package me.timeto.app.ui.symbol + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.layout.size +import androidx.compose.material.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.Dp +import me.timeto.app.R +import me.timeto.shared.Symbol + +@Composable +fun SymbolIconView( + icon: Symbol.Icon, + color: Color, + size: Dp, + modifier: Modifier, +) { + Icon( + painter = painterResource(id = icon.iconResId()), + contentDescription = icon.iconEnum.name, + modifier = modifier + .size(size), + tint = color, + ) +} + +@DrawableRes +private fun Symbol.Icon.iconResId(): Int = when (iconEnum) { + Symbol.Icon.IconEnum.book -> R.drawable.ms_book_2_fill + Symbol.Icon.IconEnum.case -> R.drawable.ms_work_fill + Symbol.Icon.IconEnum.timer -> R.drawable.ms_timer_fill + Symbol.Icon.IconEnum.exercise -> R.drawable.ms_exercise_fill + Symbol.Icon.IconEnum.piano -> R.drawable.ms_piano + Symbol.Icon.IconEnum.music_note -> R.drawable.ms_music_note + Symbol.Icon.IconEnum.option -> R.drawable.ms_keyboard_option_key_w700 + Symbol.Icon.IconEnum.graduationcap -> R.drawable.ms_school_fill + Symbol.Icon.IconEnum.megaphone -> R.drawable.ms_campaign_fill_w700 + Symbol.Icon.IconEnum.instruments -> R.drawable.ms_construction_fill + Symbol.Icon.IconEnum.meditation -> R.drawable.ms_self_improvement_fill_w700 + Symbol.Icon.IconEnum.rocket -> R.drawable.ms_rocket_launch_fill + Symbol.Icon.IconEnum.flask -> R.drawable.ms_science_fill + Symbol.Icon.IconEnum.compass -> R.drawable.ms_architecture_fill_w700 + Symbol.Icon.IconEnum.gamecontroller -> R.drawable.ms_sports_esports_fill + Symbol.Icon.IconEnum.soccerball -> R.drawable.ms_sports_soccer_fill + Symbol.Icon.IconEnum.hiking -> R.drawable.ms_hiking_fill + Symbol.Icon.IconEnum.bus -> R.drawable.ms_directions_bus_fill + Symbol.Icon.IconEnum.bulb -> R.drawable.ms_lightbulb_fill + Symbol.Icon.IconEnum.bolt -> R.drawable.ms_bolt_fill + Symbol.Icon.IconEnum.inbox -> R.drawable.ms_inbox_fill + Symbol.Icon.IconEnum.sun -> R.drawable.ms_wb_sunny_fill + Symbol.Icon.IconEnum.moon -> R.drawable.ms_bedtime_fill + Symbol.Icon.IconEnum.moon_stars -> R.drawable.ms_moon_stars_fill + Symbol.Icon.IconEnum.question -> R.drawable.ms_question_mark +} diff --git a/android/src/main/java/me/timeto/app/ui/symbol/SymbolLetterPickerFs.kt b/android/src/main/java/me/timeto/app/ui/symbol/SymbolLetterPickerFs.kt new file mode 100644 index 000000000..4c2567d0b --- /dev/null +++ b/android/src/main/java/me/timeto/app/ui/symbol/SymbolLetterPickerFs.kt @@ -0,0 +1,71 @@ +package me.timeto.app.ui.symbol + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.text.input.ImeAction +import me.timeto.app.ui.Screen +import me.timeto.app.ui.form.FormInput +import me.timeto.app.ui.form.padding.FormPaddingTop +import me.timeto.app.ui.header.Header +import me.timeto.app.ui.header.HeaderActionButton +import me.timeto.app.ui.header.HeaderCancelButton +import me.timeto.app.ui.navigation.LocalNavigationFs +import me.timeto.app.ui.navigation.LocalNavigationLayer +import me.timeto.shared.Symbol +import me.timeto.shared.vm.symbol.SymbolLetterPickerUtils + +@Composable +fun SymbolLetterPickerFs( + initText: String, + onPick: (Symbol.Letter) -> Unit, +) { + val navigationFs = LocalNavigationFs.current + val navigationLayer = LocalNavigationLayer.current + + val formText = remember { + mutableStateOf(initText) + } + + Screen { + + Header( + title = "Symbol", + scrollState = null, + actionButton = HeaderActionButton( + text = "Done", + isEnabled = true, + onClick = { + SymbolLetterPickerUtils.validateLetter( + letter = formText.value, + dialogsManager = navigationFs, + onSuccess = { symbolLetter -> + onPick(symbolLetter) + navigationLayer.close() + }, + ) + }, + ), + cancelButton = HeaderCancelButton( + text = "Cancel", + onClick = { + navigationLayer.close() + }, + ), + ) + + FormPaddingTop() + + FormInput( + initText = formText.value, + placeholder = "Any Symbol", + onChange = { newName -> + formText.value = newName + }, + isFirst = true, + isLast = true, + isAutoFocus = true, + imeAction = ImeAction.Done, + ) + } +} diff --git a/android/src/main/java/me/timeto/app/ui/symbol/SymbolLetterView.kt b/android/src/main/java/me/timeto/app/ui/symbol/SymbolLetterView.kt new file mode 100644 index 000000000..5fcf520af --- /dev/null +++ b/android/src/main/java/me/timeto/app/ui/symbol/SymbolLetterView.kt @@ -0,0 +1,25 @@ +package me.timeto.app.ui.symbol + +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.TextUnit +import me.timeto.shared.Symbol + +@Composable +fun SymbolLetterView( + letter: Symbol.Letter, + color: Color, + size: TextUnit, + modifier: Modifier, +) { + Text( + text = letter.letter, + modifier = modifier, + fontWeight = FontWeight.Bold, + color = color, + fontSize = size, + ) +} diff --git a/android/src/main/java/me/timeto/app/ui/symbol/SymbolPickerFs.kt b/android/src/main/java/me/timeto/app/ui/symbol/SymbolPickerFs.kt new file mode 100644 index 000000000..40150f44b --- /dev/null +++ b/android/src/main/java/me/timeto/app/ui/symbol/SymbolPickerFs.kt @@ -0,0 +1,159 @@ +package me.timeto.app.ui.symbol + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import me.timeto.app.ui.HStack +import me.timeto.app.ui.H_PADDING_HALF +import me.timeto.app.ui.Screen +import me.timeto.app.ui.ZStack +import me.timeto.app.ui.c +import me.timeto.app.ui.emoji.EmojiPickerFs +import me.timeto.app.ui.header.Header +import me.timeto.app.ui.header.HeaderCancelButton +import me.timeto.app.ui.navigation.LocalNavigationFs +import me.timeto.app.ui.navigation.LocalNavigationLayer +import me.timeto.app.ui.rememberVm +import me.timeto.app.ui.squircleShape +import me.timeto.shared.Symbol +import me.timeto.shared.vm.symbol.SymbolPickerVm + +private val rowHeight = 40.dp + +@Composable +fun SymbolPickerFs( + onPick: (Symbol) -> Unit, +) { + val navigationFs = LocalNavigationFs.current + val navigationLayer = LocalNavigationLayer.current + + val (_, state) = rememberVm { + SymbolPickerVm() + } + + Screen { + + val scrollState = rememberLazyListState() + + Header( + title = "Icon", + scrollState = scrollState, + actionButton = null, + cancelButton = HeaderCancelButton( + text = "Cancel", + onClick = { + navigationLayer.close() + }, + ), + ) + + LazyColumn( + state = scrollState, + ) { + + item { + PlainButton( + text = "Emoji", + onClick = { + navigationFs.push { + EmojiPickerFs( + onDone = { emoji -> + onPick(Symbol.Emoji(emoji)) + navigationLayer.close() + }, + ) + } + }, + ) + } + + item { + PlainButton( + text = "Symbol", + onClick = { + navigationFs.push { + SymbolLetterPickerFs( + initText = "", + onPick = { symbolLetter -> + onPick(symbolLetter) + navigationLayer.close() + }, + ) + } + }, + ) + } + + state.symbolChunks.forEach { symbolsRow -> + item { + HStack( + modifier = Modifier + .height(rowHeight), + ) { + symbolsRow.forEach { symbol -> + ZStack( + modifier = Modifier + .fillMaxHeight() + .weight(1f) + .clip(squircleShape) + .clickable { + onPick(symbol) + navigationLayer.close() + }, + contentAlignment = Alignment.Center, + ) { + SymbolView( + symbol = symbol, + color = c.white, + letterSize = 23.sp, // No matter + iconSize = 23.dp, + emojiSize = 18.sp, // No matter + modifier = Modifier, + ) + } + } + } + } + } + + item { + ZStack(Modifier.navigationBarsPadding()) + } + } + } +} + +@Composable +private fun PlainButton( + text: String, + onClick: () -> Unit, +) { + ZStack( + modifier = Modifier + .height(rowHeight) + .padding(horizontal = H_PADDING_HALF) + .clip(squircleShape) + .clickable { + onClick() + } + .padding(horizontal = H_PADDING_HALF), + contentAlignment = Alignment.CenterStart, + ) { + Text( + text = text, + color = c.blue, + ) + } +} + diff --git a/android/src/main/java/me/timeto/app/ui/symbol/SymbolView.kt b/android/src/main/java/me/timeto/app/ui/symbol/SymbolView.kt new file mode 100644 index 000000000..bfc42d59b --- /dev/null +++ b/android/src/main/java/me/timeto/app/ui/symbol/SymbolView.kt @@ -0,0 +1,44 @@ +package me.timeto.app.ui.symbol + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.TextUnit +import me.timeto.shared.Symbol + +@Composable +fun SymbolView( + symbol: Symbol, + color: Color, + letterSize: TextUnit, + iconSize: Dp, + emojiSize: TextUnit, + modifier: Modifier, +) { + when (symbol) { + is Symbol.Letter -> { + SymbolLetterView( + letter = symbol, + color = color, + size = letterSize, + modifier = modifier, + ) + } + is Symbol.Icon -> { + SymbolIconView( + icon = symbol, + color = color, + size = iconSize, + modifier = modifier, + ) + } + is Symbol.Emoji -> { + SymbolEmojiView( + emoji = symbol, + size = emojiSize, + modifier = modifier, + ) + } + } +} diff --git a/android/src/main/java/me/timeto/app/ui/tasks/folders/TaskFolderFormFs.kt b/android/src/main/java/me/timeto/app/ui/task_folder/TaskFolderFormFs.kt similarity index 76% rename from android/src/main/java/me/timeto/app/ui/tasks/folders/TaskFolderFormFs.kt rename to android/src/main/java/me/timeto/app/ui/task_folder/TaskFolderFormFs.kt index 6d7dd437a..106525699 100644 --- a/android/src/main/java/me/timeto/app/ui/tasks/folders/TaskFolderFormFs.kt +++ b/android/src/main/java/me/timeto/app/ui/task_folder/TaskFolderFormFs.kt @@ -1,4 +1,4 @@ -package me.timeto.app.ui.tasks.folders +package me.timeto.app.ui.task_folder import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.LazyColumn @@ -11,6 +11,7 @@ import me.timeto.app.ui.rememberVm import me.timeto.app.ui.Screen import me.timeto.app.ui.form.button.FormButton import me.timeto.app.ui.form.FormInput +import me.timeto.app.ui.form.button.FormButtonSymbol import me.timeto.app.ui.form.padding.FormPaddingTop import me.timeto.app.ui.form.padding.FormPaddingSectionSection import me.timeto.app.ui.header.Header @@ -19,9 +20,11 @@ import me.timeto.app.ui.header.HeaderCancelButton import me.timeto.app.ui.navigation.LocalNavigationFs import me.timeto.app.ui.navigation.LocalNavigationLayer import me.timeto.app.ui.navigation.picker.NavigationPickerItem +import me.timeto.app.ui.symbol.SymbolPickerFs +import me.timeto.shared.Symbol import me.timeto.shared.db.ActivityDb import me.timeto.shared.db.TaskFolderDb -import me.timeto.shared.vm.tasks.folders.TaskFolderFormVm +import me.timeto.shared.vm.task_folder.TaskFolderFormVm @Composable fun TaskFolderFormFs( @@ -86,6 +89,46 @@ fun TaskFolderFormFs( imeAction = ImeAction.Done, ) + FormPaddingSectionSection() + + fun showSymbolPicker() { + navigationFs.push { + SymbolPickerFs( + onPick = { symbol -> + vm.setSymbol(symbol) + }, + ) + } + } + + val symbol: Symbol? = + state.symbol + if (symbol == null) { + FormButton( + title = state.iconTitle, + isFirst = true, + isLast = true, + note = "Not Selected", + noteColor = c.red, + withArrow = true, + onClick = { + showSymbolPicker() + }, + ) + } else { + FormButtonSymbol( + title = state.iconTitle, + symbol = symbol, + color = c.secondaryText, + withArrow = true, + isFirst = true, + isLast = true, + onClick = { + showSymbolPicker() + }, + ) + } + if (state.isActivityAvailable) { FormPaddingSectionSection() FormButton( diff --git a/android/src/main/java/me/timeto/app/ui/tasks/folders/TaskFoldersFormFs.kt b/android/src/main/java/me/timeto/app/ui/task_folders/TaskFoldersFormFs.kt similarity index 82% rename from android/src/main/java/me/timeto/app/ui/tasks/folders/TaskFoldersFormFs.kt rename to android/src/main/java/me/timeto/app/ui/task_folders/TaskFoldersFormFs.kt index f0620bb10..afc3495d2 100644 --- a/android/src/main/java/me/timeto/app/ui/tasks/folders/TaskFoldersFormFs.kt +++ b/android/src/main/java/me/timeto/app/ui/task_folders/TaskFoldersFormFs.kt @@ -1,4 +1,4 @@ -package me.timeto.app.ui.tasks.folders +package me.timeto.app.ui.task_folders import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding @@ -6,20 +6,19 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import me.timeto.app.ui.H_PADDING_HALF -import me.timeto.app.ui.c import me.timeto.app.ui.rememberVm import me.timeto.app.ui.Screen import me.timeto.app.ui.SpacerW1 import me.timeto.app.ui.footer.Footer import me.timeto.app.ui.footer.FooterAddButton -import me.timeto.app.ui.footer.FooterPlainButton import me.timeto.app.ui.form.sorted.FormSortedList import me.timeto.app.ui.header.Header import me.timeto.app.ui.header.HeaderCancelButton import me.timeto.app.ui.navigation.LocalNavigationFs import me.timeto.app.ui.navigation.LocalNavigationLayer +import me.timeto.app.ui.task_folder.TaskFolderFormFs import me.timeto.shared.db.TaskFolderDb -import me.timeto.shared.vm.tasks.folders.TaskFoldersFormVm +import me.timeto.shared.vm.task_folders.TaskFoldersFormVm @Composable fun TaskFoldersFormFs() { @@ -98,19 +97,6 @@ fun TaskFoldersFormFs() { ) SpacerW1() - - val tmrwButtonUi = state.tmrwButtonUi - if (tmrwButtonUi != null) { - FooterPlainButton( - text = tmrwButtonUi.text, - color = c.blue, - onClick = { - tmrwButtonUi.add( - dialogsManager = navigationFs, - ) - }, - ) - } } } } diff --git a/android/src/main/java/me/timeto/app/ui/task_form/TaskFormFs.kt b/android/src/main/java/me/timeto/app/ui/task_form/TaskFormFs.kt index 5331e0ce7..28961b284 100644 --- a/android/src/main/java/me/timeto/app/ui/task_form/TaskFormFs.kt +++ b/android/src/main/java/me/timeto/app/ui/task_form/TaskFormFs.kt @@ -2,15 +2,20 @@ package me.timeto.app.ui.task_form import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Icon import androidx.compose.material.LocalTextStyle import androidx.compose.material.Text import androidx.compose.runtime.Composable @@ -24,6 +29,7 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction @@ -32,6 +38,8 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import kotlinx.coroutines.delay +import me.timeto.app.R +import me.timeto.app.toColor import me.timeto.app.ui.HStack import me.timeto.app.ui.H_PADDING import me.timeto.app.ui.VStack @@ -41,19 +49,21 @@ import me.timeto.app.ui.halfDpCeil import me.timeto.app.ui.rememberVm import me.timeto.app.ui.roundedShape import me.timeto.app.ui.Screen +import me.timeto.app.ui.SpacerW1 import me.timeto.app.ui.checklists.ChecklistsPickerFs import me.timeto.app.ui.form.button.FormButton -import me.timeto.app.ui.form.padding.FormPaddingSectionSection import me.timeto.app.ui.form.padding.FormPaddingTop import me.timeto.app.ui.header.Header import me.timeto.app.ui.header.HeaderCancelButton import me.timeto.app.ui.header.HeaderSecondaryButton +import me.timeto.app.ui.home.HomeScreen__itemCircleFontSize +import me.timeto.app.ui.home.HomeScreen__itemHeight +import me.timeto.app.ui.home.tasks.HomeTasksFolderButton import me.timeto.app.ui.navigation.LocalNavigationFs import me.timeto.app.ui.navigation.LocalNavigationLayer -import me.timeto.app.ui.navigation.picker.NavigationPickerItem import me.timeto.app.ui.shortcuts.ShortcutsPickerFs +import me.timeto.app.ui.symbol.SymbolView import me.timeto.app.ui.timer.TimerSheet -import me.timeto.shared.db.ActivityDb import me.timeto.shared.vm.task_form.TaskFormStrategy import me.timeto.shared.vm.task_form.TaskFormVm @@ -124,23 +134,38 @@ fun TaskFormFs( FormPaddingTop() FormButton( - title = state.activityTitle, + title = state.checklistsTitle, isFirst = true, isLast = false, - note = state.activityNote, - noteColor = c.secondaryText, + note = state.checklistsNote, withArrow = true, onClick = { - navigationFs.picker( - title = state.activityTitle, - items = buildGoalsPickerItems( - activitiesUi = state.activitiesUi, - selectedActivityDb = state.activityDb, - ), - onDone = { newActivity -> - vm.setActivity(newActivity.item?.activityDb) - }, - ) + navigationFs.push { + ChecklistsPickerFs( + initChecklistsDb = state.checklistsDb, + onDone = { newChecklistsDb -> + vm.setChecklists(newChecklistsDb) + } + ) + } + }, + ) + + FormButton( + title = state.shortcutsTitle, + isFirst = false, + isLast = false, + note = state.shortcutsNote, + withArrow = true, + onClick = { + navigationFs.push { + ShortcutsPickerFs( + initShortcutsDb = state.shortcutsDb, + onDone = { newShortcutsDb -> + vm.setShortcuts(newShortcutsDb) + } + ) + } }, ) @@ -165,48 +190,96 @@ fun TaskFormFs( } }, ) + } - FormPaddingSectionSection() + when (val settingsLogic = state.settingsLogic) { + is TaskFormVm.SettingsLogic.FixedTaskFolderUi -> { - FormButton( - title = state.checklistsTitle, - isFirst = true, - isLast = false, - note = state.checklistsNote, - withArrow = true, - onClick = { - navigationFs.push { - ChecklistsPickerFs( - initChecklistsDb = state.checklistsDb, - onDone = { newChecklistsDb -> - vm.setChecklists(newChecklistsDb) - } + HStack( + modifier = Modifier + .height(HomeScreen__itemHeight) + .padding(end = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + painter = painterResource(id = R.drawable.ms_folder), + contentDescription = null, + modifier = Modifier + .padding(start = 14.dp, end = 8.dp) + .size(21.dp), + tint = c.secondaryText, + ) + + Text( + text = settingsLogic.title, + color = c.white, + ) + + SpacerW1() + + settingsLogic.taskFolderHintsUi.forEach { taskFolderHintUi -> + HomeTasksFolderButton( + taskFolderUi = taskFolderHintUi.taskFolderUi, + color = when { + settingsLogic.selectedHintUi != taskFolderHintUi -> c.gray2 + taskFolderHintUi.taskFolderUi.taskFolderDb.isToday -> c.orange + else -> c.indigo + }, + modifier = Modifier, + onClick = { + vm.setSessionLogic( + settingsLogic.buildWithNewHint(taskFolderHintUi), + ) + }, ) } - }, - ) - - FormButton( - title = state.shortcutsTitle, - isFirst = false, - isLast = true, - note = state.shortcutsNote, - withArrow = true, - onClick = { - navigationFs.push { - ShortcutsPickerFs( - initShortcutsDb = state.shortcutsDb, - onDone = { newShortcutsDb -> - vm.setShortcuts(newShortcutsDb) + } + } + is TaskFormVm.SettingsLogic.ActivitiesUi -> { + HStack( + modifier = Modifier + .height(HomeScreen__itemHeight), + verticalAlignment = Alignment.CenterVertically, + ) { + LazyRow( + contentPadding = PaddingValues(horizontal = 4.dp), + ) { + settingsLogic.activitiesUi.forEach { activityUi -> + val isSelected: Boolean = + state.activityDb?.id == activityUi.activityDb.id + item { + ZStack( + modifier = Modifier + .size(HomeScreen__itemHeight) + .clip(roundedShape) + .background(if (isSelected) c.blue else c.black) + .clickable { + vm.setActivity(activityUi.activityDb) + }, + contentAlignment = Alignment.Center, + ) { + SymbolView( + symbol = activityUi.symbol, + color = remember(isSelected, activityUi.colorRgba) { + if (isSelected) c.white + else activityUi.colorRgba.toColor() + }, + letterSize = 18.sp, + iconSize = 18.dp, + emojiSize = HomeScreen__itemCircleFontSize, + modifier = Modifier, + ) + } } - ) + } } - }, - ) + } + } } HStack( modifier = Modifier + .padding(top = 8.dp) .background(c.fg) .padding(vertical = 4.dp) .navigationBarsPadding(), @@ -296,27 +369,3 @@ fun TaskFormFs( focusRequester.requestFocus() } } - -private fun buildGoalsPickerItems( - activitiesUi: List, - selectedActivityDb: ActivityDb?, -): List> { - val list = mutableListOf>() - list.add( - NavigationPickerItem( - title = "None", - isSelected = selectedActivityDb == null, - item = null, - ) - ) - activitiesUi.forEach { activityUi -> - list.add( - NavigationPickerItem( - title = activityUi.title, - isSelected = selectedActivityDb?.id == activityUi.activityDb.id, - item = activityUi, - ) - ) - } - return list -} diff --git a/android/src/main/java/me/timeto/app/ui/tasks/TaskTimerFs.kt b/android/src/main/java/me/timeto/app/ui/task_timer/TaskTimerFs.kt similarity index 98% rename from android/src/main/java/me/timeto/app/ui/tasks/TaskTimerFs.kt rename to android/src/main/java/me/timeto/app/ui/task_timer/TaskTimerFs.kt index e01aa864c..5a33a4d24 100644 --- a/android/src/main/java/me/timeto/app/ui/tasks/TaskTimerFs.kt +++ b/android/src/main/java/me/timeto/app/ui/task_timer/TaskTimerFs.kt @@ -1,4 +1,4 @@ -package me.timeto.app.ui.tasks +package me.timeto.app.ui.task_timer import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable @@ -25,7 +25,7 @@ import me.timeto.app.ui.navigation.picker.NavigationPickerItem import me.timeto.app.ui.timer.TimerSheet import me.timeto.shared.DaytimeUi import me.timeto.shared.db.TaskDb -import me.timeto.shared.vm.tasks.TaskTimerVm +import me.timeto.shared.vm.task_timer.TaskTimerVm @OptIn(ExperimentalFoundationApi::class) @Composable diff --git a/android/src/main/java/me/timeto/app/ui/tasks/tab/TasksTabDragItem.kt b/android/src/main/java/me/timeto/app/ui/tasks/tab/TasksTabDragItem.kt deleted file mode 100644 index 6ce2e4f80..000000000 --- a/android/src/main/java/me/timeto/app/ui/tasks/tab/TasksTabDragItem.kt +++ /dev/null @@ -1,9 +0,0 @@ -package me.timeto.app.ui.tasks.tab - -import androidx.compose.runtime.MutableState - -class TasksTabDragItem( - val focusedDrop: MutableState, - val isDropAllowed: (drop: TasksTabDropItem) -> Boolean, - val onDrop: (drop: TasksTabDropItem) -> Unit, -) diff --git a/android/src/main/java/me/timeto/app/ui/tasks/tab/TasksTabDropItem.kt b/android/src/main/java/me/timeto/app/ui/tasks/tab/TasksTabDropItem.kt deleted file mode 100644 index a9defb2bc..000000000 --- a/android/src/main/java/me/timeto/app/ui/tasks/tab/TasksTabDropItem.kt +++ /dev/null @@ -1,33 +0,0 @@ -package me.timeto.app.ui.tasks.tab - -import androidx.compose.ui.layout.LayoutCoordinates -import androidx.compose.ui.layout.positionInWindow -import me.timeto.shared.db.TaskFolderDb - -sealed class TasksTabDropItem( - val name: String, - val square: Square, -) { - - fun upSquareByCoordinates(c: LayoutCoordinates) { - val p = c.positionInWindow() - square.x1 = p.x.toInt() - square.y1 = p.y.toInt() - square.x2 = p.x.toInt() + c.size.width - square.y2 = p.y.toInt() + c.size.height - } - - class Square(var x1: Int, var y1: Int, var x2: Int, var y2: Int) - - // - // Types - - class Folder( - val taskFolderDb: TaskFolderDb, - square: Square, - ) : TasksTabDropItem(taskFolderDb.name, square) - - class Calendar( - square: Square, - ) : TasksTabDropItem("Calendar", square) -} diff --git a/android/src/main/java/me/timeto/app/ui/tasks/tab/TasksTabView.kt b/android/src/main/java/me/timeto/app/ui/tasks/tab/TasksTabView.kt deleted file mode 100644 index 9d4f78a65..000000000 --- a/android/src/main/java/me/timeto/app/ui/tasks/tab/TasksTabView.kt +++ /dev/null @@ -1,404 +0,0 @@ -package me.timeto.app.ui.tasks.tab - -import android.view.MotionEvent -import androidx.activity.compose.BackHandler -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.* -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.material.* -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.rotate -import androidx.compose.ui.input.pointer.* -import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import me.timeto.app.R -import kotlinx.coroutines.delay -import me.timeto.app.Haptic -import me.timeto.app.ui.HStack -import me.timeto.app.ui.H_PADDING -import me.timeto.app.ui.SpacerW1 -import me.timeto.app.ui.SquircleShape -import me.timeto.app.ui.VStack -import me.timeto.app.ui.ZStack -import me.timeto.app.ui.c -import me.timeto.app.ui.tasks.tab.tasks.TasksTabTasksView -import me.timeto.app.ui.calendar.CalendarTabsView -import me.timeto.app.ui.onePx -import me.timeto.app.ui.rememberVm -import me.timeto.app.ui.roundedShape -import me.timeto.app.ui.tasks.tab.repeatings.TasksTabRepeatingsView -import me.timeto.shared.db.TaskFolderDb -import me.timeto.shared.vm.tasks.tab.TasksTabVm -import kotlin.random.Random - -val TasksTabView__TAB_BUTTON_WIDTH: Dp = 32.dp -val TasksTabView__PADDING_END: Dp = TasksTabView__TAB_BUTTON_WIDTH + H_PADDING -val TasksTabView__LIST_SECTION_PADDING: Dp = 20.dp - -/// - -private val tabShape = SquircleShape(12.dp) -private val tabVPadding = 8.dp -private val tabActiveTextColor = c.white -private val tabInactiveTextColor = c.mainTabsMenuSecondary - -@OptIn(ExperimentalComposeUiApi::class) -@Composable -fun TasksTabView( - onClose: () -> Unit, -) { - - val (_, state) = rememberVm { - TasksTabVm() - } - - var activeTab by remember { - mutableStateOf(Tab.Folder(state.initFolder)) - } - - BackHandler { - if ((activeTab as? Tab.Folder)?.taskFolderDb?.isToday != true) - activeTab = Tab.Folder(state.initFolder) - else - onClose() - } - - val dragItem = remember { mutableStateOf(null) } - val dropItems = remember { mutableListOf() } - fun setFocusedDrop(drop: TasksTabDropItem?) { - dragItem.value?.focusedDrop?.value = drop - } - - ZStack( - modifier = Modifier - .motionEventSpy { event -> - val dragItemValue = dragItem.value ?: return@motionEventSpy - - val x = event.x - val y = event.y - val focusedDrop = dropItems - .filter { dragItemValue.isDropAllowed(it) } - .firstOrNull { drop -> - val s = drop.square - x > s.x1 && y > s.y1 && x < s.x2 && y < s.y2 - } - - if (focusedDrop == null) { - setFocusedDrop(null) - return@motionEventSpy - } - - when (event.action) { - MotionEvent.ACTION_UP -> { - dragItemValue.onDrop(focusedDrop) - setFocusedDrop(null) - } - MotionEvent.ACTION_MOVE -> { - if (dragItemValue.focusedDrop.value == null) - Haptic.shot() - setFocusedDrop(focusedDrop) - } - else -> { - setFocusedDrop(null) - } - } - } - .background(c.bg), - contentAlignment = Alignment.CenterEnd, - ) { - - when (val curTab = activeTab) { - is Tab.Folder -> TasksTabTasksView(curTab.taskFolderDb, dragItem) - is Tab.Calendar -> CalendarTabsView() - is Tab.Repeating -> TasksTabRepeatingsView() - } - - Column( - modifier = Modifier - .width(TasksTabView__TAB_BUTTON_WIDTH) - .fillMaxHeight(), - verticalArrangement = Arrangement.Center - ) { - - LazyColumn( - reverseLayout = true, - ) { - - item { - val dropItem = remember { - TasksTabDropItem.Calendar(TasksTabDropItem.Square(0, 0, 0, 0)) - } - val isActive = activeTab is Tab.Calendar - - CalendarButtonView( - isActive = isActive, - dragItem = dragItem, - dropItem = dropItem, - dropItems = dropItems, - onClick = { - if (isActive) onClose() - else activeTab = Tab.Calendar - }, - ) - } - - items(state.taskFoldersUi) { folderUi -> - val dropItem = remember { - TasksTabDropItem.Folder(folderUi.taskFolderDb, TasksTabDropItem.Square(0, 0, 0, 0)) - } - val isActive: Boolean = - (activeTab as? Tab.Folder)?.taskFolderDb?.id == folderUi.taskFolderDb.id - TabTextButton( - text = folderUi.tabText, - isActive = isActive, - dragItem = dragItem, - dropItem = dropItem, - dropItems = dropItems, - onClick = { - if (isActive) onClose() - else activeTab = Tab.Folder(folderUi.taskFolderDb) - }, - ) - } - - // - // Repeatings - - item { - val isActive = activeTab is Tab.Repeating - val backgroundColor = animateColorAsState(if (isActive) c.blue else c.bg, spring(stiffness = Spring.StiffnessMedium)) - val textColor = animateColorAsState(if (isActive) tabActiveTextColor else tabInactiveTextColor, spring(stiffness = Spring.StiffnessMedium)) - - Box( - modifier = Modifier - .width(TasksTabView__TAB_BUTTON_WIDTH) - .height(TasksTabView__TAB_BUTTON_WIDTH) - .clip(tabShape) - .background(backgroundColor.value) - .clickable { - if (isActive) onClose() - else activeTab = Tab.Repeating - }, - contentAlignment = Alignment.Center - ) { - Icon( - painterResource(id = R.drawable.sf_repeat_medium_semibold), - contentDescription = "Repeating", - tint = textColor.value, - modifier = Modifier.size(12.dp), - ) - } - } - } - } - } -} - -private sealed class Tab { - class Folder(val taskFolderDb: TaskFolderDb) : Tab() - data object Calendar : Tab() - data object Repeating : Tab() -} - -@Composable -private fun TabTextButton( - text: String, - isActive: Boolean, - dragItem: State, - dropItem: TasksTabDropItem, - dropItems: MutableList, - onClick: () -> Unit, -) { - DisposableEffect(Unit) { - dropItems.add(dropItem) - onDispose { - dropItems.remove(dropItem) - } - } - - val isAllowedToDrop = dragItem.value?.isDropAllowed?.invoke(dropItem) ?: false - val isFocusedToDrop = dragItem.value?.focusedDrop?.value == dropItem - - val bgColor = animateColorAsState( - when { - isFocusedToDrop -> c.tasksDropFocused - isAllowedToDrop -> c.purple - isActive -> c.blue - else -> c.bg - }, - spring(stiffness = Spring.StiffnessMedium) - ) - - val textColor = animateColorAsState( - when { - isFocusedToDrop -> c.white - isAllowedToDrop -> c.white - isActive -> tabActiveTextColor - else -> tabInactiveTextColor - }, - spring(stiffness = Spring.StiffnessMedium) - ) - - val rotationMaxAngle = 3f - var rotationAngle by remember { mutableFloatStateOf(0f) } - val rotationAngleAnimate by animateFloatAsState( - targetValue = rotationAngle, - animationSpec = tween(durationMillis = Random.nextInt(80, 130), easing = LinearEasing), - finishedListener = { - if (isAllowedToDrop) - rotationAngle = if (rotationAngle < 0) rotationMaxAngle else -rotationMaxAngle - } - ) - LaunchedEffect(isAllowedToDrop) { - if (isAllowedToDrop) - delay(Random.nextInt(0, 50).toLong()) - rotationAngle = if (isAllowedToDrop) (if (Random.nextBoolean()) rotationMaxAngle else -rotationMaxAngle) else 0f - } - - Column( - modifier = Modifier - .fillMaxWidth() - .padding(top = tabVPadding) - .rotate(rotationAngleAnimate) - .onGloballyPositioned { c -> - dropItem.upSquareByCoordinates(c) - } - .clip(tabShape) - .background(bgColor.value) - ) { - - Text( - text, - modifier = Modifier - .fillMaxWidth() - .clickable(onClick = onClick) - .padding(vertical = 8.dp), - textAlign = TextAlign.Center, - color = textColor.value, - fontSize = 12.sp, - lineHeight = 15.sp, - fontWeight = FontWeight.SemiBold, - fontFamily = FontFamily.Monospace, - ) - } -} - -// -// Calendar Button - -private val calendarDots: List> = listOf( - listOf(false, true, true, true), - listOf(true, true, true, true), - listOf(true, true, true, false), -) - -@Composable -private fun CalendarButtonView( - isActive: Boolean, - dragItem: State, - dropItem: TasksTabDropItem, - dropItems: MutableList, - onClick: () -> Unit, -) { - - DisposableEffect(Unit) { - dropItems.add(dropItem) - onDispose { - dropItems.remove(dropItem) - } - } - - val isAllowedToDrop = dragItem.value?.isDropAllowed?.invoke(dropItem) ?: false - val isFocusedToDrop = dragItem.value?.focusedDrop?.value == dropItem - - val bgColor = animateColorAsState( - when { - isFocusedToDrop -> c.tasksDropFocused - isAllowedToDrop -> c.purple - isActive -> c.blue - else -> c.bg - }, - spring(stiffness = Spring.StiffnessMedium) - ) - - val textColor = animateColorAsState( - when { - isFocusedToDrop -> c.white - isAllowedToDrop -> c.white - isActive -> tabActiveTextColor - else -> tabInactiveTextColor - }, - spring(stiffness = Spring.StiffnessMedium) - ) - - val rotationMaxAngle = 3f - var rotationAngle by remember { mutableFloatStateOf(0f) } - val rotationAngleAnimate by animateFloatAsState( - targetValue = rotationAngle, - animationSpec = tween(durationMillis = Random.nextInt(80, 130), easing = LinearEasing), - finishedListener = { - if (isAllowedToDrop) - rotationAngle = if (rotationAngle < 0) rotationMaxAngle else -rotationMaxAngle - } - ) - LaunchedEffect(isAllowedToDrop) { - if (isAllowedToDrop) - delay(Random.nextInt(0, 50).toLong()) - rotationAngle = if (isAllowedToDrop) (if (Random.nextBoolean()) rotationMaxAngle else -rotationMaxAngle) else 0f - } - - VStack( - modifier = Modifier - .padding(top = 11.dp) - .rotate(rotationAngleAnimate) - .onGloballyPositioned { c -> - dropItem.upSquareByCoordinates(c) - } - .size(TasksTabView__TAB_BUTTON_WIDTH) - .clip(tabShape) - .clickable { - onClick() - } - .background(bgColor.value) - .padding(horizontal = 5.dp), - verticalArrangement = Arrangement.Center, - ) { - - calendarDots.forEachIndexed { idx, dots -> - - HStack( - modifier = Modifier - .padding(top = if (idx == 0) 0.dp else 5.dp), - ) { - - dots.forEach { dot -> - - SpacerW1() - - ZStack( - modifier = Modifier - .size(2.dp + onePx) - .clip(roundedShape) - .background(if (dot) textColor.value else c.transparent), - ) - } - - SpacerW1() - } - } - } -} diff --git a/android/src/main/java/me/timeto/app/ui/tasks/tab/repeatings/TasksTabRepeatingsView.kt b/android/src/main/java/me/timeto/app/ui/tasks/tab/repeatings/TasksTabRepeatingsView.kt deleted file mode 100644 index cbdb637ad..000000000 --- a/android/src/main/java/me/timeto/app/ui/tasks/tab/repeatings/TasksTabRepeatingsView.kt +++ /dev/null @@ -1,86 +0,0 @@ -package me.timeto.app.ui.tasks.tab.repeatings - -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import me.timeto.app.ui.H_PADDING -import me.timeto.app.ui.H_PADDING_HALF -import me.timeto.app.ui.c -import me.timeto.app.ui.navigation.LocalNavigationFs -import me.timeto.app.ui.rememberVm -import me.timeto.app.ui.repeatings.form.RepeatingFormFs -import me.timeto.app.ui.squircleShape -import me.timeto.app.ui.tasks.tab.TasksTabView__LIST_SECTION_PADDING -import me.timeto.app.ui.tasks.tab.TasksTabView__PADDING_END -import me.timeto.shared.vm.tasks.tab.repeatings.TasksTabRepeatingsVm - -@Composable -fun TasksTabRepeatingsView() { - - val navigationFs = LocalNavigationFs.current - - val (_, state) = rememberVm { - TasksTabRepeatingsVm() - } - - LazyColumn( - reverseLayout = true, - contentPadding = PaddingValues( - end = TasksTabView__PADDING_END, - bottom = TasksTabView__LIST_SECTION_PADDING, - top = TasksTabView__LIST_SECTION_PADDING, - ), - modifier = Modifier - .fillMaxHeight(), - ) { - - item { - - Text( - text = "New Repeating Task", - modifier = Modifier - .fillMaxWidth() - .padding(start = H_PADDING - 2.dp) - .padding(top = TasksTabView__LIST_SECTION_PADDING) - .clip(squircleShape) - .background(c.blue) - .clickable { - navigationFs.push { - RepeatingFormFs( - initRepeatingDb = null, - ) - } - } - .padding(vertical = 10.dp), - color = c.white, - fontWeight = FontWeight.SemiBold, - textAlign = TextAlign.Center, - ) - } - - val repeatingsUi = state.repeatingsUi - repeatingsUi.forEachIndexed { idx, repeatingUi -> - item { - TasksTabRepeatingsItemView( - repeatingUi = repeatingUi, - // Remember that the list is reversed - withTopDivider = (idx != repeatingsUi.size - 1), - modifier = Modifier - .fillMaxWidth() - .padding(start = H_PADDING_HALF), - ) - } - } - } -} diff --git a/android/src/main/java/me/timeto/app/ui/tasks/tab/tasks/TasksTabTasksView.kt b/android/src/main/java/me/timeto/app/ui/tasks/tab/tasks/TasksTabTasksView.kt deleted file mode 100644 index 58a8a95ac..000000000 --- a/android/src/main/java/me/timeto/app/ui/tasks/tab/tasks/TasksTabTasksView.kt +++ /dev/null @@ -1,497 +0,0 @@ -package me.timeto.app.ui.tasks.tab.tasks - -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.* -import androidx.compose.material.* -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.* -import me.timeto.app.R -import me.timeto.app.Haptic -import me.timeto.app.toColor -import me.timeto.app.ui.HStack -import me.timeto.app.ui.H_PADDING -import me.timeto.app.ui.SquircleShape -import me.timeto.app.ui.SwipeToAction -import me.timeto.app.ui.SwipeToAction__DeleteView -import me.timeto.app.ui.SwipeToAction__StartView -import me.timeto.app.ui.TriggersIconsView -import me.timeto.app.ui.ZStack -import me.timeto.app.ui.c -import me.timeto.app.ui.events.EventFormFs -import me.timeto.app.ui.navigation.LocalNavigationFs -import me.timeto.app.ui.onePx -import me.timeto.app.ui.rememberVm -import me.timeto.app.ui.roundedShape -import me.timeto.app.ui.squircleShape -import me.timeto.app.ui.task_form.TaskFormFs -import me.timeto.app.ui.tasks.TaskTimerFs -import me.timeto.app.ui.tasks.tab.TasksTabDragItem -import me.timeto.app.ui.tasks.tab.TasksTabDropItem -import me.timeto.app.ui.tasks.tab.TasksTabView__LIST_SECTION_PADDING -import me.timeto.app.ui.tasks.tab.TasksTabView__PADDING_END -import me.timeto.shared.TextFeatures -import me.timeto.shared.db.TaskFolderDb -import me.timeto.shared.launchEx -import me.timeto.shared.vm.task_form.TaskFormStrategy -import me.timeto.shared.vm.tasks.tab.tasks.TasksTabTasksVm - -private val inputShape = SquircleShape(16.dp) -private val highlightTimeShape = SquircleShape(8.dp) - -@OptIn(ExperimentalMaterialApi::class) -@Composable -fun TasksTabTasksView( - taskFolderDb: TaskFolderDb, - dragItem: MutableState, -) { - - val navigationFs = LocalNavigationFs.current - - val (_, state) = rememberVm(taskFolderDb) { - TasksTabTasksVm(taskFolderDb) - } - val tmrwUi = state.tmrwUi - - val scope = rememberCoroutineScope() - - val listState = rememberLazyListState() - - LazyColumn( - modifier = Modifier - .fillMaxHeight(), - reverseLayout = true, - contentPadding = PaddingValues( - top = TasksTabView__LIST_SECTION_PADDING, - end = TasksTabView__PADDING_END - ), - state = listState, - ) { - - item { - - Column( - modifier = Modifier - .pointerInput(Unit) { } // Ignore clicks through - .padding( - top = TasksTabView__LIST_SECTION_PADDING, - bottom = TasksTabView__LIST_SECTION_PADDING, - ) - ) { - - Row( - modifier = Modifier - .padding(start = H_PADDING - 2.dp) - .border(width = onePx, color = c.gray5, shape = inputShape) - .height(IntrinsicSize.Min) // To use fillMaxHeight() inside - .clickable { - navigationFs.push { - TaskFormFs( - strategy = TaskFormStrategy.NewTask( - taskFolderDb = taskFolderDb, - ) - ) - } - }, - verticalAlignment = Alignment.CenterVertically - ) { - - ZStack( - modifier = Modifier - .weight(1f) - .defaultMinSize(minHeight = 42.dp), - contentAlignment = Alignment.CenterStart, - ) { - Text( - text = "Task", - modifier = Modifier - .padding(start = 14.dp), - color = c.text.copy(alpha = 0.5f) - ) - } - - Box( - modifier = Modifier - .padding(top = 4.dp, bottom = 4.dp, end = 4.dp) - .fillMaxHeight() - .clip(squircleShape) - .background(c.blue) - .padding(horizontal = 12.dp), - contentAlignment = Alignment.CenterStart - ) { - Text( - text = "SAVE", - color = c.white, - fontSize = 14.sp, - fontWeight = FontWeight.W600, - ) - } - } - } - } - - if (tmrwUi != null) { - item { - Box( - modifier = Modifier - .fillMaxWidth() - .padding(top = TasksTabView__LIST_SECTION_PADDING), - contentAlignment = Alignment.Center - ) { - Text( - text = tmrwUi.curTimeString, - fontSize = 14.sp, - fontWeight = FontWeight.W300, - color = c.secondaryText, - ) - } - } - } - - val tasksVmUi = state.tasksVmUi - items( - items = tasksVmUi, - key = { it.taskUi.taskDb.id } - ) { taskVmUi -> - val isFirst = taskVmUi == tasksVmUi.firstOrNull() - - ZStack( - modifier = Modifier - .background(c.bg), - ) { - - val ignoreOneSwipeToAction = remember { mutableStateOf(false) } - val isEditOrDelete = remember { mutableStateOf(null) } - val stateOffsetAbsDp = remember { mutableStateOf(0.dp) } - - val localDragItem = remember(tasksVmUi) { - TasksTabDragItem( - mutableStateOf(null), - { drop -> - when (drop) { - is TasksTabDropItem.Calendar -> true - is TasksTabDropItem.Folder -> drop.taskFolderDb.id != taskVmUi.taskUi.taskDb.folder_id - } - } - ) { drop -> - // Otherwise, the mod of editing is activated, so the keyboard is started. - // And since the task is transferred, sometimes just opens the keyboard. - ignoreOneSwipeToAction.value = true - scope.launchEx { - when (drop) { - is TasksTabDropItem.Calendar -> { - Haptic.shot() - navigationFs.push { - EventFormFs( - initEventDb = null, - initText = taskVmUi.taskUi.taskDb.text, - initTime = null, - onDone = { - taskVmUi.delete() - }, - ) - } - } - - is TasksTabDropItem.Folder -> { - Haptic.long() - taskVmUi.upFolder(drop.taskFolderDb) - } - } - } - } - } - - LaunchedEffect(isEditOrDelete.value, stateOffsetAbsDp.value) { - dragItem.value = if (isEditOrDelete.value == true && stateOffsetAbsDp.value > 10.dp) - localDragItem - else null - } - DisposableEffect(Unit) { - onDispose { - dragItem.value = null - } - } - - SwipeToAction( - isStartOrEnd = isEditOrDelete, - ignoreOneAction = ignoreOneSwipeToAction, - startView = { - val dropItem = localDragItem.focusedDrop.value - SwipeToAction__StartView( - text = if (dropItem != null) "Move to ${dropItem.name}" else "Edit", - bgColor = if (dropItem != null) c.tasksDropFocused else c.blue - ) - }, - endView = { state -> - SwipeToAction__DeleteView(state, taskVmUi.taskUi.taskDb.text) { - Haptic.long() - taskVmUi.delete() - } - }, - onStart = { - navigationFs.push { - TaskFormFs( - strategy = TaskFormStrategy.EditTask( - taskDb = taskVmUi.taskUi.taskDb, - ), - ) - } - false - }, - onEnd = { - true - }, - toVibrateStartEnd = listOf(true, false), - stateOffsetAbsDp = stateOffsetAbsDp, - ) { - - Box( - modifier = Modifier - .background(c.bg) - .clickable { - val taskDb = taskVmUi.taskUi.taskDb - taskDb.startIntervalForUi( - ifJustStarted = {}, - ifTimerNeeded = { - navigationFs.push { - TaskTimerFs( - taskDb = taskDb, - ) - } - }, - ) - } - .padding(start = H_PADDING), - contentAlignment = Alignment.BottomCenter - ) { - - Column( - modifier = Modifier - .padding(top = 10.dp, bottom = 10.dp), - verticalArrangement = Arrangement.Center - ) { - - val vPadding = 3.dp - - val timeUi = taskVmUi.timeUi - if (timeUi != null) { - Row( - modifier = Modifier - .padding(bottom = vPadding), - verticalAlignment = Alignment.CenterVertically, - ) { - - when (timeUi) { - - is TasksTabTasksVm.TaskVmUi.TimeUi.HighlightUi -> { - Row( - modifier = Modifier - .offset(x = (-1).dp) - .clip(highlightTimeShape) - .background(timeUi.backgroundColorEnum.toColor()) - .padding(start = 5.dp, end = 4.dp, top = 3.dp, bottom = 3.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - - when (timeUi.timeData.type) { - - TextFeatures.TimeData.TYPE.EVENT -> { - Icon( - painterResource(id = R.drawable.sf_calendar_medium_light), - contentDescription = "Event", - tint = c.white, - modifier = Modifier - .padding(end = 5.dp) - .size(14.dp), - ) - } - - TextFeatures.TimeData.TYPE.REPEATING -> { - Icon( - painterResource(id = R.drawable.sf_repeat_medium_semibold), - contentDescription = "Repeating", - tint = c.white, - modifier = Modifier - .padding(end = 5.dp) - .size(12.dp), - ) - } - } - - Text( - text = timeUi.title, - modifier = Modifier - .padding(top = 1.dp), - fontSize = 12.sp, - lineHeight = 14.sp, - color = c.white, - ) - } - - Text( - text = timeUi.timeLeftText, - modifier = Modifier - .padding(start = 6.dp), - fontSize = 13.sp, - fontWeight = FontWeight.W300, - color = timeUi.timeLeftColorEnum.toColor(), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } - - is TasksTabTasksVm.TaskVmUi.TimeUi.RegularUi -> { - Text( - text = timeUi.text, - fontSize = 13.sp, - lineHeight = 18.sp, - fontWeight = FontWeight.W300, - color = timeUi.textColorEnum.toColor(), - ) - } - } - } - } - - HStack( - verticalAlignment = Alignment.CenterVertically, - ) { - - Text( - text = taskVmUi.text, - color = c.text, - modifier = Modifier - .weight(1f), - ) - - TriggersIconsView( - checklistsDb = taskVmUi.taskUi.tf.checklistsDb, - shortcutsDb = taskVmUi.taskUi.tf.shortcutsDb, - ) - - if (taskVmUi.taskUi.tf.isImportant) { - Icon( - painterResource(R.drawable.sf_flag_fill_medium_regular), - contentDescription = "Important", - tint = c.red, - modifier = Modifier - .padding(start = 8.dp) - .offset(y = 1.dp) - .size(16.dp), - ) - } - - if (taskVmUi.taskFolderDb.activity_id != null) { - ZStack( - modifier = Modifier - .padding(start = 8.dp) - .clip(roundedShape) - .clickable { - taskVmUi.toggleOnHomeActivity() - }, - contentAlignment = Alignment.Center, - ) { - Icon( - painter = painterResource(R.drawable.sf_house_medium_semibold), - contentDescription = "New Task", - tint = if (taskVmUi.taskUi.taskDb.onHomeActivity) c.secondaryText else c.homeFg, - modifier = Modifier - .size(23.dp), - ) - } - } - } - } - - if (!isFirst) - Divider() - } - } - } - } - - if (tmrwUi != null) { - - item { - Divider( - Modifier - .padding(horizontal = 80.dp) - .padding(top = 22.dp, bottom = if (tasksVmUi.isEmpty()) 0.dp else 18.dp) - ) - } - - val tmrwTasksUi = tmrwUi.tasksUi - items( - items = tmrwTasksUi, - key = { taskUI -> "tmrw_${taskUI.taskDb.id}" } - ) { taskUi -> - - val isFirst = taskUi == tmrwTasksUi.lastOrNull() - - TmrwTaskView( - taskUi = taskUi, - isFirst = isFirst, - ) - } - } - } -} - -@Composable -private fun TmrwTaskView( - taskUi: TasksTabTasksVm.TmrwTaskUi, - isFirst: Boolean, -) { - Column( - modifier = Modifier - .padding(start = H_PADDING), - verticalArrangement = Arrangement.Center, - ) { - - if (!isFirst) - Divider() - - Box(Modifier.height(8.dp)) - - val vPadding = 3.dp - - val timeUi = taskUi.timeUi - if (timeUi != null) { - Text( - text = timeUi.text, - modifier = Modifier - .padding(bottom = vPadding), - fontSize = 13.sp, - fontWeight = FontWeight.W300, - color = timeUi.textColorEnum.toColor(), - ) - } - - HStack( - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = taskUi.text, - color = c.text, - modifier = Modifier - .weight(1f), - ) - TriggersIconsView( - checklistsDb = taskUi.textFeatures.checklistsDb, - shortcutsDb = taskUi.textFeatures.shortcutsDb, - ) - } - - Box(Modifier.height(8.dp)) - } -} diff --git a/android/src/main/java/me/timeto/app/ui/whats_new/WhatsNewFs.kt b/android/src/main/java/me/timeto/app/ui/whats_new/WhatsNewFs.kt index 48353aaa9..b7c57d1b3 100644 --- a/android/src/main/java/me/timeto/app/ui/whats_new/WhatsNewFs.kt +++ b/android/src/main/java/me/timeto/app/ui/whats_new/WhatsNewFs.kt @@ -29,17 +29,16 @@ import me.timeto.app.ui.H_PADDING_HALF import me.timeto.app.ui.Screen import me.timeto.app.ui.VStack import me.timeto.app.ui.c +import me.timeto.app.ui.doc.DocFs import me.timeto.app.ui.donations.DonationsFs import me.timeto.app.ui.halfDpFloor import me.timeto.app.ui.header.Header import me.timeto.app.ui.header.HeaderCancelButton import me.timeto.app.ui.navigation.LocalNavigationFs import me.timeto.app.ui.navigation.LocalNavigationLayer -import me.timeto.app.ui.readme.ReadmeFs import me.timeto.app.ui.rememberVm import me.timeto.app.ui.squircleShape import me.timeto.shared.SystemInfo -import me.timeto.shared.vm.readme.ReadmeVm import me.timeto.shared.vm.whats_new.WhatsNewVm @Composable @@ -137,8 +136,8 @@ fun WhatsNewFs() { when (buttonUi) { WhatsNewVm.HistoryItemUi.ButtonUi.pomodoro -> { navigationFs.push { - ReadmeFs( - defaultItem = ReadmeVm.DefaultItem.pomodoro, + DocFs( + forceRead = false, ) } } diff --git a/android/src/main/res/drawable/doc_activities_evening.png b/android/src/main/res/drawable/doc_activities_evening.png new file mode 100644 index 000000000..ca574d5fa Binary files /dev/null and b/android/src/main/res/drawable/doc_activities_evening.png differ diff --git a/android/src/main/res/drawable/doc_activities_morning.png b/android/src/main/res/drawable/doc_activities_morning.png new file mode 100644 index 000000000..fb5ff60b3 Binary files /dev/null and b/android/src/main/res/drawable/doc_activities_morning.png differ diff --git a/android/src/main/res/drawable/doc_calendar_button.png b/android/src/main/res/drawable/doc_calendar_button.png new file mode 100644 index 000000000..501d59ed2 Binary files /dev/null and b/android/src/main/res/drawable/doc_calendar_button.png differ diff --git a/android/src/main/res/drawable/doc_calendar_screen.png b/android/src/main/res/drawable/doc_calendar_screen.png new file mode 100644 index 000000000..2a3ac4647 Binary files /dev/null and b/android/src/main/res/drawable/doc_calendar_screen.png differ diff --git a/android/src/main/res/drawable/doc_folders_example.png b/android/src/main/res/drawable/doc_folders_example.png new file mode 100644 index 000000000..139042161 Binary files /dev/null and b/android/src/main/res/drawable/doc_folders_example.png differ diff --git a/android/src/main/res/drawable/doc_folders_swipe.png b/android/src/main/res/drawable/doc_folders_swipe.png new file mode 100644 index 000000000..b37ea0d03 Binary files /dev/null and b/android/src/main/res/drawable/doc_folders_swipe.png differ diff --git a/android/src/main/res/drawable/doc_folders_tomorrow.png b/android/src/main/res/drawable/doc_folders_tomorrow.png new file mode 100644 index 000000000..0865e2add Binary files /dev/null and b/android/src/main/res/drawable/doc_folders_tomorrow.png differ diff --git a/android/src/main/res/drawable/doc_free_time_form.png b/android/src/main/res/drawable/doc_free_time_form.png new file mode 100644 index 000000000..abf2d250a Binary files /dev/null and b/android/src/main/res/drawable/doc_free_time_form.png differ diff --git a/android/src/main/res/drawable/doc_free_time_start.png b/android/src/main/res/drawable/doc_free_time_start.png new file mode 100644 index 000000000..693f17961 Binary files /dev/null and b/android/src/main/res/drawable/doc_free_time_start.png differ diff --git a/android/src/main/res/drawable/doc_morning_completed.png b/android/src/main/res/drawable/doc_morning_completed.png new file mode 100644 index 000000000..78e94b6da Binary files /dev/null and b/android/src/main/res/drawable/doc_morning_completed.png differ diff --git a/android/src/main/res/drawable/doc_morning_form.png b/android/src/main/res/drawable/doc_morning_form.png new file mode 100644 index 000000000..382bcfae1 Binary files /dev/null and b/android/src/main/res/drawable/doc_morning_form.png differ diff --git a/android/src/main/res/drawable/doc_morning_start.png b/android/src/main/res/drawable/doc_morning_start.png new file mode 100644 index 000000000..38d0544ee Binary files /dev/null and b/android/src/main/res/drawable/doc_morning_start.png differ diff --git a/android/src/main/res/drawable/doc_music_form.png b/android/src/main/res/drawable/doc_music_form.png new file mode 100644 index 000000000..954bfef88 Binary files /dev/null and b/android/src/main/res/drawable/doc_music_form.png differ diff --git a/android/src/main/res/drawable/doc_music_progress.png b/android/src/main/res/drawable/doc_music_progress.png new file mode 100644 index 000000000..e86ba1749 Binary files /dev/null and b/android/src/main/res/drawable/doc_music_progress.png differ diff --git a/android/src/main/res/drawable/doc_reading_form.png b/android/src/main/res/drawable/doc_reading_form.png new file mode 100644 index 000000000..e68eb9d87 Binary files /dev/null and b/android/src/main/res/drawable/doc_reading_form.png differ diff --git a/android/src/main/res/drawable/doc_reading_progress.png b/android/src/main/res/drawable/doc_reading_progress.png new file mode 100644 index 000000000..99aeac1ec Binary files /dev/null and b/android/src/main/res/drawable/doc_reading_progress.png differ diff --git a/android/src/main/res/drawable/doc_repeating_form_1.png b/android/src/main/res/drawable/doc_repeating_form_1.png new file mode 100644 index 000000000..61758e34f Binary files /dev/null and b/android/src/main/res/drawable/doc_repeating_form_1.png differ diff --git a/android/src/main/res/drawable/doc_repeating_form_2.png b/android/src/main/res/drawable/doc_repeating_form_2.png new file mode 100644 index 000000000..7168d1cf7 Binary files /dev/null and b/android/src/main/res/drawable/doc_repeating_form_2.png differ diff --git a/android/src/main/res/drawable/doc_sleep_form.png b/android/src/main/res/drawable/doc_sleep_form.png new file mode 100644 index 000000000..0b9782ab9 Binary files /dev/null and b/android/src/main/res/drawable/doc_sleep_form.png differ diff --git a/android/src/main/res/drawable/doc_sleep_start.png b/android/src/main/res/drawable/doc_sleep_start.png new file mode 100644 index 000000000..29eaad0c0 Binary files /dev/null and b/android/src/main/res/drawable/doc_sleep_start.png differ diff --git a/android/src/main/res/drawable/doc_small_tasks_form.png b/android/src/main/res/drawable/doc_small_tasks_form.png new file mode 100644 index 000000000..39274ef4a Binary files /dev/null and b/android/src/main/res/drawable/doc_small_tasks_form.png differ diff --git a/android/src/main/res/drawable/doc_small_tasks_progress.png b/android/src/main/res/drawable/doc_small_tasks_progress.png new file mode 100644 index 000000000..a769d9e7a Binary files /dev/null and b/android/src/main/res/drawable/doc_small_tasks_progress.png differ diff --git a/android/src/main/res/drawable/doc_tasks_delete.png b/android/src/main/res/drawable/doc_tasks_delete.png new file mode 100644 index 000000000..c91558a6b Binary files /dev/null and b/android/src/main/res/drawable/doc_tasks_delete.png differ diff --git a/android/src/main/res/drawable/doc_tasks_example1.png b/android/src/main/res/drawable/doc_tasks_example1.png new file mode 100644 index 000000000..682f7a9a0 Binary files /dev/null and b/android/src/main/res/drawable/doc_tasks_example1.png differ diff --git a/android/src/main/res/drawable/doc_tasks_field.png b/android/src/main/res/drawable/doc_tasks_field.png new file mode 100644 index 000000000..4ee0bfe2c Binary files /dev/null and b/android/src/main/res/drawable/doc_tasks_field.png differ diff --git a/android/src/main/res/drawable/doc_tasks_form.png b/android/src/main/res/drawable/doc_tasks_form.png new file mode 100644 index 000000000..28a38b00a Binary files /dev/null and b/android/src/main/res/drawable/doc_tasks_form.png differ diff --git a/android/src/main/res/drawable/doc_tasks_started.png b/android/src/main/res/drawable/doc_tasks_started.png new file mode 100644 index 000000000..d8c9fd0e9 Binary files /dev/null and b/android/src/main/res/drawable/doc_tasks_started.png differ diff --git a/android/src/main/res/drawable/doc_timer_summary.png b/android/src/main/res/drawable/doc_timer_summary.png new file mode 100644 index 000000000..5abfafe3d Binary files /dev/null and b/android/src/main/res/drawable/doc_timer_summary.png differ diff --git a/android/src/main/res/drawable/doc_timetome_break.png b/android/src/main/res/drawable/doc_timetome_break.png new file mode 100644 index 000000000..f7e69f7a8 Binary files /dev/null and b/android/src/main/res/drawable/doc_timetome_break.png differ diff --git a/android/src/main/res/drawable/doc_timetome_form.png b/android/src/main/res/drawable/doc_timetome_form.png new file mode 100644 index 000000000..38135e023 Binary files /dev/null and b/android/src/main/res/drawable/doc_timetome_form.png differ diff --git a/android/src/main/res/drawable/doc_timetome_overdue.png b/android/src/main/res/drawable/doc_timetome_overdue.png new file mode 100644 index 000000000..a5be1ce7e Binary files /dev/null and b/android/src/main/res/drawable/doc_timetome_overdue.png differ diff --git a/android/src/main/res/drawable/doc_timetome_start.png b/android/src/main/res/drawable/doc_timetome_start.png new file mode 100644 index 000000000..d19a9f86d Binary files /dev/null and b/android/src/main/res/drawable/doc_timetome_start.png differ diff --git a/android/src/main/res/drawable/doc_work_form.png b/android/src/main/res/drawable/doc_work_form.png new file mode 100644 index 000000000..6b29e1908 Binary files /dev/null and b/android/src/main/res/drawable/doc_work_form.png differ diff --git a/android/src/main/res/drawable/doc_work_history.png b/android/src/main/res/drawable/doc_work_history.png new file mode 100644 index 000000000..54a4fee17 Binary files /dev/null and b/android/src/main/res/drawable/doc_work_history.png differ diff --git a/android/src/main/res/drawable/doc_work_note.png b/android/src/main/res/drawable/doc_work_note.png new file mode 100644 index 000000000..95c91226d Binary files /dev/null and b/android/src/main/res/drawable/doc_work_note.png differ diff --git a/android/src/main/res/drawable/doc_workout_form.png b/android/src/main/res/drawable/doc_workout_form.png new file mode 100644 index 000000000..e9428a349 Binary files /dev/null and b/android/src/main/res/drawable/doc_workout_form.png differ diff --git a/android/src/main/res/drawable/doc_workout_start.png b/android/src/main/res/drawable/doc_workout_start.png new file mode 100644 index 000000000..00d53b68f Binary files /dev/null and b/android/src/main/res/drawable/doc_workout_start.png differ diff --git a/android/src/main/res/drawable/ms_architecture_fill_w700.xml b/android/src/main/res/drawable/ms_architecture_fill_w700.xml new file mode 100644 index 000000000..abccd9fd1 --- /dev/null +++ b/android/src/main/res/drawable/ms_architecture_fill_w700.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/ms_bedtime_fill.xml b/android/src/main/res/drawable/ms_bedtime_fill.xml new file mode 100644 index 000000000..ed2c430d6 --- /dev/null +++ b/android/src/main/res/drawable/ms_bedtime_fill.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/ms_bolt_fill.xml b/android/src/main/res/drawable/ms_bolt_fill.xml new file mode 100644 index 000000000..afcf9e25e --- /dev/null +++ b/android/src/main/res/drawable/ms_bolt_fill.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/ms_book_2_fill.xml b/android/src/main/res/drawable/ms_book_2_fill.xml new file mode 100644 index 000000000..85f231435 --- /dev/null +++ b/android/src/main/res/drawable/ms_book_2_fill.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/ms_campaign_fill_w700.xml b/android/src/main/res/drawable/ms_campaign_fill_w700.xml new file mode 100644 index 000000000..726b46fc3 --- /dev/null +++ b/android/src/main/res/drawable/ms_campaign_fill_w700.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/ms_construction_fill.xml b/android/src/main/res/drawable/ms_construction_fill.xml new file mode 100644 index 000000000..00c976c90 --- /dev/null +++ b/android/src/main/res/drawable/ms_construction_fill.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/ms_dark_mode_fill.xml b/android/src/main/res/drawable/ms_dark_mode_fill.xml new file mode 100644 index 000000000..e4f428c80 --- /dev/null +++ b/android/src/main/res/drawable/ms_dark_mode_fill.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/ms_directions_bus_fill.xml b/android/src/main/res/drawable/ms_directions_bus_fill.xml new file mode 100644 index 000000000..3b60f8a84 --- /dev/null +++ b/android/src/main/res/drawable/ms_directions_bus_fill.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/ms_exercise_fill.xml b/android/src/main/res/drawable/ms_exercise_fill.xml new file mode 100644 index 000000000..bb1d4b690 --- /dev/null +++ b/android/src/main/res/drawable/ms_exercise_fill.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/ms_folder.xml b/android/src/main/res/drawable/ms_folder.xml new file mode 100644 index 000000000..a1e978b69 --- /dev/null +++ b/android/src/main/res/drawable/ms_folder.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/ms_hiking_fill.xml b/android/src/main/res/drawable/ms_hiking_fill.xml new file mode 100644 index 000000000..527476834 --- /dev/null +++ b/android/src/main/res/drawable/ms_hiking_fill.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/ms_inbox_fill.xml b/android/src/main/res/drawable/ms_inbox_fill.xml new file mode 100644 index 000000000..dc224be0f --- /dev/null +++ b/android/src/main/res/drawable/ms_inbox_fill.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/ms_keyboard_option_key_w700.xml b/android/src/main/res/drawable/ms_keyboard_option_key_w700.xml new file mode 100644 index 000000000..a205603e5 --- /dev/null +++ b/android/src/main/res/drawable/ms_keyboard_option_key_w700.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/ms_lightbulb_fill.xml b/android/src/main/res/drawable/ms_lightbulb_fill.xml new file mode 100644 index 000000000..65be18596 --- /dev/null +++ b/android/src/main/res/drawable/ms_lightbulb_fill.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/ms_moon_stars_fill.xml b/android/src/main/res/drawable/ms_moon_stars_fill.xml new file mode 100644 index 000000000..6704ce1e8 --- /dev/null +++ b/android/src/main/res/drawable/ms_moon_stars_fill.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/ms_music_note.xml b/android/src/main/res/drawable/ms_music_note.xml new file mode 100644 index 000000000..ae15e7365 --- /dev/null +++ b/android/src/main/res/drawable/ms_music_note.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/ms_piano.xml b/android/src/main/res/drawable/ms_piano.xml new file mode 100644 index 000000000..e2a18ca43 --- /dev/null +++ b/android/src/main/res/drawable/ms_piano.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/ms_question_mark.xml b/android/src/main/res/drawable/ms_question_mark.xml new file mode 100644 index 000000000..76736a219 --- /dev/null +++ b/android/src/main/res/drawable/ms_question_mark.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/ms_rocket_launch_fill.xml b/android/src/main/res/drawable/ms_rocket_launch_fill.xml new file mode 100644 index 000000000..060d1ec0c --- /dev/null +++ b/android/src/main/res/drawable/ms_rocket_launch_fill.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/ms_school_fill.xml b/android/src/main/res/drawable/ms_school_fill.xml new file mode 100644 index 000000000..3c441c50e --- /dev/null +++ b/android/src/main/res/drawable/ms_school_fill.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/ms_science_fill.xml b/android/src/main/res/drawable/ms_science_fill.xml new file mode 100644 index 000000000..24a7ce136 --- /dev/null +++ b/android/src/main/res/drawable/ms_science_fill.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/ms_self_improvement_fill_w700.xml b/android/src/main/res/drawable/ms_self_improvement_fill_w700.xml new file mode 100644 index 000000000..17a50a441 --- /dev/null +++ b/android/src/main/res/drawable/ms_self_improvement_fill_w700.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/ms_sports_esports_fill.xml b/android/src/main/res/drawable/ms_sports_esports_fill.xml new file mode 100644 index 000000000..e098349dc --- /dev/null +++ b/android/src/main/res/drawable/ms_sports_esports_fill.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/ms_sports_soccer_fill.xml b/android/src/main/res/drawable/ms_sports_soccer_fill.xml new file mode 100644 index 000000000..f1f604bf5 --- /dev/null +++ b/android/src/main/res/drawable/ms_sports_soccer_fill.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/ms_timer_fill.xml b/android/src/main/res/drawable/ms_timer_fill.xml new file mode 100644 index 000000000..9db1bde4e --- /dev/null +++ b/android/src/main/res/drawable/ms_timer_fill.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/ms_wb_sunny_fill.xml b/android/src/main/res/drawable/ms_wb_sunny_fill.xml new file mode 100644 index 000000000..ae619169f --- /dev/null +++ b/android/src/main/res/drawable/ms_wb_sunny_fill.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/ms_work_fill.xml b/android/src/main/res/drawable/ms_work_fill.xml new file mode 100644 index 000000000..260797f68 --- /dev/null +++ b/android/src/main/res/drawable/ms_work_fill.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/readme_activities_1.jpg b/android/src/main/res/drawable/readme_activities_1.jpg deleted file mode 100644 index 8cceb0bb0..000000000 Binary files a/android/src/main/res/drawable/readme_activities_1.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_calendar_1.jpg b/android/src/main/res/drawable/readme_calendar_1.jpg deleted file mode 100644 index 25e263d87..000000000 Binary files a/android/src/main/res/drawable/readme_calendar_1.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_calendar_2.jpg b/android/src/main/res/drawable/readme_calendar_2.jpg deleted file mode 100644 index d65768978..000000000 Binary files a/android/src/main/res/drawable/readme_calendar_2.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_chart_1.jpg b/android/src/main/res/drawable/readme_chart_1.jpg deleted file mode 100644 index 8f702ef95..000000000 Binary files a/android/src/main/res/drawable/readme_chart_1.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_chart_2.jpg b/android/src/main/res/drawable/readme_chart_2.jpg deleted file mode 100644 index 9120832f9..000000000 Binary files a/android/src/main/res/drawable/readme_chart_2.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_chart_3.jpg b/android/src/main/res/drawable/readme_chart_3.jpg deleted file mode 100644 index 3d5b9ba07..000000000 Binary files a/android/src/main/res/drawable/readme_chart_3.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_checklist.png b/android/src/main/res/drawable/readme_checklist.png deleted file mode 100644 index 4a36cd795..000000000 Binary files a/android/src/main/res/drawable/readme_checklist.png and /dev/null differ diff --git a/android/src/main/res/drawable/readme_checklists_1.jpg b/android/src/main/res/drawable/readme_checklists_1.jpg deleted file mode 100644 index 070918d57..000000000 Binary files a/android/src/main/res/drawable/readme_checklists_1.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_checklists_2.jpg b/android/src/main/res/drawable/readme_checklists_2.jpg deleted file mode 100644 index c34c64d3c..000000000 Binary files a/android/src/main/res/drawable/readme_checklists_2.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_checklists_3.jpg b/android/src/main/res/drawable/readme_checklists_3.jpg deleted file mode 100644 index c652d0527..000000000 Binary files a/android/src/main/res/drawable/readme_checklists_3.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_checklists_practice_1.jpg b/android/src/main/res/drawable/readme_checklists_practice_1.jpg deleted file mode 100644 index 85617d72c..000000000 Binary files a/android/src/main/res/drawable/readme_checklists_practice_1.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_checklists_practice_2.jpg b/android/src/main/res/drawable/readme_checklists_practice_2.jpg deleted file mode 100644 index 7c9b5a4a0..000000000 Binary files a/android/src/main/res/drawable/readme_checklists_practice_2.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_checklists_practice_3.jpg b/android/src/main/res/drawable/readme_checklists_practice_3.jpg deleted file mode 100644 index c4ebbeb64..000000000 Binary files a/android/src/main/res/drawable/readme_checklists_practice_3.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_checklists_practice_4.jpg b/android/src/main/res/drawable/readme_checklists_practice_4.jpg deleted file mode 100644 index de4b08d06..000000000 Binary files a/android/src/main/res/drawable/readme_checklists_practice_4.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_checklists_practice_5.jpg b/android/src/main/res/drawable/readme_checklists_practice_5.jpg deleted file mode 100644 index d73da688a..000000000 Binary files a/android/src/main/res/drawable/readme_checklists_practice_5.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_checklists_practice_6.jpg b/android/src/main/res/drawable/readme_checklists_practice_6.jpg deleted file mode 100644 index 540a7854e..000000000 Binary files a/android/src/main/res/drawable/readme_checklists_practice_6.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_checklists_practice_7.jpg b/android/src/main/res/drawable/readme_checklists_practice_7.jpg deleted file mode 100644 index d785a1352..000000000 Binary files a/android/src/main/res/drawable/readme_checklists_practice_7.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_checklists_practice_8.jpg b/android/src/main/res/drawable/readme_checklists_practice_8.jpg deleted file mode 100644 index d20df4f16..000000000 Binary files a/android/src/main/res/drawable/readme_checklists_practice_8.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_checklists_practice_9.jpg b/android/src/main/res/drawable/readme_checklists_practice_9.jpg deleted file mode 100644 index b4e7e9e7a..000000000 Binary files a/android/src/main/res/drawable/readme_checklists_practice_9.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_goals.png b/android/src/main/res/drawable/readme_goals.png deleted file mode 100644 index f0bbad3eb..000000000 Binary files a/android/src/main/res/drawable/readme_goals.png and /dev/null differ diff --git a/android/src/main/res/drawable/readme_goals_1.jpg b/android/src/main/res/drawable/readme_goals_1.jpg deleted file mode 100644 index 8f67d0b19..000000000 Binary files a/android/src/main/res/drawable/readme_goals_1.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_pomodoro_1.jpg b/android/src/main/res/drawable/readme_pomodoro_1.jpg deleted file mode 100644 index 410b35874..000000000 Binary files a/android/src/main/res/drawable/readme_pomodoro_1.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_pomodoro_2.jpg b/android/src/main/res/drawable/readme_pomodoro_2.jpg deleted file mode 100644 index 3857a79f0..000000000 Binary files a/android/src/main/res/drawable/readme_pomodoro_2.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_pomodoro_3.jpg b/android/src/main/res/drawable/readme_pomodoro_3.jpg deleted file mode 100644 index ab910a84a..000000000 Binary files a/android/src/main/res/drawable/readme_pomodoro_3.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_repeating_practice_1.jpg b/android/src/main/res/drawable/readme_repeating_practice_1.jpg deleted file mode 100644 index f9405cdc3..000000000 Binary files a/android/src/main/res/drawable/readme_repeating_practice_1.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_repeating_practice_2.jpg b/android/src/main/res/drawable/readme_repeating_practice_2.jpg deleted file mode 100644 index c5c7f8f42..000000000 Binary files a/android/src/main/res/drawable/readme_repeating_practice_2.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_repeating_practice_3.jpg b/android/src/main/res/drawable/readme_repeating_practice_3.jpg deleted file mode 100644 index 1ba130462..000000000 Binary files a/android/src/main/res/drawable/readme_repeating_practice_3.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_repeating_practice_4.jpg b/android/src/main/res/drawable/readme_repeating_practice_4.jpg deleted file mode 100644 index fc444aac7..000000000 Binary files a/android/src/main/res/drawable/readme_repeating_practice_4.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_repeating_practice_5.jpg b/android/src/main/res/drawable/readme_repeating_practice_5.jpg deleted file mode 100644 index 1ce189308..000000000 Binary files a/android/src/main/res/drawable/readme_repeating_practice_5.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_repeatings_1.jpg b/android/src/main/res/drawable/readme_repeatings_1.jpg deleted file mode 100644 index 1ff6cc668..000000000 Binary files a/android/src/main/res/drawable/readme_repeatings_1.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_repeatings_2.jpg b/android/src/main/res/drawable/readme_repeatings_2.jpg deleted file mode 100644 index 0a02120b3..000000000 Binary files a/android/src/main/res/drawable/readme_repeatings_2.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_timer.png b/android/src/main/res/drawable/readme_timer.png deleted file mode 100644 index 87d5c6837..000000000 Binary files a/android/src/main/res/drawable/readme_timer.png and /dev/null differ diff --git a/android/src/main/res/drawable/readme_timer_1.jpg b/android/src/main/res/drawable/readme_timer_1.jpg deleted file mode 100644 index 9d5196ed1..000000000 Binary files a/android/src/main/res/drawable/readme_timer_1.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_timer_practice_1.jpg b/android/src/main/res/drawable/readme_timer_practice_1.jpg deleted file mode 100644 index 55a67637e..000000000 Binary files a/android/src/main/res/drawable/readme_timer_practice_1.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_timer_practice_2.jpg b/android/src/main/res/drawable/readme_timer_practice_2.jpg deleted file mode 100644 index 06d60f034..000000000 Binary files a/android/src/main/res/drawable/readme_timer_practice_2.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_timer_practice_3.jpg b/android/src/main/res/drawable/readme_timer_practice_3.jpg deleted file mode 100644 index 41d80bb47..000000000 Binary files a/android/src/main/res/drawable/readme_timer_practice_3.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_timer_practice_4.jpg b/android/src/main/res/drawable/readme_timer_practice_4.jpg deleted file mode 100644 index ba45958e9..000000000 Binary files a/android/src/main/res/drawable/readme_timer_practice_4.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/readme_timer_practice_5.jpg b/android/src/main/res/drawable/readme_timer_practice_5.jpg deleted file mode 100644 index fa1038caf..000000000 Binary files a/android/src/main/res/drawable/readme_timer_practice_5.jpg and /dev/null differ diff --git a/android/src/main/res/drawable/sf_calendar_medium_regular.xml b/android/src/main/res/drawable/sf_calendar_medium_regular.xml new file mode 100644 index 000000000..7f5b1be9b --- /dev/null +++ b/android/src/main/res/drawable/sf_calendar_medium_regular.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/sf_chevron_down_medium_bold.xml b/android/src/main/res/drawable/sf_chevron_down_medium_bold.xml deleted file mode 100644 index b3758c91f..000000000 --- a/android/src/main/res/drawable/sf_chevron_down_medium_bold.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/src/main/res/drawable/sf_house_medium_semibold.xml b/android/src/main/res/drawable/sf_house_medium_semibold.xml deleted file mode 100644 index 824dcaae0..000000000 --- a/android/src/main/res/drawable/sf_house_medium_semibold.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/apple/iphone/Assets.xcassets/doc_activities_evening.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_activities_evening.imageset/Contents.json new file mode 100644 index 000000000..a14ba0393 --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_activities_evening.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_activities_evening.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_activities_evening.imageset/doc_activities_evening.png b/apple/iphone/Assets.xcassets/doc_activities_evening.imageset/doc_activities_evening.png new file mode 100644 index 000000000..ca574d5fa Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_activities_evening.imageset/doc_activities_evening.png differ diff --git a/apple/iphone/Assets.xcassets/doc_activities_morning.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_activities_morning.imageset/Contents.json new file mode 100644 index 000000000..28cd4097c --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_activities_morning.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_activities_morning.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_activities_morning.imageset/doc_activities_morning.png b/apple/iphone/Assets.xcassets/doc_activities_morning.imageset/doc_activities_morning.png new file mode 100644 index 000000000..fb5ff60b3 Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_activities_morning.imageset/doc_activities_morning.png differ diff --git a/apple/iphone/Assets.xcassets/readme_activities_1.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_calendar_button.imageset/Contents.json similarity index 85% rename from apple/iphone/Assets.xcassets/readme_activities_1.imageset/Contents.json rename to apple/iphone/Assets.xcassets/doc_calendar_button.imageset/Contents.json index 8deb58375..099174ff5 100644 --- a/apple/iphone/Assets.xcassets/readme_activities_1.imageset/Contents.json +++ b/apple/iphone/Assets.xcassets/doc_calendar_button.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "readme_activities_1.jpg", + "filename" : "doc_calendar_button.png", "idiom" : "universal", "scale" : "1x" }, diff --git a/apple/iphone/Assets.xcassets/doc_calendar_button.imageset/doc_calendar_button.png b/apple/iphone/Assets.xcassets/doc_calendar_button.imageset/doc_calendar_button.png new file mode 100644 index 000000000..501d59ed2 Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_calendar_button.imageset/doc_calendar_button.png differ diff --git a/apple/iphone/Assets.xcassets/doc_calendar_screen.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_calendar_screen.imageset/Contents.json new file mode 100644 index 000000000..586124e75 --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_calendar_screen.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_calendar_screen.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_calendar_screen.imageset/doc_calendar_screen.png b/apple/iphone/Assets.xcassets/doc_calendar_screen.imageset/doc_calendar_screen.png new file mode 100644 index 000000000..2a3ac4647 Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_calendar_screen.imageset/doc_calendar_screen.png differ diff --git a/apple/iphone/Assets.xcassets/doc_folders_example.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_folders_example.imageset/Contents.json new file mode 100644 index 000000000..1da480000 --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_folders_example.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_folders_example.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_folders_example.imageset/doc_folders_example.png b/apple/iphone/Assets.xcassets/doc_folders_example.imageset/doc_folders_example.png new file mode 100644 index 000000000..139042161 Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_folders_example.imageset/doc_folders_example.png differ diff --git a/apple/iphone/Assets.xcassets/readme_calendar_1.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_folders_swipe.imageset/Contents.json similarity index 86% rename from apple/iphone/Assets.xcassets/readme_calendar_1.imageset/Contents.json rename to apple/iphone/Assets.xcassets/doc_folders_swipe.imageset/Contents.json index 14919e289..e6ccc85bc 100644 --- a/apple/iphone/Assets.xcassets/readme_calendar_1.imageset/Contents.json +++ b/apple/iphone/Assets.xcassets/doc_folders_swipe.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "readme_calendar_1.jpg", + "filename" : "doc_folders_swipe.png", "idiom" : "universal", "scale" : "1x" }, diff --git a/apple/iphone/Assets.xcassets/doc_folders_swipe.imageset/doc_folders_swipe.png b/apple/iphone/Assets.xcassets/doc_folders_swipe.imageset/doc_folders_swipe.png new file mode 100644 index 000000000..b37ea0d03 Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_folders_swipe.imageset/doc_folders_swipe.png differ diff --git a/apple/iphone/Assets.xcassets/doc_folders_tomorrow.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_folders_tomorrow.imageset/Contents.json new file mode 100644 index 000000000..12081af6d --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_folders_tomorrow.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_folders_tomorrow.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_folders_tomorrow.imageset/doc_folders_tomorrow.png b/apple/iphone/Assets.xcassets/doc_folders_tomorrow.imageset/doc_folders_tomorrow.png new file mode 100644 index 000000000..0865e2add Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_folders_tomorrow.imageset/doc_folders_tomorrow.png differ diff --git a/apple/iphone/Assets.xcassets/doc_free_time_form.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_free_time_form.imageset/Contents.json new file mode 100644 index 000000000..7a4c52ef2 --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_free_time_form.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_free_time_form.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_free_time_form.imageset/doc_free_time_form.png b/apple/iphone/Assets.xcassets/doc_free_time_form.imageset/doc_free_time_form.png new file mode 100644 index 000000000..abf2d250a Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_free_time_form.imageset/doc_free_time_form.png differ diff --git a/apple/iphone/Assets.xcassets/doc_free_time_start.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_free_time_start.imageset/Contents.json new file mode 100644 index 000000000..643f02ab6 --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_free_time_start.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_free_time_start.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_free_time_start.imageset/doc_free_time_start.png b/apple/iphone/Assets.xcassets/doc_free_time_start.imageset/doc_free_time_start.png new file mode 100644 index 000000000..693f17961 Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_free_time_start.imageset/doc_free_time_start.png differ diff --git a/apple/iphone/Assets.xcassets/doc_morning_completed.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_morning_completed.imageset/Contents.json new file mode 100644 index 000000000..065120529 --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_morning_completed.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_morning_completed.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_morning_completed.imageset/doc_morning_completed.png b/apple/iphone/Assets.xcassets/doc_morning_completed.imageset/doc_morning_completed.png new file mode 100644 index 000000000..78e94b6da Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_morning_completed.imageset/doc_morning_completed.png differ diff --git a/apple/iphone/Assets.xcassets/readme_checklist.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_morning_form.imageset/Contents.json similarity index 86% rename from apple/iphone/Assets.xcassets/readme_checklist.imageset/Contents.json rename to apple/iphone/Assets.xcassets/doc_morning_form.imageset/Contents.json index 93cb25b9c..64b35a8a4 100644 --- a/apple/iphone/Assets.xcassets/readme_checklist.imageset/Contents.json +++ b/apple/iphone/Assets.xcassets/doc_morning_form.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "readme_checklist.png", + "filename" : "doc_morning_form.png", "idiom" : "universal", "scale" : "1x" }, diff --git a/apple/iphone/Assets.xcassets/doc_morning_form.imageset/doc_morning_form.png b/apple/iphone/Assets.xcassets/doc_morning_form.imageset/doc_morning_form.png new file mode 100644 index 000000000..382bcfae1 Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_morning_form.imageset/doc_morning_form.png differ diff --git a/apple/iphone/Assets.xcassets/readme_calendar_2.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_morning_start.imageset/Contents.json similarity index 86% rename from apple/iphone/Assets.xcassets/readme_calendar_2.imageset/Contents.json rename to apple/iphone/Assets.xcassets/doc_morning_start.imageset/Contents.json index eb108ee82..027e99e04 100644 --- a/apple/iphone/Assets.xcassets/readme_calendar_2.imageset/Contents.json +++ b/apple/iphone/Assets.xcassets/doc_morning_start.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "readme_calendar_2.jpg", + "filename" : "doc_morning_start.png", "idiom" : "universal", "scale" : "1x" }, diff --git a/apple/iphone/Assets.xcassets/doc_morning_start.imageset/doc_morning_start.png b/apple/iphone/Assets.xcassets/doc_morning_start.imageset/doc_morning_start.png new file mode 100644 index 000000000..38d0544ee Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_morning_start.imageset/doc_morning_start.png differ diff --git a/apple/iphone/Assets.xcassets/readme_chart_2.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_music_form.imageset/Contents.json similarity index 86% rename from apple/iphone/Assets.xcassets/readme_chart_2.imageset/Contents.json rename to apple/iphone/Assets.xcassets/doc_music_form.imageset/Contents.json index f4e32fe44..486edef68 100644 --- a/apple/iphone/Assets.xcassets/readme_chart_2.imageset/Contents.json +++ b/apple/iphone/Assets.xcassets/doc_music_form.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "readme_chart_2.jpg", + "filename" : "doc_music_form.png", "idiom" : "universal", "scale" : "1x" }, diff --git a/apple/iphone/Assets.xcassets/doc_music_form.imageset/doc_music_form.png b/apple/iphone/Assets.xcassets/doc_music_form.imageset/doc_music_form.png new file mode 100644 index 000000000..954bfef88 Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_music_form.imageset/doc_music_form.png differ diff --git a/apple/iphone/Assets.xcassets/doc_music_progress.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_music_progress.imageset/Contents.json new file mode 100644 index 000000000..43b410d65 --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_music_progress.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_music_progress.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_music_progress.imageset/doc_music_progress.png b/apple/iphone/Assets.xcassets/doc_music_progress.imageset/doc_music_progress.png new file mode 100644 index 000000000..e86ba1749 Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_music_progress.imageset/doc_music_progress.png differ diff --git a/apple/iphone/Assets.xcassets/doc_reading_form.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_reading_form.imageset/Contents.json new file mode 100644 index 000000000..5d21c96c7 --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_reading_form.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_reading_form.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_reading_form.imageset/doc_reading_form.png b/apple/iphone/Assets.xcassets/doc_reading_form.imageset/doc_reading_form.png new file mode 100644 index 000000000..e68eb9d87 Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_reading_form.imageset/doc_reading_form.png differ diff --git a/apple/iphone/Assets.xcassets/doc_reading_progress.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_reading_progress.imageset/Contents.json new file mode 100644 index 000000000..8f46de03b --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_reading_progress.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_reading_progress.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_reading_progress.imageset/doc_reading_progress.png b/apple/iphone/Assets.xcassets/doc_reading_progress.imageset/doc_reading_progress.png new file mode 100644 index 000000000..99aeac1ec Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_reading_progress.imageset/doc_reading_progress.png differ diff --git a/apple/iphone/Assets.xcassets/doc_repeating_form_1.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_repeating_form_1.imageset/Contents.json new file mode 100644 index 000000000..744b3bf48 --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_repeating_form_1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_repeating_form_1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_repeating_form_1.imageset/doc_repeating_form_1.png b/apple/iphone/Assets.xcassets/doc_repeating_form_1.imageset/doc_repeating_form_1.png new file mode 100644 index 000000000..61758e34f Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_repeating_form_1.imageset/doc_repeating_form_1.png differ diff --git a/apple/iphone/Assets.xcassets/doc_repeating_form_2.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_repeating_form_2.imageset/Contents.json new file mode 100644 index 000000000..cf57df0c8 --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_repeating_form_2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_repeating_form_2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_repeating_form_2.imageset/doc_repeating_form_2.png b/apple/iphone/Assets.xcassets/doc_repeating_form_2.imageset/doc_repeating_form_2.png new file mode 100644 index 000000000..7168d1cf7 Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_repeating_form_2.imageset/doc_repeating_form_2.png differ diff --git a/apple/iphone/Assets.xcassets/readme_chart_3.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_sleep_form.imageset/Contents.json similarity index 86% rename from apple/iphone/Assets.xcassets/readme_chart_3.imageset/Contents.json rename to apple/iphone/Assets.xcassets/doc_sleep_form.imageset/Contents.json index 27d5ed201..f312424ae 100644 --- a/apple/iphone/Assets.xcassets/readme_chart_3.imageset/Contents.json +++ b/apple/iphone/Assets.xcassets/doc_sleep_form.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "readme_chart_3.jpg", + "filename" : "doc_sleep_form.png", "idiom" : "universal", "scale" : "1x" }, diff --git a/apple/iphone/Assets.xcassets/doc_sleep_form.imageset/doc_sleep_form.png b/apple/iphone/Assets.xcassets/doc_sleep_form.imageset/doc_sleep_form.png new file mode 100644 index 000000000..0b9782ab9 Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_sleep_form.imageset/doc_sleep_form.png differ diff --git a/apple/iphone/Assets.xcassets/doc_sleep_start.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_sleep_start.imageset/Contents.json new file mode 100644 index 000000000..6ceac004e --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_sleep_start.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_sleep_start.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_sleep_start.imageset/doc_sleep_start.png b/apple/iphone/Assets.xcassets/doc_sleep_start.imageset/doc_sleep_start.png new file mode 100644 index 000000000..29eaad0c0 Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_sleep_start.imageset/doc_sleep_start.png differ diff --git a/apple/iphone/Assets.xcassets/doc_small_tasks_form.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_small_tasks_form.imageset/Contents.json new file mode 100644 index 000000000..c7b3ef65b --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_small_tasks_form.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_small_tasks_form.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_small_tasks_form.imageset/doc_small_tasks_form.png b/apple/iphone/Assets.xcassets/doc_small_tasks_form.imageset/doc_small_tasks_form.png new file mode 100644 index 000000000..39274ef4a Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_small_tasks_form.imageset/doc_small_tasks_form.png differ diff --git a/apple/iphone/Assets.xcassets/doc_small_tasks_progress.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_small_tasks_progress.imageset/Contents.json new file mode 100644 index 000000000..ef66f0f83 --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_small_tasks_progress.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_small_tasks_progress.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_small_tasks_progress.imageset/doc_small_tasks_progress.png b/apple/iphone/Assets.xcassets/doc_small_tasks_progress.imageset/doc_small_tasks_progress.png new file mode 100644 index 000000000..a769d9e7a Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_small_tasks_progress.imageset/doc_small_tasks_progress.png differ diff --git a/apple/iphone/Assets.xcassets/doc_tasks_delete.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_tasks_delete.imageset/Contents.json new file mode 100644 index 000000000..7d0d7c2c0 --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_tasks_delete.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_tasks_delete.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_tasks_delete.imageset/doc_tasks_delete.png b/apple/iphone/Assets.xcassets/doc_tasks_delete.imageset/doc_tasks_delete.png new file mode 100644 index 000000000..c91558a6b Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_tasks_delete.imageset/doc_tasks_delete.png differ diff --git a/apple/iphone/Assets.xcassets/doc_tasks_example1.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_tasks_example1.imageset/Contents.json new file mode 100644 index 000000000..b6d872054 --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_tasks_example1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_tasks_example1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_tasks_example1.imageset/doc_tasks_example1.png b/apple/iphone/Assets.xcassets/doc_tasks_example1.imageset/doc_tasks_example1.png new file mode 100644 index 000000000..682f7a9a0 Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_tasks_example1.imageset/doc_tasks_example1.png differ diff --git a/apple/iphone/Assets.xcassets/doc_tasks_field.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_tasks_field.imageset/Contents.json new file mode 100644 index 000000000..0359d81b3 --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_tasks_field.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_tasks_field.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_tasks_field.imageset/doc_tasks_field.png b/apple/iphone/Assets.xcassets/doc_tasks_field.imageset/doc_tasks_field.png new file mode 100644 index 000000000..4ee0bfe2c Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_tasks_field.imageset/doc_tasks_field.png differ diff --git a/apple/iphone/Assets.xcassets/readme_chart_1.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_tasks_form.imageset/Contents.json similarity index 86% rename from apple/iphone/Assets.xcassets/readme_chart_1.imageset/Contents.json rename to apple/iphone/Assets.xcassets/doc_tasks_form.imageset/Contents.json index c79e2f93d..a14ef665d 100644 --- a/apple/iphone/Assets.xcassets/readme_chart_1.imageset/Contents.json +++ b/apple/iphone/Assets.xcassets/doc_tasks_form.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "readme_chart_1.jpg", + "filename" : "doc_tasks_form.png", "idiom" : "universal", "scale" : "1x" }, diff --git a/apple/iphone/Assets.xcassets/doc_tasks_form.imageset/doc_tasks_form.png b/apple/iphone/Assets.xcassets/doc_tasks_form.imageset/doc_tasks_form.png new file mode 100644 index 000000000..28a38b00a Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_tasks_form.imageset/doc_tasks_form.png differ diff --git a/apple/iphone/Assets.xcassets/doc_tasks_started.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_tasks_started.imageset/Contents.json new file mode 100644 index 000000000..568996642 --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_tasks_started.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_tasks_started.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_tasks_started.imageset/doc_tasks_started.png b/apple/iphone/Assets.xcassets/doc_tasks_started.imageset/doc_tasks_started.png new file mode 100644 index 000000000..d8c9fd0e9 Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_tasks_started.imageset/doc_tasks_started.png differ diff --git a/apple/iphone/Assets.xcassets/doc_timer_summary.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_timer_summary.imageset/Contents.json new file mode 100644 index 000000000..fda9a9279 --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_timer_summary.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_timer_summary.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_timer_summary.imageset/doc_timer_summary.png b/apple/iphone/Assets.xcassets/doc_timer_summary.imageset/doc_timer_summary.png new file mode 100644 index 000000000..5abfafe3d Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_timer_summary.imageset/doc_timer_summary.png differ diff --git a/apple/iphone/Assets.xcassets/doc_timetome_break.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_timetome_break.imageset/Contents.json new file mode 100644 index 000000000..c2891eaac --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_timetome_break.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_timetome_break.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_timetome_break.imageset/doc_timetome_break.png b/apple/iphone/Assets.xcassets/doc_timetome_break.imageset/doc_timetome_break.png new file mode 100644 index 000000000..f7e69f7a8 Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_timetome_break.imageset/doc_timetome_break.png differ diff --git a/apple/iphone/Assets.xcassets/doc_timetome_form.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_timetome_form.imageset/Contents.json new file mode 100644 index 000000000..db023bf0c --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_timetome_form.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_timetome_form.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_timetome_form.imageset/doc_timetome_form.png b/apple/iphone/Assets.xcassets/doc_timetome_form.imageset/doc_timetome_form.png new file mode 100644 index 000000000..38135e023 Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_timetome_form.imageset/doc_timetome_form.png differ diff --git a/apple/iphone/Assets.xcassets/doc_timetome_overdue.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_timetome_overdue.imageset/Contents.json new file mode 100644 index 000000000..b22928ba8 --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_timetome_overdue.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_timetome_overdue.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_timetome_overdue.imageset/doc_timetome_overdue.png b/apple/iphone/Assets.xcassets/doc_timetome_overdue.imageset/doc_timetome_overdue.png new file mode 100644 index 000000000..a5be1ce7e Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_timetome_overdue.imageset/doc_timetome_overdue.png differ diff --git a/apple/iphone/Assets.xcassets/doc_timetome_start.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_timetome_start.imageset/Contents.json new file mode 100644 index 000000000..1c0172a49 --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_timetome_start.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_timetome_start.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_timetome_start.imageset/doc_timetome_start.png b/apple/iphone/Assets.xcassets/doc_timetome_start.imageset/doc_timetome_start.png new file mode 100644 index 000000000..d19a9f86d Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_timetome_start.imageset/doc_timetome_start.png differ diff --git a/apple/iphone/Assets.xcassets/readme_timer.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_work_form.imageset/Contents.json similarity index 87% rename from apple/iphone/Assets.xcassets/readme_timer.imageset/Contents.json rename to apple/iphone/Assets.xcassets/doc_work_form.imageset/Contents.json index 2a7e82545..13bf15845 100644 --- a/apple/iphone/Assets.xcassets/readme_timer.imageset/Contents.json +++ b/apple/iphone/Assets.xcassets/doc_work_form.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "readme_timer.png", + "filename" : "doc_work_form.png", "idiom" : "universal", "scale" : "1x" }, diff --git a/apple/iphone/Assets.xcassets/doc_work_form.imageset/doc_work_form.png b/apple/iphone/Assets.xcassets/doc_work_form.imageset/doc_work_form.png new file mode 100644 index 000000000..6b29e1908 Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_work_form.imageset/doc_work_form.png differ diff --git a/apple/iphone/Assets.xcassets/doc_work_history.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_work_history.imageset/Contents.json new file mode 100644 index 000000000..e1804aa76 --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_work_history.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_work_history.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_work_history.imageset/doc_work_history.png b/apple/iphone/Assets.xcassets/doc_work_history.imageset/doc_work_history.png new file mode 100644 index 000000000..54a4fee17 Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_work_history.imageset/doc_work_history.png differ diff --git a/apple/iphone/Assets.xcassets/readme_goals.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_work_note.imageset/Contents.json similarity index 87% rename from apple/iphone/Assets.xcassets/readme_goals.imageset/Contents.json rename to apple/iphone/Assets.xcassets/doc_work_note.imageset/Contents.json index 681ba9214..f4b7ece9f 100644 --- a/apple/iphone/Assets.xcassets/readme_goals.imageset/Contents.json +++ b/apple/iphone/Assets.xcassets/doc_work_note.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "readme_goals.png", + "filename" : "doc_work_note.png", "idiom" : "universal", "scale" : "1x" }, diff --git a/apple/iphone/Assets.xcassets/doc_work_note.imageset/doc_work_note.png b/apple/iphone/Assets.xcassets/doc_work_note.imageset/doc_work_note.png new file mode 100644 index 000000000..95c91226d Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_work_note.imageset/doc_work_note.png differ diff --git a/apple/iphone/Assets.xcassets/doc_workout_form.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_workout_form.imageset/Contents.json new file mode 100644 index 000000000..41bb5041e --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_workout_form.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_workout_form.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_workout_form.imageset/doc_workout_form.png b/apple/iphone/Assets.xcassets/doc_workout_form.imageset/doc_workout_form.png new file mode 100644 index 000000000..e9428a349 Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_workout_form.imageset/doc_workout_form.png differ diff --git a/apple/iphone/Assets.xcassets/doc_workout_start.imageset/Contents.json b/apple/iphone/Assets.xcassets/doc_workout_start.imageset/Contents.json new file mode 100644 index 000000000..1df47dda8 --- /dev/null +++ b/apple/iphone/Assets.xcassets/doc_workout_start.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "doc_workout_start.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/iphone/Assets.xcassets/doc_workout_start.imageset/doc_workout_start.png b/apple/iphone/Assets.xcassets/doc_workout_start.imageset/doc_workout_start.png new file mode 100644 index 000000000..00d53b68f Binary files /dev/null and b/apple/iphone/Assets.xcassets/doc_workout_start.imageset/doc_workout_start.png differ diff --git a/apple/iphone/Assets.xcassets/readme_activities_1.imageset/readme_activities_1.jpg b/apple/iphone/Assets.xcassets/readme_activities_1.imageset/readme_activities_1.jpg deleted file mode 100644 index 8cceb0bb0..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_activities_1.imageset/readme_activities_1.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_calendar_1.imageset/readme_calendar_1.jpg b/apple/iphone/Assets.xcassets/readme_calendar_1.imageset/readme_calendar_1.jpg deleted file mode 100644 index 25e263d87..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_calendar_1.imageset/readme_calendar_1.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_calendar_2.imageset/readme_calendar_2.jpg b/apple/iphone/Assets.xcassets/readme_calendar_2.imageset/readme_calendar_2.jpg deleted file mode 100644 index d65768978..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_calendar_2.imageset/readme_calendar_2.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_chart_1.imageset/readme_chart_1.jpg b/apple/iphone/Assets.xcassets/readme_chart_1.imageset/readme_chart_1.jpg deleted file mode 100644 index 8f702ef95..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_chart_1.imageset/readme_chart_1.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_chart_2.imageset/readme_chart_2.jpg b/apple/iphone/Assets.xcassets/readme_chart_2.imageset/readme_chart_2.jpg deleted file mode 100644 index 9120832f9..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_chart_2.imageset/readme_chart_2.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_chart_3.imageset/readme_chart_3.jpg b/apple/iphone/Assets.xcassets/readme_chart_3.imageset/readme_chart_3.jpg deleted file mode 100644 index 3d5b9ba07..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_chart_3.imageset/readme_chart_3.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_checklist.imageset/readme_checklist.png b/apple/iphone/Assets.xcassets/readme_checklist.imageset/readme_checklist.png deleted file mode 100644 index 4a36cd795..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_checklist.imageset/readme_checklist.png and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_checklists_1.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_checklists_1.imageset/Contents.json deleted file mode 100644 index 898f34a39..000000000 --- a/apple/iphone/Assets.xcassets/readme_checklists_1.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_checklists_1.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_checklists_1.imageset/readme_checklists_1.jpg b/apple/iphone/Assets.xcassets/readme_checklists_1.imageset/readme_checklists_1.jpg deleted file mode 100644 index 070918d57..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_checklists_1.imageset/readme_checklists_1.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_checklists_2.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_checklists_2.imageset/Contents.json deleted file mode 100644 index 1b234f384..000000000 --- a/apple/iphone/Assets.xcassets/readme_checklists_2.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_checklists_2.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_checklists_2.imageset/readme_checklists_2.jpg b/apple/iphone/Assets.xcassets/readme_checklists_2.imageset/readme_checklists_2.jpg deleted file mode 100644 index c34c64d3c..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_checklists_2.imageset/readme_checklists_2.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_checklists_3.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_checklists_3.imageset/Contents.json deleted file mode 100644 index 101169837..000000000 --- a/apple/iphone/Assets.xcassets/readme_checklists_3.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_checklists_3.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_checklists_3.imageset/readme_checklists_3.jpg b/apple/iphone/Assets.xcassets/readme_checklists_3.imageset/readme_checklists_3.jpg deleted file mode 100644 index c652d0527..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_checklists_3.imageset/readme_checklists_3.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_checklists_practice_1.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_checklists_practice_1.imageset/Contents.json deleted file mode 100644 index f52b55256..000000000 --- a/apple/iphone/Assets.xcassets/readme_checklists_practice_1.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_checklists_practice_1.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_checklists_practice_1.imageset/readme_checklists_practice_1.jpg b/apple/iphone/Assets.xcassets/readme_checklists_practice_1.imageset/readme_checklists_practice_1.jpg deleted file mode 100644 index 85617d72c..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_checklists_practice_1.imageset/readme_checklists_practice_1.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_checklists_practice_2.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_checklists_practice_2.imageset/Contents.json deleted file mode 100644 index a8d869bd8..000000000 --- a/apple/iphone/Assets.xcassets/readme_checklists_practice_2.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_checklists_practice_2.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_checklists_practice_2.imageset/readme_checklists_practice_2.jpg b/apple/iphone/Assets.xcassets/readme_checklists_practice_2.imageset/readme_checklists_practice_2.jpg deleted file mode 100644 index 7c9b5a4a0..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_checklists_practice_2.imageset/readme_checklists_practice_2.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_checklists_practice_3.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_checklists_practice_3.imageset/Contents.json deleted file mode 100644 index 9dcfd3fe0..000000000 --- a/apple/iphone/Assets.xcassets/readme_checklists_practice_3.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_checklists_practice_3.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_checklists_practice_3.imageset/readme_checklists_practice_3.jpg b/apple/iphone/Assets.xcassets/readme_checklists_practice_3.imageset/readme_checklists_practice_3.jpg deleted file mode 100644 index c4ebbeb64..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_checklists_practice_3.imageset/readme_checklists_practice_3.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_checklists_practice_4.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_checklists_practice_4.imageset/Contents.json deleted file mode 100644 index b86f36278..000000000 --- a/apple/iphone/Assets.xcassets/readme_checklists_practice_4.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_checklists_practice_4.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_checklists_practice_4.imageset/readme_checklists_practice_4.jpg b/apple/iphone/Assets.xcassets/readme_checklists_practice_4.imageset/readme_checklists_practice_4.jpg deleted file mode 100644 index de4b08d06..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_checklists_practice_4.imageset/readme_checklists_practice_4.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_checklists_practice_5.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_checklists_practice_5.imageset/Contents.json deleted file mode 100644 index 16f87605d..000000000 --- a/apple/iphone/Assets.xcassets/readme_checklists_practice_5.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_checklists_practice_5.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_checklists_practice_5.imageset/readme_checklists_practice_5.jpg b/apple/iphone/Assets.xcassets/readme_checklists_practice_5.imageset/readme_checklists_practice_5.jpg deleted file mode 100644 index d73da688a..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_checklists_practice_5.imageset/readme_checklists_practice_5.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_checklists_practice_6.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_checklists_practice_6.imageset/Contents.json deleted file mode 100644 index a0b87bb09..000000000 --- a/apple/iphone/Assets.xcassets/readme_checklists_practice_6.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_checklists_practice_6.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_checklists_practice_6.imageset/readme_checklists_practice_6.jpg b/apple/iphone/Assets.xcassets/readme_checklists_practice_6.imageset/readme_checklists_practice_6.jpg deleted file mode 100644 index 540a7854e..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_checklists_practice_6.imageset/readme_checklists_practice_6.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_checklists_practice_7.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_checklists_practice_7.imageset/Contents.json deleted file mode 100644 index 8ec419f91..000000000 --- a/apple/iphone/Assets.xcassets/readme_checklists_practice_7.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_checklists_practice_7.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_checklists_practice_7.imageset/readme_checklists_practice_7.jpg b/apple/iphone/Assets.xcassets/readme_checklists_practice_7.imageset/readme_checklists_practice_7.jpg deleted file mode 100644 index d785a1352..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_checklists_practice_7.imageset/readme_checklists_practice_7.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_checklists_practice_8.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_checklists_practice_8.imageset/Contents.json deleted file mode 100644 index e92c11242..000000000 --- a/apple/iphone/Assets.xcassets/readme_checklists_practice_8.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_checklists_practice_8.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_checklists_practice_8.imageset/readme_checklists_practice_8.jpg b/apple/iphone/Assets.xcassets/readme_checklists_practice_8.imageset/readme_checklists_practice_8.jpg deleted file mode 100644 index d20df4f16..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_checklists_practice_8.imageset/readme_checklists_practice_8.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_checklists_practice_9.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_checklists_practice_9.imageset/Contents.json deleted file mode 100644 index 2b07b5602..000000000 --- a/apple/iphone/Assets.xcassets/readme_checklists_practice_9.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_checklists_practice_9.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_checklists_practice_9.imageset/readme_checklists_practice_9.jpg b/apple/iphone/Assets.xcassets/readme_checklists_practice_9.imageset/readme_checklists_practice_9.jpg deleted file mode 100644 index b4e7e9e7a..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_checklists_practice_9.imageset/readme_checklists_practice_9.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_goals.imageset/readme_goals.png b/apple/iphone/Assets.xcassets/readme_goals.imageset/readme_goals.png deleted file mode 100644 index f0bbad3eb..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_goals.imageset/readme_goals.png and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_goals_1.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_goals_1.imageset/Contents.json deleted file mode 100644 index 8f24f1975..000000000 --- a/apple/iphone/Assets.xcassets/readme_goals_1.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_goals_1.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_goals_1.imageset/readme_goals_1.jpg b/apple/iphone/Assets.xcassets/readme_goals_1.imageset/readme_goals_1.jpg deleted file mode 100644 index 8f67d0b19..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_goals_1.imageset/readme_goals_1.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_pomodoro_1.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_pomodoro_1.imageset/Contents.json deleted file mode 100644 index 4574c2db4..000000000 --- a/apple/iphone/Assets.xcassets/readme_pomodoro_1.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_pomodoro_1.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_pomodoro_1.imageset/readme_pomodoro_1.jpg b/apple/iphone/Assets.xcassets/readme_pomodoro_1.imageset/readme_pomodoro_1.jpg deleted file mode 100644 index 410b35874..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_pomodoro_1.imageset/readme_pomodoro_1.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_pomodoro_2.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_pomodoro_2.imageset/Contents.json deleted file mode 100644 index 61544eb65..000000000 --- a/apple/iphone/Assets.xcassets/readme_pomodoro_2.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_pomodoro_2.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_pomodoro_2.imageset/readme_pomodoro_2.jpg b/apple/iphone/Assets.xcassets/readme_pomodoro_2.imageset/readme_pomodoro_2.jpg deleted file mode 100644 index 3857a79f0..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_pomodoro_2.imageset/readme_pomodoro_2.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_pomodoro_3.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_pomodoro_3.imageset/Contents.json deleted file mode 100644 index 96c412557..000000000 --- a/apple/iphone/Assets.xcassets/readme_pomodoro_3.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_pomodoro_3.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_pomodoro_3.imageset/readme_pomodoro_3.jpg b/apple/iphone/Assets.xcassets/readme_pomodoro_3.imageset/readme_pomodoro_3.jpg deleted file mode 100644 index ab910a84a..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_pomodoro_3.imageset/readme_pomodoro_3.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_repeating_practice_1.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_repeating_practice_1.imageset/Contents.json deleted file mode 100644 index 52a68314b..000000000 --- a/apple/iphone/Assets.xcassets/readme_repeating_practice_1.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_repeating_practice_1.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_repeating_practice_1.imageset/readme_repeating_practice_1.jpg b/apple/iphone/Assets.xcassets/readme_repeating_practice_1.imageset/readme_repeating_practice_1.jpg deleted file mode 100644 index f9405cdc3..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_repeating_practice_1.imageset/readme_repeating_practice_1.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_repeating_practice_2.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_repeating_practice_2.imageset/Contents.json deleted file mode 100644 index 85670c545..000000000 --- a/apple/iphone/Assets.xcassets/readme_repeating_practice_2.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_repeating_practice_2.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_repeating_practice_2.imageset/readme_repeating_practice_2.jpg b/apple/iphone/Assets.xcassets/readme_repeating_practice_2.imageset/readme_repeating_practice_2.jpg deleted file mode 100644 index c5c7f8f42..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_repeating_practice_2.imageset/readme_repeating_practice_2.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_repeating_practice_3.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_repeating_practice_3.imageset/Contents.json deleted file mode 100644 index 0c7c73c74..000000000 --- a/apple/iphone/Assets.xcassets/readme_repeating_practice_3.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_repeating_practice_3.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_repeating_practice_3.imageset/readme_repeating_practice_3.jpg b/apple/iphone/Assets.xcassets/readme_repeating_practice_3.imageset/readme_repeating_practice_3.jpg deleted file mode 100644 index 1ba130462..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_repeating_practice_3.imageset/readme_repeating_practice_3.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_repeating_practice_4.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_repeating_practice_4.imageset/Contents.json deleted file mode 100644 index 12aa58ffb..000000000 --- a/apple/iphone/Assets.xcassets/readme_repeating_practice_4.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_repeating_practice_4.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_repeating_practice_4.imageset/readme_repeating_practice_4.jpg b/apple/iphone/Assets.xcassets/readme_repeating_practice_4.imageset/readme_repeating_practice_4.jpg deleted file mode 100644 index fc444aac7..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_repeating_practice_4.imageset/readme_repeating_practice_4.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_repeating_practice_5.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_repeating_practice_5.imageset/Contents.json deleted file mode 100644 index 3225ffbbe..000000000 --- a/apple/iphone/Assets.xcassets/readme_repeating_practice_5.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_repeating_practice_5.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_repeating_practice_5.imageset/readme_repeating_practice_5.jpg b/apple/iphone/Assets.xcassets/readme_repeating_practice_5.imageset/readme_repeating_practice_5.jpg deleted file mode 100644 index 1ce189308..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_repeating_practice_5.imageset/readme_repeating_practice_5.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_repeatings_1.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_repeatings_1.imageset/Contents.json deleted file mode 100644 index ade530f78..000000000 --- a/apple/iphone/Assets.xcassets/readme_repeatings_1.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_repeatings_1.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_repeatings_1.imageset/readme_repeatings_1.jpg b/apple/iphone/Assets.xcassets/readme_repeatings_1.imageset/readme_repeatings_1.jpg deleted file mode 100644 index 1ff6cc668..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_repeatings_1.imageset/readme_repeatings_1.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_repeatings_2.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_repeatings_2.imageset/Contents.json deleted file mode 100644 index 053e5f86f..000000000 --- a/apple/iphone/Assets.xcassets/readme_repeatings_2.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_repeatings_2.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_repeatings_2.imageset/readme_repeatings_2.jpg b/apple/iphone/Assets.xcassets/readme_repeatings_2.imageset/readme_repeatings_2.jpg deleted file mode 100644 index 0a02120b3..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_repeatings_2.imageset/readme_repeatings_2.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_timer.imageset/readme_timer.png b/apple/iphone/Assets.xcassets/readme_timer.imageset/readme_timer.png deleted file mode 100644 index 87d5c6837..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_timer.imageset/readme_timer.png and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_timer_1.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_timer_1.imageset/Contents.json deleted file mode 100644 index 9b96ab0d7..000000000 --- a/apple/iphone/Assets.xcassets/readme_timer_1.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_timer_1.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_timer_1.imageset/readme_timer_1.jpg b/apple/iphone/Assets.xcassets/readme_timer_1.imageset/readme_timer_1.jpg deleted file mode 100644 index 9d5196ed1..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_timer_1.imageset/readme_timer_1.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_timer_practice_1.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_timer_practice_1.imageset/Contents.json deleted file mode 100644 index 54d8565cd..000000000 --- a/apple/iphone/Assets.xcassets/readme_timer_practice_1.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_timer_practice_1.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_timer_practice_1.imageset/readme_timer_practice_1.jpg b/apple/iphone/Assets.xcassets/readme_timer_practice_1.imageset/readme_timer_practice_1.jpg deleted file mode 100644 index 55a67637e..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_timer_practice_1.imageset/readme_timer_practice_1.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_timer_practice_2.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_timer_practice_2.imageset/Contents.json deleted file mode 100644 index ba54954e2..000000000 --- a/apple/iphone/Assets.xcassets/readme_timer_practice_2.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_timer_practice_2.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_timer_practice_2.imageset/readme_timer_practice_2.jpg b/apple/iphone/Assets.xcassets/readme_timer_practice_2.imageset/readme_timer_practice_2.jpg deleted file mode 100644 index 06d60f034..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_timer_practice_2.imageset/readme_timer_practice_2.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_timer_practice_3.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_timer_practice_3.imageset/Contents.json deleted file mode 100644 index 1719235eb..000000000 --- a/apple/iphone/Assets.xcassets/readme_timer_practice_3.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_timer_practice_3.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_timer_practice_3.imageset/readme_timer_practice_3.jpg b/apple/iphone/Assets.xcassets/readme_timer_practice_3.imageset/readme_timer_practice_3.jpg deleted file mode 100644 index 41d80bb47..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_timer_practice_3.imageset/readme_timer_practice_3.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_timer_practice_4.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_timer_practice_4.imageset/Contents.json deleted file mode 100644 index 77dc559d0..000000000 --- a/apple/iphone/Assets.xcassets/readme_timer_practice_4.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_timer_practice_4.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_timer_practice_4.imageset/readme_timer_practice_4.jpg b/apple/iphone/Assets.xcassets/readme_timer_practice_4.imageset/readme_timer_practice_4.jpg deleted file mode 100644 index ba45958e9..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_timer_practice_4.imageset/readme_timer_practice_4.jpg and /dev/null differ diff --git a/apple/iphone/Assets.xcassets/readme_timer_practice_5.imageset/Contents.json b/apple/iphone/Assets.xcassets/readme_timer_practice_5.imageset/Contents.json deleted file mode 100644 index 252520bc1..000000000 --- a/apple/iphone/Assets.xcassets/readme_timer_practice_5.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "readme_timer_practice_5.jpg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/apple/iphone/Assets.xcassets/readme_timer_practice_5.imageset/readme_timer_practice_5.jpg b/apple/iphone/Assets.xcassets/readme_timer_practice_5.imageset/readme_timer_practice_5.jpg deleted file mode 100644 index fa1038caf..000000000 Binary files a/apple/iphone/Assets.xcassets/readme_timer_practice_5.imageset/readme_timer_practice_5.jpg and /dev/null differ diff --git a/apple/iphone/UI/ActivityForm/ActivityFormSheet.swift b/apple/iphone/UI/ActivityForm/ActivityFormSheet.swift index 0b21adf6d..ec3b00d63 100644 --- a/apple/iphone/UI/ActivityForm/ActivityFormSheet.swift +++ b/apple/iphone/UI/ActivityForm/ActivityFormSheet.swift @@ -75,29 +75,6 @@ private struct ActivityFormSheetInner: View { .onChange(of: name) { _, newName in vm.setName(newName: newName) } - - NavigationLinkSheet( - label: { - HStack { - Text(state.emojiTitle) - .foregroundColor(.primary) - Spacer() - if let emoji = state.emoji { - FormButtonEmojiView(emoji: emoji) - } else { - Text("Not Selected") - .foregroundColor(.red) - } - } - }, - sheet: { - EmojiPickerSheet( - onDone: { emoji in - vm.setEmoji(emoji: emoji) - } - ) - } - ) } let isChecklistGoalType: Bool = state.goalTypeUi == .checklist @@ -164,28 +141,6 @@ private struct ActivityFormSheetInner: View { } } - Section { - - NavigationLinkSheet( - label: { - HStack { - Text(state.periodTitle) - Spacer() - Text(state.periodNote) - .foregroundColor(.secondary) - } - }, - sheet: { - ActivityFormPeriodSheet( - initActivityDbPeriod: state.period, - onDone: { newPeriod in - vm.setPeriod(newPeriod: newPeriod) - } - ) - } - ) - } - Section { Picker(state.timerTypeTitle, selection: $timerTypeUi) { @@ -254,6 +209,25 @@ private struct ActivityFormSheetInner: View { Section { + NavigationLinkSheet( + label: { + HStack { + Text(state.periodTitle) + Spacer() + Text(state.periodNote) + .foregroundColor(.secondary) + } + }, + sheet: { + ActivityFormPeriodSheet( + initActivityDbPeriod: state.period, + onDone: { newPeriod in + vm.setPeriod(newPeriod: newPeriod) + } + ) + } + ) + Picker(state.parentActivityTitle, selection: $parentActivityUi) { Text("None") .tag(nil as ActivityFormVm.ActivityUi?) // Support optional (nil) selection @@ -265,9 +239,32 @@ private struct ActivityFormSheetInner: View { .onChange(of: parentActivityUi) { _, newParentActivityUi in vm.setParentActivityUi(activityUi: newParentActivityUi) } - } - - Section { + + NavigationLinkSheet( + label: { + HStack { + Text(state.iconTitle) + .foregroundColor(.primary) + Spacer() + if let symbol = state.symbol { + FormButtonSymbolView( + symbol: symbol, + color: .secondary, + ) + } else { + Text("Not Selected") + .foregroundColor(.red) + } + } + }, + sheet: { + SymbolPickerSheet( + onPick: { symbol in + vm.setSymbol(symbol: symbol) + } + ) + }, + ) NavigationLinkAction( label: { @@ -292,9 +289,6 @@ private struct ActivityFormSheetInner: View { } } ) - } - - Section { Picker(state.pomodoroTitle, selection: $pomodoroTimer) { ForEach(state.pomodoroItemsUi, id: \.timer) { itemUi in diff --git a/apple/iphone/UI/Calendar/CalendarDayView.swift b/apple/iphone/UI/Calendar/CalendarDayView.swift index c6de3dbf5..5cf1ab55f 100644 --- a/apple/iphone/UI/Calendar/CalendarDayView.swift +++ b/apple/iphone/UI/Calendar/CalendarDayView.swift @@ -79,7 +79,7 @@ private struct CalendarDayViewInner: View { .padding(.horizontal, 8) } else if let repeatingItemUi = itemUi as? CalendarDayVm.ItemUiRepeatingUi { - TasksTabRepeatingsItemView( + RepeatingsListItemView( repeatingUi: repeatingItemUi.repeatingsListRepeatingUi, withTopDivider: !isFirst, ) diff --git a/apple/iphone/UI/Calendar/CalendarTabsView.swift b/apple/iphone/UI/Calendar/CalendarTabsView.swift index f6900c3e9..f9596db49 100644 --- a/apple/iphone/UI/Calendar/CalendarTabsView.swift +++ b/apple/iphone/UI/Calendar/CalendarTabsView.swift @@ -4,6 +4,8 @@ struct CalendarTabsView: View { @State private var tab: CalendarTab = .calendar + @Environment(\.dismiss) private var dismiss + var body: some View { VStack { @@ -25,6 +27,13 @@ struct CalendarTabsView: View { .padding(.horizontal, H_PADDING) } } + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Close") { + dismiss() + } + } + } } } diff --git a/apple/iphone/UI/Calendar/CalendarView.swift b/apple/iphone/UI/Calendar/CalendarView.swift index df4b75300..ece65f1fe 100644 --- a/apple/iphone/UI/Calendar/CalendarView.swift +++ b/apple/iphone/UI/Calendar/CalendarView.swift @@ -150,7 +150,6 @@ private struct CalendarViewInner: View { } } } - .padding(.horizontal, H_PADDING) .padding(.top, 6) } } diff --git a/apple/iphone/UI/Doc/DocFullScreen.swift b/apple/iphone/UI/Doc/DocFullScreen.swift new file mode 100644 index 000000000..6dd83e814 --- /dev/null +++ b/apple/iphone/UI/Doc/DocFullScreen.swift @@ -0,0 +1,1275 @@ +import SwiftUI +import shared + +struct DocFullScreen: View { + + let forceRead: Bool + + var body: some View { + VmView({ + DocVm() + }) { vm, state in + let state = vm.state.value as! DocVm.State + DocFullScreenInner( + vm: vm, + state: state, + forceRead: forceRead, + ) + } + } +} + +private struct DocFullScreenInner: View { + + let vm: DocVm + let state: DocVm.State + + let forceRead: Bool + + /// + + @Environment(\.dismiss) private var dismiss + + var body: some View { + + List { + + if forceRead { + + PView { + Text("I force you to read this guide because without it, you will not understand how to use the app.") + .forceText() + .padding(.top, 4) + } + + PView { + Text("Please DO NOT SKIP this! It will help you get started and begin improving your life.") + .forceText() + } + + PView { + Text("Good luck!") + .forceText() + } + + Divider() + .fillMaxWidth() + .frame(height: 1) + .background(.separator) + } + + PView { + Text("I built this app to manage my productivity. Here, I will ") + + Text("SHARE") + .greenSemiBold() + + Text(" my productivity system and how I use the app.") + } + + PView { + Text("My system ") + + Text("IS NOT") + .redSemiBold() + + Text(" about time tracking, ") + + Text("IS NOT") + .redSemiBold() + + Text(" about getting nice activity charts, ") + + Text("IS NOT") + .redSemiBold() + + Text(" about reducing wasted time.") + } + + PView { + Text("My system ") + + Text("IS ALL ABOUT") + .greenSemiBold() + + Text(" achieving my ") + + Text("REAL-LIFE") + .greenSemiBold() + + Text(" goals.") + } + + PView { + Text("For example, ") + + Text("I DO NOT") + .redSemiBold() + + Text(" care how much time I waste, but ") + + Text("I CARE") + .greenSemiBold() + + Text(" if I read a book every day, ") + + Text("I CARE") + .greenSemiBold() + + Text(" if I exercise every day, ") + + Text("I CARE") + .greenSemiBold() + + Text(" if I don't forget anything, ") + + Text("I CARE") + .greenSemiBold() + + Text(" if I constantly follow my long-term goals.") + } + + PView { + Text("Now I will show ") + + Text("MY PERSONAL") + .greenSemiBold() + + Text(" app setup with ") + + Text("REAL-LIFE") + .greenSemiBold() + + Text(" scenarios.") + } + + PView { + Text("IMPORTANT!") + .blueSemiBold() + + Text(" Life is hard, life is tricky. No way to have a perfect app or system.") + + Text(" Some solutions seem strange, but they work, they help me achieve my ") + + Text("REAL-LIFE") + .greenSemiBold() + + Text(" goals.") + } + + HeaderView("Activities") + + PView { + Text("The first thing you have to do is ") + + Text("SET UP ACTIVITIES.") + .greenSemiBold() + } + + PView { + Text("This is how ") + + Text("MY ACTIVITIES") + .greenSemiBold() + + Text(" look in the morning, right after I wake up:") + } + + ScreenshotView("doc_activities_morning", width: .infinity) + + PView { + Text("During the day, I have to turn it into this:") + } + + ScreenshotView("doc_activities_evening", width: .infinity) + + PView { + Text("I ") + + Text("ONLY") + .greenSemiBold() + + Text(" create activities to follow my ") + + Text("REAL-LIFE") + .greenSemiBold() + + Text(" goals. I ") + + Text("DO NOT") + .redSemiBold() + + Text(" create activities just to track, like commute, eating, etc.") + } + + PView { + Text("Every activity has ") + + Text("PRACTICAL") + .greenSemiBold() + + Text(" value. Now I'll show how I set up and use each activity.") + } + + HeaderView("Morning") + + PView { + Text("Right after waking up, I tap the ") + + Text("Morning") + .greenSemiBold() + + Text(" activity. This is what I see:") + } + + ScreenshotView("doc_morning_start") + + PView { + Text("There are two important things: ") + + Text("TIMER") + .greenSemiBold() + + Text(" and ") + + Text("CHECKLIST.") + .greenSemiBold() + } + + PView { + Text("TIMER") + .greenSemiBold() + + Text(" helps me limit my morning routine time. I set 2 hours, it's enough to do everything smoothly, but I don't have to spend more time.") + } + + PView { + Text("CHECKLIST") + .greenSemiBold() + + Text(" helps me make sure I don't forget anything. I'm just doing step by step.") + } + + PView { + Text("Once I finish the checklist, ") + + Text("Morning") + .greenSemiBold() + + Text(" will be marked as complete:") + } + + ScreenshotView("doc_morning_completed") + + PView { + Text("To make ") + + Text("Morning") + .greenSemiBold() + + Text(" works this way, you have to set up two options:") + } + + ScreenshotView("doc_morning_form") + + HeaderView("Workout") + + PView { + Text("Workout") + .greenSemiBold() + + Text(" works absolutely ") + + Text("DIFFERENT.") + .greenSemiBold() + + Text(" Just after tapping ") + + Text("Workout,") + .greenSemiBold() + + Text(" I see this:") + } + + ScreenshotView("doc_workout_start") + + PView { + Text("Two differences:") + + Text("\n1. Workout") + .greenSemiBold() + + Text(" marked ") + + Text("AS COMPLETED") + .greenSemiBold() + + Text(" even if a checklist ") + + Text("IS NOT") + .redSemiBold() + + Text(" completed;") + + Text("\n2.") + .greenSemiBold() + + Text(" Instead of timer ") + + Text("(COUNT DOWN)") + .redSemiBold() + + Text(" we see a stopwatch ") + + Text("(COUNT UP FROM 00:00).") + .greenSemiBold() + } + + PView { + Text("Why it works this way? As I said, I focus on ") + + Text("PRACTICAL") + .greenSemiBold() + + Text(" value. I exercise to stay healthy. ") + + Text("I have to find a way to exercise ") + + Text("EVERY DAY.") + .greenSemiBold() + } + + PView { + Text("We know, the most difficult thing is getting started. ") + + Text("I just tap ") + + Text("Workout") + .greenSemiBold() + + Text(" (feels like I've done the first step), then commute to the place, do my workout, come back, take a shower, and have dinner.") + } + + PView { + Text("Usually, it takes up to 4 hours. ") + + Text("I DO NOT") + .redSemiBold() + + Text(" care about tracking every single step, but ") + + Text("I CARE") + .greenSemiBold() + + Text(" I do workout every day.") + } + + PView { + Text("I DO NOT FORCE MYSELF") + .redSemiBold() + + Text(" completing checklists, setting timer, etc.") + + Text(" Only this way works best for me for ") + + Text("Workout.") + .greenSemiBold() + } + + PView { + Text("Let's see the ") + + Text("Workout's") + .greenSemiBold() + + Text(" settings:") + } + + ScreenshotView("doc_workout_form") + + HeaderView("Small Tasks") + + PView { + Text("We all have plenty of non-urgent tasks that we constantly postpone.") + + Text(" It could be personal matters, housework, etc.") + + Text(" Every day, ") + + Text("I FORCE MYSELF") + .greenSemiBold() + + Text(" to spend 30 minutes for that.") + } + + PView { + Text("I just tap ") + + Text("Small Tasks") + .greenSemiBold() + + Text(" and do these tasks.") + + Text(" After 30 minutes, the activity will be marked as complete.") + } + + ScreenshotView("doc_small_tasks_progress") + + PView { + Text("Settings:") + } + + ScreenshotView("doc_small_tasks_form") + + HeaderView("timeto.me") + + PView { + Text("As a ") + + Text("timeto.me") + .greenSemiBold() + + Text(" developer, I dedicate all the time I can to the project.") + + Text(" Here's how I manage that.") + } + + PView { + Text("Tapping the ") + + Text("timeto.me,") + .greenSemiBold() + + Text(" I see this:") + } + + ScreenshotView("doc_timetome_start") + + PView { + Text("We see") + + Text(" TIMER, CHECKLIST,") + .greenSemiBold() + + Text(" and ") + + Text("TASKS.") + .greenSemiBold() + } + + PView { + Text("I will talk about ") + + Text("TASKS") + .greenSemiBold() + + Text(" later. Let's see the ") + + Text("CHECKLIST") + .greenSemiBold() + + Text(" and ") + + Text("TIMER") + .greenSemiBold() + + Text(" for now.") + } + + PView { + Text("CHECKLIST.") + .greenSemiBold() + + Text(" Every day, I start by answering user questions.") + + Text(" Then mark the checklist ") + + Text("AS COMPLETED.") + .greenSemiBold() + + Text(" And only then start working.") + } + + PView { + Text("It may seem ") + + Text("ILLOGICAL") + .redSemiBold() + + Text(" that I mark ") + + Text("timeto.me") + .greenSemiBold() + + Text(" as completed and only then start working on it.") + + Text(" But it works in ") + + Text("REAL-LIFE.") + .greenSemiBold() + } + + PView { + Text("It works because, I don't know how much work I'll be able to get done today,") + + Text(" and it's really frustrating that one of the activities will always remain uncompleted.") + } + + PView { + Text("I don't forget the essential tasks thanks the checklist, then I work whatever hours I can.") + } + + PView { + Text("This way, I can constantly follow my long-term plans as a ") + + Text("timeto.me") + .greenSemiBold() + + Text(" developer, without overwhelming by \"Task Management\" rituals.") + } + + PView { + Text("TIMER.") + .greenSemiBold() + + Text(" I use a ") + + Text("POMODORO-LIKE") + .greenSemiBold() + + Text(" technique. I set the timer for 45 minutes, then take a break, and set the timer again.") + } + + PView { + Text("After the timer ends, it turns red and display the overdue time:") + } + + ScreenshotView("doc_timetome_overdue") + + PView { + Text("It ") + + Text("DOES NOT") + .redSemiBold() + + Text(" mean I'm taking a break immediately.") + + Text(" Sometimes I want to continue working. ") + + Text("KEEP IN MIND:") + .greenSemiBold() + + Text(" the most important is ") + + Text("PRACTICAL VALUE.") + .greenSemiBold() + } + + PView { + Text("You can tap the timer to start a ") + + Text("BREAK") + .greenSemiBold() + + Text(" timer:") + } + + ScreenshotView("doc_timetome_break") + + PView { + Text("But ") + + Text("HONESTLY,") + .greenSemiBold() + + Text(" I don't use this feature. After the break, I just tap ") + + Text("timeto.me") + .greenSemiBold() + + Text(" again to start a new 45 min timer.") + } + + PView { + Text("Settings:") + } + + ScreenshotView("doc_timetome_form") + + HeaderView("Option1") + + PView { + Text("[option1.io](https://option1.io)") + .underline() + .blueSemiBold() + + Text(" is also my personal project.") + + Text(" Here, I'm building a pragmatic window manager for macOS.") + } + + PView { + Text("Settings are the same as for ") + + Text("timeto.me.") + .greenSemiBold() + } + + HeaderView("Work") + + PView { + Text("Work") + .greenSemiBold() + + Text(" is a special case.") + + Text(" Working as a developer, I have to track my working hours, there are a set of features for that.") + } + + PView { + Text("Let's see the settings:") + } + + ScreenshotView("doc_work_form") + + PView { + Text("Total Stopwatch") + .greenSemiBold() + + Text(" makes the timer display ") + + Text("TOTAL TIME") + .greenSemiBold() + + Text(" spent on work ") + + Text("FOR TODAY.") + .greenSemiBold() + } + + PView { + Text("In other words, it's a regular stopwatch ") + + Text("(COUNT UP),") + .greenSemiBold() + + Text(" but it ") + + Text("DOES NOT") + .redSemiBold() + + Text(" start from ") + + Text("00:00,") + .redSemiBold() + + Text(" it continues for the activity.") + } + + PView { + Text("This way, I can always see how much time I've spent on work today.") + } + + PView { + Text("Another feature is adding notes to the current task:") + } + + ScreenshotView("doc_work_note") + + PView { + Text("Tap the edit icon to make a note about the task you're working on.") + } + + PView { + Text("Then, in the history, you can see how much time you spent on each task:") + } + + ScreenshotView("doc_work_history") + + PView { + Text("If you're a ") + + Text("NIGHT OWL") + .greenSemiBold() + + Text(" and work after ") + + Text("12:00 AM,") + .greenSemiBold() + + Text(" you can set a ") + + Text("DAY START TIME") + .greenSemiBold() + + Text(" setting, to track the working time right.") + + Text(" I suggest set it for 2 hours before you wake up to refresh activities while you sleep.") + } + + HeaderView("Reading") + + PView { + Text("I don't know how it works, but I see that constant reading ") + + Text("MAKES PEOPLE BETTER.") + .greenSemiBold() + } + + PView { + Text("Some people set a goal to read for a hour a day. It ") + + Text("DOES NOT") + .redSemiBold() + + Text(" work for me.") + } + + PView { + Text("I like to read a fixed number of chapters per day. In the book I'm reading now, I read ") + + Text("FIVE") + .greenSemiBold() + + Text(" chapters a day.") + } + + ScreenshotView("doc_reading_progress") + + PView { + Text("Technically, it works like a counter. In practice, I tap ") + + Text("Reading") + .greenSemiBold() + + Text(" and start reading. Then I count how many chapters I've read.") + } + + PView { + Text("Settings:") + } + + ScreenshotView("doc_reading_form") + + PView { + Text("Timer ") + + Text("DOES NOT") + .redSemiBold() + + Text(" matter.") + } + + HeaderView("Music") + + PView { + Text("Music") + .greenSemiBold() + + Text(" is my hobby. I try to play the piano twice a day.") + } + + ScreenshotView("doc_music_progress") + + PView { + Text("Settings are very similar to ") + + Text("Reading:") + .greenSemiBold() + } + + ScreenshotView("doc_music_form") + + PView { + Text("Timer ") + + Text("DOES NOT") + .redSemiBold() + + Text(" matter.") + } + + HeaderView("Free Time") + + PView { + Text("I use ") + + Text("Free Time") + .greenSemiBold() + + Text(" for activities I don't need to track, like eating, walking, meeting, etc.") + } + + PView { + Text("I would like to highlight ") + + Text("THREE") + .greenSemiBold() + + Text(" key points:") + } + + ScreenshotView("doc_free_time_start") + + PView { + Text("1. ALWAYS COMPLETED.") + .greenSemiBold() + + Text(" No sense in setting goals.") + } + + PView { + Text("2. STOPWATCH (COUNT UP FROM 00:00).") + .greenSemiBold() + + Text(" Helps me control the time I spend on different tasks.") + + Text(" Sometimes it's useful to notice that I spend too much time on something.") + } + + PView { + Text("3. NESTED CHECKLISTS.") + .greenSemiBold() + + Text(" The ") + + Text("Free Time") + .greenSemiBold() + + Text(" checklist contains all sorts of things.") + + Text(" For example, the ") + + Text("Shopping") + .greenSemiBold() + + Text(" item contains a nested checklist with a list of goods I have to buy.") + } + + PView { + Text("Settings:") + } + + ScreenshotView("doc_free_time_form") + + HeaderView("Sleep") + + PView { + Text("Sleep") + .greenSemiBold() + + Text(" is another special case:") + } + + ScreenshotView("doc_sleep_start") + + PView { + Text("1. ALWAYS COMPLETED.") + .greenSemiBold() + + Text(" Since during the day I try to mark all activities as completed, it’s really frustrating that one of them will always remain uncompleted.") + } + + PView { + Text("That is why, despite the checklist, it's better when ") + + Text("Sleep") + .greenSemiBold() + + Text(" is always completed.") + } + + PView { + Text("2. STOPWATCH (COUNT UP FROM 00:00).") + .greenSemiBold() + + Text(" Some people prefer to set a timer for sleep, for example, for 7 hours.") + + Text(" That doesn't work for me.") + } + + PView { + Text("I sleep as much as I feel I need to today.") + + Text(" I prefer to use a stopwatch and check in the morning how long I slept.") + } + + PView { + Text("Settings:") + } + + ScreenshotView("doc_sleep_form") + + HeaderView("Conclusion") + + PView { + Text("That is all the activities I use.") + + Text(" By default, the app comes with almost the same activities and settings.") + + Text(" You can use this setup.") + .greenSemiBold() + } + + PView { + Text("I want to give you ") + + Text("THREE TIPS") + .greenSemiBold() + + Text(" dealing with activities that are extremely important to me: ") + + Text("PROCRASTINATION, ") + .greenSemiBold() + + Text("PRIORITIES,") + .greenSemiBold() + + Text(" and ") + + Text("FLEXIBILITY.") + .greenSemiBold() + } + + HeaderView("Procrastination") + + PView { + Text("All of us have been there - when it's crystal clear what we should do, but we just don't do it.") + } + + PView { + Text("I solve it simply: I open the app, see uncompleted activity, and tap on it without thinking.") + } + + PView { + Text("DO NOT THINK!") + .redSemiBold() + + Text(" JUST TAP THE ACTIVITY IMMEDIATELY!") + .greenSemiBold() + } + + PView { + Text("ONCE AGAIN:") + .blueSemiBold() + + Text(" OPEN THE APP AND TAP ON UNCOMPLETED ACTIVITY WITHOUT THINKING!") + .greenSemiBold() + } + + PView { + Text("The most difficult is to get started.") + + Text(" Tapping the activity feels like you've made the first step.") + + Text(" If you start thinking, you will continue procrastinating.") + } + + PView { + Text("It always works for me. For example, I open the app, see an uncompleted ") + + Text("Piano,") + .greenSemiBold() + + Text(" tap it immediately, make some tea, and start practicing.") + } + + PView { + Text("I hope you get the idea. Just tap ") + + Text("WITHOUT THINKING.") + .greenSemiBold() + } + + PView { + Text("IMPORTANT:") + .blueSemiBold() + + Text(" You should trust your activities. Only important things should be here. Otherwise, you will be overwhelmed and fail.") + } + + HeaderView("Priorities") + + PView { + Text("We usually start the day with the most urgent tasks and ") + + Text("SACRIFICE") + .redSemiBold() + + Text(" long-term goals, because long-term goals are not usually urgent.") + } + + PView { + Text("NO MATTER") + .greenSemiBold() + + Text(" what happens, I try to start my day focusing only on what really ") + + Text("MATTERS TO ME.") + .greenSemiBold() + } + + PView { + Text("My perfect day: after the morning routine, I read, then practice the piano, then work on my personal projects, and only then I get to work.") + } + + PView { + Text("Only this way I'm able to keep developing this app for years.") + } + + PView { + Text("It's very difficult, but you have to remember what is really ") + + Text("IMPORTANT TO YOU.") + .greenSemiBold() + } + + HeaderView("Flexibility") + + PView { + Text("Unexpected things happen every day. We have to accept this fact.") + + Text(" It's ") + + Text("ABSOLUTELY OKAY") + .greenSemiBold() + + Text(" if we can't do some activity today.") + } + + PView { + Text("For example, I have a meeting today, so I don't have time to ") + + Text("Workout.") + .greenSemiBold() + + Text(" It's absolutely okay. ") + + Text("I JUST MARK WORKOUT AS COMPLETED") + .greenSemiBold() + + Text(" and move to other activities.") + } + + PView { + Text("It may seem strange that I mark ") + + Text("Workout") + .greenSemiBold() + + Text(" as completed even if ") + + Text("I HAVEN'T") + .redSemiBold() + + Text(" done it, but I just don't want to get distracted by uncompleted activity.") + } + + PView { + Text("NOTE: ") + .blueSemiBold() + + Text("I have the same activities for every day, even if ") + + Text("I DON'T") + .redSemiBold() + + Text(" need ") + + Text("Work") + .greenSemiBold() + + Text(" activity on weekends.") + + Text(" There is an option to hide activities on selected days, but ") + + Text("HONESTLY,") + .greenSemiBold() + + Text(" I don't use it.") + + Text(" On weekends, every morning, while planning my day, I just mark ") + + Text("Work") + .greenSemiBold() + + Text(" as complete and focus on the remaining activities.") + } + + PView { + Text("KEEP IN MIND:") + .blueSemiBold() + + Text(" the most important is ") + + Text("REAL-LIFE") + .greenSemiBold() + + Text(" and ") + + Text("PRACTICAL VALUE.") + .greenSemiBold() + } + + HeaderView("DO NOT RUSH") + + PView { + Text("I believe we do more when we don't rush.") + } + + PView { + Text("In the ") + + Text("Procrastination") + .greenSemiBold() + + Text(" section, I suggest to start an activity without thinking, it's right, but it doesn't mean you have to immediately jump into action.") + } + + PView { + Text("For example, I open the app, see an uncompleted ") + + Text("Reading,") + .greenSemiBold() + + Text(" and tap it immediately.") + + Text(" Then I go to the park with a book and start reading there.") + } + + PView { + Text("Stay calm and start slowly.") + .greenSemiBold() + } + + Divider() + .fillMaxWidth() + .frame(height: 1) + .background(.separator) + + PView { + Text("That's all for ") + + Text("Activities.") + .greenSemiBold() + + Text(" Let's move to other features.") + } + + HeaderView("Timer") + + PView { + Text("You may notice that every screenshot has a timer.") + } + + PView { + Text("Timer is running ") + + Text("ALL THE TIME.") + .greenSemiBold() + + Text(" There is ") + + Text("NO STOP OPTION!") + .redSemiBold() + + Text(" To stop the current activity, you have to start the next one.") + } + + PView { + Text("This way I always remember what I have to do. Also, it provides 24/7 data on how long everything takes:") + } + + ScreenshotView("doc_timer_summary") + + HeaderView("Tasks") + + PView { + Text("TASKS") + .greenSemiBold() + + Text(" is a big part of the app.") + + Text(" Let's create a task:") + } + + ScreenshotView("doc_tasks_field") + + PView { + Text("You can select an activity for this task.") + + Text(" But ") + + Text("HONESTLY,") + .greenSemiBold() + + Text(" I always keep the default ") + + Text("Free Time:") + .greenSemiBold() + } + + ScreenshotView("doc_tasks_form") + + PView { + Text("Nice!") + .greenSemiBold() + + Text(" Now you will not forget to buy fruits:") + } + + ScreenshotView("doc_tasks_example1") + + PView { + Text("You can tap it to start a stopwatch:") + } + + ScreenshotView("doc_tasks_started") + + PView { + Text("But ") + + Text("HONESTLY,") + .greenSemiBold() + + Text(" I newer tap on tasks.") + + Text(" I prefer to complete the task first, then just delete it by swiping left:") + } + + ScreenshotView("doc_tasks_delete") + + HeaderView("Task Folders") + + ScreenshotView("doc_folders_example") + + PView { + Text("TODAY:") + .greenSemiBold() + + Text(" Tasks you need to do today.") + } + + PView { + Text("TOMORROW:") + .greenSemiBold() + + Text(" Tasks that will be moved to ") + + Text("TODAY") + .greenSemiBold() + + Text(" folder tomorrow.") + } + + PView { + Text("Let's schedule a call with Ann for tomorrow.") + + Text(" Just tap the folder and add the task:") + } + + ScreenshotView("doc_folders_tomorrow") + + PView { + Text("If you want to move it to another folder, like ") + + Text("TODAY,") + .greenSemiBold() + + Text(" swipe right and tap the folder you need:") + } + + ScreenshotView("doc_folders_swipe") + + PView { + Text("CUSTOM FOLDERS:") + .greenSemiBold() + + Text(" I use a few folders:") + + Text("\n- tasks and ideas for ") + + Text("timeto.me;") + .greenSemiBold() + + Text("\n- tasks and ideas for ") + + Text("Option1;") + .greenSemiBold() + + Text("\n- interesting quotes from ") + + Text("books") + .greenSemiBold() + + Text(" I've read.") + + Text("\nNote that the last one is not actually \"tasks\", but it’s very convenient to store them this way.") + } + + PView { + Text("Sometimes I create temporary folders. For example, while I was writing this guide, I created a folder to store the ideas.") + } + + HeaderView("Conclusion") + + PView { + Text("I want to give you ") + + Text("TWO TIPS") + .greenSemiBold() + + Text(" dealing with ") + + Text("TASKS") + .greenSemiBold() + + Text(" that are extremely important to me:") + } + + PView { + Text("NEVER KEEP ANYTHING IN MIND!") + .redSemiBold() + + Text(" As soon as a task or idea comes to mind, leave it to the list.") + + Text(" We get really tired when we try to keep everything in mind. ") + + Text("TRY TO EXPERIENCE") + .greenSemiBold() + + Text(" the feeling when you don't need to remember anything. ") + + Text("EVERYTHING") + .greenSemiBold() + + Text(" in the task list.") + } + + PView { + Text("ADD NEW TASKS ONLY TO THE TOMORROW FOLDER.") + .greenSemiBold() + + Text(" If I add this to ") + + Text("TODAY,") + .greenSemiBold() + + Text(" it breaks my plans and overwhelms me.") + } + + HeaderView("Repeating Tasks") + + PView { + Text("There are many repeating tasks or events that we have to remember.") + + Text(" Like birthdays, recurring payments, special dates, etc.") + } + + PView { + Text("I have about 30, and have no idea how to keep them all in mind. ") + + Text("EVERYTHING IN THE APP.") + .greenSemiBold() + } + + PView { + Text("You can create any kind of repeating task:") + + Text("\n- Every Day;") + + Text("\n- Every N Days;") + + Text("\n- Days of the Week;") + + Text("\n- Days of the Month;") + + Text("\n- Days of the Year.") + } + + PView { + Text("Let's create a birthday reminder:") + } + + ScreenshotView("doc_repeating_form_1") + + PView { + Text("Now, on ") + + Text("MARCH 30,") + .greenSemiBold() + + Text(" this task will appear in ") + + Text("TODAY") + .greenSemiBold() + + Text(" folder, so you can't miss it.") + } + + PView { + Text("One more example: paying for internet service at the ") + + Text("END OF THE MONTH.") + .greenSemiBold() + } + + ScreenshotView("doc_repeating_form_2") + + PView { + Text("Also, these tasks will appear on the ") + + Text("CALENDAR.") + .greenSemiBold() + } + + HeaderView("Calendar") + + ScreenshotView("doc_calendar_button") + + PView { + Text("A regular calendar where you can schedule tasks. As I mentioned, repeating tasks are also here.") + } + + ScreenshotView("doc_calendar_screen") + + Divider() + .fillMaxWidth() + .frame(height: 1) + .background(.separator) + + HeaderView("Let's Go") + + PView { + Text("I hope my app will lead you to what matters to you the most in your life.") + } + + PView { + Text("If you have any questions, please feel free to ask.") + } + + AskQuestionView( + subject: state.askQuestionSubject, + ) { + Text("Ask a Question") + .foregroundColor(.white) + .fontWeight(.semibold) + .padding(.horizontal, 12) + .padding(.vertical, 6) + .background(roundedShape.fill(.blue)) + } + .listRowSeparator(.hidden) + + Text("Go to the App") + .foregroundColor(.white) + .fontWeight(.semibold) + .padding(.horizontal, 12) + .padding(.vertical, 6) + .background(roundedShape.fill(.blue)) + .listRowSeparator(.hidden) + .onTapGesture { + vm.onRead() + dismiss() + } + + PView { + Text("Best regards,\n") + + Text("[Ivan](https://github.com/Medvedev91)") + .underline() + .blueSemiBold() + } + .padding(.top, 40) + .padding(.bottom, 20) + } + .listStyle(.plain) + .navigationTitle("How to Use the App") + .toolbar { + if !forceRead { + ToolbarItem(placement: .cancellationAction) { + Button("Close") { + dismiss() + } + } + } + } + } +} + +private struct PView: View { + + @ViewBuilder private let content: () -> Content + + init(_ content: @escaping () -> Content) { + self.content = content + } + + var body: some View { + content() + .listRowSeparator(.hidden) + } +} + +private struct HeaderView: View { + + private let text: String + + init(_ text: String) { + self.text = text + } + + var body: some View { + Text(text) + .font(.system(size: 30, weight: .bold)) + .listRowSeparator(.hidden) + .padding(.top, 32) + } +} + +private struct ScreenshotView: View { + + private let name: String + private let width: CGFloat + + init( + _ name: String, + width: CGFloat = 240.0, + ) { + self.name = name + self.width = width + } + + var body: some View { + Image(name) + .resizable() + .aspectRatio(contentMode: .fit) + .cornerRadius(16) + .frame(width: width) + .shadow(color: .primary, radius: onePx) + .padding(.vertical, 4) // Paddings for shadow radius + .listRowSeparator(.hidden) + .listRowInsets(EdgeInsets(top: 4, leading: 16, bottom: 4, trailing: 16)) + } +} + +private extension Text { + + func redSemiBold() -> Text { + foregroundColor(.red).fontWeight(.bold) + } + + func greenSemiBold() -> Text { + foregroundColor(.green).fontWeight(.bold) + } + + func blueSemiBold() -> Text { + foregroundColor(.blue).fontWeight(.bold) + } + + /// + + func forceText() -> Text { + foregroundColor(.blue).font(.system(size: 20, weight: .bold)) + } +} diff --git a/apple/iphone/UI/Form/FormButtonSymbolView.swift b/apple/iphone/UI/Form/FormButtonSymbolView.swift new file mode 100644 index 000000000..3da95fd3d --- /dev/null +++ b/apple/iphone/UI/Form/FormButtonSymbolView.swift @@ -0,0 +1,18 @@ +import SwiftUI +import shared + +struct FormButtonSymbolView: View { + + let symbol: Symbol + let color: Color + + var body: some View { + SymbolView( + symbol: symbol, + color: color, + letterSize: 22, + iconSize: 18, + emojiSize: 22, + ) + } +} diff --git a/apple/iphone/UI/Home/Buttons/HomeButtonActivityView.swift b/apple/iphone/UI/Home/Buttons/HomeButtonActivityView.swift index fab0d3536..cec13a767 100644 --- a/apple/iphone/UI/Home/Buttons/HomeButtonActivityView.swift +++ b/apple/iphone/UI/Home/Buttons/HomeButtonActivityView.swift @@ -56,10 +56,17 @@ struct HomeButtonActivityView: View { ZStack { HStack { - Text(activity.activityDb.emoji) - .padding(.leading, HomeScreen__itemCircleHPadding) - .foregroundColor(.white) - .font(.system(size: HomeScreen__itemCircleFontSize, weight: HomeScreen__itemCircleFontWeight)) + // todo calc once + let symbol = activity.activityDb.symbolOrDefault() + + SymbolView( + symbol: symbol, + color: .white, + letterSize: HomeScreen__itemCircleFontSize, + iconSize: 14, + emojiSize: HomeScreen__itemCircleFontSize, + ) + .padding(.leading, symbol is Symbol.Emoji ? 4 : 6) Spacer() } @@ -196,14 +203,19 @@ struct HomeButtonActivityView: View { Button( action: { - navigation.fullScreen { - HomeSettingsButtonsFullScreen( - onClose: {} - ) - } + navigation.showTaskForm(strategy: activity.newTaskTodayFormStrategy) + }, + label: { + Label("New Task Today", systemImage: "sun.min.fill") + } + ) + + Button( + action: { + navigation.showTaskForm(strategy: activity.newTaskTomorrowFormStrategy) }, label: { - Label("Home Screen Settings", systemImage: "gear") + Label("New Task Tomorrow", systemImage: "moon.fill") } ) } diff --git a/apple/iphone/UI/Home/HomeReadmeView.swift b/apple/iphone/UI/Home/HomeReadmeView.swift index c40df6d9d..bb7718180 100644 --- a/apple/iphone/UI/Home/HomeReadmeView.swift +++ b/apple/iphone/UI/Home/HomeReadmeView.swift @@ -3,11 +3,11 @@ import SwiftUI private let bottomMargin: CGFloat = (HomeScreen__itemHeight - HomeScreen__itemCircleHeight) / 2 private let shape = RoundedRectangle(cornerRadius: 14, style: .continuous) +// todo remove after update July 2026 struct HomeReadmeView: View { let title: String let buttonText: String - let onButtonClick: () -> Void @Environment(Navigation.self) private var navigation @@ -20,9 +20,10 @@ struct HomeReadmeView: View { Button( action: { - onButtonClick() navigation.fullScreen { - Readme2FullScreen() + DocFullScreen( + forceRead: true + ) } }, label: { diff --git a/apple/iphone/UI/Home/HomeScreen.swift b/apple/iphone/UI/Home/HomeScreen.swift index af50277f5..efb8ee016 100644 --- a/apple/iphone/UI/Home/HomeScreen.swift +++ b/apple/iphone/UI/Home/HomeScreen.swift @@ -40,41 +40,46 @@ private struct HomeScreenInner: View { var body: some View { + let isToday: Bool = state.taskFolderUi.taskFolderDb.isToday + VStack { let checklistDb: ChecklistDb? = state.checklistDb - HomeTimerView(vm: vm, state: state) - - if let whatsNewMessage = state.whatsNewMessage { - MessageButton( - title: whatsNewMessage, - onTap: { - navigation.push(.whatsNew) - }, - ) - } - - if let privacyMessage = state.privacyMessage { - MessageButton( - title: privacyMessage, - onTap: { - navigation.fullScreen { - PrivacyScreen( - toForceChoice: true, - titleDisplayMode: .large, - scrollBottomMargin: MainTabsView__HEIGHT, - ) - } - }, - ) - } - - if let checklistHintUi = state.checklistHintUi { - HomeChecklistHintView(hintUi: checklistHintUi) + if isToday { + + HomeTimerView(vm: vm, state: state) + + if let whatsNewMessage = state.whatsNewMessage { + MessageButton( + title: whatsNewMessage, + onTap: { + navigation.push(.whatsNew) + }, + ) + } + + if let privacyMessage = state.privacyMessage { + MessageButton( + title: privacyMessage, + onTap: { + navigation.fullScreen { + PrivacyScreen( + toForceChoice: true, + titleDisplayMode: .large, + scrollBottomMargin: MainTabsView__HEIGHT, + ) + } + }, + ) + } + + if let checklistHintUi = state.checklistHintUi { + HomeChecklistHintView(hintUi: checklistHintUi) + } } - let isMainListItemsExists = !state.mainListItemsUi.isEmpty + let isMainListItemsExists = !state.homeTasksItemsUi.isEmpty GeometryReader { geometry in @@ -85,24 +90,24 @@ private struct HomeScreenInner: View { VStack { - if let checklistDb = checklistDb { + if let checklistDb = checklistDb, isToday { VStack { ChecklistView( checklistDb: checklistDb, maxLines: 1, withAddButton: false, - onDelete: {} + onDelete: {}, ) } .frame(height: CGFloat(state.listsSizes.checklist)) } - if isMainListItemsExists { + if isMainListItemsExists || !isToday { HomeTasksView( homeVm: vm, homeState: state, ) - .frame(height: CGFloat(state.listsSizes.mainTasks)) + .frame(height: !isToday ? .infinity : CGFloat(state.listsSizes.mainTasks)) } Spacer() @@ -113,13 +118,10 @@ private struct HomeScreenInner: View { HomeNotificationsView(notificationsPermissionUi: notificationsPermissionUi) } - if state.showReadme { + if state.showDocBanner { HomeReadmeView( title: state.readmeTitle, buttonText: state.readmeButtonText, - onButtonClick: { - vm.onReadmeOpen() - } ) } @@ -130,11 +132,27 @@ private struct HomeScreenInner: View { ) } + HomeTasksBarView( + tasksBarUi: state.tasksBarUi, + changeTaskFolder: { taskFolderUi in + vm.updateTaskFolder(taskFolderUi: taskFolderUi) + }, + ) + HomeButtonsView() Padding(vertical: 10.0) } .padding(.bottom, MainTabsView__HEIGHT) + .onChange(of: state.forceOpenDoc, initial: true) { old, newValue in + if newValue { + navigation.fullScreen { + DocFullScreen( + forceRead: true + ) + } + } + } } } diff --git a/apple/iphone/UI/Home/HomeTasksView.swift b/apple/iphone/UI/Home/HomeTasksView.swift deleted file mode 100644 index 2336b4e71..000000000 --- a/apple/iphone/UI/Home/HomeTasksView.swift +++ /dev/null @@ -1,216 +0,0 @@ -import SwiftUI -import shared - -struct HomeTasksView: View { - - let homeVm: HomeVm - let homeState: HomeVm.State - - var body: some View { - ScrollView(showsIndicators: false) { - VStack { - ForEach(homeState.mainListItemsUi.reversed(), id: \.id) { mainListItemUi in - if let taskItemUi = mainListItemUi as? HomeVm.MainListItemUiMainTaskUi { - TaskItemView( - mainListItemUi: taskItemUi, - showOnHomeActivity: - !homeState.onHomeActivity && (taskItemUi.taskUi.taskDb.folder_id == homeState.activityTaskFolderDb?.id), - ) - } else if let barItemUi = mainListItemUi as? HomeVm.MainListItemUiTaskFolderBarUi { - TaskFolderBarView( - barUi: barItemUi, - onHomeActivity: homeState.onHomeActivity, - toggleOnHomeActivity: { - homeVm.toggleOnHomeActivity() - }, - ) - } - } - } - } - .defaultScrollAnchor(.bottom) - } -} - -/// - -private struct TaskItemView: View { - - let mainListItemUi: HomeVm.MainListItemUiMainTaskUi - let showOnHomeActivity: Bool - - @Environment(Navigation.self) private var navigation - - var body: some View { - - Button( - action: { - mainListItemUi.taskUi.taskDb.startIntervalForUi( - ifJustStarted: {}, - ifTimerNeeded: { - navigation.showTaskTimerSheet( - taskDb: mainListItemUi.taskUi.taskDb - ) - } - ) - }, - label: { - - HStack { - - if let timeUi = mainListItemUi.timeUi { - let bgColor: Color = switch timeUi.status { - case .in: homeFgColor - case .soon: .blue - case .overdue: .red - default: fatalError("timeUi.status bgColor not handled") - } - Text(timeUi.text) - .foregroundColor(.white) - .font(.system(size: HomeScreen__itemCircleFontSize, weight: HomeScreen__itemCircleFontWeight)) - .padding(.horizontal, HomeScreen__itemCircleHPadding) - .frame(height: HomeScreen__itemCircleHeight) - .background(roundedShape.fill(bgColor)) - .padding(.trailing, mainListItemUi.taskUi.tf.paused != nil ? 9 : HomeScreen__itemCircleMarginTrailing) - } - - if mainListItemUi.taskUi.tf.paused != nil { - ZStack { - Image(systemName: "pause") - .foregroundColor(.white) - .font(.system(size: 12, weight: .black)) - } - .frame(width: HomeScreen__itemCircleHeight, height: HomeScreen__itemCircleHeight) - .background(roundedShape.fill(.green)) - .padding(.trailing, 8) - } - - Text(mainListItemUi.text) - .font(.system(size: HomeScreen__primaryFontSize)) - .foregroundColor(.white) - .padding(.trailing, 4) - - Spacer() - - if let timeUi = mainListItemUi.timeUi { - let noteColor: Color = switch timeUi.status { - case .in: .secondary - case .soon: .blue - case .overdue: .red - default: fatalError("timeUi.status noteColor not handled") - } - Text(timeUi.note) - .foregroundColor(noteColor) - .font(.system(size: HomeScreen__primaryFontSize)) - } - - if showOnHomeActivity { - Button( - action: { - mainListItemUi.toggleOnHomeActivity() - }, - label: { - Image(systemName: "house") - .foregroundColor(mainListItemUi.taskUi.taskDb.onHomeActivity ? .secondary : homeFgColor) - .font(.system(size: 19, weight: .semibold)) - }, - ) - .frame(width: HomeScreen__itemCircleHeight, height: HomeScreen__itemCircleHeight) - .buttonStyle(.plain) - } - } - .frame(height: HomeScreen__itemHeight) - .padding(.horizontal, HomeScreen__hPadding) - } - ) - } -} - -private struct TaskFolderBarView: View { - - let barUi: HomeVm.MainListItemUiTaskFolderBarUi - let onHomeActivity: Bool - let toggleOnHomeActivity: () -> Void - - /// - - @Environment(Navigation.self) private var navigation - - var body: some View { - HStack { - - Button( - action: { - navigation.showTaskForm( - strategy: TaskFormStrategy.NewTask( - taskFolderDb: barUi.taskFolderDb, - ) - ) - }, - label: { - HStack { - - ZStack { - Image(systemName: "plus") - .foregroundColor(.black) - .font(.system(size: 14, weight: .bold)) - } - .frame(width: HomeScreen__itemCircleHeight, height: HomeScreen__itemCircleHeight) - .background(roundedShape.fill(.blue)) - - Text("New Task") - .foregroundColor(.white) - .font(.system(size: HomeScreen__primaryFontSize)) - .padding(.leading, 8) - } - }, - ) - - Spacer() - - Button( - action: { - toggleOnHomeActivity() - }, - label: { - Image(systemName: "house") - .foregroundColor(onHomeActivity ? .secondary : homeFgColor) - .font(.system(size: 19, weight: .semibold)) - }, - ) - .frame(width: HomeScreen__itemCircleHeight, height: HomeScreen__itemCircleHeight) - .buttonStyle(.plain) - .padding(.top, onePx) - - if barUi.todayTasksCount > 0 { - Button( - action: { - barUi.toggleCollapseToday() - }, - label: { - ZStack { - if (barUi.isCollapsed) { - Text("\(barUi.todayTasksCount)") - .foregroundColor(.secondary) - .font(.system(size: HomeScreen__itemCircleFontSize, weight: HomeScreen__itemCircleFontWeight)) - } else { - Image(systemName: "chevron.down") - .foregroundColor(.secondary) - .font(.system(size: 12, weight: .bold)) - } - } - .frame(width: HomeScreen__itemCircleHeight, height: HomeScreen__itemCircleHeight) - .background( - Circle() - .strokeBorder(.secondary, lineWidth: 2) - ) - }, - ) - .buttonStyle(.plain) - .padding(.leading, 10) - } - } - .frame(height: HomeScreen__itemHeight) - .padding(.horizontal, HomeScreen__hPadding) - } -} diff --git a/apple/iphone/UI/Home/HomeTimerView.swift b/apple/iphone/UI/Home/HomeTimerView.swift index 6b62fb125..3f714422f 100644 --- a/apple/iphone/UI/Home/HomeTimerView.swift +++ b/apple/iphone/UI/Home/HomeTimerView.swift @@ -178,7 +178,9 @@ struct HomeTimerView: View { color: timerColor, onClick: { navigation.fullScreen { - ReadmeFullScreen(defaultItem: .pomodoro) + DocFullScreen( + forceRead: false, + ) } } ) diff --git a/apple/iphone/UI/Home/Tasks/HomeTaskStaEndView.swift b/apple/iphone/UI/Home/Tasks/HomeTaskStaEndView.swift new file mode 100644 index 000000000..900c9613d --- /dev/null +++ b/apple/iphone/UI/Home/Tasks/HomeTaskStaEndView.swift @@ -0,0 +1,48 @@ +import SwiftUI +import shared + +struct HomeTaskStaEndView: View { + + let onMoveToTimer: () -> Void + let onDelete: () -> Void + let onCancel: () -> Void + + var body: some View { + HStack { + + Text("Move to Timer") + .padding(.leading, 8) + .padding(.trailing, 4) + .foregroundColor(.white) + .lineLimit(1) + .onTapGesture { + onMoveToTimer() + } + + Spacer() + + Button("Cancel") { + onCancel() + } + .foregroundColor(.white) + .padding(.trailing, 12) + + Button( + action: { + onDelete() + }, + label: { + Text("Delete") + .fontWeight(.bold) + .padding(.horizontal, 9) + .padding(.vertical, 5) + .foregroundColor(.red) + } + ) + .background(RoundedRectangle(cornerRadius: 999, style: .circular).fill(.white)) + .padding(.trailing, 8) + } + .fillMaxHeight() + .background(.red) + } +} diff --git a/apple/iphone/UI/Home/Tasks/HomeTaskStaStartView.swift b/apple/iphone/UI/Home/Tasks/HomeTaskStaStartView.swift new file mode 100644 index 000000000..1182cdb3c --- /dev/null +++ b/apple/iphone/UI/Home/Tasks/HomeTaskStaStartView.swift @@ -0,0 +1,77 @@ +import SwiftUI +import shared + +struct HomeTaskStaStartView: View { + + let homeTaskUi: HomeTasksItemUi.HomeTaskUi + let onCancel: () -> Void + + /// + + @Environment(Navigation.self) private var navigation + + var body: some View { + + HStack { + + ZStack { + Image(systemName: "xmark") + .font(.system(size: 15, weight: .medium)) + .foregroundStyle(.blue) + } + .frame( + width: HomeScreen__itemCircleHeight, + height: HomeScreen__itemCircleHeight, + ) + .background(Circle().fill(.white)) + .onTapGesture { + onCancel() + } + .padding(.leading, 8) + + HStack { + Text("Edit..") + .padding(.leading, 8) + .foregroundColor(.white) + .lineLimit(1) + Spacer() + } + .onTapGesture { + navigation.showTaskForm(strategy: homeTaskUi.editStrategy) + onCancel() + } + .fillMaxHeight() + .padding(.trailing, 8) + + ForEach(homeTaskUi.staTaskFoldersUi, id: \.self) { staFolderUi in + HomeTasksFolderButton( + taskFolderUi: staFolderUi.taskFolderUi, + color: staFolderUi.isSelected ? .white : .secondary, + onClick: { + staFolderUi.onTap() + onCancel() + }, + ) + } + + HomeTasksCalendarButton( + color: .secondary, + onClick: { + navigation.fullScreen { + EventFormFullScreen( + initEventDb: nil, + initText: homeTaskUi.taskUi.taskDb.text, + initTime: nil, + onDone: { + homeTaskUi.taskUi.delete() + }, + ) + } + onCancel() + }, + ) + } + .fillMaxSize() + .background(.blue) + } +} diff --git a/apple/iphone/UI/Home/Tasks/HomeTaskView.swift b/apple/iphone/UI/Home/Tasks/HomeTaskView.swift new file mode 100644 index 000000000..85ea33d41 --- /dev/null +++ b/apple/iphone/UI/Home/Tasks/HomeTaskView.swift @@ -0,0 +1,194 @@ +import SwiftUI +import shared + +struct HomeTaskView: View { + + let homeTaskUi: HomeTasksItemUi.HomeTaskUi + let homeState: HomeVm.State + + /// + + @Environment(Navigation.self) private var navigation + + @State private var xSwipeOffset: CGFloat = 0 + @State private var width: CGFloat? = nil + + var body: some View { + + let taskDb: TaskDb = homeTaskUi.taskUi.taskDb + + ZStack { + + GeometryReader { proxy in + ZStack { + } + .onChange(of: proxy.size.width, initial: true) { _, newValue in + width = newValue + } + .onChange(of: proxy.frame(in: .global).minY) { + xSwipeOffset = 0 + } + } + .frame(height: 1) // height: 1, or full height items if short + + if (xSwipeOffset < 0) { + HomeTaskStaEndView( + onMoveToTimer: { + Haptic.mediumShot() + homeTaskUi.taskUi.moveToTimer() + }, + onDelete: { + Haptic.mediumShot() + homeTaskUi.taskUi.delete() + }, + onCancel: { + withAnimation { + xSwipeOffset = 0 + } + }, + ) + } else if (xSwipeOffset > 0) { + HomeTaskStaStartView( + homeTaskUi: homeTaskUi, + onCancel: { + withAnimation { + xSwipeOffset = 0 + } + } + ) + } + + /// + + Button( + action: { + taskDb.startIntervalForUi( + ifJustStarted: {}, + ifTimerNeeded: { + navigation.showTaskTimerSheet( + taskDb: taskDb, + ) + }, + ) + }, + label: { + + HStack { + + if let timeUi = homeTaskUi.timeUi { + let bgColor: Color = switch timeUi.status { + case .in: homeFgColor + case .soon: .blue + case .overdue: .red + default: fatalError("timeUi.status bgColor not handled") + } + Text(timeUi.text) + .foregroundColor(.white) + .font(.system(size: HomeScreen__itemCircleFontSize, weight: HomeScreen__itemCircleFontWeight)) + .padding(.horizontal, HomeScreen__itemCircleHPadding) + .frame(height: HomeScreen__itemCircleHeight) + .background(roundedShape.fill(bgColor)) + .padding(.trailing, homeTaskUi.taskUi.tf.paused != nil ? 9 : HomeScreen__itemCircleMarginTrailing) + } + + if homeTaskUi.taskUi.tf.paused != nil { + ZStack { + Image(systemName: "pause") + .foregroundColor(.white) + .font(.system(size: 12, weight: .black)) + } + .frame(width: HomeScreen__itemCircleHeight, height: HomeScreen__itemCircleHeight) + .background(roundedShape.fill(.green)) + .padding(.trailing, 8) + } + + if let activityUi = homeTaskUi.taskUi.activityUi { + let symbol: Symbol = activityUi.symbol + HStack { + + let offsetX: CGFloat = { + if symbol is Symbol.Letter { + return 0 + } + if symbol is Symbol.Icon { + return -2 + } + if symbol is Symbol.Emoji { + return -2 + } + return 0 + }() + + SymbolView( + symbol: symbol, + color: activityUi.colorRgba.toColor(), + letterSize: HomeScreen__primaryFontSize, + iconSize: 15, + emojiSize: HomeScreen__itemCircleFontSize, + ) + .offset(x: offsetX) + + Spacer() + } + .frame(width: 20) + } + + Text(homeTaskUi.text) + .font(.system(size: HomeScreen__primaryFontSize)) + .foregroundColor(.white) + .padding(.trailing, 4) + + Spacer() + + if let timeUi = homeTaskUi.timeUi { + let noteColor: Color = switch timeUi.status { + case .in: .secondary + case .soon: .blue + case .overdue: .red + default: fatalError("timeUi.status noteColor not handled") + } + Text(timeUi.note) + .foregroundColor(noteColor) + .font(.system(size: HomeScreen__primaryFontSize)) + .padding(.trailing, HomeScreen__itemCircleHPadding) + } + + if (homeState.taskFolderUi.activityDb != nil) && (taskDb.isToday || taskDb.isTomorrow) { + HomeTasksFolderButton( + taskFolderUi: homeTaskUi.taskUi.taskFolderUi, + color: taskDb.isToday ? .orange : .indigo, + onClick: { + homeTaskUi.taskUi.updateTaskFolder( + taskFolderDb: homeState.taskFolderUi.taskFolderDb, + ) + }, + ) + } + } + .frame(height: HomeScreen__itemHeight) + .padding(.leading, HomeScreen__hPadding) + } + ) + .background(.black) + .offset(x: xSwipeOffset) + .highPriorityGesture(gesture) + } + } + + // https://stackoverflow.com/a/79037514 + private var gesture: some Gesture { + DragGesture(minimumDistance: 25, coordinateSpace: .global) + .onChanged { value in + xSwipeOffset = value.translation.width + } + .onEnded { value in + if value.translation.width < -80 { + xSwipeOffset = (width ?? 999) * -1 + } else if value.translation.width > 60 { + xSwipeOffset = width ?? 999 + } else { + xSwipeOffset = 0 + } + } + } +} diff --git a/apple/iphone/UI/Home/Tasks/HomeTasksBarView.swift b/apple/iphone/UI/Home/Tasks/HomeTasksBarView.swift new file mode 100644 index 000000000..02c524403 --- /dev/null +++ b/apple/iphone/UI/Home/Tasks/HomeTasksBarView.swift @@ -0,0 +1,54 @@ +import SwiftUI +import shared + +struct HomeTasksBarView: View { + + let tasksBarUi: HomeTasksBarUi + let changeTaskFolder: (TaskFolderUi) -> Void + + /// + + @Environment(Navigation.self) private var navigation + + var body: some View { + HStack { + HStack { + Text("Task..") + .font(.system(size: HomeScreen__primaryFontSize)) + .foregroundColor(.secondary) + .lineLimit(1) + .padding(.trailing, 8) + Spacer() + } + .fillMaxHeight() + .background(.black) // Clickable area + .padding(.trailing, 8) + .onTapGesture { + navigation.showTaskForm(strategy: tasksBarUi.addTaskStrategy) + } + .padding(.leading, homeTasksInnerHPadding) + + ForEach(tasksBarUi.taskFoldersUi, id: \.self) { taskFolderUi in + HomeTasksFolderButton( + taskFolderUi: taskFolderUi, + color: taskFolderUi.taskFolderDb.id != tasksBarUi.taskFolderDb.id ? Color(.systemGray2) : taskFolderUi.colorRgba.toColor(), + onClick: { + changeTaskFolder(taskFolderUi) + }, + ) + } + + HomeTasksCalendarButton( + color: Color(.systemGray2), + onClick: { + navigation.fullScreen { + CalendarTabsView() + } + }, + ) + } + .frame(height: HomeScreen__itemHeight) + .fillMaxWidth() + .padding(.leading, homeTasksOuterHPadding) + } +} diff --git a/apple/iphone/UI/Home/Tasks/HomeTasksCalendarButton.swift b/apple/iphone/UI/Home/Tasks/HomeTasksCalendarButton.swift new file mode 100644 index 000000000..5ce6e53cb --- /dev/null +++ b/apple/iphone/UI/Home/Tasks/HomeTasksCalendarButton.swift @@ -0,0 +1,19 @@ +import SwiftUI +import shared + +struct HomeTasksCalendarButton: View { + + let color: Color + let onClick: () -> Void + + var body: some View { + HomeTasksIconButton( + onClick: onClick, + content: { + Image(systemName: "calendar") + .font(.system(size: 20, weight: .regular)) + .foregroundColor(color) + }, + ) + } +} diff --git a/apple/iphone/UI/Home/Tasks/HomeTasksFolderButton.swift b/apple/iphone/UI/Home/Tasks/HomeTasksFolderButton.swift new file mode 100644 index 000000000..d5c882cb0 --- /dev/null +++ b/apple/iphone/UI/Home/Tasks/HomeTasksFolderButton.swift @@ -0,0 +1,60 @@ +import SwiftUI +import shared + +private let iconSize: CGFloat = 19 +private let letterSize: CGFloat = 24 + +struct HomeTasksFolderButton: View { + + let taskFolderUi: TaskFolderUi + let color: Color + let onClick: () -> Void + + var body: some View { + + /* + // todo + val scaleAnimation = remember { Animatable(1f) } + + // todo + LaunchedEffect(Unit) { + homeTasksBarFolderAnimateFlow.collect { folderId -> + if (folderId == taskFolderUi.taskFolderDb.id) { + scaleAnimation.animateTo(1.40f) + scaleAnimation.animateTo(1f) + scaleAnimation.animateTo(1.25f) + scaleAnimation.animateTo(1f) + } + } + } + */ + + Button( + action: { + onClick() + }, + label: { + ZStack { + if (taskFolderUi.taskFolderDb.isToday) { + Image(systemName: "sun.min.fill") + .font(.system(size: iconSize, weight: .semibold)) + .foregroundColor(color) + } else if (taskFolderUi.taskFolderDb.isTomorrow) { + Image(systemName: "moon.fill") + .font(.system(size: iconSize, weight: .semibold)) + .foregroundColor(color) + } else { + SymbolView( + symbol: taskFolderUi.symbol, + color: color, + letterSize: letterSize, + iconSize: iconSize, + emojiSize: letterSize, + ) + } + } + }, + ) + .frame(width: HomeScreen__itemHeight, height: HomeScreen__itemHeight) + } +} diff --git a/apple/iphone/UI/Home/Tasks/HomeTasksIconButton.swift b/apple/iphone/UI/Home/Tasks/HomeTasksIconButton.swift new file mode 100644 index 000000000..5944eca57 --- /dev/null +++ b/apple/iphone/UI/Home/Tasks/HomeTasksIconButton.swift @@ -0,0 +1,21 @@ +import SwiftUI +import shared + +struct HomeTasksIconButton: View { + + let onClick: () -> Void + @ViewBuilder let content: () -> Content + + var body: some View { + ZStack { + content() + } + .frame( + width: HomeScreen__itemHeight, + height: HomeScreen__itemHeight, + ) + .onTapGesture { + onClick() + } + } +} diff --git a/apple/iphone/UI/Home/Tasks/HomeTasksTomorrowView.swift b/apple/iphone/UI/Home/Tasks/HomeTasksTomorrowView.swift new file mode 100644 index 000000000..a33bd802d --- /dev/null +++ b/apple/iphone/UI/Home/Tasks/HomeTasksTomorrowView.swift @@ -0,0 +1,63 @@ +import SwiftUI +import shared + +struct HomeTasksTomorrowView: View { + + let tomorrowUi: HomeTasksItemUi.HomeTomorrowItemUi + + var body: some View { + + HStack { + + TypeIcon( + type: tomorrowUi.type, + ) + + if let timeUi = tomorrowUi.timeUi { + Text(timeUi.text) + .foregroundColor(.white) + .font(.system(size: HomeScreen__itemCircleFontSize, weight: HomeScreen__itemCircleFontWeight)) + .padding(.horizontal, HomeScreen__itemCircleHPadding) + .frame(height: HomeScreen__itemCircleHeight) + .background(roundedShape.fill(.blue)) + .padding(.trailing, HomeScreen__itemCircleMarginTrailing) + } + + Text(tomorrowUi.text) + .font(.system(size: HomeScreen__primaryFontSize)) + .foregroundColor(.white) + .padding(.trailing, 4) + + Spacer() + } + .frame(height: HomeScreen__itemHeight) + .padding(.leading, HomeScreen__hPadding) + } +} + +private struct TypeIcon: View { + + let type: HomeTasksItemUi.HomeTomorrowItemUiTomorrowType + + var body: some View { + ZStack { + Image(systemName: { + switch type { + case .repeating: return "repeat" + case .calendar: return "calendar" + default: return "" + } + }()) + .foregroundColor(.blue) + .font(.system(size: 18)) + } + .fillMaxHeight() + .padding(.trailing, { + switch type { + case .repeating: return 9 + case .calendar: return 8 + default: return 0 + } + }()) + } +} diff --git a/apple/iphone/UI/Home/Tasks/HomeTasksView.swift b/apple/iphone/UI/Home/Tasks/HomeTasksView.swift new file mode 100644 index 000000000..95d5c6887 --- /dev/null +++ b/apple/iphone/UI/Home/Tasks/HomeTasksView.swift @@ -0,0 +1,41 @@ +import SwiftUI +import shared + +struct HomeTasksView: View { + + let homeVm: HomeVm + let homeState: HomeVm.State + + var body: some View { + + VStack { + + if !homeState.taskFolderUi.taskFolderDb.isToday { + Text(homeState.taskFolderUi.taskFolderDb.name) + .font(.system(size: 30, weight: .bold)) + .foregroundColor(.primary) + .padding(.leading, HomeScreen__hPadding) + .padding(.bottom, 8) + .textAlign(.leading) + } + + ScrollView(showsIndicators: false) { + VStack { + ForEach(homeState.homeTasksItemsUi.reversed(), id: \.id) { itemUi in + if let homeTaskUi = itemUi as? HomeTasksItemUi.HomeTaskUi { + HomeTaskView( + homeTaskUi: homeTaskUi, + homeState: homeState, + ) + } else if let homeTomorrowItemUi = itemUi as? HomeTasksItemUi.HomeTomorrowItemUi { + HomeTasksTomorrowView( + tomorrowUi: homeTomorrowItemUi, + ) + } + } + } + } + .defaultScrollAnchor(.bottom) + } + } +} diff --git a/apple/iphone/UI/Home/Tasks/homeTasksInnerHPadding.swift b/apple/iphone/UI/Home/Tasks/homeTasksInnerHPadding.swift new file mode 100644 index 000000000..db749bf28 --- /dev/null +++ b/apple/iphone/UI/Home/Tasks/homeTasksInnerHPadding.swift @@ -0,0 +1,3 @@ +import Foundation + +let homeTasksInnerHPadding: CGFloat = 7 diff --git a/apple/iphone/UI/Home/Tasks/homeTasksOuterHPadding.swift b/apple/iphone/UI/Home/Tasks/homeTasksOuterHPadding.swift new file mode 100644 index 000000000..581d3ec3a --- /dev/null +++ b/apple/iphone/UI/Home/Tasks/homeTasksOuterHPadding.swift @@ -0,0 +1,4 @@ +import Foundation + +let homeTasksOuterHPadding: CGFloat = + HomeScreen__hPadding - homeTasksInnerHPadding diff --git a/apple/iphone/UI/Main/MainScreen.swift b/apple/iphone/UI/Main/MainScreen.swift index d2243adce..cad88ea41 100644 --- a/apple/iphone/UI/Main/MainScreen.swift +++ b/apple/iphone/UI/Main/MainScreen.swift @@ -29,10 +29,6 @@ struct MainScreen: View { .attachNavigation() .zIndex(tab == .home ? 1 : 0) - TasksTabView(tab: $tab) - .attachNavigation() - .zIndex(tab == .tasks ? 1 : 0) - SettingsScreen(tab: $tab) .attachNavigation() .zIndex(tab == .settings ? 1 : 0) diff --git a/apple/iphone/UI/Main/MainTabEnum.swift b/apple/iphone/UI/Main/MainTabEnum.swift index 744fe043f..9fd09c788 100644 --- a/apple/iphone/UI/Main/MainTabEnum.swift +++ b/apple/iphone/UI/Main/MainTabEnum.swift @@ -1,6 +1,5 @@ enum MainTabEnum { case home case activity - case tasks case settings } diff --git a/apple/iphone/UI/Main/MainTabsView.swift b/apple/iphone/UI/Main/MainTabsView.swift index 75fb76248..3911f2091 100644 --- a/apple/iphone/UI/Main/MainTabsView.swift +++ b/apple/iphone/UI/Main/MainTabsView.swift @@ -68,7 +68,8 @@ private struct MainTabsViewInner: View { Button( action: { - tab = (tab == .home ? .tasks : .home) + tab = .home + HomeVm.companion.showStartScreen() }, label: { diff --git a/apple/iphone/UI/Readme/Readme2FullScreen.swift b/apple/iphone/UI/Readme/Readme2FullScreen.swift deleted file mode 100644 index 3886d9b89..000000000 --- a/apple/iphone/UI/Readme/Readme2FullScreen.swift +++ /dev/null @@ -1,77 +0,0 @@ -import SwiftUI - -private let pTextLineHeight = 3.2 - -struct Readme2FullScreen: View { - - @Environment(\.dismiss) private var dismiss - - var body: some View { - ScrollView(showsIndicators: false) { - - TextView(text: Text("The main feature of this app is ") + Text("goals").underline() + Text(":")) - - ScreenshotView(image: "readme_goals") - - TextView(text: Text("Tap a goal to start a timer with the remaining time for that goal:")) - - ScreenshotView(image: "readme_timer") - - TextView(text: Text("Timer is running ") + - Text("all the time").underline() + - Text(", 24/7, even for sleep or breakfast. ") + - Text("No stop option!").underline() + - Text(" To stop the current goal, you have to start the next one.") - ) - - TextView(text: Text("You can add a checklist for goals. Useful for morning/evening routines, work, exercises, etc. Like this:")) - - ScreenshotView(image: "readme_checklist") - - TextView(text: Text("This way I control my time and don't forget anything.")) - - TextView(text: Text("Try to adapt it to your life.")) - - TextView(text: Text("Best regards,\nIvan")) - .padding(.bottom, 16) - } - .navigationTitle("How to Use the App") - .toolbar { - ToolbarItem(placement: .primaryAction) { - Button("Done") { - dismiss() - } - .fontWeight(.semibold) - } - } - } -} - -private struct TextView: View { - - let text: Text - - var body: some View { - text - .textAlign(.leading) - .foregroundColor(.primary) - .padding(.horizontal, H_PADDING) - .lineSpacing(pTextLineHeight) - .padding(.vertical, 4) - } -} - -private struct ScreenshotView: View { - - let image: String - - var body: some View { - Image(image) - .resizable() - .aspectRatio(contentMode: .fit) - .cornerRadius(16) - .shadow(color: .primary, radius: onePx) - .padding(.horizontal, 8) - .padding(.vertical, 4) // Paddings for shadow radius - } -} diff --git a/apple/iphone/UI/Readme/ReadmeFullScreen.swift b/apple/iphone/UI/Readme/ReadmeFullScreen.swift deleted file mode 100644 index 627aabd5d..000000000 --- a/apple/iphone/UI/Readme/ReadmeFullScreen.swift +++ /dev/null @@ -1,82 +0,0 @@ -import SwiftUI -import shared - -struct ReadmeFullScreen: View { - - let defaultItem: ReadmeVm.DefaultItem - - var body: some View { - VmView({ - ReadmeVm(defaultItem: defaultItem) - }) { vm, state in - let state = vm.state.value as! ReadmeVm.State - ReadmeFullScreenInner( - vm: vm, - state: state, - selectedTab: state.tabUi - ) - } - } -} - -private struct ReadmeFullScreenInner: View { - - let vm: ReadmeVm - let state: ReadmeVm.State - - @State var selectedTab: ReadmeVm.TabUi - - /// - - @Environment(\.dismiss) private var dismiss - @State private var isScrolled: Bool = false - - var body: some View { - - ZStack { - ReadmeTabView(tabUi: state.tabUi, isScrolled: $isScrolled) - .id("tab_\(state.tabUi.id)") // To scroll to top - } - .safeAreaInset(edge: .top) { - - VStack { - - ZStack { - - HStack { - Spacer() - Button("Done") { - dismiss() - } - .fontWeight(.semibold) - } - .padding(.horizontal, 16) - - Text(state.title) - .fontWeight(.semibold) - .foregroundColor(.primary) - } - .padding(.top, 6) - - Picker("", selection: $selectedTab) { - ForEach(state.tabsUi, id: \.id) { tabUi in - Text(tabUi.title) - .tag(tabUi) - } - } - .pickerStyle(.segmented) - .padding(.horizontal, 16) - .padding(.top, 18) - .padding(.bottom, 12) - - Color(isScrolled ? .systemGray5 : .clear).frame(height: onePx) - } - .background(isScrolled ? AnyShapeStyle(.bar) : AnyShapeStyle(.clear)) - } - .navigationBarHidden(true) - .statusBarHidden(true) - .onChange(of: selectedTab) { _, new in - vm.setTabUi(tabUi: new) - } - } -} diff --git a/apple/iphone/UI/Readme/ReadmeImagesFullScreen.swift b/apple/iphone/UI/Readme/ReadmeImagesFullScreen.swift deleted file mode 100644 index f94db0a0a..000000000 --- a/apple/iphone/UI/Readme/ReadmeImagesFullScreen.swift +++ /dev/null @@ -1,62 +0,0 @@ -import SwiftUI - -struct ReadmeImagesFullScreen: View { - - let images: [String] - - @Environment(\.dismiss) private var dismiss - - var body: some View { - - VStack { - - ScrollView(.horizontal, showsIndicators: false) { - - HStack { - - ForEach(images, id: \.self) { item in - - Image(item) - .resizable() - .aspectRatio(contentMode: .fit) - .cornerRadius(16) - .shadow(color: .primary, radius: onePx) - .fillMaxHeight() - .padding(.trailing, 12) - .padding(.bottom, 8) // Paddings for shadow radius - } - } - .scrollTargetLayout() - } - .contentMargins(.leading, 16, for: .scrollContent) - .contentMargins(.trailing, 4, for: .scrollContent) - .scrollTargetBehavior(.viewAligned) - } - .toolbar { - - ToolbarItem(placement: .topBarTrailing) { - - Button( - action: { - dismiss() - }, - label: { - Text("Done") - .fontWeight(.bold) - .foregroundColor(.blue) - } - ) - } - } - .gesture( - DragGesture().onEnded { value in - let xDiffAbs = abs(value.location.x - value.startLocation.x) - let yDiff = value.location.y - value.startLocation.y - if yDiff > 50, xDiffAbs < 50 { - dismiss() - } - } - ) - .attachNavigation() - } -} diff --git a/apple/iphone/UI/Readme/ReadmeImagesPreview.swift b/apple/iphone/UI/Readme/ReadmeImagesPreview.swift deleted file mode 100644 index a6f53d98b..000000000 --- a/apple/iphone/UI/Readme/ReadmeImagesPreview.swift +++ /dev/null @@ -1,46 +0,0 @@ -import SwiftUI - -struct ReadmeImagesPreview: View { - - let images: [String] - - @State private var isFullScreenPresented = false - - var body: some View { - - ScrollView(.horizontal, showsIndicators: false) { - - HStack { - - ForEach(images, id: \.self) { item in - - Button( - action: { - isFullScreenPresented = true - }, - label: { - Image(item) - .resizable() - .aspectRatio(contentMode: .fit) - .cornerRadius(16) - .shadow(color: .primary, radius: onePx) - .frame(height: 350) - .padding(.trailing, 8) - .padding(.vertical, 4) // Paddings for shadow radius - } - ) - } - } - .scrollTargetLayout() - } - .contentMargins(.leading, 16, for: .scrollContent) - .contentMargins(.trailing, 8, for: .scrollContent) - .scrollTargetBehavior(.viewAligned) - .padding(.top, 20) - .fullScreenCover(isPresented: $isFullScreenPresented) { - ReadmeImagesFullScreen( - images: images - ) - } - } -} diff --git a/apple/iphone/UI/Readme/ReadmeTabView.swift b/apple/iphone/UI/Readme/ReadmeTabView.swift deleted file mode 100644 index 33972efc0..000000000 --- a/apple/iphone/UI/Readme/ReadmeTabView.swift +++ /dev/null @@ -1,260 +0,0 @@ -import SwiftUI -import shared - -private let pTextLineHeight = 3.2 - -struct ReadmeTabView: View { - - let tabUi: ReadmeVm.TabUi - @Binding var isScrolled: Bool - - var body: some View { - - List { - - let paragraphs = tabUi.paragraphs - - ForEachIndexed(paragraphs) { idx, paragraph in - - let prevP: ReadmeVm.Paragraph? = (idx == 0) ? nil : paragraphs[idx - 1] - - ZStack { - - if let paragraph = paragraph as? ReadmeVm.ParagraphTitle { - let paddingTop: CGFloat = { - guard let prevP = prevP else { - return 12 - } - if prevP.isSlider { - return 40 - } - if prevP is ReadmeVm.ParagraphText { - return 39 - } - fatalError() - }() - HStack { - Text(paragraph.text) - .foregroundColor(.primary) - .font(.system(size: 27, weight: .heavy)) - .padding(.top, paddingTop) - .padding(.horizontal, H_PADDING) - Spacer() - } - } else if let paragraph = paragraph as? ReadmeVm.ParagraphText { - let paddingTop: CGFloat = { - guard let prevP = prevP else { - return 14 - } - if prevP.isSlider { - return 16 - } - if prevP is ReadmeVm.ParagraphTitle { - return 17 - } - if prevP is ReadmeVm.ParagraphText { - return 16 - } - if prevP is ReadmeVm.ParagraphTextHighlight { - return 20 - } - fatalError() - }() - HStack { - Text(paragraph.text) - .foregroundColor(.primary) - .padding(.top, paddingTop) - .padding(.horizontal, H_PADDING) - .lineSpacing(pTextLineHeight) - Spacer() - } - } else if let paragraph = paragraph as? ReadmeVm.ParagraphTextHighlight { - let paddingTop: CGFloat = { - guard let prevP = prevP else { - fatalError() - } - if prevP.isSlider { - return 24 - } - if prevP is ReadmeVm.ParagraphText { - return 23 - } - fatalError() - }() - HStack { - Text(paragraph.text) - .foregroundColor(.primary) - .padding(.vertical, 13) - .padding(.horizontal, 14) - .lineSpacing(pTextLineHeight) - Spacer() - } - .background(squircleShape.fill(.blue)) - .padding(.top, paddingTop) - .padding(.horizontal, H_PADDING - 2) - } else if let paragraph = paragraph as? ReadmeVm.ParagraphAskAQuestion { - AskQuestionView( - subject: paragraph.subject - ) { - Text(paragraph.title) - .foregroundColor(.blue) - } - .padding(.top, 24) - .padding(.leading, H_PADDING) - } else if paragraph is ReadmeVm.ParagraphTimerTypical { - ReadmeImagesPreview( - images: [ - "readme_timer_1" - ] - ) - } else if paragraph is ReadmeVm.ParagraphTimerMyActivities { - ReadmeImagesPreview( - images: [ - "readme_activities_1" - ] - ) - } else if paragraph is ReadmeVm.ParagraphTimerCharts { - ReadmeImagesPreview( - images: [ - "readme_chart_1", - "readme_chart_2", - "readme_chart_3" - ] - ) - } else if paragraph is ReadmeVm.ParagraphTimerPractice1 { - ReadmeImagesPreview( - images: [ - "readme_timer_practice_1", - "readme_timer_practice_2", - "readme_timer_practice_3", - "readme_timer_practice_4" - ] - ) - } else if paragraph is ReadmeVm.ParagraphTimerPractice2 { - ReadmeImagesPreview( - images: [ - "readme_timer_practice_5", - "readme_chart_2", - "readme_chart_3" - ] - ) - } else if paragraph is ReadmeVm.ParagraphRepeatingsMy { - ReadmeImagesPreview( - images: [ - "readme_repeatings_1" - ] - ) - } else if paragraph is ReadmeVm.ParagraphRepeatingsToday { - ReadmeImagesPreview( - images: [ - "readme_repeatings_2" - ] - ) - } else if paragraph is ReadmeVm.ParagraphRepeatingsPractice1 { - ReadmeImagesPreview( - images: [ - "readme_repeating_practice_1", - "readme_repeating_practice_2", - "readme_repeating_practice_3" - ] - ) - } else if paragraph is ReadmeVm.ParagraphRepeatingsPractice2 { - ReadmeImagesPreview( - images: [ - "readme_repeating_practice_4", - "readme_repeating_practice_5" - ] - ) - } else if paragraph is ReadmeVm.ParagraphChecklistsExamples { - ReadmeImagesPreview( - images: [ - "readme_checklists_1", - "readme_checklists_2", - "readme_checklists_3" - ] - ) - } else if paragraph is ReadmeVm.ParagraphChecklistsPractice1 { - ReadmeImagesPreview( - images: [ - "readme_checklists_practice_1", - "readme_checklists_practice_2", - "readme_checklists_practice_3", - "readme_checklists_practice_4", - "readme_checklists_practice_5", - "readme_checklists_practice_6", - "readme_checklists_practice_7" - ] - ) - } else if paragraph is ReadmeVm.ParagraphChecklistsPractice2 { - ReadmeImagesPreview( - images: [ - "readme_checklists_practice_8", - "readme_checklists_practice_9" - ] - ) - } else if paragraph is ReadmeVm.ParagraphPomodoroExamples { - ReadmeImagesPreview( - images: [ - "readme_pomodoro_1", - "readme_pomodoro_2", - "readme_pomodoro_3", - ] - ) - } else if paragraph is ReadmeVm.ParagraphGoalsExamples { - ReadmeImagesPreview( - images: [ - "readme_goals_1" - ] - ) - } else if paragraph is ReadmeVm.ParagraphCalendarExamples { - ReadmeImagesPreview( - images: [ - "readme_calendar_1", - "readme_calendar_2" - ] - ) - } else { - fatalError() - } - } - } - .customListItem() - - // Not contentMargin to right scroll bar - ZStack {} - .padding(.top, 8) - .customListItem() - } - .onScrolled { new in - DispatchQueue.main.async { - isScrolled = new - } - } - .customList() - .scrollTargetLayout() - } -} - -/// - -private extension View { - - func onScrolled( - action: @escaping (Bool) -> Void - ) -> some View { - if #available(iOS 18.0, *) { - return onScrollGeometryChange( - for: Double.self, - of: { geometry in - let realScroll = geometry.contentInsets.top + geometry.contentOffset.y - action(realScroll > 15) - return geometry.contentSize.height - }, - action: { _, _ in } - ) - } else { - action(true) - return self - } - } -} diff --git a/apple/iphone/UI/Repeatings/Form/RepeatingFormSheet.swift b/apple/iphone/UI/Repeatings/Form/RepeatingFormSheet.swift index e053d772d..963fa82f0 100644 --- a/apple/iphone/UI/Repeatings/Form/RepeatingFormSheet.swift +++ b/apple/iphone/UI/Repeatings/Form/RepeatingFormSheet.swift @@ -73,6 +73,9 @@ private struct RepeatingFormSheetInner: View { ) } ) + } + + Section { NavigationLinkSheet( label: { diff --git a/apple/iphone/UI/Tasks/Tab/Repeatings/TasksTabRepeatingsView.swift b/apple/iphone/UI/Repeatings/List/RepeatingsListFullScreen.swift similarity index 71% rename from apple/iphone/UI/Tasks/Tab/Repeatings/TasksTabRepeatingsView.swift rename to apple/iphone/UI/Repeatings/List/RepeatingsListFullScreen.swift index 5515e0d3f..abe1f677d 100644 --- a/apple/iphone/UI/Tasks/Tab/Repeatings/TasksTabRepeatingsView.swift +++ b/apple/iphone/UI/Repeatings/List/RepeatingsListFullScreen.swift @@ -1,26 +1,27 @@ import SwiftUI import shared -struct TasksTabRepeatingsView: View { +struct RepeatingsListFullScreen: View { var body: some View { VmView({ - TasksTabRepeatingsVm() + RepeatingsListVm() }) { vm, state in - let state = vm.state.value as! TasksTabRepeatingsVm.State - TasksTabRepeatingsViewInner( - state: state + let state = vm.state.value as! RepeatingsListVm.State + RepeatingsListViewInner( + state: state, ) } } } -private struct TasksTabRepeatingsViewInner: View { +private struct RepeatingsListViewInner: View { - let state: TasksTabRepeatingsVm.State + let state: RepeatingsListVm.State /// + @Environment(\.dismiss) private var dismiss @Environment(Navigation.self) private var navigation @Environment(\.defaultMinListRowHeight) private var minListRowHeight @@ -33,7 +34,7 @@ private struct TasksTabRepeatingsViewInner: View { let repeatingsUi = state.repeatingsUi.reversed() ForEach(repeatingsUi, id: \.repeatingDb.id) { repeatingUi in - TasksTabRepeatingsItemView( + RepeatingsListItemView( repeatingUi: repeatingUi, withTopDivider: repeatingsUi.first != repeatingUi, ) @@ -62,5 +63,14 @@ private struct TasksTabRepeatingsViewInner: View { .padding(.horizontal, H_PADDING) } .defaultScrollAnchor(.bottom) + .toolbarTitleDisplayMode(.inline) + .navigationTitle("Repeating Tasks") + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Close") { + dismiss() + } + } + } } } diff --git a/apple/iphone/UI/Tasks/Tab/Repeatings/TasksTabRepeatingsItemView.swift b/apple/iphone/UI/Repeatings/List/RepeatingsListItemView.swift similarity index 96% rename from apple/iphone/UI/Tasks/Tab/Repeatings/TasksTabRepeatingsItemView.swift rename to apple/iphone/UI/Repeatings/List/RepeatingsListItemView.swift index 8872a4220..bff06a761 100644 --- a/apple/iphone/UI/Tasks/Tab/Repeatings/TasksTabRepeatingsItemView.swift +++ b/apple/iphone/UI/Repeatings/List/RepeatingsListItemView.swift @@ -1,9 +1,9 @@ import SwiftUI import shared -struct TasksTabRepeatingsItemView: View { +struct RepeatingsListItemView: View { - let repeatingUi: TasksTabRepeatingsVm.RepeatingUi + let repeatingUi: RepeatingsListVm.RepeatingUi let withTopDivider: Bool /// diff --git a/apple/iphone/UI/Settings/SettingsScreen.swift b/apple/iphone/UI/Settings/SettingsScreen.swift index 55b34482d..9ce1029cc 100644 --- a/apple/iphone/UI/Settings/SettingsScreen.swift +++ b/apple/iphone/UI/Settings/SettingsScreen.swift @@ -15,7 +15,6 @@ struct SettingsScreen: View { vm: vm, state: state, tab: $tab, - todayOnHomeScreen: state.todayOnHomeScreen, ) } } @@ -28,8 +27,6 @@ private struct SettingsScreenInner: View { @Binding var tab: MainTabEnum - @State var todayOnHomeScreen: Bool - /// @Environment(Navigation.self) private var navigation @@ -55,8 +52,9 @@ private struct SettingsScreenInner: View { .foregroundColor(.primary) }, fullScreen: { - // ReadmeFullScreen(defaultItem: .basics) - Readme2FullScreen() + DocFullScreen( + forceRead: false, + ) } ) @@ -70,7 +68,17 @@ private struct SettingsScreenInner: View { } } - Section("GOALS") { + Section { + + Button("Repeating Tasks") { + navigation.fullScreen { + RepeatingsListFullScreen() + } + } + .foregroundColor(.primary) + } + + Section("ACTIVITIES") { ForEach(state.activitiesUi, id: \.activityDb.id) { activityUi in let leadingPadding: CGFloat = CGFloat(activityUi.nestedLevel * 12) @@ -195,7 +203,7 @@ private struct SettingsScreenInner: View { } } - Button("New Goal") { + Button("New Activity") { navigation.sheet { ActivityFormSheet( activityDb: nil, @@ -387,14 +395,6 @@ private struct SettingsScreenInner: View { } ) - Toggle( - state.todayOnHomeScreenText, - isOn: $todayOnHomeScreen - ) - .onChange(of: todayOnHomeScreen) { _, new in - vm.setTodayOnHomeScreen(isOn: new) - } - if !isLiveActivityGranted { HStack { Text("Live Activities Not Granted") diff --git a/apple/iphone/UI/Symbol/SymbolEmojiView.swift b/apple/iphone/UI/Symbol/SymbolEmojiView.swift new file mode 100644 index 000000000..12d76784a --- /dev/null +++ b/apple/iphone/UI/Symbol/SymbolEmojiView.swift @@ -0,0 +1,13 @@ +import SwiftUI +import shared + +struct SymbolEmojiView: View { + + let emoji: Symbol.Emoji + let size: CGFloat + + var body: some View { + Text(emoji.emoji) + .font(.system(size: size, weight: .semibold)) + } +} diff --git a/apple/iphone/UI/Symbol/SymbolIconView.swift b/apple/iphone/UI/Symbol/SymbolIconView.swift new file mode 100644 index 000000000..e5278a451 --- /dev/null +++ b/apple/iphone/UI/Symbol/SymbolIconView.swift @@ -0,0 +1,49 @@ +import SwiftUI +import shared + +struct SymbolIconView: View { + + let icon: Symbol.Icon + let color: Color + let size: CGFloat + + var body: some View { + Image(systemName: icon.systemName()) + .font(.system(size: size, weight: .semibold)) + .foregroundColor(color) + } +} + +private extension Symbol.Icon { + + func systemName() -> String { + switch self.iconEnum { + case .book: "book.closed.fill" + case .case_: "case.fill" + case .timer: "timer" + case .exercise: "dumbbell.fill" + case .piano: "pianokeys.inverse" + case .musicNote: "music.note" + case .option: "option" + case .graduationcap: "graduationcap.fill" + case .megaphone: "megaphone.fill" + case .instruments: "wrench.and.screwdriver.fill" + case .meditation: "figure.mind.and.body" + case .rocket: "airplane.up.forward" // todo no rocket symbol :( + case .flask: "flask.fill" + case .compass: "compass.drawing" + case .gamecontroller: "gamecontroller.fill" + case .soccerball: "soccerball.inverse" + case .hiking: "figure.hiking" + case .bus: "bus" + case .bulb: "lightbulb.fill" + case .bolt: "bolt.fill" + case .inbox: "tray.fill" + case .sun: "sun.min.fill" + case .moon: "moon.fill" + case .moonStars: "moon.stars.fill" + case .question: "questionmark" + default: "questionmark" + } + } +} diff --git a/apple/iphone/UI/Symbol/SymbolLetterPickerSheet.swift b/apple/iphone/UI/Symbol/SymbolLetterPickerSheet.swift new file mode 100644 index 000000000..13fbea4cc --- /dev/null +++ b/apple/iphone/UI/Symbol/SymbolLetterPickerSheet.swift @@ -0,0 +1,56 @@ +import SwiftUI +import shared + +struct SymbolLetterPickerSheet: View { + + let onPick: (Symbol.Letter) -> Void + + /// + + @State var text: String = "" + + @FocusState private var isFocused: Bool + @Environment(\.dismiss) private var dismiss + @Environment(Navigation.self) private var navigation + + var body: some View { + + List { + + TextField( + text: $text + ) { + } + .focused($isFocused) + } + .myFormContentMargins() + .interactiveDismissDisabled() + .toolbarTitleDisplayMode(.inline) + .navigationTitle("Symbol") + .toolbar { + + ToolbarItem(placement: .cancellationAction) { + Button("Cancel") { + dismiss() + } + } + + ToolbarItem(placement: .primaryAction) { + Button("Done") { + SymbolLetterPickerUtils.shared.validateLetter( + letter: text, + dialogsManager: navigation, + onSuccess: { symbolLetter in + onPick(symbolLetter) + dismiss() + }, + ) + } + .fontWeight(.semibold) + } + } + .onAppear { + isFocused = true + } + } +} diff --git a/apple/iphone/UI/Symbol/SymbolLetterView.swift b/apple/iphone/UI/Symbol/SymbolLetterView.swift new file mode 100644 index 000000000..d3cfb56d9 --- /dev/null +++ b/apple/iphone/UI/Symbol/SymbolLetterView.swift @@ -0,0 +1,15 @@ +import SwiftUI +import shared + +struct SymbolLetterView: View { + + let letter: Symbol.Letter + let color: Color + let size: CGFloat + + var body: some View { + Text(letter.letter) + .font(.system(size: size, weight: .semibold)) + .foregroundColor(color) + } +} diff --git a/apple/iphone/UI/Symbol/SymbolPickerSheet.swift b/apple/iphone/UI/Symbol/SymbolPickerSheet.swift new file mode 100644 index 000000000..de2318cfd --- /dev/null +++ b/apple/iphone/UI/Symbol/SymbolPickerSheet.swift @@ -0,0 +1,96 @@ +import SwiftUI +import shared + +struct SymbolPickerSheet: View { + + let onPick: (Symbol) -> Void + + var body: some View { + VmView({ SymbolPickerVm() }) { vm, _ in + let state = vm.state.value as! SymbolPickerVm.State + SymbolPickerSheetInner( + state: state, + onPick: onPick, + ) + } + } +} + +private struct SymbolPickerSheetInner: View { + + let state: SymbolPickerVm.State + let onPick: (Symbol) -> Void + + /// + + @Environment(\.dismiss) private var dismiss + @Environment(Navigation.self) private var navigation + + var body: some View { + List { + + Button("Emoji") { + navigation.sheet { + EmojiPickerSheet( + onDone: { emoji in + onPick(Symbol.Emoji(emoji: emoji)) + dismiss() + }, + ) + } + } + .foregroundColor(.blue) + .listRowSeparator(.hidden) + + Button("Symbol") { + navigation.sheet { + SymbolLetterPickerSheet( + onPick: { letter in + onPick(letter) + dismiss() + }, + ) + } + } + .foregroundColor(.blue) + .listRowSeparator(.hidden) + + ForEach(state.symbolChunks, id: \.self) { row in + HStack { + Spacer() + ForEach(row, id: \.self.raw) { symbol in + Button( + action: { + onPick(symbol) + dismiss() + }, + label: { + SymbolView( + symbol: symbol, + color: .white, + letterSize: 23, // No matter + iconSize: 18, + emojiSize: 12, // No matter + ) + } + ) + .buttonStyle(.plain) + Spacer() + } + } + .listRowSeparator(.hidden) + } + } + .listStyle(.plain) + .interactiveDismissDisabled() + .navigationTitle("Icon") + .toolbarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Cancel") { + dismiss() + } + } + } + } +} diff --git a/apple/iphone/UI/Symbol/SymbolView.swift b/apple/iphone/UI/Symbol/SymbolView.swift new file mode 100644 index 000000000..3713874f5 --- /dev/null +++ b/apple/iphone/UI/Symbol/SymbolView.swift @@ -0,0 +1,34 @@ +import SwiftUI +import shared + +struct SymbolView: View { + + let symbol: Symbol + let color: Color + let letterSize: CGFloat + let iconSize: CGFloat + let emojiSize: CGFloat + + var body: some View { + if let letter = symbol as? Symbol.Letter { + SymbolLetterView( + letter: letter, + color: color, + size: letterSize, + ) + } else if let icon = symbol as? Symbol.Icon { + SymbolIconView( + icon: icon, + color: color, + size: iconSize, + ) + } else if let emoji = symbol as? Symbol.Emoji { + SymbolEmojiView( + emoji: emoji, + size: emojiSize, + ) + } else { + // todo handle sealed like enum + } + } +} diff --git a/apple/iphone/UI/Tasks/Folders/TaskFolderFormSheet.swift b/apple/iphone/UI/TaskFolder/TaskFolderFormSheet.swift similarity index 77% rename from apple/iphone/UI/Tasks/Folders/TaskFolderFormSheet.swift rename to apple/iphone/UI/TaskFolder/TaskFolderFormSheet.swift index 49e863a14..ba4d55204 100644 --- a/apple/iphone/UI/Tasks/Folders/TaskFolderFormSheet.swift +++ b/apple/iphone/UI/TaskFolder/TaskFolderFormSheet.swift @@ -51,6 +51,34 @@ private struct TaskFolderFormSheetInner: View { } } + Section { + NavigationLinkSheet( + label: { + HStack { + Text(state.iconTitle) + .foregroundColor(.primary) + Spacer() + if let symbol = state.symbol { + FormButtonSymbolView( + symbol: symbol, + color: .secondary, + ) + } else { + Text("Not Selected") + .foregroundColor(.red) + } + } + }, + sheet: { + SymbolPickerSheet( + onPick: { symbol in + vm.setSymbol(symbol: symbol) + } + ) + }, + ) + } + if state.isActivityAvailable { Section { Picker(state.activityTitle, selection: $activityDb) { diff --git a/apple/iphone/UI/TaskForm/TaskFormFullScreen.swift b/apple/iphone/UI/TaskForm/TaskFormFullScreen.swift index c0c5869f9..ce890760a 100644 --- a/apple/iphone/UI/TaskForm/TaskFormFullScreen.swift +++ b/apple/iphone/UI/TaskForm/TaskFormFullScreen.swift @@ -58,23 +58,6 @@ private struct TaskFormFullScreenInner: View { Section { - Picker(state.activityTitle, selection: $activityDb) { - if activityDb == nil { - Text("None") - .tag(nil as ActivityDb?) // Support optional (nil) selection - } - ForEach(state.activitiesUi, id: \.activityDb) { activityUi in - Text(activityUi.title) - .tag(activityUi.activityDb as ActivityDb?) // Support optional (nil) selection - } - } - .pickerStyle(.menu) - .accentColor(.secondary) - .foregroundColor(.primary) - .onChange(of: activityDb) { _, newActivityDb in - vm.setActivity(activityDb: newActivityDb) - } - NavigationLinkSheet( label: { HStack { @@ -144,6 +127,77 @@ private struct TaskFormFullScreenInner: View { } .listStyle(.plain) + let settingsLogic = state.settingsLogic + if let settingsLogic = settingsLogic as? TaskFormVm.SettingsLogicFixedTaskFolderUi { + HStack { + + Image(systemName: "folder") + .foregroundColor(.secondary) + .font(.system(size: 18)) + .padding(.leading, 14) + .padding(.trailing, 8) + + Text(settingsLogic.title) + .foregroundColor(.white) + + Spacer() + + ForEach(settingsLogic.taskFolderHintsUi, id: \.self) { taskFolderHintUi in + HomeTasksFolderButton( + taskFolderUi: taskFolderHintUi.taskFolderUi, + color: { + if settingsLogic.selectedHintUi != taskFolderHintUi { + return Color(.systemGray2) + } + if taskFolderHintUi.taskFolderUi.taskFolderDb.isToday { + return .orange + } + return .indigo + }(), + onClick: { + vm.setSessionLogic( + sessionLogic: settingsLogic.buildWithNewHint(hintUi: taskFolderHintUi), + ) + }, + ) + } + } + .frame(height: HomeScreen__itemHeight) + .padding(.trailing, 8) + } + else if let settingsLogic = settingsLogic as? TaskFormVm.SettingsLogicActivitiesUi { + HStack { + ScrollView(.horizontal) { + + ZStack {}.frame(width: 4) + + HStack { + + ForEach(settingsLogic.activitiesUi, id: \.self) { activityUi in + let isSelected: Bool = state.activityDb?.id == activityUi.activityDb.id + ZStack { + SymbolView( + symbol: activityUi.symbol, + color: isSelected ? .white : activityUi.colorRgba.toColor(), + letterSize: 16, + iconSize: 16, + emojiSize: HomeScreen__itemCircleFontSize, + ) + } + .frame(width: HomeScreen__itemHeight, height: HomeScreen__itemHeight) + .background(Circle().fill(isSelected ? .blue : .black)) + .onTapGesture { + vm.setActivity(activityDb: activityUi.activityDb) + } + } + } + + ZStack {}.frame(width: 4) + } + } + .frame(height: HomeScreen__itemHeight) + } + HStack { TextField( @@ -181,6 +235,7 @@ private struct TaskFormFullScreenInner: View { } .padding(.vertical, 4) .background(Color(.secondarySystemBackground)) + .padding(.top, 8) } .toolbar { diff --git a/apple/iphone/UI/Tasks/TaskTimerSheet.swift b/apple/iphone/UI/TaskTimer/TaskTimerSheet.swift similarity index 100% rename from apple/iphone/UI/Tasks/TaskTimerSheet.swift rename to apple/iphone/UI/TaskTimer/TaskTimerSheet.swift diff --git a/apple/iphone/UI/Tasks/Tab/Tasks/TasksTabTasksView.swift b/apple/iphone/UI/Tasks/Tab/Tasks/TasksTabTasksView.swift deleted file mode 100644 index 0acd43b5b..000000000 --- a/apple/iphone/UI/Tasks/Tab/Tasks/TasksTabTasksView.swift +++ /dev/null @@ -1,474 +0,0 @@ -import SwiftUI -import shared - -struct TasksTabTasksView: View { - - let taskFolderDb: TaskFolderDb - let tasksTabView: TasksTabViewInner - - var body: some View { - VmView({ - TasksTabTasksVm( - taskFolderDb: taskFolderDb - ) - }) { vm, state in - let state = vm.state.value as! TasksTabTasksVm.State - TasksTabTasksViewInner( - vm: vm, - state: state, - tasksTabView: tasksTabView - ) - } - .id("TasksTabTasksView_\(taskFolderDb.id)") - } -} - -private struct TasksTabTasksViewInner: View { - - let vm: TasksTabTasksVm - let state: TasksTabTasksVm.State - - let tasksTabView: TasksTabViewInner - - /// - - @Environment(Navigation.self) private var navigation - - var body: some View { - - ScrollView(.vertical, showsIndicators: false) { - - VStack { - - if let tmrwUi = state.tmrwUi { - let tmrwTasksUi = tmrwUi.tasksUi.reversed() - ForEach(tmrwTasksUi, id: \.taskDb.id) { taskUi in - let isFirst = tmrwTasksUi.first == taskUi - ZStack(alignment: .top) { - TmrwTaskView(taskUi: taskUi) - .id("tmrw \(taskUi.taskDb.id)") - if !isFirst { - Divider() - .padding(.leading, H_PADDING) - } - } - } - - Divider() - .padding(.horizontal, 80) - .padding(.top, 20) - .padding(.bottom, state.tasksVmUi.isEmpty ? 0 : 20) - } - - let tasksUiReversed = state.tasksVmUi.reversed() - VStack { - ForEach(tasksUiReversed, id: \.taskUi.taskDb.id) { taskVmUi in - TaskRowView( - taskVmUi: taskVmUi, - tasksTabView: tasksTabView, - withDivider: tasksUiReversed.last != taskVmUi - ) - .id(taskVmUi.taskUi.taskDb.id) - } - } - - if let tmrwUi = state.tmrwUi { - HStack { - Spacer() - Text(tmrwUi.curTimeString) - .font(.system(size: 14, weight: .light)) - - Spacer() - } - .padding(.top, 24) - } - - HStack { - - HStack { - - Text("Task") - .foregroundColor(.secondary) - .padding(.leading, 12) - - Spacer() - } - .frame(minHeight: 44) - - Text("SAVE") - .padding(.horizontal, 12) - .font(.system(size: 14, weight: .bold)) - .frame(height: 34) - .foregroundColor(.white) - .background( - RoundedRectangle(cornerRadius: 8, style: .continuous) - .fill(.blue) - ) - .padding(.trailing, 6) - } - // trick x2 overlay looks better - .overlay(squircleShape.stroke(Color(.systemGray4), lineWidth: onePx)) - .overlay(squircleShape.stroke(Color(.systemGray4), lineWidth: onePx)) - .padding(.leading, H_PADDING - 2) - .padding(.top, 20) - .padding(.bottom, 20) - .background(.black) - .onTapGesture { - navigation.showTaskForm( - strategy: TaskFormStrategy.NewTask( - taskFolderDb: vm.taskFolderDb - ) - ) - } - } - } - .padding(.trailing, H_PADDING) - .defaultScrollAnchor(.bottom) - } -} - -private let taskRowButtonStyle = MyButtonStyle() - -private struct TaskRowView: View { - - @Environment(Navigation.self) private var navigation - - private let taskVmUi: TasksTabTasksVm.TaskVmUi - - let tasksTabView: TasksTabViewInner - @State private var dragItem: TasksTabDragItem - - @State private var xSwipeOffset: CGFloat = 0 - @State private var width: CGFloat? = nil - - @State private var itemHeight: CGFloat = 0 - - private let withDivider: Bool - - init( - taskVmUi: TasksTabTasksVm.TaskVmUi, - tasksTabView: TasksTabViewInner, - withDivider: Bool - ) { - self.taskVmUi = taskVmUi - self.tasksTabView = tasksTabView - self.withDivider = withDivider - - _dragItem = State(initialValue: TasksTabDragItem( - isDropAllowed: { drop in - if drop is TasksTabDropItemCalendar { - return true - } - if let dropFolder = drop as? TasksTabDropItemTaskFolder { - return dropFolder.taskFolderDb.id != taskVmUi.taskUi.taskDb.folder_id - } - fatalError("Unknown tasks list drop type") - } - )) - } - - var body: some View { - - ZStack(alignment: .bottom) { - - GeometryReader { proxy in - ZStack { - } - .onAppear { - width = proxy.size.width - } - .onChange(of: proxy.frame(in: .global).minY) { - xSwipeOffset = 0 - _ = tasksTabView.onDragStop() - } - } - .frame(height: 1) // height: 1, or full height items if short - - if (xSwipeOffset > 0) { - let editOrMoveTitle = tasksTabView.focusedDrop != nil ? "Move to \(tasksTabView.focusedDrop!.name)" : "Edit" - HStack { - Text(editOrMoveTitle) - .foregroundColor(.white) - .padding(.leading, 16) - Spacer() - } - .frame(maxHeight: itemHeight) - .background(tasksTabView.focusedDrop == nil ? .blue : .green) - .offset(x: xSwipeOffset > 0 ? 0 : xSwipeOffset) - } - - if (xSwipeOffset < 0) { - - HStack { - - Text(taskVmUi.text) - .padding(.leading, 12) - .padding(.trailing, 4) - .foregroundColor(.white) - .lineLimit(1) - .font(.system(size: 13, weight: .light)) - - Spacer() - - Button("Cancel") { - xSwipeOffset = 0 - } - .foregroundColor(.white) - .padding(.trailing, 12) - - Button( - action: { - taskVmUi.delete() - }, - label: { - Text("Delete") - .fontWeight(.bold) - .padding(.horizontal, 9) - .padding(.vertical, 5) - .foregroundColor(.red) - } - ) - .background( - RoundedRectangle(cornerRadius: 8, style: .continuous) - .fill(.white) - ) - .padding(.trailing, 12) - } - .frame(maxHeight: itemHeight) - .background(.red) - .offset(x: xSwipeOffset < 0 ? 0 : xSwipeOffset) - } - - /// - - ZStack { - - Button( - action: { - hideKeyboard() - taskVmUi.taskUi.taskDb.startIntervalForUi( - ifJustStarted: {}, - ifTimerNeeded: { - navigation.showTaskTimerSheet( - taskDb: taskVmUi.taskUi.taskDb, - ) - } - ) - }, - label: { - - VStack { - - if let timeUi = taskVmUi.timeUi as? TasksTabTasksVm.TaskVmUiTimeUiHighlightUi { - - HStack { - - HStack { - - switch timeUi.timeData.type { - case .event: - Image(systemName: "calendar") - .foregroundColor(.white) - .font(.system(size: 16)) - .padding(.trailing, 3) - case .repeating: - Image(systemName: "repeat") - .foregroundColor(.white) - .font(.system(size: 13, weight: .medium)) - .padding(.trailing, 3) - default: - fatalError() - } - - Text(timeUi.title) - .foregroundColor(.white) - .font(.system(size: 13)) - } - .padding(.leading, 4) - .padding(.trailing, 5) - .padding(.top, 4) - .padding(.bottom, 4) - .background( - RoundedRectangle(cornerRadius: 8, style: .continuous) - .fill(timeUi.backgroundColorEnum.toColor()) - ) - - Text(timeUi.timeLeftText) - .foregroundColor(timeUi.timeLeftColorEnum.toColor()) - .font(.system(size: 14, weight: .light)) - .padding(.leading, 8) - .lineLimit(1) - - Spacer() - } - .padding(.top, 2) - .padding(.bottom, 6) - .padding(.leading, H_PADDING - 1) - - } else if let timeUi = taskVmUi.timeUi as? TasksTabTasksVm.TaskVmUiTimeUiRegularUi { - HStack { - Text(timeUi.text) - .padding(.leading, H_PADDING) - .padding(.top, 1) - .padding(.bottom, 6) - .font(.system(size: 14, weight: .light)) - .foregroundColor(timeUi.textColorEnum.toColor()) - .lineLimit(1) - Spacer() - } - } - - HStack { - - Text(taskVmUi.text) - .lineSpacing(4) - .textAlign(.leading) - - Spacer() - - TriggersIconsView( - checklistsDb: taskVmUi.taskUi.tf.checklistsDb, - shortcutsDb: taskVmUi.taskUi.tf.shortcutsDb - ) - - if (taskVmUi.taskUi.tf.isImportant) { - Image(systemName: "flag.fill") - .font(.system(size: 18)) - .foregroundColor(.red) - .padding(.leading, 8) - } - - if taskVmUi.taskFolderDb.activity_id != nil { - Button( - action: { - taskVmUi.toggleOnHomeActivity() - }, - label: { - Image(systemName: "house") - .foregroundColor(taskVmUi.taskUi.taskDb.onHomeActivity ? .secondary : homeFgColor) - .font(.system(size: 19, weight: .semibold)) - }, - ) - .frame(width: HomeScreen__itemCircleHeight, height: HomeScreen__itemCircleHeight) - .buttonStyle(.plain) - .padding(.leading, 4) - } - } - .padding(.leading, H_PADDING) - } - .padding(.top, 10) - .padding(.bottom, 11) - } - ) - .offset(x: xSwipeOffset) - // .background(Color.white.opacity(0.001)) // Without background DnD does not work. WTF?! Work after highPriorityGesture - .highPriorityGesture(gesture) - // .gesture(gesture) - .buttonStyle(taskRowButtonStyle) - .foregroundColor(.primary) - .background(GeometryReader { geometry -> Color in - // Or "Modifying state during view update, this will cause undefined behavior." - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - itemHeight = geometry.size.height - } - return Color.clear - }) - } - - if (withDivider) { - Divider() - .padding(.leading, H_PADDING) - } - } - } - - // https://stackoverflow.com/a/79037514 - var gesture: some Gesture { - DragGesture(minimumDistance: 25, coordinateSpace: .global) - .onChanged { value in - xSwipeOffset = value.translation.width - if xSwipeOffset > 1 { - tasksTabView.onDragMove(curDragItem: dragItem, value: value) - } - } - .onEnded { value in - let drop = tasksTabView.onDragStop() - if let drop = drop { - xSwipeOffset = 0 - if drop is TasksTabDropItemCalendar { - navigation.fullScreen(withAnimation: false) { - EventFormFullScreen( - initEventDb: nil, - initText: taskVmUi.taskUi.taskDb.text, - initTime: nil, - onDone: { - taskVmUi.delete() - } - ) - } - } else if let dropFolder = drop as? TasksTabDropItemTaskFolder { - taskVmUi.upFolder(newFolder: dropFolder.taskFolderDb) - } - } else if value.translation.width < -80 { - xSwipeOffset = (width ?? 999) * -1 - } else if value.translation.width > 60 { - xSwipeOffset = 0 - navigation.showTaskForm( - strategy: TaskFormStrategy.EditTask(taskDb: taskVmUi.taskUi.taskDb) - ) - } else { - xSwipeOffset = 0 - } - } - } -} - -private struct TmrwTaskView: View { - - let taskUi: TasksTabTasksVm.TmrwTaskUi - - var body: some View { - - VStack { - - let vPadding = 8.0 - - if let timeUI = taskUi.timeUi { - HStack { - Text(timeUI.text) - .padding(.top, 1) - .padding(.bottom, vPadding) - .font(.system(size: 14, weight: .light)) - .foregroundColor(timeUI.textColorEnum.toColor()) - .lineLimit(1) - Spacer() - } - } - - HStack { - - Text(taskUi.text) - .lineSpacing(4) - .textAlign(.leading) - - Spacer() - - TriggersIconsView( - checklistsDb: taskUi.textFeatures.checklistsDb, - shortcutsDb: taskUi.textFeatures.shortcutsDb - ) - - } - } - .padding(.leading, H_PADDING) - .padding(.vertical, 10) - } -} - -private struct MyButtonStyle: ButtonStyle { - func makeBody(configuration: Self.Configuration) -> some View { - configuration - .label - .background(configuration.isPressed ? Color(.systemGray5) : .black) - } -} diff --git a/apple/iphone/UI/Tasks/Tab/TasksTabDragItem.swift b/apple/iphone/UI/Tasks/Tab/TasksTabDragItem.swift deleted file mode 100644 index fad4847e6..000000000 --- a/apple/iphone/UI/Tasks/Tab/TasksTabDragItem.swift +++ /dev/null @@ -1,4 +0,0 @@ -struct TasksTabDragItem { - - let isDropAllowed: (_ drop: TasksTabDropItem) -> Bool -} diff --git a/apple/iphone/UI/Tasks/Tab/TasksTabDropItem.swift b/apple/iphone/UI/Tasks/Tab/TasksTabDropItem.swift deleted file mode 100644 index 61b8b6511..000000000 --- a/apple/iphone/UI/Tasks/Tab/TasksTabDropItem.swift +++ /dev/null @@ -1,54 +0,0 @@ -import SwiftUI -import shared - -class TasksTabDropItem: ObservableObject { - - let name: String - let square: Square - - init(name: String, square: Square) { - self.name = name - self.square = square - } - - class Square { - - var x1: CGFloat - var y1: CGFloat - var x2: CGFloat - var y2: CGFloat - - init(x1: CGFloat = 0, y1: CGFloat = 0, x2: CGFloat = 0, y2: CGFloat = 0) { - self.x1 = x1 - self.y1 = y1 - self.x2 = x2 - self.y2 = y2 - } - - func upByRect(rect: CGRect) { - x1 = rect.origin.x - y1 = rect.origin.y - x2 = rect.origin.x + rect.width - y2 = rect.origin.y + rect.height - } - } -} - -/// - -class TasksTabDropItemCalendar: TasksTabDropItem { - - init() { - super.init(name: "Calendar", square: TasksTabDropItem.Square()) - } -} - -class TasksTabDropItemTaskFolder: TasksTabDropItem { - - let taskFolderDb: TaskFolderDb - - init(_ taskFolderDb: TaskFolderDb) { - self.taskFolderDb = taskFolderDb - super.init(name: taskFolderDb.name, square: TasksTabDropItem.Square()) - } -} diff --git a/apple/iphone/UI/Tasks/Tab/TasksTabSectionEnum.swift b/apple/iphone/UI/Tasks/Tab/TasksTabSectionEnum.swift deleted file mode 100644 index 42bc3b8fd..000000000 --- a/apple/iphone/UI/Tasks/Tab/TasksTabSectionEnum.swift +++ /dev/null @@ -1,10 +0,0 @@ -import shared - -enum TasksTabSectionEnum { - - case taskFolder(taskFolderDb: TaskFolderDb) - - case repeatings - - case calendar -} diff --git a/apple/iphone/UI/Tasks/Tab/TasksTabView.swift b/apple/iphone/UI/Tasks/Tab/TasksTabView.swift deleted file mode 100644 index 589e4f8b3..000000000 --- a/apple/iphone/UI/Tasks/Tab/TasksTabView.swift +++ /dev/null @@ -1,307 +0,0 @@ -import SwiftUI -import shared - -private let tabWidth: CGFloat = 34 -private let tabShape = RoundedRectangle(cornerRadius: 8, style: .continuous) -private let tabPadding: CGFloat = 15 - -struct TasksTabView: View { - - @Binding var tab: MainTabEnum - - var body: some View { - VmView({ - TasksTabVm() - }) { vm, state in - let state = vm.state.value as! TasksTabVm.State - TasksTabViewInner( - state: state, - tab: $tab, - ) - } - } -} - -struct TasksTabViewInner: View { - - let state: TasksTabVm.State - @Binding var tab: MainTabEnum - - /// - - @State var section: TasksTabSectionEnum = - .taskFolder(taskFolderDb: Cache.shared.getTodayFolderDb()) - - @State var dropItems: [TasksTabDropItem] = [] - @State var focusedDrop: TasksTabDropItem? = nil - @State var activeDrag: TasksTabDragItem? = nil - - @State private var dropCalendar = TasksTabDropItemCalendar() - - var body: some View { - - ZStack { - - Color.black.ignoresSafeArea() - - HStack { - - switch section { - case .taskFolder(let taskFolderDb): - TasksTabTasksView( - taskFolderDb: taskFolderDb, - tasksTabView: self - ) - case .repeatings: - TasksTabRepeatingsView() - case .calendar: - CalendarTabsView() - } - - VStack { - - // - // Repeating - - let isActiveRepeating: Bool = if case .repeatings = section { true } else { false } - - Button( - action: { - section = .repeatings - }, - label: { - Image(systemName: "repeat") - .padding(.top, 9) - .padding(.bottom, 9) - .foregroundColor(isActiveRepeating ? .white : .primary) - .font(.system(size: 14, weight: .light)) - } - ) - .background( - RoundedRectangle(cornerRadius: 8, style: .continuous) - .fill(isActiveRepeating ? .blue : .black) - .frame(width: tabWidth) - ) - - // - // Folders - - ForEach(state.taskFoldersUi.reversed(), id: \.taskFolderDb.id) { folderUi in - - let isActive: Bool = if case .taskFolder(let taskFolderDb) = section { - taskFolderDb.id == folderUi.taskFolderDb.id - } else { false } - - FolderButtonView( - isActive: isActive, - folderUi: folderUi, - tasksTabView: self, - drop: TasksTabDropItemTaskFolder(folderUi.taskFolderDb) - ) - .padding(.top, tabPadding) - } - - // - // Calendar - - let isActiveCalendar: Bool = if case .calendar = section { true } else { false } - - CalendarButtonView( - isActive: isActiveCalendar, - focusedDrop: $focusedDrop, - dragItem: $activeDrag, - dropItem: dropCalendar, - dropItems: $dropItems, - onClick: { - section = .calendar - } - ) - .padding(.top, tabPadding) - } - .padding(.trailing, 4) - } - } - .padding(.bottom, MainTabsView__HEIGHT) - .ignoresSafeArea(.keyboard) - .onChange(of: tab) { _, newTab in - if newTab == .tasks { - section = .taskFolder(taskFolderDb: Cache.shared.getTodayFolderDb()) - } - } - } - - func onDragMove( - curDragItem: TasksTabDragItem, - value: DragGesture.Value - ) { - let x = value.location.x - let y = value.location.y - focusedDrop = dropItems.first { drop in - if !curDragItem.isDropAllowed(drop) { - return false - } - let s = drop.square - return x > s.x1 && y > s.y1 && x < s.x2 && y < s.y2 - } - activeDrag = curDragItem - } - - func onDragStop() -> TasksTabDropItem? { - let dropSave: TasksTabDropItem? = focusedDrop - focusedDrop = nil - activeDrag = nil - return dropSave - } -} - -private struct FolderButtonView: View { - - let isActive: Bool - let folderUi: TasksTabVm.TaskFolderUi - let tasksTabView: TasksTabViewInner - @State var drop: TasksTabDropItemTaskFolder - - /// - - private var isAllowedForDrop: Bool { - tasksTabView.activeDrag?.isDropAllowed(drop) == true - } - - private var bgColor: Color { - if (tasksTabView.focusedDrop as? TasksTabDropItemTaskFolder)?.taskFolderDb.id == folderUi.taskFolderDb.id { .green } - else if isAllowedForDrop { .purple } - else { isActive ? .blue : .black } - } - - var body: some View { - Button( - action: { - tasksTabView.section = .taskFolder(taskFolderDb: folderUi.taskFolderDb) - }, - label: { - - VStack { - - Text(folderUi.tabText) - .textCase(.uppercase) - .lineSpacing(0) - .font(.system(size: 14, weight: isActive ? .semibold : .regular, design: .monospaced)) - .frame(width: tabWidth) - .foregroundColor(isActive || isAllowedForDrop ? .white : .primary) - .padding(.top, 8) - .padding(.bottom, 8) - } - .background(tabShape.fill(bgColor)) - .background(GeometryReader { geometry -> Color in - drop.square.upByRect(rect: geometry.frame(in: CoordinateSpace.global)) - return Color.clear - }) - /// - .onAppear { - tasksTabView.dropItems.append(drop) - } - .onDisappear { - tasksTabView.dropItems.removeAll { - $0 === drop - } - } - } - ) - } -} - -// -// Calendar Button - -private let calendarDots: [[Bool]] = [ - [false, true, true, true], - [true, true, true, true], - [true, true, true, false], -] - -private struct CalendarButtonView: View { - - let isActive: Bool - @Binding var focusedDrop: TasksTabDropItem? - @Binding var dragItem: TasksTabDragItem? - let dropItem: TasksTabDropItem - @Binding var dropItems: [TasksTabDropItem] - let onClick: () -> Void - - /// - - private var isAllowedToDrop: Bool { - dragItem?.isDropAllowed(dropItem) == true - } - - private var isFocusedToDrop: Bool { - focusedDrop is TasksTabDropItemCalendar - } - - private var bgColor: Color { - if isFocusedToDrop { .green } - else if isAllowedToDrop { .purple } - else if isActive { .blue } - else { .clear } - } - - private var fgColor: Color { - if isFocusedToDrop { .white } - else if isAllowedToDrop { .white } - else if isActive { .white } - else { .primary } - } - - var body: some View { - - Button( - action: { - onClick() - }, - label: { - - GeometryReader { geometry in - - let _ = dropItem.square.upByRect(rect: geometry.frame(in: CoordinateSpace.global)) - - VStack { - - ForEachIndexed(calendarDots) { dotsIdx, dots in - - HStack { - - ForEachIndexed(dots) { dotIdx, dot in - - Spacer() - - ZStack { - } - .frame(width: 2 + onePx, height: 2 + onePx) - .background(roundedShape.fill(dot ? fgColor : .clear)) - } - - Spacer() - } - .id("TasksCalendarButtonView dots \(dotsIdx)") - .padding(.top, (dotsIdx == 0 ? 1 : 5)) - .padding(.horizontal, 5) - } - } - .frame(width: tabWidth, height: tabWidth) - .background(tabShape.fill(bgColor)) - /// - .onAppear { - dropItems.append(dropItem) - } - .onDisappear { - dropItems.removeAll { - $0 === dropItem - } - } - } - .frame(width: tabWidth, height: tabWidth) - .padding(.top, 2) - } - ) - } -} diff --git a/apple/iphone/UI/Tasks/Folders/TaskFoldersFormScreen.swift b/apple/iphone/UI/TasksFolders/TaskFoldersFormScreen.swift similarity index 81% rename from apple/iphone/UI/Tasks/Folders/TaskFoldersFormScreen.swift rename to apple/iphone/UI/TasksFolders/TaskFoldersFormScreen.swift index 8e22d2fd9..9fff1a684 100644 --- a/apple/iphone/UI/Tasks/Folders/TaskFoldersFormScreen.swift +++ b/apple/iphone/UI/TasksFolders/TaskFoldersFormScreen.swift @@ -12,7 +12,6 @@ struct TaskFoldersFormScreen: View { vm: vm, state: state, foldersUiAnimate: state.foldersUi, - tmrwButtonUiAnimation: state.tmrwButtonUi ) } } @@ -24,7 +23,6 @@ private struct TaskFoldersFormScreenInner: View { let state: TaskFoldersFormVm.State @State var foldersUiAnimate: [TaskFoldersFormVm.TaskFolderUi] - @State var tmrwButtonUiAnimation: TaskFoldersFormVm.TmrwButtonUi? /// @@ -79,16 +77,6 @@ private struct TaskFoldersFormScreenInner: View { } } } - - if let tmrwButtonUi = tmrwButtonUiAnimation { - Section { - Button(tmrwButtonUi.text) { - tmrwButtonUi.add( - dialogsManager: navigation - ) - } - } - } } .animateVmValue( vmValue: state.foldersUi, @@ -96,11 +84,7 @@ private struct TaskFoldersFormScreenInner: View { enabled: withFoldersAnimation, onChange: { withFoldersAnimation = true - } - ) - .animateVmValue( - vmValue: state.tmrwButtonUi, - swiftState: $tmrwButtonUiAnimation + }, ) .environment(\.editMode, $editMode) .myFormContentMargins() diff --git a/apple/iphone/UI/WhatsNew/WhatsNewScreen.swift b/apple/iphone/UI/WhatsNew/WhatsNewScreen.swift index 0f67a5d63..d622e02e6 100644 --- a/apple/iphone/UI/WhatsNew/WhatsNewScreen.swift +++ b/apple/iphone/UI/WhatsNew/WhatsNewScreen.swift @@ -49,7 +49,7 @@ struct WhatsNewScreen: View { if buttonUi == .pomodoro { Button(buttonUi.text) { navigation.fullScreen { - ReadmeFullScreen(defaultItem: .basics) + DocFullScreen(forceRead: false) } } .foregroundColor(.blue) diff --git a/build.gradle.kts b/build.gradle.kts index 9d8cb0c0b..6479f2173 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ buildscript { dependencies { classpath("com.android.tools.build:gradle:8.12.3") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.3.20") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.3.21") } } diff --git a/misc/builds/614-fdroid.apk b/misc/builds/614-fdroid.apk new file mode 100644 index 000000000..bdc3ad191 Binary files /dev/null and b/misc/builds/614-fdroid.apk differ diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index f73b525e8..9054e90c8 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -26,7 +26,7 @@ kotlin { sourceSets { - val ktor_version = "3.4.2" + val ktor_version = "3.4.3" val sqldelight_version = "2.3.2" val commonMain by getting { diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/ActivityUi.kt b/shared/src/commonMain/kotlin/me/timeto/shared/ActivityUi.kt new file mode 100644 index 000000000..c6c9ff3d7 --- /dev/null +++ b/shared/src/commonMain/kotlin/me/timeto/shared/ActivityUi.kt @@ -0,0 +1,14 @@ +package me.timeto.shared + +import me.timeto.shared.db.ActivityDb + +data class ActivityUi( + val activityDb: ActivityDb, +) { + + val symbol: Symbol = + activityDb.symbolOrDefault() + + val colorRgba: ColorRgba = + activityDb.colorRgba +} diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/Cache.kt b/shared/src/commonMain/kotlin/me/timeto/shared/Cache.kt index 3163b528c..1d6bbb111 100644 --- a/shared/src/commonMain/kotlin/me/timeto/shared/Cache.kt +++ b/shared/src/commonMain/kotlin/me/timeto/shared/Cache.kt @@ -33,8 +33,9 @@ object Cache { /// - fun getTodayFolderDb(): TaskFolderDb = - taskFoldersDbSorted.first { it.isToday } + lateinit var todayTaskFolderDb: TaskFolderDb + lateinit var tomorrowTaskFolderDb: TaskFolderDb + lateinit var somedayTaskFolderDb: TaskFolderDb /// @@ -63,8 +64,17 @@ object Cache { tasksDb = TaskDb.selectAsc() TaskDb.selectAscFlow().onEachExIn(scope) { tasksDb = it } - taskFoldersDbSorted = TaskFolderDb.selectAllSorted() - TaskFolderDb.selectAllSortedFlow().onEachExIn(scope) { taskFoldersDbSorted = it } + val taskFoldersDbSortedLocal = TaskFolderDb.selectAllSorted() + taskFoldersDbSorted = taskFoldersDbSortedLocal + taskFoldersDbSortedLocal.firstOrNull { it.isToday }?.let { todayTaskFolderDb = it } + taskFoldersDbSortedLocal.firstOrNull { it.isTomorrow }?.let { tomorrowTaskFolderDb = it } + taskFoldersDbSortedLocal.firstOrNull { it.isSomeday }?.let { somedayTaskFolderDb = it } + TaskFolderDb.selectAllSortedFlow().onEachExIn(scope) { taskFoldersDbSorted_ -> + taskFoldersDbSorted = taskFoldersDbSorted_ + taskFoldersDbSorted_.firstOrNull { it.isToday }?.let { todayTaskFolderDb = it } + taskFoldersDbSorted_.firstOrNull { it.isTomorrow }?.let { tomorrowTaskFolderDb = it } + taskFoldersDbSorted_.firstOrNull { it.isSomeday }?.let { somedayTaskFolderDb = it } + } eventsDb = EventDb.selectAscByTime() EventDb.selectAscByTimeFlow().onEachExIn(scope) { eventsDb = it } diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/Symbol.kt b/shared/src/commonMain/kotlin/me/timeto/shared/Symbol.kt new file mode 100644 index 000000000..842c842f9 --- /dev/null +++ b/shared/src/commonMain/kotlin/me/timeto/shared/Symbol.kt @@ -0,0 +1,75 @@ +package me.timeto.shared + +sealed class Symbol( + val raw: String, +) { + + data class Letter( + val letter: String, + ) : Symbol("letter--$letter") + + data class Icon( + val iconEnum: IconEnum, + ) : Symbol("icon--${iconEnum.code}") { + + companion object { + + val map: Map = + IconEnum.entries.associate { it.code to it.toIcon() } + } + + enum class IconEnum(val code: String) { + + book("book"), + case("case"), + timer("timer"), + exercise("exercise"), + piano("piano"), + music_note("music_note"), + rocket("rocket"), + bus("bus"), + bulb("bulb"), + bolt("bolt"), + option("option"), + graduationcap("graduationcap"), + megaphone("megaphone"), + instruments("instruments"), + meditation("meditation"), + flask("flask"), + compass("compass"), + gamecontroller("gamecontroller"), + soccerball("soccerball"), + hiking("hiking"), + inbox("inbox"), + sun("sun"), + moon("moon"), + moon_stars("moon_stars"), + question("question"); + + fun toIcon(): Icon = + Icon(this) + } + } + + data class Emoji( + val emoji: String, + ) : Symbol("emoji--$emoji") + + /// + + companion object { + + fun fromRawOrNull(raw: String): Symbol? { + val parts: List = + raw.split("--", limit = 2) + if (parts.size != 2) + return null + return when (parts[0]) { + "letter" -> Letter(parts[1]) + "icon" -> Icon.map[parts[1]] + "emoji" -> Emoji(parts[1]) + else -> null + } + } + } +} diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/TaskFolderUi.kt b/shared/src/commonMain/kotlin/me/timeto/shared/TaskFolderUi.kt new file mode 100644 index 000000000..e7ffc402c --- /dev/null +++ b/shared/src/commonMain/kotlin/me/timeto/shared/TaskFolderUi.kt @@ -0,0 +1,20 @@ +package me.timeto.shared + +import me.timeto.shared.db.ActivityDb +import me.timeto.shared.db.TaskFolderDb + +data class TaskFolderUi( + val taskFolderDb: TaskFolderDb, + val activityDb: ActivityDb?, +) { + + val colorRgba: ColorRgba = when { + taskFolderDb.isToday -> Palette.orange.dark + taskFolderDb.isTomorrow -> Palette.indigo.dark + taskFolderDb.isSomeday -> Palette.blue.dark + else -> activityDb?.colorRgba ?: Palette.blue.dark + } + + val symbol: Symbol = + taskFolderDb.symbolOrDefault() +} diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/TaskUi.kt b/shared/src/commonMain/kotlin/me/timeto/shared/TaskUi.kt index 5cef217b4..5433ca40c 100644 --- a/shared/src/commonMain/kotlin/me/timeto/shared/TaskUi.kt +++ b/shared/src/commonMain/kotlin/me/timeto/shared/TaskUi.kt @@ -1,12 +1,64 @@ package me.timeto.shared +import me.timeto.shared.db.IntervalDb import me.timeto.shared.db.TaskDb +import me.timeto.shared.db.TaskFolderDb data class TaskUi( val taskDb: TaskDb, ) { - val tf: TextFeatures = taskDb.text.textFeatures() + val tf: TextFeatures = + taskDb.text.textFeatures() + + val taskFolderUi: TaskFolderUi = run { + val taskFolderDb: TaskFolderDb = + taskDb.selectTaskFolderDbCached() + TaskFolderUi(taskFolderDb, taskFolderDb.selectActivityDbOrNullCached()) + } + + val activityUi: ActivityUi? = + tf.activityDb?.let { ActivityUi(it) } + + fun updateTaskFolder(taskFolderDb: TaskFolderDb) { + launchExIo { + taskDb.updateFolder( + taskFolderDb = taskFolderDb, + updateFolderActivity = true, + replaceIfTmrw = true, + ) + } + } + + fun moveToTimer() { + launchExIo { + val taskText: String = + taskDb.text.textFeatures().textNoFeatures + if (taskText.isEmpty()) { + taskDb.delete() + return@launchExIo + } + val intervalDb: IntervalDb = + IntervalDb.selectLastOneOrNull()!! + val intervalTf: TextFeatures = + (intervalDb.note ?: "").textFeatures() + val intervalText: String = + intervalTf.textNoFeatures + val newTf: TextFeatures = intervalTf.copy( + textNoFeatures = + if (intervalText.isBlank()) taskText + else "$intervalText\n$taskText" + ) + intervalDb.updateNote(newTf.textWithFeatures()) + taskDb.delete() + } + } + + fun delete() { + launchExIo { + taskDb.delete() + } + } } fun List.sortedUi( diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/TextFeatures.kt b/shared/src/commonMain/kotlin/me/timeto/shared/TextFeatures.kt index 6738fb69d..a5c0c40af 100644 --- a/shared/src/commonMain/kotlin/me/timeto/shared/TextFeatures.kt +++ b/shared/src/commonMain/kotlin/me/timeto/shared/TextFeatures.kt @@ -25,13 +25,10 @@ data class TextFeatures( } fun textUi( - withActivityEmoji: Boolean = false, withPausedEmoji: Boolean = false, withTimer: Boolean = true, ): String { val a = mutableListOf() - if (withActivityEmoji && activityDb != null) - a.add(activityDb.emoji) if (textNoFeatures.isNotBlank()) a.add(textNoFeatures) else if (activityDb != null) diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/db/ActivityDb.kt b/shared/src/commonMain/kotlin/me/timeto/shared/db/ActivityDb.kt index 9282aec59..9ba60ba9d 100644 --- a/shared/src/commonMain/kotlin/me/timeto/shared/db/ActivityDb.kt +++ b/shared/src/commonMain/kotlin/me/timeto/shared/db/ActivityDb.kt @@ -16,6 +16,8 @@ import me.timeto.shared.Cache import me.timeto.shared.ColorRgba import me.timeto.shared.DaytimeUi import me.timeto.shared.HomeButtonSort +import me.timeto.shared.Symbol +import me.timeto.shared.Symbol.Icon import me.timeto.shared.TextFeatures import me.timeto.shared.UiException import me.timeto.shared.UnixTime @@ -40,7 +42,7 @@ data class ActivityDb( val goal_json: String?, val timer: Int, val period_json: String, - val emoji: String, + val symbol_raw: String, val home_button_sort: String, val color_rgba: String, val keep_screen_on: Int, @@ -98,7 +100,7 @@ data class ActivityDb( goalType: GoalType?, timerType: TimerType, period: Period, - emoji: String, + symbol: Symbol, colorRgba: ColorRgba, keepScreenOn: Boolean, pomodoroTimer: Int, @@ -119,7 +121,7 @@ data class ActivityDb( goal_json = goalType?.toJson(), timer = timerType.dbValue, period_json = period.toJson().toString(), - emoji = emoji, + symbol_raw = symbol.raw, home_button_sort = HomeButtonSort.findNextPositionSync( isHidden = false, barSize = homeButtonsCellsCount, @@ -166,7 +168,7 @@ data class ActivityDb( goal_json = j.getStringOrNull(4), timer = j.getInt(5), period_json = j.getString(6), - emoji = j.getString(7), + symbol_raw = j.getString(7), home_button_sort = j.getString(8), color_rgba = j.getString(9), keep_screen_on = j.getInt(10), @@ -189,6 +191,9 @@ data class ActivityDb( val keepScreenOn: Boolean = keep_screen_on.toBoolean10() + fun symbolOrDefault(): Symbol = + Symbol.fromRawOrNull(symbol_raw) ?: Icon.IconEnum.question.toIcon() + fun buildTimerType(): TimerType = TimerType.build(dbValue = timer) @@ -242,7 +247,7 @@ data class ActivityDb( goalType: GoalType?, timerType: TimerType, period: Period, - emoji: String, + symbol: Symbol, colorRgba: ColorRgba, keepScreenOn: Boolean, pomodoroTimer: Int, @@ -271,7 +276,7 @@ data class ActivityDb( goal_json = goalType?.toJson(), timer = timerType.dbValue, period_json = period.toJson().toString(), - emoji = emoji, + symbol_raw = symbol.raw, home_button_sort = home_button_sort, color_rgba = colorRgba.toRgbaString(), keep_screen_on = keepScreenOn.toInt10(), @@ -350,7 +355,7 @@ data class ActivityDb( override fun backupable__backup(): JsonElement = listOf( id, parent_id, type_id, name, - goal_json, timer, period_json, emoji, + goal_json, timer, period_json, symbol_raw, home_button_sort, color_rgba, keep_screen_on, pomodoro_timer, checklist_hint, timer_hints, @@ -366,7 +371,7 @@ data class ActivityDb( goal_json = j.getStringOrNull(4), timer = j.getInt(5), period_json = j.getString(6), - emoji = j.getString(7), + symbol_raw = j.getString(7), home_button_sort = j.getString(8), color_rgba = j.getString(9), keep_screen_on = j.getInt(10), @@ -594,7 +599,7 @@ data class ActivityDb( private fun ActivitySq.toDb() = ActivityDb( id = id, parent_id = parent_id, type_id = type_id, name = name, goal_json = goal_json, timer = timer, period_json = period_json, - emoji = emoji, home_button_sort = home_button_sort, + symbol_raw = symbol_raw, home_button_sort = home_button_sort, color_rgba = color_rgba, keep_screen_on = keep_screen_on, pomodoro_timer = pomodoro_timer, checklist_hint = checklist_hint, timer_hints = timer_hints, diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/db/EventDb.kt b/shared/src/commonMain/kotlin/me/timeto/shared/db/EventDb.kt index 7cdf203a0..e4ed5a01e 100644 --- a/shared/src/commonMain/kotlin/me/timeto/shared/db/EventDb.kt +++ b/shared/src/commonMain/kotlin/me/timeto/shared/db/EventDb.kt @@ -55,8 +55,7 @@ data class EventDb( } .forEach { event -> TaskDb.insertWithValidation_transactionRequired( - folder = Cache.getTodayFolderDb(), - onHomeActivity = true, + folder = Cache.todayTaskFolderDb, text = event.prepTextForTask(), ) db.eventQueries.deleteById(event.id) diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/db/IntervalDb.kt b/shared/src/commonMain/kotlin/me/timeto/shared/db/IntervalDb.kt index 5da764e31..02042ad43 100644 --- a/shared/src/commonMain/kotlin/me/timeto/shared/db/IntervalDb.kt +++ b/shared/src/commonMain/kotlin/me/timeto/shared/db/IntervalDb.kt @@ -215,8 +215,7 @@ data class IntervalDb( ) val pausedTaskId: Int = TaskDb.insertWithValidation_transactionRequired( - folder = Cache.getTodayFolderDb(), - onHomeActivity = true, + folder = Cache.todayTaskFolderDb, text = pausedTf.textWithFeatures(), ) val pauseIntervalTf = "Break".textFeatures().copy( @@ -287,6 +286,14 @@ data class IntervalDb( db.intervalQueries.updateNoteById(id = id, note = newNote) } + @Throws(UiException::class, CancellationException::class) + suspend fun updateNote(note: String): Unit = dbIo { + db.intervalQueries.updateNoteById( + id = id, + note = validateNote(note), + ) + } + @Throws(UiException::class, CancellationException::class) suspend fun updateEx( newId: Int, @@ -325,8 +332,7 @@ data class IntervalDb( activityDb = activityDb, ) TaskDb.insertWithValidation_transactionRequired( - folder = Cache.getTodayFolderDb(), - onHomeActivity = true, + folder = Cache.todayTaskFolderDb, text = textTf.textWithFeatures(), ) db.intervalQueries.deleteById(id) diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/db/KvDb.kt b/shared/src/commonMain/kotlin/me/timeto/shared/db/KvDb.kt index bb090a038..e1e743946 100644 --- a/shared/src/commonMain/kotlin/me/timeto/shared/db/KvDb.kt +++ b/shared/src/commonMain/kotlin/me/timeto/shared/db/KvDb.kt @@ -66,9 +66,6 @@ data class KvDb( KEY.IS_SENDING_REPORTS.upsertInt(time) } - fun KvDb?.todayOnHomeScreen(): Boolean = - this?.value?.toBoolean10() ?: true - // // Backupable Holder @@ -95,10 +92,8 @@ data class KvDb( TOKEN_PASSWORD, WHATS_NEW_CHECK_UNIX_DAY, FEEDBACK_SUBJECT, - TODAY_ON_HOME_SCREEN, IS_SENDING_REPORTS, - IS_COLLAPSE_HOME_TASKS, - HOME_README_OPEN_TIME; + DOC_FORCE_READ_TIME; // selectOrNull.. diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/db/RepeatingDb.kt b/shared/src/commonMain/kotlin/me/timeto/shared/db/RepeatingDb.kt index 8f5aecc7a..767346d5c 100644 --- a/shared/src/commonMain/kotlin/me/timeto/shared/db/RepeatingDb.kt +++ b/shared/src/commonMain/kotlin/me/timeto/shared/db/RepeatingDb.kt @@ -72,14 +72,13 @@ data class RepeatingDb( suspend fun syncTodaySafe(today: Int): Unit = dbIo { // Select within a transaction to avoid duplicate additions db.transaction { - val todayFolderDb: TaskFolderDb = Cache.getTodayFolderDb() + val todayFolderDb: TaskFolderDb = Cache.todayTaskFolderDb db.repeatingQueries.selectAsc() .asList { toDb() } .filter { it.getNextDay() <= today } .forEach { repeatingDb -> TaskDb.insertWithValidation_transactionRequired( folder = todayFolderDb, - onHomeActivity = true, text = repeatingDb.prepTextForTask(today), ) db.repeatingQueries.updateLastDayById(last_day = today, id = repeatingDb.id) diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/db/TaskDb.kt b/shared/src/commonMain/kotlin/me/timeto/shared/db/TaskDb.kt index 9e0a041bf..f6b4dc411 100644 --- a/shared/src/commonMain/kotlin/me/timeto/shared/db/TaskDb.kt +++ b/shared/src/commonMain/kotlin/me/timeto/shared/db/TaskDb.kt @@ -5,6 +5,7 @@ import dbsq.TaskSq import kotlinx.coroutines.flow.Flow import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.jsonArray +import me.timeto.shared.Cache import me.timeto.shared.TextFeatures import me.timeto.shared.TimerTimeParser import me.timeto.shared.launchExIo @@ -17,14 +18,11 @@ import me.timeto.shared.toJsonArray import me.timeto.shared.textFeatures import me.timeto.shared.UiException import me.timeto.shared.TaskUi -import me.timeto.shared.toBoolean10 -import me.timeto.shared.toInt10 import kotlin.math.max data class TaskDb( val id: Int, val folder_id: Int, - val onHomeActivity: Boolean, val text: String, ) : Backupable__Item { @@ -49,13 +47,11 @@ data class TaskDb( suspend fun insertWithValidation( text: String, - onHomeActivity: Boolean, folder: TaskFolderDb, ): Unit = dbIo { db.transaction { insertWithValidation_transactionRequired( folder = folder, - onHomeActivity = onHomeActivity, text = text, ) } @@ -63,14 +59,12 @@ data class TaskDb( fun insertWithValidation_transactionRequired( folder: TaskFolderDb, - onHomeActivity: Boolean, text: String, ): Int { val newId = getNextId_ioRequired() db.taskQueries.insert( id = newId, folder_id = folder.id, - on_home_activity = onHomeActivity.toInt10(), text = validateText(text), ) return newId @@ -111,15 +105,18 @@ data class TaskDb( db.taskQueries.insert( id = j.getInt(0), folder_id = j.getInt(1), - on_home_activity = j.getInt(2), - text = j.getString(3), + text = j.getString(2), ) } } - val isToday: Boolean = folder_id == TaskFolderDb.ID_TODAY - val isTmrw: Boolean = folder_id == TaskFolderDb.ID_TMRW + val isToday: Boolean = + folder_id == TaskFolderDb.ID_TODAY + val isTomorrow: Boolean = + folder_id == TaskFolderDb.ID_TOMORROW + + // todo remove? fun toUi() = TaskUi(this) suspend fun startInterval( @@ -155,10 +152,10 @@ data class TaskDb( val activityDb: ActivityDb? = tf.activityDb val tfTimerType: TextFeatures.TimerType? = tf.timerType - if (activityDb != null && tfTimerType != null) { + if (activityDb != null) { launchExIo { startInterval( - tfTimerType = tfTimerType, + tfTimerType = tfTimerType ?: TextFeatures.TimerType.Stopwatch(startSeconds = 0), activityDb = activityDb, ) ifJustStarted() @@ -169,11 +166,8 @@ data class TaskDb( ifTimerNeeded() } - suspend fun toggleOnHomeActivity(): Unit = dbIo { - db.taskQueries.updateOnHomeActivityById( - on_home_activity = onHomeActivity.not().toInt10(), - id = id, - ) + fun selectTaskFolderDbCached(): TaskFolderDb { + return Cache.taskFoldersDbSorted.first { it.id == folder_id } } suspend fun updateTextWithValidation(newText: String): Unit = dbIo { @@ -202,7 +196,7 @@ data class TaskDb( ) } // To know which day the task moved to "Tomorrow" - if (replaceIfTmrw && taskFolderDb.isTmrw) { + if (replaceIfTmrw && taskFolderDb.isTomorrow) { db.taskQueries.updateId( oldId = id, newId = getNextId_ioRequired(), @@ -222,7 +216,7 @@ data class TaskDb( id.toString() override fun backupable__backup(): JsonElement = listOf( - id, folder_id, onHomeActivity.toInt10(), text, + id, folder_id, text, ).toJsonArray() override fun backupable__update(json: JsonElement) { @@ -230,8 +224,7 @@ data class TaskDb( db.taskQueries.updateById( id = j.getInt(0), folder_id = j.getInt(1), - on_home_activity = j.getInt(2), - text = j.getString(3), + text = j.getString(2), ) } @@ -243,6 +236,5 @@ data class TaskDb( private fun TaskSq.toDb() = TaskDb( id = id, folder_id = folder_id, - onHomeActivity = on_home_activity.toBoolean10(), text = text, ) diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/db/TaskFolderDb.kt b/shared/src/commonMain/kotlin/me/timeto/shared/db/TaskFolderDb.kt index ab2a88d91..3b3721056 100644 --- a/shared/src/commonMain/kotlin/me/timeto/shared/db/TaskFolderDb.kt +++ b/shared/src/commonMain/kotlin/me/timeto/shared/db/TaskFolderDb.kt @@ -7,6 +7,8 @@ import kotlinx.coroutines.flow.map import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.jsonArray import me.timeto.shared.Cache +import me.timeto.shared.Symbol +import me.timeto.shared.Symbol.Icon import me.timeto.shared.backups.Backupable__Holder import me.timeto.shared.backups.Backupable__Item import me.timeto.shared.getInt @@ -23,12 +25,14 @@ data class TaskFolderDb( val sort: Int, val activity_id: Int?, val name: String, + val symbol_raw: String, ) : Backupable__Item { companion object : Backupable__Holder { const val ID_TODAY = 1 - const val ID_TMRW = 4 + const val ID_TOMORROW = 4 + const val ID_SOMEDAY = 5 fun anyChangeFlow(): Flow<*> = db.taskFolderQueries.anyChange().asFlow() @@ -42,19 +46,11 @@ data class TaskFolderDb( /// - suspend fun insertTmrw(): Unit = dbIo { - db.taskFolderQueries.insert( - id = ID_TMRW, - sort = 2, - activity_id = null, - name = "TMRW", - ) - } - @Throws(UiException::class, CancellationException::class) suspend fun insertWithValidation( rawName: String, activityDb: ActivityDb?, + symbol: Symbol, ) = dbIo { db.transaction { val allTaskFoldersDb: List = @@ -66,6 +62,7 @@ data class TaskFolderDb( sort = allTaskFoldersDb.maxOf { it.sort } + 1, activity_id = activityDb?.id, name = validateName(rawName), + symbol_raw = symbol.raw, ) } } @@ -75,12 +72,14 @@ data class TaskFolderDb( sort: Int, activityDb: ActivityDb?, name: String, + symbol: Symbol, ): Unit = dbIo { db.taskFolderQueries.insert( id = id, sort = sort, activity_id = activityDb?.id, name = name, + symbol_raw = symbol.raw, ) } @@ -110,6 +109,7 @@ data class TaskFolderDb( sort = j.getInt(1), activity_id = j.getIntOrNull(2), name = j.getString(3), + symbol_raw = j.getString(4), ) } } @@ -119,8 +119,14 @@ data class TaskFolderDb( val isToday: Boolean = id == ID_TODAY - val isTmrw: Boolean = - id == ID_TMRW + val isTomorrow: Boolean = + id == ID_TOMORROW + + val isSomeday: Boolean = + id == ID_SOMEDAY + + fun symbolOrDefault(): Symbol = + Symbol.fromRawOrNull(symbol_raw) ?: Icon.IconEnum.inbox.toIcon() fun selectActivityDbOrNullCached(): ActivityDb? { if (activity_id == null) @@ -133,6 +139,7 @@ data class TaskFolderDb( sort: Int, activityDb: ActivityDb?, rawName: String, + symbol: Symbol, ): Unit = dbIo { db.transaction { if (activityDb != null) { @@ -148,6 +155,7 @@ data class TaskFolderDb( sort = sort, activity_id = activityDb?.id, name = validateName(rawName), + symbol_raw = symbol.raw, ) } } @@ -167,7 +175,7 @@ data class TaskFolderDb( id.toString() override fun backupable__backup(): JsonElement = listOf( - id, sort, activity_id, name, + id, sort, activity_id, name, symbol_raw, ).toJsonArray() override fun backupable__update(json: JsonElement) { @@ -177,6 +185,7 @@ data class TaskFolderDb( sort = j.getInt(1), activity_id = j.getIntOrNull(2), name = j.getString(3), + symbol_raw = j.getString(4), ) } @@ -202,6 +211,7 @@ private fun TaskFolderSq.toDb() = TaskFolderDb( sort = sort, activity_id = activity_id, name = name, + symbol_raw = symbol_raw, ) private fun List.uiSorted(): List = diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/fillDemoData.kt b/shared/src/commonMain/kotlin/me/timeto/shared/fillDemoData.kt index 8da09db9a..e1d896117 100644 --- a/shared/src/commonMain/kotlin/me/timeto/shared/fillDemoData.kt +++ b/shared/src/commonMain/kotlin/me/timeto/shared/fillDemoData.kt @@ -6,11 +6,10 @@ import kotlin.random.Random suspend fun fillDemoData( morningActivityDb: ActivityDb, - commuteActivityDb: ActivityDb, workActivityDb: ActivityDb, - eatingActivityDb: ActivityDb, - exercisesActivityDb: ActivityDb, + smallTasksActivityDb: ActivityDb, readingActivityDb: ActivityDb, + workoutActivityDb: ActivityDb, freeTimeActivityDb: ActivityDb, sleepActivityDb: ActivityDb, ) { @@ -41,36 +40,36 @@ suspend fun fillDemoData( val day1Time: Int = startTime IntervalDb.insertWithValidation(sleepActivityDb, null, day1Time - h1) IntervalDb.insertWithValidation(morningActivityDb, null, day1Time + h7_30) - IntervalDb.insertWithValidation(commuteActivityDb, null, day1Time + h8_30) + IntervalDb.insertWithValidation(freeTimeActivityDb, null, day1Time + h8_30) IntervalDb.insertWithValidation(workActivityDb, null, day1Time + h9) - IntervalDb.insertWithValidation(commuteActivityDb, null, day1Time + h17) - IntervalDb.insertWithValidation(eatingActivityDb, null, day1Time + h17_30) + IntervalDb.insertWithValidation(freeTimeActivityDb, null, day1Time + h17) + IntervalDb.insertWithValidation(smallTasksActivityDb, null, day1Time + h17_30) IntervalDb.insertWithValidation(freeTimeActivityDb, null, day1Time + h18) - IntervalDb.insertWithValidation(exercisesActivityDb, null, day1Time + h19) + IntervalDb.insertWithValidation(workoutActivityDb, null, day1Time + h19) IntervalDb.insertWithValidation(freeTimeActivityDb, null, day1Time + h20) IntervalDb.insertWithValidation(readingActivityDb, null, day1Time + h22) // Day 2 - Business Day. Urgent Free Time. No Exercises. val day2Time: Int = startTime + (daySeconds * 1) IntervalDb.insertWithValidation(sleepActivityDb, null, day2Time - h1 + rand(45)) IntervalDb.insertWithValidation(morningActivityDb, null, day2Time + h7_30) - IntervalDb.insertWithValidation(commuteActivityDb, null, day2Time + h8_30 + rand(15)) + IntervalDb.insertWithValidation(freeTimeActivityDb, null, day2Time + h8_30 + rand(15)) IntervalDb.insertWithValidation(workActivityDb, null, day2Time + h9 + rand(10)) IntervalDb.insertWithValidation(freeTimeActivityDb, null, day2Time + h13 + rand(10)) IntervalDb.insertWithValidation(workActivityDb, null, day2Time + h16 + rand(10)) - IntervalDb.insertWithValidation(commuteActivityDb, null, day2Time + h19 + rand(10)) - IntervalDb.insertWithValidation(eatingActivityDb, null, day2Time + h19_30 + rand(10)) + IntervalDb.insertWithValidation(freeTimeActivityDb, null, day2Time + h19 + rand(10)) + IntervalDb.insertWithValidation(smallTasksActivityDb, null, day2Time + h19_30 + rand(10)) IntervalDb.insertWithValidation(freeTimeActivityDb, null, day2Time + h20 + rand(10)) IntervalDb.insertWithValidation(readingActivityDb, null, day2Time + h22 + rand(10)) // Day 3 - Business Day val day3Time: Int = startTime + (daySeconds * 2) IntervalDb.insertWithValidation(sleepActivityDb, null, day3Time - h1 + rand(45)) IntervalDb.insertWithValidation(morningActivityDb, null, day3Time + h6_30) - IntervalDb.insertWithValidation(commuteActivityDb, null, day3Time + h8_30 + rand(15)) + IntervalDb.insertWithValidation(freeTimeActivityDb, null, day3Time + h8_30 + rand(15)) IntervalDb.insertWithValidation(workActivityDb, null, day3Time + h9 + rand(10)) - IntervalDb.insertWithValidation(commuteActivityDb, null, day3Time + h17 + rand(10)) - IntervalDb.insertWithValidation(eatingActivityDb, null, day3Time + h17_30 + rand(10)) + IntervalDb.insertWithValidation(freeTimeActivityDb, null, day3Time + h17 + rand(10)) + IntervalDb.insertWithValidation(smallTasksActivityDb, null, day3Time + h17_30 + rand(10)) IntervalDb.insertWithValidation(freeTimeActivityDb, null, day3Time + h18 + rand(10)) - IntervalDb.insertWithValidation(exercisesActivityDb, null, day3Time + h19 + rand(10)) + IntervalDb.insertWithValidation(workoutActivityDb, null, day3Time + h19 + rand(10)) IntervalDb.insertWithValidation(freeTimeActivityDb, null, day3Time + h22 + rand(10)) IntervalDb.insertWithValidation(readingActivityDb, null, day3Time + h22 + rand(10)) // Day 4 Saturday @@ -79,9 +78,9 @@ suspend fun fillDemoData( IntervalDb.insertWithValidation(morningActivityDb, null, day4Time + h9) IntervalDb.insertWithValidation(freeTimeActivityDb, null, day4Time + h11 + rand(10)) IntervalDb.insertWithValidation(readingActivityDb, null, day4Time + h13 + rand(10)) - IntervalDb.insertWithValidation(commuteActivityDb, null, day4Time + h13_30 + rand(10)) + IntervalDb.insertWithValidation(freeTimeActivityDb, null, day4Time + h13_30 + rand(10)) IntervalDb.insertWithValidation(freeTimeActivityDb, null, day4Time + h14 + rand(10)) - IntervalDb.insertWithValidation(eatingActivityDb, null, day4Time + h18 + rand(10)) + IntervalDb.insertWithValidation(smallTasksActivityDb, null, day4Time + h18 + rand(10)) IntervalDb.insertWithValidation(readingActivityDb, null, day4Time + h20 + rand(10)) IntervalDb.insertWithValidation(freeTimeActivityDb, null, day4Time + h22 + rand(10)) // Day 5 Sunday @@ -89,68 +88,68 @@ suspend fun fillDemoData( IntervalDb.insertWithValidation(sleepActivityDb, null, day5Time + rand(45)) IntervalDb.insertWithValidation(morningActivityDb, null, day5Time + h8_30) IntervalDb.insertWithValidation(freeTimeActivityDb, null, day5Time + h11 + rand(10)) - IntervalDb.insertWithValidation(eatingActivityDb, null, day5Time + h13 + rand(10)) + IntervalDb.insertWithValidation(smallTasksActivityDb, null, day5Time + h13 + rand(10)) IntervalDb.insertWithValidation(readingActivityDb, null, day5Time + h14 + rand(10)) - IntervalDb.insertWithValidation(exercisesActivityDb, null, day5Time + h16 + rand(10)) + IntervalDb.insertWithValidation(workoutActivityDb, null, day5Time + h16 + rand(10)) IntervalDb.insertWithValidation(freeTimeActivityDb, null, day5Time + h20 + rand(10)) // Day 6 - Business Day val day6Time: Int = startTime + (daySeconds * 5) IntervalDb.insertWithValidation(sleepActivityDb, null, day6Time - h1 + rand(45)) IntervalDb.insertWithValidation(morningActivityDb, null, day6Time + h7_30) - IntervalDb.insertWithValidation(commuteActivityDb, null, day6Time + h8_30 + rand(15)) + IntervalDb.insertWithValidation(freeTimeActivityDb, null, day6Time + h8_30 + rand(15)) IntervalDb.insertWithValidation(workActivityDb, null, day6Time + h9 + rand(10)) - IntervalDb.insertWithValidation(commuteActivityDb, null, day6Time + h17 + rand(10)) - IntervalDb.insertWithValidation(eatingActivityDb, null, day6Time + h17_30 + rand(10)) + IntervalDb.insertWithValidation(freeTimeActivityDb, null, day6Time + h17 + rand(10)) + IntervalDb.insertWithValidation(smallTasksActivityDb, null, day6Time + h17_30 + rand(10)) IntervalDb.insertWithValidation(freeTimeActivityDb, null, day6Time + h18 + rand(10)) - IntervalDb.insertWithValidation(exercisesActivityDb, null, day6Time + h19 + rand(10)) + IntervalDb.insertWithValidation(workoutActivityDb, null, day6Time + h19 + rand(10)) IntervalDb.insertWithValidation(freeTimeActivityDb, null, day6Time + h20 + rand(10)) IntervalDb.insertWithValidation(readingActivityDb, null, day6Time + h22 + rand(10)) // Day 7 - Business Day val day7Time: Int = startTime + (daySeconds * 6) IntervalDb.insertWithValidation(sleepActivityDb, null, day7Time - h1 + rand(45)) IntervalDb.insertWithValidation(morningActivityDb, null, day7Time + h7_30) - IntervalDb.insertWithValidation(commuteActivityDb, null, day7Time + h8_30 + rand(15)) + IntervalDb.insertWithValidation(freeTimeActivityDb, null, day7Time + h8_30 + rand(15)) IntervalDb.insertWithValidation(workActivityDb, null, day7Time + h9 + rand(10)) - IntervalDb.insertWithValidation(commuteActivityDb, null, day7Time + h17 + rand(10)) - IntervalDb.insertWithValidation(eatingActivityDb, null, day7Time + h17_30 + rand(10)) + IntervalDb.insertWithValidation(freeTimeActivityDb, null, day7Time + h17 + rand(10)) + IntervalDb.insertWithValidation(smallTasksActivityDb, null, day7Time + h17_30 + rand(10)) IntervalDb.insertWithValidation(freeTimeActivityDb, null, day7Time + h18 + rand(10)) - IntervalDb.insertWithValidation(exercisesActivityDb, null, day7Time + h19 + rand(10)) + IntervalDb.insertWithValidation(workoutActivityDb, null, day7Time + h19 + rand(10)) IntervalDb.insertWithValidation(freeTimeActivityDb, null, day7Time + h20 + rand(10)) IntervalDb.insertWithValidation(readingActivityDb, null, day7Time + h23 + rand(10)) // Copy Day 1 val day8Time: Int = startTime + (daySeconds * 7) IntervalDb.insertWithValidation(sleepActivityDb, null, day8Time - h1) IntervalDb.insertWithValidation(morningActivityDb, null, day8Time + h7_30) - IntervalDb.insertWithValidation(commuteActivityDb, null, day8Time + h8_30) + IntervalDb.insertWithValidation(freeTimeActivityDb, null, day8Time + h8_30) IntervalDb.insertWithValidation(workActivityDb, null, day8Time + h9) - IntervalDb.insertWithValidation(commuteActivityDb, null, day8Time + h17) - IntervalDb.insertWithValidation(eatingActivityDb, null, day8Time + h17_30) + IntervalDb.insertWithValidation(freeTimeActivityDb, null, day8Time + h17) + IntervalDb.insertWithValidation(smallTasksActivityDb, null, day8Time + h17_30) IntervalDb.insertWithValidation(freeTimeActivityDb, null, day8Time + h18) - IntervalDb.insertWithValidation(exercisesActivityDb, null, day8Time + h19) + IntervalDb.insertWithValidation(workoutActivityDb, null, day8Time + h19) IntervalDb.insertWithValidation(freeTimeActivityDb, null, day8Time + h20) IntervalDb.insertWithValidation(readingActivityDb, null, day8Time + h22) // Copy Day 2 val day9Time: Int = startTime + (daySeconds * 8) IntervalDb.insertWithValidation(sleepActivityDb, null, day9Time - h1 + rand(45)) IntervalDb.insertWithValidation(morningActivityDb, null, day9Time + h7_30) - IntervalDb.insertWithValidation(commuteActivityDb, null, day9Time + h8_30 + rand(15)) + IntervalDb.insertWithValidation(freeTimeActivityDb, null, day9Time + h8_30 + rand(15)) IntervalDb.insertWithValidation(workActivityDb, null, day9Time + h9 + rand(10)) IntervalDb.insertWithValidation(freeTimeActivityDb, null, day9Time + h13 + rand(10)) IntervalDb.insertWithValidation(workActivityDb, null, day9Time + h16 + rand(10)) - IntervalDb.insertWithValidation(commuteActivityDb, null, day9Time + h19 + rand(10)) - IntervalDb.insertWithValidation(eatingActivityDb, null, day9Time + h19_30 + rand(10)) + IntervalDb.insertWithValidation(freeTimeActivityDb, null, day9Time + h19 + rand(10)) + IntervalDb.insertWithValidation(smallTasksActivityDb, null, day9Time + h19_30 + rand(10)) IntervalDb.insertWithValidation(freeTimeActivityDb, null, day9Time + h20 + rand(10)) IntervalDb.insertWithValidation(readingActivityDb, null, day9Time + h22 + rand(10)) // Copy Day 3 But Morning Difference val day10Time: Int = startTime + (daySeconds * 9) IntervalDb.insertWithValidation(sleepActivityDb, null, day10Time - h1 + rand(45)) IntervalDb.insertWithValidation(morningActivityDb, null, day10Time + h7_30) - IntervalDb.insertWithValidation(commuteActivityDb, null, day10Time + h8_30 + rand(15)) + IntervalDb.insertWithValidation(freeTimeActivityDb, null, day10Time + h8_30 + rand(15)) IntervalDb.insertWithValidation(workActivityDb, null, day10Time + h9 + rand(10)) - IntervalDb.insertWithValidation(commuteActivityDb, null, day10Time + h17 + rand(10)) - IntervalDb.insertWithValidation(eatingActivityDb, null, day10Time + h17_30 + rand(10)) + IntervalDb.insertWithValidation(freeTimeActivityDb, null, day10Time + h17 + rand(10)) + IntervalDb.insertWithValidation(smallTasksActivityDb, null, day10Time + h17_30 + rand(10)) IntervalDb.insertWithValidation(freeTimeActivityDb, null, day10Time + h18 + rand(10)) - IntervalDb.insertWithValidation(exercisesActivityDb, null, day10Time + h19 + rand(10)) + IntervalDb.insertWithValidation(workoutActivityDb, null, day10Time + h19 + rand(10)) IntervalDb.insertWithValidation(freeTimeActivityDb, null, day10Time + h22 + rand(10)) IntervalDb.insertWithValidation(readingActivityDb, null, day10Time + h22 + rand(10)) // Copy Day 4 @@ -159,9 +158,9 @@ suspend fun fillDemoData( IntervalDb.insertWithValidation(morningActivityDb, null, day11Time + h9) IntervalDb.insertWithValidation(freeTimeActivityDb, null, day11Time + h11 + rand(10)) IntervalDb.insertWithValidation(readingActivityDb, null, day11Time + h13 + rand(10)) - IntervalDb.insertWithValidation(commuteActivityDb, null, day11Time + h13_30 + rand(10)) + IntervalDb.insertWithValidation(freeTimeActivityDb, null, day11Time + h13_30 + rand(10)) IntervalDb.insertWithValidation(freeTimeActivityDb, null, day11Time + h14 + rand(10)) - IntervalDb.insertWithValidation(eatingActivityDb, null, day11Time + h18 + rand(10)) + IntervalDb.insertWithValidation(smallTasksActivityDb, null, day11Time + h18 + rand(10)) IntervalDb.insertWithValidation(readingActivityDb, null, day11Time + h20 + rand(10)) IntervalDb.insertWithValidation(freeTimeActivityDb, null, day11Time + h22 + rand(10)) } diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/initKmp.kt b/shared/src/commonMain/kotlin/me/timeto/shared/initKmp.kt index 2a15d047a..42230918a 100644 --- a/shared/src/commonMain/kotlin/me/timeto/shared/initKmp.kt +++ b/shared/src/commonMain/kotlin/me/timeto/shared/initKmp.kt @@ -77,7 +77,6 @@ internal fun initKmp( TaskSqAdapter = TaskSq.Adapter( IntColumnAdapter, IntColumnAdapter, - IntColumnAdapter, ), NoteSQAdapter = NoteSQ.Adapter( IntColumnAdapter, diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/vm/activity_form/ActivityFormVm.kt b/shared/src/commonMain/kotlin/me/timeto/shared/vm/activity_form/ActivityFormVm.kt index a3e2b6665..cc7719f20 100644 --- a/shared/src/commonMain/kotlin/me/timeto/shared/vm/activity_form/ActivityFormVm.kt +++ b/shared/src/commonMain/kotlin/me/timeto/shared/vm/activity_form/ActivityFormVm.kt @@ -6,6 +6,7 @@ import me.timeto.shared.Cache import me.timeto.shared.ColorRgba import me.timeto.shared.DaytimeUi import me.timeto.shared.DialogsManager +import me.timeto.shared.Symbol import me.timeto.shared.UiException import me.timeto.shared.db.ActivityDb import me.timeto.shared.db.ChecklistDb @@ -25,7 +26,7 @@ class ActivityFormVm( data class State( val initActivityDb: ActivityDb?, val name: String, - val emoji: String?, + val symbol: Symbol?, // region Goal val goalTypeUi: GoalTypeUi?, val goalTimerSeconds: Int, @@ -54,12 +55,12 @@ class ActivityFormVm( name.isNotBlank() val namePlaceholder = "Name" - val emojiTitle = "Emoji" + val iconTitle = "Icon" // region Goal val goalHeader = "GOAL" - val goalTypeTitle = "Goal" + val goalTypeTitle = "Goal Type" val goalTypeNote: String = goalTypeUi?.title ?: "None" val goalTimerTitle: String = GoalTypeUi.Timer.title @@ -84,7 +85,7 @@ class ActivityFormVm( // region Timer - val timerTypeTitle = "Timer on Bar Pressed" + val timerTypeTitle = "Timer Type" val showFixedTimerPicker: Boolean = timerTypeUi == TimerTypeUi.FixedTimer @@ -169,7 +170,7 @@ class ActivityFormVm( State( initActivityDb = initActivityDb, name = tf.textNoFeatures, - emoji = initActivityDb?.emoji, + symbol = initActivityDb?.symbolOrDefault(), // region Goal goalTypeUi = when (goalType) { is ActivityDb.GoalType.Timer -> GoalTypeUi.Timer @@ -215,8 +216,8 @@ class ActivityFormVm( state.update { it.copy(name = newName) } } - fun setEmoji(emoji: String) { - state.update { it.copy(emoji = emoji) } + fun setSymbol(symbol: Symbol) { + state.update { it.copy(symbol = symbol) } } fun setGoalType(goalTypeUi: GoalTypeUi?) { @@ -300,9 +301,8 @@ class ActivityFormVm( null -> null } - val emoji: String = state.emoji ?: run { - throw UiException("Emoji Not Selected") - } + val symbol: Symbol = + state.symbol ?: throw UiException("Icon Not Selected") if (goalType == ActivityDb.GoalType.Checklist && state.checklistsDb.isEmpty()) throw UiException("No Checklist Selected") @@ -327,7 +327,7 @@ class ActivityFormVm( goalType = goalType, timerType = timerType, period = state.period, - emoji = emoji, + symbol = symbol, colorRgba = state.colorRgba, keepScreenOn = state.keepScreenOn, pomodoroTimer = state.pomodoroTimer, @@ -340,7 +340,7 @@ class ActivityFormVm( goalType = goalType, timerType = timerType, period = state.period, - emoji = emoji, + symbol = symbol, colorRgba = state.colorRgba, keepScreenOn = state.keepScreenOn, pomodoroTimer = state.pomodoroTimer, @@ -386,7 +386,7 @@ class ActivityFormVm( /// enum class GoalTypeUi(val id: Int, val title: String) { - Timer(1, "Amount of Time"), + Timer(1, "Timer"), Counter(2, "Number of Times"), Checklist(3, "Complete Checklist"), } @@ -410,7 +410,7 @@ class ActivityFormVm( TimerPicker(3, "Timer Picker"), Daytime(4, "Time of Day"), StopwatchZero(5, "Stopwatch"), - StopwatchDaily(6, "Daily Stopwatch"), + StopwatchDaily(6, "Total Stopwatch"), } data class PomodoroItemUi( diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/vm/app/AppVm.kt b/shared/src/commonMain/kotlin/me/timeto/shared/vm/app/AppVm.kt index bb1535bb7..6fc110dba 100644 --- a/shared/src/commonMain/kotlin/me/timeto/shared/vm/app/AppVm.kt +++ b/shared/src/commonMain/kotlin/me/timeto/shared/vm/app/AppVm.kt @@ -45,6 +45,22 @@ class AppVm : Vm() { activityDb.updateGoal(ActivityDb.GoalType.Timer(seconds = oldTimer)) } + // todo remove migration starts July 2026 + val allTaskFolders = TaskFolderDb.selectAllSorted() + if (!allTaskFolders.any { it.isTomorrow }) + TaskFolderDb.insertNoValidation(id = TaskFolderDb.ID_TOMORROW, sort = 2, activityDb = null, name = "Tomorrow", symbol = Symbol.Icon.IconEnum.moon.toIcon()) + if (!allTaskFolders.any { it.isSomeday }) { + val smday = allTaskFolders.firstOrNull { it.name.lowercase() == "smday" } + if (smday != null) { + db.transaction { + db.taskFolderQueries.updateIdTodoRemove(newId = TaskFolderDb.ID_SOMEDAY, oldId = smday.id) + db.taskQueries.updateFolderIdTodoRemove(newFolderId = TaskFolderDb.ID_SOMEDAY, oldFolderId = smday.id) + } + } else { + TaskFolderDb.insertNoValidation(id = TaskFolderDb.ID_SOMEDAY, sort = 3, activityDb = null, name = "Someday", symbol = Symbol.Icon.IconEnum.inbox.toIcon()) + } + } + state.update { it.copy(isAppReady = true) } /// @@ -69,7 +85,7 @@ class AppVm : Vm() { checklistDb.resetIfNeeded(todayWithDayStartOffset = todayWithDayStartOffset) } RepeatingDb.syncTodaySafe(todayWithDayStartOffset) - syncTmrw(todayWithDayStartOffset) + syncTomorrow(todayWithDayStartOffset) } TimeFlows.todayFlow.onEachExIn(this) { today -> @@ -127,11 +143,11 @@ private fun performShortcutForInterval( /// -private suspend fun syncTmrw(todayWithDayStartOffset: Int) { +private suspend fun syncTomorrow(todayWithDayStartOffset: Int) { val todayFolder: TaskFolderDb = TaskFolderDb.selectAllSorted().first { it.isToday } val dayStartOffsetSeconds: Int = DayStartOffsetUtils.getOffsetSeconds() Cache.tasksDb - .filter { it.isTmrw } + .filter { it.isTomorrow } .filter { DayStartOffsetUtils.calcDay( time = it.id, @@ -155,18 +171,35 @@ private suspend fun fillInitData( withDemoData: Boolean, ) { - TaskFolderDb.insertNoValidation(id = TaskFolderDb.ID_TODAY, sort = 1, activityDb = null, name = "Today") - TaskFolderDb.insertTmrw() - TaskFolderDb.insertNoValidation(id = time(), sort = 3, activityDb = null, name = "SMDAY") + TaskFolderDb.insertNoValidation( + id = TaskFolderDb.ID_TODAY, + sort = 1, + activityDb = null, + name = "Today", + symbol = Symbol.Icon.IconEnum.sun.toIcon(), + ) + TaskFolderDb.insertNoValidation( + id = TaskFolderDb.ID_TOMORROW, + sort = 2, + activityDb = null, + name = "Tomorrow", + symbol = Symbol.Icon.IconEnum.moon.toIcon(), + ) + TaskFolderDb.insertNoValidation( + id = TaskFolderDb.ID_SOMEDAY, + sort = 3, + activityDb = null, + name = "Someday", + symbol = Symbol.Icon.IconEnum.inbox.toIcon(), + ) KvDb.KEY.WHATS_NEW_CHECK_UNIX_DAY.upsertInt(WhatsNewVm.historyItemsUi.first().unixDay) - val readingActivityDb = addReadingActivity() - val workActivityDb = addWorkActivity() - val exercisesActivityDb = addExercisesActivity() val (morningActivityDb, initIntervalDb) = addMorningActivityAndStartInterval() - val eatingActivityDb = addEatingActivity() - val commuteActivityDb = addCommuteActivity() + val workActivityDb = addWorkActivity() + val smallTasksActivityDb = addSmallTasksActivity() + val readingActivityDb = addReadingActivity() + val workoutActivityDb = addWorkoutActivity() val freeTimeActivityDb = addFreeTimeActivity() val sleepActivityDb = addSleepActivity() @@ -176,11 +209,10 @@ private suspend fun fillInitData( if (withDemoData) { fillDemoData( morningActivityDb = morningActivityDb, - commuteActivityDb = commuteActivityDb, workActivityDb = workActivityDb, - eatingActivityDb = eatingActivityDb, - exercisesActivityDb = exercisesActivityDb, + smallTasksActivityDb = smallTasksActivityDb, readingActivityDb = readingActivityDb, + workoutActivityDb = workoutActivityDb, freeTimeActivityDb = freeTimeActivityDb, sleepActivityDb = sleepActivityDb, ) @@ -193,46 +225,52 @@ private suspend fun fillInitData( private val everyDayActivityPeriod: ActivityDb.Period = ActivityDb.Period.DaysOfWeek.everyDay -private suspend fun addReadingActivity(): ActivityDb { +private suspend fun addMorningActivityAndStartInterval(): Pair { // Checklist - val checklistDb = ChecklistDb.insertWithValidation("Reading", isResetOnDayStarts = true) - ChecklistItemDb.insertWithValidation("Read 30 Pages", checklistDb, false) + val checklistDb = ChecklistDb.insertWithValidation("Morning", isResetOnDayStarts = true) + ChecklistItemDb.insertWithValidation("Glass of Water", checklistDb, true) + ChecklistItemDb.insertWithValidation("Stretching", checklistDb, true) + ChecklistItemDb.insertWithValidation("Shower", checklistDb, true) + ChecklistItemDb.insertWithValidation("Breakfast", checklistDb, false) + ChecklistItemDb.insertWithValidation("Pills", checklistDb, false) + ChecklistItemDb.insertWithValidation("Day Plan", checklistDb, false) // Activity - val activityTitle = "Reading".textFeatures() + val activityTitle = "Morning".textFeatures() .copy(checklistsDb = listOf(checklistDb)) .textWithFeatures() + val timerSeconds = 2 * 3_600 val activityDb = ActivityDb.insertWithValidation( name = activityTitle, - goalType = ActivityDb.GoalType.Timer(seconds = 3_600), - timerType = ActivityDb.TimerType.RestOfGoal, + goalType = ActivityDb.GoalType.Checklist, + timerType = ActivityDb.TimerType.FixedTimer(timer = timerSeconds), period = everyDayActivityPeriod, - emoji = "📖", - colorRgba = Palette.purple.dark, + symbol = Symbol.Icon.IconEnum.rocket.toIcon(), + colorRgba = Palette.indigo.dark, keepScreenOn = true, pomodoroTimer = 5 * 60, - timerHints = listOf(30 * 60, 60 * 60), + timerHints = listOf(30 * 60, 60 * 60, 60 * 60 + 30 * 60), parentActivityDb = null, type = ActivityDb.Type.general, ) - activityDb.updateHomeButtonSort(HomeButtonSort(rowIdx = 2, cellIdx = 4, size = 2)) - return activityDb + activityDb.updateHomeButtonSort(HomeButtonSort(rowIdx = 0, cellIdx = 0, size = 2)) + // Start Goal + return activityDb to activityDb.startTimer(timerSeconds) } private suspend fun addWorkActivity(): ActivityDb { // Checklist val checklistDb = ChecklistDb.insertWithValidation("Work", isResetOnDayStarts = true) - ChecklistItemDb.insertWithValidation("Workday Plan", checklistDb, false) - ChecklistItemDb.insertWithValidation("Done", checklistDb, false) + ChecklistItemDb.insertWithValidation("Track Working Hours", checklistDb, false) // Activity val activityTitle = "Work".textFeatures() .copy(checklistsDb = listOf(checklistDb)) .textWithFeatures() val activityDb = ActivityDb.insertWithValidation( name = activityTitle, - goalType = ActivityDb.GoalType.Timer(seconds = 8 * 3_600), - timerType = ActivityDb.TimerType.RestOfGoal, + goalType = ActivityDb.GoalType.Checklist, + timerType = ActivityDb.TimerType.StopwatchDaily, period = everyDayActivityPeriod, - emoji = "📁", + symbol = Symbol.Icon.IconEnum.instruments.toIcon(), colorRgba = Palette.blue.dark, keepScreenOn = true, pomodoroTimer = 5 * 60, @@ -240,123 +278,95 @@ private suspend fun addWorkActivity(): ActivityDb { parentActivityDb = null, type = ActivityDb.Type.general, ) - activityDb.updateHomeButtonSort(HomeButtonSort(rowIdx = 1, cellIdx = 0, size = 6)) + activityDb.updateHomeButtonSort(HomeButtonSort(rowIdx = 0, cellIdx = 2, size = 2)) return activityDb } -private suspend fun addExercisesActivity(): ActivityDb { - // Checklist - val checklistDb = ChecklistDb.insertWithValidation("Exercises", isResetOnDayStarts = true) - ChecklistItemDb.insertWithValidation("Smart Watch", checklistDb, false) - ChecklistItemDb.insertWithValidation("Bottle of Water", checklistDb, false) - ChecklistItemDb.insertWithValidation("Shower", checklistDb, false) - // Activity - val activityTitle = "Exercises".textFeatures() - .copy(checklistsDb = listOf(checklistDb)) - .textWithFeatures() +private suspend fun addSmallTasksActivity(): ActivityDb { val activityDb = ActivityDb.insertWithValidation( - name = activityTitle, - goalType = ActivityDb.GoalType.Timer(seconds = 3_600), + name = "Small Tasks", + goalType = ActivityDb.GoalType.Timer(seconds = 30 * 60), timerType = ActivityDb.TimerType.RestOfGoal, period = everyDayActivityPeriod, - emoji = "💪", - colorRgba = Palette.orange.dark, - keepScreenOn = false, + symbol = Symbol.Icon.IconEnum.bolt.toIcon(), + colorRgba = Palette.cyan.light, + keepScreenOn = true, pomodoroTimer = 5 * 60, - timerHints = listOf(20 * 60, 60 * 60, 3 * 60 * 60), + timerHints = listOf(30 * 60, 60 * 60), parentActivityDb = null, type = ActivityDb.Type.general, ) - activityDb.updateHomeButtonSort(HomeButtonSort(rowIdx = 2, cellIdx = 2, size = 2)) + activityDb.updateHomeButtonSort(HomeButtonSort(rowIdx = 0, cellIdx = 4, size = 2)) return activityDb } -private suspend fun addMorningActivityAndStartInterval(): Pair { - // Checklist - val checklistDb = ChecklistDb.insertWithValidation("Morning", isResetOnDayStarts = true) - ChecklistItemDb.insertWithValidation("Glass of Water", checklistDb, true) - ChecklistItemDb.insertWithValidation("Stretching", checklistDb, true) - ChecklistItemDb.insertWithValidation("Shower", checklistDb, true) - ChecklistItemDb.insertWithValidation("Breakfast", checklistDb, false) - ChecklistItemDb.insertWithValidation("Pills", checklistDb, false) - ChecklistItemDb.insertWithValidation("Day Plan", checklistDb, false) +private suspend fun addReadingActivity(): ActivityDb { // Activity - val activityTitle = "Morning".textFeatures() - .copy(checklistsDb = listOf(checklistDb)) - .textWithFeatures() - val goalSeconds = 3_600 + val activityTitle = "Reading" val activityDb = ActivityDb.insertWithValidation( name = activityTitle, - goalType = ActivityDb.GoalType.Timer(seconds = goalSeconds), - timerType = ActivityDb.TimerType.RestOfGoal, - period = everyDayActivityPeriod, - emoji = "🚀", - colorRgba = Palette.indigo.dark, - keepScreenOn = true, - pomodoroTimer = 5 * 60, - timerHints = listOf(30 * 60, 60 * 60, 60 * 60 + 30 * 60), - parentActivityDb = null, - type = ActivityDb.Type.general, - ) - activityDb.updateHomeButtonSort(HomeButtonSort(rowIdx = 0, cellIdx = 0, size = 3)) - // Start Goal - return activityDb to activityDb.startTimer(goalSeconds) -} - -private suspend fun addEatingActivity(): ActivityDb { - val activityDb = ActivityDb.insertWithValidation( - name = "Eating", - goalType = ActivityDb.GoalType.Timer(seconds = 3_600), - timerType = ActivityDb.TimerType.RestOfGoal, + goalType = ActivityDb.GoalType.Counter(count = 2), + timerType = ActivityDb.TimerType.StopwatchDaily, period = everyDayActivityPeriod, - emoji = "🥦", - colorRgba = Palette.indigo.dark, + symbol = Symbol.Icon.IconEnum.book.toIcon(), + colorRgba = Palette.purple.dark, keepScreenOn = true, pomodoroTimer = 5 * 60, - timerHints = listOf(15 * 60, 60 * 60), + timerHints = listOf(30 * 60, 60 * 60), parentActivityDb = null, type = ActivityDb.Type.general, ) - activityDb.updateHomeButtonSort(HomeButtonSort(rowIdx = 2, cellIdx = 0, size = 2)) + activityDb.updateHomeButtonSort(HomeButtonSort(rowIdx = 1, cellIdx = 0, size = 1)) return activityDb } -private suspend fun addCommuteActivity(): ActivityDb { +private suspend fun addWorkoutActivity(): ActivityDb { + // Checklist + val checklistDb = ChecklistDb.insertWithValidation("Workout", isResetOnDayStarts = true) + ChecklistItemDb.insertWithValidation("Smart Watch", checklistDb, false) + ChecklistItemDb.insertWithValidation("Bottle of Water", checklistDb, false) + // Activity + val activityTitle = "Workout".textFeatures() + .copy(checklistsDb = listOf(checklistDb)) + .textWithFeatures() val activityDb = ActivityDb.insertWithValidation( - name = "Commute", - goalType = ActivityDb.GoalType.Timer(seconds = 3_600), - timerType = ActivityDb.TimerType.RestOfGoal, + name = activityTitle, + goalType = ActivityDb.GoalType.Counter(count = 1), + timerType = ActivityDb.TimerType.StopwatchZero, period = everyDayActivityPeriod, - emoji = "🚗", - colorRgba = Palette.cyan.dark, + symbol = Symbol.Icon.IconEnum.exercise.toIcon(), + colorRgba = Palette.orange.dark, keepScreenOn = false, pomodoroTimer = 5 * 60, - timerHints = listOf(30 * 60, 60 * 60), + timerHints = listOf(20 * 60, 60 * 60, 3 * 60 * 60), parentActivityDb = null, type = ActivityDb.Type.general, ) - activityDb.updateHomeButtonSort(HomeButtonSort(rowIdx = 0, cellIdx = 3, size = 3)) + activityDb.updateHomeButtonSort(HomeButtonSort(rowIdx = 1, cellIdx = 1, size = 1)) return activityDb } private suspend fun addFreeTimeActivity(): ActivityDb { + // Shopping + val shoppingDb = ChecklistDb.insertWithValidation("Shopping", isResetOnDayStarts = false) + ChecklistItemDb.insertWithValidation("Bread", shoppingDb, false) + ChecklistItemDb.insertWithValidation("Butter", shoppingDb, false) + ChecklistItemDb.insertWithValidation("Milk", shoppingDb, false) // Checklist val checklistDb = ChecklistDb.insertWithValidation("Free Time", isResetOnDayStarts = true) - ChecklistItemDb.insertWithValidation("Walk", checklistDb, false) - ChecklistItemDb.insertWithValidation("Meditation", checklistDb, false) - ChecklistItemDb.insertWithValidation("Hobby", checklistDb, false) - ChecklistItemDb.insertWithValidation("News", checklistDb, false) - ChecklistItemDb.insertWithValidation("Small Tasks", checklistDb, false) + val shoppingItemTitle: String = + "Shopping".textFeatures().copy(checklistsDb = listOf(shoppingDb)).textWithFeatures() + ChecklistItemDb.insertWithValidation(shoppingItemTitle, checklistDb, false) // Activity val activityTitle = "Free Time".textFeatures() .copy(checklistsDb = listOf(checklistDb)) .textWithFeatures() val activityDb = ActivityDb.insertWithValidation( name = activityTitle, - goalType = ActivityDb.GoalType.Timer(seconds = 3 * 3_600), + goalType = null, timerType = ActivityDb.TimerType.RestOfGoal, period = everyDayActivityPeriod, - emoji = "💡", + symbol = Symbol.Icon.IconEnum.bulb.toIcon(), colorRgba = Palette.gray.dark, keepScreenOn = true, pomodoroTimer = 5 * 60, @@ -364,28 +374,25 @@ private suspend fun addFreeTimeActivity(): ActivityDb { parentActivityDb = null, type = ActivityDb.Type.other, ) - activityDb.updateHomeButtonSort(HomeButtonSort(rowIdx = 3, cellIdx = 0, size = 2)) + activityDb.updateHomeButtonSort(HomeButtonSort(rowIdx = 1, cellIdx = 2, size = 2)) return activityDb } private suspend fun addSleepActivity(): ActivityDb { // Checklist val checklistDb = ChecklistDb.insertWithValidation("Sleep", isResetOnDayStarts = true) - ChecklistItemDb.insertWithValidation("Set Alarm", checklistDb, false) + ChecklistItemDb.insertWithValidation("Glass of Water", checklistDb, true) ChecklistItemDb.insertWithValidation("Check Tomorrow", checklistDb, false) - ChecklistItemDb.insertWithValidation("Prepare Breakfast", checklistDb, false) - ChecklistItemDb.insertWithValidation("Day Reflection", checklistDb, false) - ChecklistItemDb.insertWithValidation("Wake Up", checklistDb, false) // Activity val activityTitle = "Sleep".textFeatures() .copy(checklistsDb = listOf(checklistDb)) .textWithFeatures() val activityDb = ActivityDb.insertWithValidation( name = activityTitle, - goalType = ActivityDb.GoalType.Timer(seconds = 8 * 3_600), - timerType = ActivityDb.TimerType.RestOfGoal, + goalType = null, + timerType = ActivityDb.TimerType.StopwatchZero, period = everyDayActivityPeriod, - emoji = "🌙", + symbol = Symbol.Icon.IconEnum.moon_stars.toIcon(), colorRgba = Palette.green.dark, keepScreenOn = false, pomodoroTimer = 5 * 60, @@ -393,6 +400,6 @@ private suspend fun addSleepActivity(): ActivityDb { parentActivityDb = null, type = ActivityDb.Type.general, ) - activityDb.updateHomeButtonSort(HomeButtonSort(rowIdx = 3, cellIdx = 2, size = 4)) + activityDb.updateHomeButtonSort(HomeButtonSort(rowIdx = 1, cellIdx = 4, size = 2)) return activityDb } diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/vm/calendar/CalendarDayVm.kt b/shared/src/commonMain/kotlin/me/timeto/shared/vm/calendar/CalendarDayVm.kt index d5f825a36..5bffb6124 100644 --- a/shared/src/commonMain/kotlin/me/timeto/shared/vm/calendar/CalendarDayVm.kt +++ b/shared/src/commonMain/kotlin/me/timeto/shared/vm/calendar/CalendarDayVm.kt @@ -9,7 +9,7 @@ import me.timeto.shared.UnixTime import me.timeto.shared.db.EventDb import me.timeto.shared.db.RepeatingDb import me.timeto.shared.vm.Vm -import me.timeto.shared.vm.tasks.tab.repeatings.TasksTabRepeatingsVm +import me.timeto.shared.vm.repeatings.list.RepeatingsListVm class CalendarDayVm( private val unixDay: Int, @@ -65,7 +65,7 @@ class CalendarDayVm( ) : ItemUi() data class RepeatingUi( - val repeatingsListRepeatingUi: TasksTabRepeatingsVm.RepeatingUi, + val repeatingsListRepeatingUi: RepeatingsListVm.RepeatingUi, ) : ItemUi() } } @@ -79,6 +79,6 @@ private fun buildItemsUi( CalendarDayVm.ItemUi.EventUi(CalendarListVm.EventUi(eventDb)) }, allRepeatingsDb.filter { it.inCalendar && it.isInDay(unixDay) }.map { repeatingDb -> - CalendarDayVm.ItemUi.RepeatingUi(TasksTabRepeatingsVm.RepeatingUi(repeatingDb)) + CalendarDayVm.ItemUi.RepeatingUi(RepeatingsListVm.RepeatingUi(repeatingDb)) }, ).flatten() diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/vm/doc/DocVm.kt b/shared/src/commonMain/kotlin/me/timeto/shared/vm/doc/DocVm.kt new file mode 100644 index 000000000..a319623bb --- /dev/null +++ b/shared/src/commonMain/kotlin/me/timeto/shared/vm/doc/DocVm.kt @@ -0,0 +1,28 @@ +package me.timeto.shared.vm.doc + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import me.timeto.shared.db.KvDb +import me.timeto.shared.launchExIo +import me.timeto.shared.time +import me.timeto.shared.vm.Vm + +class DocVm : Vm() { + + data class State( + val tmp: Int, + ) { + + val askQuestionSubject = "Documentation" + } + + override val state: StateFlow = MutableStateFlow( + State(tmp = 1) + ) + + fun onRead() { + launchExIo { + KvDb.KEY.DOC_FORCE_READ_TIME.upsertInt(time()) + } + } +} diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/HomeVm.kt b/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/HomeVm.kt index bbb54c6b9..1f73bacbf 100644 --- a/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/HomeVm.kt +++ b/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/HomeVm.kt @@ -5,34 +5,48 @@ import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import me.timeto.shared.* import me.timeto.shared.db.* -import me.timeto.shared.db.KvDb.Companion.todayOnHomeScreen import me.timeto.shared.limitMin import me.timeto.shared.SystemInfo import me.timeto.shared.TaskUi -import me.timeto.shared.sortedUi import me.timeto.shared.time import me.timeto.shared.TimerStateUi -import me.timeto.shared.db.KvDb.Companion.isCollapseHomeTasks import me.timeto.shared.vm.whats_new.WhatsNewVm import me.timeto.shared.vm.Vm +import me.timeto.shared.vm.home.tasks.HomeTasksBarUi +import me.timeto.shared.vm.home.tasks.HomeTasksItemUi +import me.timeto.shared.vm.home.tasks.homeTasksFoldersSorted import kotlin.math.absoluteValue class HomeVm : Vm() { + companion object { + + private val showStartScreenFlow = MutableSharedFlow() + + fun showStartScreen() { + launchExIo { + showStartScreenFlow.emit(Unit) + } + } + } + data class State( val intervalDbAndActivityDb: IntervalDbAndActivityDb, val isPurple: Boolean, val allTasksUi: List, val privacyMessage: String?, - val showReadme: Boolean, + // todo remove after update July 2026 + val showDocBanner: Boolean, + val forceOpenDoc: Boolean, val showRate: Boolean, val whatsNewMessage: String?, val listsContainerSize: ListsContainerSize?, val notificationsPermissionUi: NotificationsPermissionUi?, val donationsMessage: String?, - val allTaskFoldersDb: List, - val isCollapseHomeTasks: Boolean, - val onHomeActivity: Boolean, + val allRepeatingsDb: List, + val allEventsDb: List, + val allTaskFoldersUi: List, + val taskFolderUi: TaskFolderUi, val idToUpdate: Long, ) { @@ -41,26 +55,17 @@ class HomeVm : Vm() { val activityDb: ActivityDb = intervalDbAndActivityDb.activityDb - val readmeTitle = "Goals is the main feature of this app." + // todo remove. Needed only for old users. + val readmeTitle = "New Readme is Here!" val readmeButtonText = "Read How to Use the App" val rateLine1 = "Hi," val rateLine2 = "I try to build the best productivity app possible and would love to read your review." val rateNoThanks = "No Thanks" - val todayTasksUi: List = - allTasksUi.filter { it.taskDb.isToday } - - val activityTaskFolderDb: TaskFolderDb? = - allTaskFoldersDb.firstOrNull { it.activity_id == activityDb.id } - - val activityFolderTasksUi: List = - if (activityTaskFolderDb == null) emptyList() - else allTasksUi.filter { it.taskDb.folder_id == activityTaskFolderDb.id } - val timerStateUi = TimerStateUi( intervalDb = intervalDb, - todayTasksDb = todayTasksUi.map { it.taskDb }, + todayTasksDb = allTasksUi.filter { it.taskDb.isToday }.map { it.taskDb }, isPurple = isPurple, ) @@ -87,60 +92,59 @@ class HomeVm : Vm() { shortcutsDb = textFeaturesForTriggers.shortcutsDb, ) - val mainListItemsUi: List = run { - val listItemsUi = mutableListOf() - - val tasksUi: List = - if (KvDb.KEY.TODAY_ON_HOME_SCREEN.selectOrNullCached().todayOnHomeScreen()) - todayTasksUi - else - todayTasksUi.filter { taskUi -> - val taskTf = taskUi.tf - // Condition - (taskTf.paused != null) || - taskTf.isImportant || - (taskTf.calcTimeData()?.type?.isEvent() == true) - } - if (activityTaskFolderDb == null || !isCollapseHomeTasks) - listItemsUi.addAll(tasksUi.sortedUi(isToday = true).map { MainListItemUi.MainTaskUi(it) }) - - if (activityTaskFolderDb != null) { - listItemsUi.add( - MainListItemUi.TaskFolderBarUi( - taskFolderDb = activityTaskFolderDb, - todayTasksCount = tasksUi.size, - isCollapsed = isCollapseHomeTasks, + val homeTasksItemsUi: List = run { + val listItemsUi = mutableListOf() + + val taskFolderActivityId: Int? = + taskFolderUi.taskFolderDb.activity_id + val tasksUi: List = allTasksUi + .filter { + (it.taskDb.folder_id == taskFolderUi.taskFolderDb.id) || + (taskFolderActivityId != null && taskFolderActivityId == it.tf.activityDb?.id) + } + .map { + HomeTasksItemUi.HomeTaskUi( + taskUi = it, + allTaskFoldersUi = allTaskFoldersUi, ) - ) + } + .reversed() + listItemsUi.addAll(tasksUi) + + if (taskFolderUi.taskFolderDb.isTomorrow) { listItemsUi.addAll( - activityFolderTasksUi - .reversed() - .filter { !onHomeActivity || it.taskDb.onHomeActivity } - .sortedWith(compareBy({ !it.taskDb.onHomeActivity }, { -it.taskDb.id })) - .map { MainListItemUi.MainTaskUi(it) } + buildTomorrowItemsUi( + allRepeatingsDb = allRepeatingsDb, + allEventsDb = allEventsDb, + ) ) } return@run listItemsUi } + val tasksBarUi = HomeTasksBarUi( + taskFolderDb = taskFolderUi.taskFolderDb, + taskFoldersUi = allTaskFoldersUi.homeTasksFoldersSorted(), + ) + val listsSizes: ListsSizes = run { val lc = listsContainerSize ?: return@run ListsSizes(0f, 0f) // // No one - if (checklistDb == null && mainListItemsUi.isEmpty()) + if (checklistDb == null && homeTasksItemsUi.isEmpty()) return@run ListsSizes(0f, 0f) // // Only one - if (checklistDb != null && mainListItemsUi.isEmpty()) + if (checklistDb != null && homeTasksItemsUi.isEmpty()) return@run ListsSizes(checklist = lc.totalHeight, mainTasks = 0f) - if (checklistDb == null && mainListItemsUi.isNotEmpty()) + if (checklistDb == null && homeTasksItemsUi.isNotEmpty()) return@run ListsSizes(checklist = 0f, mainTasks = lc.totalHeight) // // Both checklistDb!! val halfHeight: Float = lc.totalHeight / 2 - val tasksFullHeight: Float = mainListItemsUi.size * lc.itemHeight + val tasksFullHeight: Float = homeTasksItemsUi.size * lc.itemHeight // Tasks smaller the half if (tasksFullHeight < halfHeight) return@run ListsSizes( @@ -178,15 +182,22 @@ class HomeVm : Vm() { isPurple = false, allTasksUi = Cache.tasksDb.map { it.toUi() }, privacyMessage = null, // todo init data - showReadme = false, // todo init data + showDocBanner = false, // todo init data + forceOpenDoc = false, // todo init data showRate = false, // todo init data whatsNewMessage = null, // todo init data listsContainerSize = null, notificationsPermissionUi = null, // todo init data donationsMessage = null, // todo init data - allTaskFoldersDb = Cache.taskFoldersDbSorted, - isCollapseHomeTasks = KvDb.KEY.IS_COLLAPSE_HOME_TASKS.selectOrNullCached().isCollapseHomeTasks(), - onHomeActivity = true, + allRepeatingsDb = Cache.repeatingsDb, + allEventsDb = Cache.eventsDb, + allTaskFoldersUi = Cache.taskFoldersDbSorted.map { + TaskFolderUi(it, it.selectActivityDbOrNullCached()) + }, + taskFolderUi = TaskFolderUi( + taskFolderDb = Cache.todayTaskFolderDb, + activityDb = null, // Always null for today + ), idToUpdate = 0, ) ) @@ -200,17 +211,19 @@ class HomeVm : Vm() { ActivityDb.selectAllFlow(), KvDb.KEY.RATE_TIME.selectIntOrNullFlow(), NotificationsPermission.flow, + RepeatingDb.selectAscFlow(), + EventDb.selectAscByTimeFlow(), TaskDb.selectAscFlow(), TaskFolderDb.selectAllSortedFlow(), - KvDb.KEY.IS_COLLAPSE_HOME_TASKS.selectOrNullFlow(), ) { firstIntervalDb, lastIntervalDb, activitiesDb, rateTime, notificationsPermission, + allRepeatingsDb, + allEventsDb, allTasksDb, - allTaskFoldersDb, - isCollapseHomeTasksKvDb -> + allTaskFoldersDb -> val showRate: Boolean = run { val twoWeeks = 86_400 * 14 @@ -244,13 +257,23 @@ class HomeVm : Vm() { isPurple = if (isNewInterval) false else state.isPurple, showRate = showRate, notificationsPermissionUi = notificationsPermissionUi, + allRepeatingsDb = allRepeatingsDb, + allEventsDb = allEventsDb, allTasksUi = allTasksDb.map { it.toUi() }, - allTaskFoldersDb = allTaskFoldersDb, - isCollapseHomeTasks = isCollapseHomeTasksKvDb.isCollapseHomeTasks(), + allTaskFoldersUi = allTaskFoldersDb.map { taskFolderDb -> + TaskFolderUi( + taskFolderDb = taskFolderDb, + activityDb = activitiesDb.firstOrNull { it.id == taskFolderDb.activity_id }, + ) + }, ) } }.launchIn(scopeVm) + showStartScreenFlow.onEachExIn(scopeVm) { + setTodayTaskFolder() + } + combine( KvDb.KEY.IS_SENDING_REPORTS.selectOrNullFlow(), TimeFlows.todayFlow, // To triggering every day @@ -273,11 +296,19 @@ class HomeVm : Vm() { } }.launchIn(scopeVm) - KvDb.KEY.HOME_README_OPEN_TIME + KvDb.KEY.DOC_FORCE_READ_TIME .selectOrNullFlow() .onEachExIn(scopeVm) { kvDb -> + // todo always show after update July 2026 + val forceOpenDoc: Boolean = when { + kvDb != null -> false + else -> (Cache.firstIntervalDb.id + 3_600 * 60) > time() + } state.update { - it.copy(showReadme = kvDb == null) + it.copy( + showDocBanner = kvDb == null, + forceOpenDoc = forceOpenDoc, + ) } } @@ -332,8 +363,17 @@ class HomeVm : Vm() { } } - fun toggleOnHomeActivity() { - state.update { it.copy(onHomeActivity = !it.onHomeActivity) } + fun updateTaskFolder(taskFolderUi: TaskFolderUi) { + state.update { it.copy(taskFolderUi = taskFolderUi) } + } + + fun setTodayTaskFolder() { + updateTaskFolder( + TaskFolderUi( + taskFolderDb = Cache.todayTaskFolderDb, + activityDb = null, // Always null for today + ) + ) } fun upListsContainerSize( @@ -346,12 +386,6 @@ class HomeVm : Vm() { state.update { it.copy(listsContainerSize = lc) } } - fun onReadmeOpen() { - launchExIo { - KvDb.KEY.HOME_README_OPEN_TIME.upsertInt(time()) - } - } - // region Rate fun onRateStart() { @@ -392,54 +426,6 @@ class HomeVm : Vm() { val activityDb: ActivityDb, ) - sealed class MainListItemUi( - val id: String, - ) { - - data class MainTaskUi( - val taskUi: TaskUi, - ) : MainListItemUi(id = "MainTaskUi_${taskUi.taskDb.id}") { - - val text: String = - taskUi.tf.textUi() - - val timeUi: TimeUi? = taskUi.tf.calcTimeData()?.let { timeData -> - TimeUi( - text = timeData.timeText(), - note = timeData.timeLeftText(), - status = timeData.status, - ) - } - - fun toggleOnHomeActivity() { - ioScope().launchEx { - taskUi.taskDb.toggleOnHomeActivity() - } - } - - class TimeUi( - val text: String, - val note: String, - val status: TextFeatures.TimeData.STATUS, - ) - } - - data class TaskFolderBarUi( - val taskFolderDb: TaskFolderDb, - val todayTasksCount: Int, - val isCollapsed: Boolean, - ) : MainListItemUi(id = "TaskFolderBarUi") { - - val addButtonText = "New Task" - - fun toggleCollapseToday() { - ioScope().launchEx { - KvDb.KEY.IS_COLLAPSE_HOME_TASKS.upsertBoolean(!isCollapsed) - } - } - } - } - data class ExtraTriggers( val checklistsDb: List, val shortcutsDb: List, @@ -493,3 +479,46 @@ class HomeVm : Vm() { object Denied : NotificationsPermissionUi() } } + +private fun buildTomorrowItemsUi( + allRepeatingsDb: List, + allEventsDb: List, +): List { + + val itemsUi: MutableList = + mutableListOf() + val unixTimeDs: UnixTime = + UnixTime(utcOffset = DayStartOffsetUtils.getLocalUtcOffsetCached()).inDays(1) + val unixDayDs: Int = + unixTimeDs.localDay + var lastFakeTaskId: Int = + unixTimeDs.localDayStartTime() + + // Repeatings + allRepeatingsDb + .filter { it.getNextDay() == unixDayDs } + .forEach { repeatingDb -> + itemsUi.add( + HomeTasksItemUi.HomeTomorrowItemUi( + tf = repeatingDb.prepTextForTask(unixDayDs).textFeatures(), + type = HomeTasksItemUi.HomeTomorrowItemUi.TomorrowType.repeating, + listId = ++lastFakeTaskId, + ) + ) + } + + // Calendar + allEventsDb + .filter { it.getLocalTime().localDay == unixDayDs } + .forEach { eventDb -> + itemsUi.add( + HomeTasksItemUi.HomeTomorrowItemUi( + tf = eventDb.prepTextForTask().textFeatures(), + type = HomeTasksItemUi.HomeTomorrowItemUi.TomorrowType.calendar, + listId = ++lastFakeTaskId, + ) + ) + } + + return itemsUi +} diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/buttons/HomeButtonType.kt b/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/buttons/HomeButtonType.kt index 93bd4e0b0..ce79e034b 100644 --- a/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/buttons/HomeButtonType.kt +++ b/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/buttons/HomeButtonType.kt @@ -12,6 +12,7 @@ import me.timeto.shared.textFeatures import me.timeto.shared.timeMls import me.timeto.shared.toHms import me.timeto.shared.toTimerHintNote +import me.timeto.shared.vm.task_form.TaskFormStrategy import kotlin.math.absoluteValue sealed class HomeButtonType { @@ -24,6 +25,8 @@ sealed class HomeButtonType { val sort: HomeButtonSort, val timerHintUi: List, val childActivitiesUi: List, + val newTaskTodayFormStrategy: TaskFormStrategy.NewTask, + val newTaskTomorrowFormStrategy: TaskFormStrategy.NewTask, val update: Long = timeMls(), ) : HomeButtonType() { diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/buttons/HomeButtonsVm.kt b/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/buttons/HomeButtonsVm.kt index 44afef5f4..e95b62de9 100644 --- a/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/buttons/HomeButtonsVm.kt +++ b/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/buttons/HomeButtonsVm.kt @@ -2,7 +2,6 @@ package me.timeto.shared.vm.home.buttons import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -10,12 +9,15 @@ import me.timeto.shared.Cache import me.timeto.shared.DayBarsUi import me.timeto.shared.HomeButtonSort import me.timeto.shared.TimeFlows +import me.timeto.shared.combine import me.timeto.shared.db.ActivityDb import me.timeto.shared.db.ChecklistItemDb import me.timeto.shared.db.IntervalDb import me.timeto.shared.db.KvDb +import me.timeto.shared.db.TaskFolderDb import me.timeto.shared.textFeatures import me.timeto.shared.vm.Vm +import me.timeto.shared.vm.task_form.TaskFormStrategy class HomeButtonsVm( val width: Float, @@ -51,8 +53,9 @@ class HomeButtonsVm( ChecklistItemDb.anyChangeFlow(), ActivityDb.anyChangeFlow(), KvDb.anyChangeFlow(), + TaskFolderDb.anyChangeFlow(), TimeFlows.eachMinuteSecondsFlow, - ) { _, _, _, _, _ -> + ) { _, _, _, _, _, _ -> fullUpdate() // Видимо из-за использованния кешированных данных при // обновлении не все данные успевают обновиться в кеше. @@ -77,6 +80,7 @@ class HomeButtonsVm( private suspend fun buildButtonsUi(): List { val allBarsUi: DayBarsUi = DayBarsUi.buildToday() + val allTaskFolders: List = TaskFolderDb.selectAllSorted() val activityButtons: List = Cache.activitiesDb.mapNotNull { activityDb -> if (!activityDb.buildPeriod().isToday()) @@ -101,6 +105,16 @@ class HomeButtonsVm( childActivitiesUi = Cache.activitiesDb .filter { it.parent_id == activityDb.id } .map { HomeButtonType.Activity.ChildActivityUi(it) }, + newTaskTodayFormStrategy = + TaskFormStrategy.NewTask( + activityDb = activityDb, + taskFolderDb = Cache.todayTaskFolderDb, + ), + newTaskTomorrowFormStrategy = + TaskFormStrategy.NewTask( + activityDb = activityDb, + taskFolderDb = Cache.tomorrowTaskFolderDb, + ), ) HomeButtonNoSorted( diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/settings/buttons/HomeSettingsButtonsVm.kt b/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/settings/buttons/HomeSettingsButtonsVm.kt index f61f1a877..b0fb14572 100644 --- a/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/settings/buttons/HomeSettingsButtonsVm.kt +++ b/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/settings/buttons/HomeSettingsButtonsVm.kt @@ -37,7 +37,7 @@ class HomeSettingsButtonsVm( val height: Float = rowHeight * buttonsData.rowsCount - val newActivityText = "New Goal" + val newActivityText = "New Activity" } override val state: MutableStateFlow @@ -45,11 +45,11 @@ class HomeSettingsButtonsVm( init { val scopeVm = scopeVm() scopeVm.launch { - ActivityDb.selectAll().forEach { actvityDb -> - val sort: HomeButtonSort? = HomeButtonSort.parseOrNull(actvityDb.home_button_sort) + ActivityDb.selectAll().forEach { activityDb -> + val sort: HomeButtonSort? = HomeButtonSort.parseOrNull(activityDb.home_button_sort) if (sort == null || (sort.rowIdx > (rowsCount - 1))) { val newSort = HomeButtonSort.findNextPosition(true, barSize = 2) - actvityDb.updateHomeButtonSort(newSort) + activityDb.updateHomeButtonSort(newSort) } } } @@ -304,7 +304,7 @@ private fun buildButtonsData( offsetY = 0f, ), HomeSettingsButtonsVm.HeaderUi( - title = "Other Goals", + title = "Other Activities", offsetY = rowHeight * (HomeButtonSort.visibleRows + 1), ), ), diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/tasks/HomeTaskStaTaskFolderUi.kt b/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/tasks/HomeTaskStaTaskFolderUi.kt new file mode 100644 index 000000000..61e001542 --- /dev/null +++ b/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/tasks/HomeTaskStaTaskFolderUi.kt @@ -0,0 +1,31 @@ +package me.timeto.shared.vm.home.tasks + +import me.timeto.shared.TaskFolderUi +import me.timeto.shared.TaskUi +import me.timeto.shared.db.TaskFolderDb +import me.timeto.shared.launchExIo + +// STA - Swipe to Action +data class HomeTaskStaTaskFolderUi( + val taskUi: TaskUi, + val taskFolderUi: TaskFolderUi, +) { + + val taskFolderDb: TaskFolderDb = + taskFolderUi.taskFolderDb + + val isSelected: Boolean = + (taskUi.taskDb.folder_id == taskFolderDb.id) || + (taskFolderDb.activity_id != null && taskFolderDb.activity_id == taskUi.tf.activityDb?.id) + + fun onTap() { + launchExIo { + taskUi.taskDb.updateFolder( + taskFolderDb = taskFolderDb, + updateFolderActivity = true, + replaceIfTmrw = true, + ) + homeTasksBarFolderAnimateFlow.emit(taskFolderDb.id) + } + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/tasks/HomeTasksBarUi.kt b/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/tasks/HomeTasksBarUi.kt new file mode 100644 index 000000000..d87cf2639 --- /dev/null +++ b/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/tasks/HomeTasksBarUi.kt @@ -0,0 +1,24 @@ +package me.timeto.shared.vm.home.tasks + +import me.timeto.shared.Cache +import me.timeto.shared.TaskFolderUi +import me.timeto.shared.db.ActivityDb +import me.timeto.shared.db.TaskFolderDb +import me.timeto.shared.vm.task_form.TaskFormStrategy + +data class HomeTasksBarUi( + val taskFolderDb: TaskFolderDb, + val taskFoldersUi: List, +) { + + // + // Add Task + + val addTaskActivityDb: ActivityDb = + taskFolderDb.selectActivityDbOrNullCached() ?: Cache.activitiesDb.first { it.isOther } + + val addTaskStrategy = TaskFormStrategy.NewTask( + activityDb = addTaskActivityDb, + taskFolderDb = taskFolderDb, + ) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/tasks/HomeTasksItemUi.kt b/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/tasks/HomeTasksItemUi.kt new file mode 100644 index 000000000..be49b06cb --- /dev/null +++ b/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/tasks/HomeTasksItemUi.kt @@ -0,0 +1,75 @@ +package me.timeto.shared.vm.home.tasks + +import me.timeto.shared.TaskFolderUi +import me.timeto.shared.TaskUi +import me.timeto.shared.TextFeatures +import me.timeto.shared.UnixTime +import me.timeto.shared.vm.task_form.TaskFormStrategy + +sealed class HomeTasksItemUi( + val id: String, +) { + + data class HomeTaskUi( + val taskUi: TaskUi, + val allTaskFoldersUi: List, + ) : HomeTasksItemUi(id = "HomeTaskUi_${taskUi.taskDb.id}") { + + val text: String = + taskUi.tf.textUi() + + val staTaskFoldersUi: List = allTaskFoldersUi + .homeTasksFoldersSorted() + .map { HomeTaskStaTaskFolderUi(taskUi, it) } + + val timeUi: TimeUi? = taskUi.tf.calcTimeData()?.let { timeData -> + TimeUi( + text = timeData.timeText(), + note = timeData.timeLeftText(), + status = timeData.status, + ) + } + + val editStrategy = TaskFormStrategy.EditTask( + taskDb = taskUi.taskDb, + ) + + class TimeUi( + val text: String, + val note: String, + val status: TextFeatures.TimeData.STATUS, + ) + } + + class HomeTomorrowItemUi( + val tf: TextFeatures, + val type: TomorrowType, + listId: Int, + ) : HomeTasksItemUi(id = "HomeTomorrowTaskUi_$listId") { + + val text: String = + tf.textUi() + + val timeUi: TomorrowTimeUi? = tf.calcTimeData()?.let { timeData -> + val text = timeData.unixTime.getStringByComponents( + UnixTime.StringComponent.dayOfMonth, + UnixTime.StringComponent.space, + UnixTime.StringComponent.month3, + UnixTime.StringComponent.comma, + UnixTime.StringComponent.space, + UnixTime.StringComponent.hhmm24, + ) + TomorrowTimeUi( + text = text, + ) + } + + class TomorrowTimeUi( + val text: String, + ) + + enum class TomorrowType { + repeating, calendar, + } + } +} diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/tasks/homeTasksBarFolderAnimteFlow.kt b/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/tasks/homeTasksBarFolderAnimteFlow.kt new file mode 100644 index 000000000..2a9cfa882 --- /dev/null +++ b/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/tasks/homeTasksBarFolderAnimteFlow.kt @@ -0,0 +1,6 @@ +package me.timeto.shared.vm.home.tasks + +import kotlinx.coroutines.flow.MutableSharedFlow + +val homeTasksBarFolderAnimateFlow = + MutableSharedFlow< /* Folder ID */ Int>() diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/tasks/homeTasksFoldersSorted.kt b/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/tasks/homeTasksFoldersSorted.kt new file mode 100644 index 000000000..c6b64046f --- /dev/null +++ b/shared/src/commonMain/kotlin/me/timeto/shared/vm/home/tasks/homeTasksFoldersSorted.kt @@ -0,0 +1,12 @@ +package me.timeto.shared.vm.home.tasks + +import me.timeto.shared.TaskFolderUi + +fun List.homeTasksFoldersSorted(): List = sortedBy { + when { + it.taskFolderDb.isToday -> -3 + it.taskFolderDb.isTomorrow -> -2 + it.taskFolderDb.isSomeday -> Int.MAX_VALUE + else -> it.taskFolderDb.sort + } +} diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/vm/readme/ReadmeVm.kt b/shared/src/commonMain/kotlin/me/timeto/shared/vm/readme/ReadmeVm.kt deleted file mode 100644 index 5d08f4c2b..000000000 --- a/shared/src/commonMain/kotlin/me/timeto/shared/vm/readme/ReadmeVm.kt +++ /dev/null @@ -1,193 +0,0 @@ -package me.timeto.shared.vm.readme - -import kotlinx.coroutines.flow.* -import me.timeto.shared.db.KvDb -import me.timeto.shared.vm.Vm - -class ReadmeVm( - defaultItem: DefaultItem, -) : Vm() { - - enum class DefaultItem { - basics, pomodoro, - } - - data class State( - val tabUi: TabUi, - ) { - val title = "How to Use the App" - val tabsUi: List = listOf(tabBasics, tabAdvanced) - } - - override val state = MutableStateFlow( - State( - tabUi = when (defaultItem) { - DefaultItem.basics -> tabBasics - DefaultItem.pomodoro -> tabAdvanced - }, - ) - ) - - fun setTabUi(tabUi: TabUi) { - state.update { it.copy(tabUi = tabUi) } - } - - /// - - data class TabUi( - val id: String, - val title: String, - val paragraphs: List, - ) - - sealed class Paragraph( - val isSlider: Boolean = false, - ) { - - class Title(val text: String) : Paragraph() - class Text(val text: String) : Paragraph() - class TextHighlight(val text: String) : Paragraph() - - class TimerTypical() : Paragraph(isSlider = true) - class TimerCharts() : Paragraph(isSlider = true) - class TimerMyActivities() : Paragraph(isSlider = true) - class TimerPractice1() : Paragraph(isSlider = true) - class TimerPractice2() : Paragraph(isSlider = true) - - class RepeatingsMy() : Paragraph(isSlider = true) - class RepeatingsToday() : Paragraph(isSlider = true) - class RepeatingsPractice1() : Paragraph(isSlider = true) - class RepeatingsPractice2() : Paragraph(isSlider = true) - - class ChecklistsExamples() : Paragraph(isSlider = true) - class ChecklistsPractice1() : Paragraph(isSlider = true) - class ChecklistsPractice2() : Paragraph(isSlider = true) - - class PomodoroExamples() : Paragraph(isSlider = true) - - class GoalsExamples() : Paragraph(isSlider = true) - - class CalendarExamples() : Paragraph(isSlider = true) - - class AskAQuestion() : Paragraph() { - val title = "Ask a Question" - val subject: String = - when (val fs = KvDb.KEY.FEEDBACK_SUBJECT.selectStringOrNullCached()) { - null -> "Feedback Readme" - else -> "$fs Readme" - } - } - } -} - -/// - -private typealias PTitle = ReadmeVm.Paragraph.Title -private typealias PText = ReadmeVm.Paragraph.Text -private typealias PTextHighlight = ReadmeVm.Paragraph.TextHighlight - -private val tabBasics = ReadmeVm.TabUi( - id = "tab_basics", - title = "Basics", - paragraphs = listOf( - - PText("Hi,"), - PText("Developer is here."), - PText("I built this app to manage my productivity. Here I will show how I use it."), - PTextHighlight("It is not just a list of features, it is my real day-to-day experience."), - PText("Enjoy!"), - - PTitle("Timer"), - PText("You must set a timer for each activity, like eating, working, reading, etc."), - PTextHighlight("Timer is running all the time, even for sleep or breakfast."), - PText("There is NO stop option! To stop the current activity, you have to start the next one."), - PText("In other words, once you complete one activity, you must start the timer for the next one."), - PText("This way I always remember what I have to do. Most of the time my screen looks like a typical pomodoro timer:"), - ReadmeVm.Paragraph.TimerTypical(), - PText("This way also provides 24/7 data on how long everything takes:"), - ReadmeVm.Paragraph.TimerCharts(), - PText("The app has some activities by default. Here are mine:"), - ReadmeVm.Paragraph.TimerMyActivities(), - PTitle("Practice"), - PText("Let's start a 45 min timer to work."), - ReadmeVm.Paragraph.TimerPractice1(), - PText("The timer starts. Let's see the summary and history."), - ReadmeVm.Paragraph.TimerPractice2(), - - PTitle("Repeating Tasks"), - PText("You may think choosing activity and timer for each task is overwhelming. This is where repeating tasks come in."), - PText("How I use it:"), - PText("Most of my activities are repeated. I wake up at the same time, then 1 hour to get ready, 2 hours working, 1 hour eating, etc. So I created a repeating task for each of these."), - ReadmeVm.Paragraph.RepeatingsMy(), - PText("Each day, these tasks move to the \"Today\" folder. It's like a schedule:"), - ReadmeVm.Paragraph.RepeatingsToday(), - PTextHighlight("The most important, when I press it, it automatically starts a timer. You don't have to choose an activity with a timer."), - PText("You can create not only everyday tasks. Like watering a cactus once a week or paying for internet once a month."), - PText("I believe this is the main feature of the app. I recommend using it to the max."), - PTitle("Practice"), - PText("Let's create a repeating task for a daily workout at 18:00."), - ReadmeVm.Paragraph.RepeatingsPractice1(), - PText("Every day, a \"Workout\" task will be added to the \"Today\" folder."), - PText("This task is already created for today. Open \"Today\" and tap the \"Workout\" task. The timer will start automatically."), - ReadmeVm.Paragraph.RepeatingsPractice2(), - - PTitle("First Steps"), - PText("We learned timer and repeating tasks. It is 80% I use."), - PText("From now on, you have to use a timer for everything you do."), - PText("Right now, I recommend creating repeating tasks to make a daily schedule. You can rely on my example:"), - ReadmeVm.Paragraph.RepeatingsMy(), - PText("Try to follow that schedule the rest of this day. The next day, move on to advanced features."), - PText("Good luck! 🍀"), - - ReadmeVm.Paragraph.AskAQuestion(), - ), -) - -private val tabAdvanced = ReadmeVm.TabUi( - id = "tab_advanced", - title = "Advanced", - paragraphs = listOf( - - PTitle("Pomodoro"), - PText("I use the Pomodoro only for work:\n- start the timer for 45 min,\n- work until the timer rings,\n- tap the timer to start a break,\n- tap it again to restart."), - PTextHighlight("In other words you only need one tap before the break and one tap after the break."), - ReadmeVm.Paragraph.PomodoroExamples(), - - PTitle("Checklists"), - PText("Checklists are an addition to repeating tasks that are placed under the timer."), - ReadmeVm.Paragraph.ChecklistsExamples(), - PTitle("Practice"), - PText("Let's create a repeating task for a daily morning routine at 7:00 with a checklist."), - ReadmeVm.Paragraph.ChecklistsPractice1(), - PText("Test it! Open \"Today\" and tap the \"Morning Routine\" task. You will see the checklist."), - ReadmeVm.Paragraph.ChecklistsPractice2(), - - PTitle("Goals"), - PText("Look at the bottom of the screenshot. For me, I set a goal to work 8 hours a day and read for 30 minutes."), - ReadmeVm.Paragraph.GoalsExamples(), - PText("To create goals, go to the activity edit form."), - - PTitle("Tasks List"), - PText("A typical task list with folders. But there are 2 special folders: today and tmrw (tomorrow)."), - PText("Today - tasks from repeating tasks and calendar go here on a set day, you can add your own."), - PText("Tmrw (tomorrow) - tasks that will be tomorrow including repeating tasks and calendar. In other words, the tasks that will be moved to \"Today\" tomorrow."), - PText("Swipe left to delete and right to edit."), - - PTitle("Calendar"), - PText("A typical calendar. Tasks from the calendar will be displayed not only in \"Today\" but also on the Home Screen. \"Call Ann\" example:"), - ReadmeVm.Paragraph.CalendarExamples(), - - PTitle("Shortcuts"), - PText("Real life example: I meditate every day, to start I open a special video on YouTube."), - PText("Shortcuts automate this. When I start the \"Meditation\" activity, the video will start automatically."), - PText("This works especially well with repeating tasks. \"Meditation\" is automatically created every day, I just tap on it, the timer and video starts."), - - PTitle("Day Start Time"), - PText("Especially for night owls. You can set the time when repeating tasks will be added for the next day. Default 00:00."), - - PTitle("Conclusion"), - PText("I hope the app will improve your life like it improved mine. I would be very happy to get feedback and answer questions."), - - ReadmeVm.Paragraph.AskAQuestion(), - ), -) diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/vm/tasks/tab/repeatings/TasksTabRepeatingsVm.kt b/shared/src/commonMain/kotlin/me/timeto/shared/vm/repeatings/list/RepeatingsListVm.kt similarity index 83% rename from shared/src/commonMain/kotlin/me/timeto/shared/vm/tasks/tab/repeatings/TasksTabRepeatingsVm.kt rename to shared/src/commonMain/kotlin/me/timeto/shared/vm/repeatings/list/RepeatingsListVm.kt index 1483dec5a..b60c78e0e 100644 --- a/shared/src/commonMain/kotlin/me/timeto/shared/vm/tasks/tab/repeatings/TasksTabRepeatingsVm.kt +++ b/shared/src/commonMain/kotlin/me/timeto/shared/vm/repeatings/list/RepeatingsListVm.kt @@ -1,4 +1,4 @@ -package me.timeto.shared.vm.tasks.tab.repeatings +package me.timeto.shared.vm.repeatings.list import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update @@ -11,7 +11,7 @@ import me.timeto.shared.onEachExIn import me.timeto.shared.textFeatures import me.timeto.shared.vm.Vm -class TasksTabRepeatingsVm : Vm() { +class RepeatingsListVm : Vm() { data class State( val repeatingsUi: List, @@ -38,11 +38,11 @@ class TasksTabRepeatingsVm : Vm() { val dayLeftString: String = repeatingDb.getPeriod().title + - (repeatingDb.daytime?.let { " at ${DaytimeUi.byDaytime(it).text}" } ?: "") + (repeatingDb.daytime?.let { " at ${DaytimeUi.byDaytime(it).text}" } ?: "") val dayRightString: String = "${repeatingDb.getNextDayString()}, " + - "${repeatingDb.getNextDay() - UnixTime().localDay}d" + "${repeatingDb.getNextDay() - UnixTime().localDay}d" val textFeatures: TextFeatures = repeatingDb.text.textFeatures() @@ -53,13 +53,13 @@ class TasksTabRepeatingsVm : Vm() { } private fun List.toUiList( -): List = this +): List = this .groupBy { it.getNextDay() } .toList() .sortedBy { it.first } .map { it.second.sortedInsideDay() } .flatten() - .map { TasksTabRepeatingsVm.RepeatingUi(it) } + .map { RepeatingsListVm.RepeatingUi(it) } private fun List.sortedInsideDay( ): List { diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/vm/settings/SettingsVm.kt b/shared/src/commonMain/kotlin/me/timeto/shared/vm/settings/SettingsVm.kt index 70cdb33ce..5a58a8a6e 100644 --- a/shared/src/commonMain/kotlin/me/timeto/shared/vm/settings/SettingsVm.kt +++ b/shared/src/commonMain/kotlin/me/timeto/shared/vm/settings/SettingsVm.kt @@ -14,7 +14,6 @@ import me.timeto.shared.db.ChecklistDb import me.timeto.shared.db.KvDb import me.timeto.shared.db.KvDb.Companion.asDayStartOffsetSeconds import me.timeto.shared.db.KvDb.Companion.isSendingReports -import me.timeto.shared.db.KvDb.Companion.todayOnHomeScreen import me.timeto.shared.db.NoteDb import me.timeto.shared.db.ShortcutDb import me.timeto.shared.launchExIo @@ -44,7 +43,6 @@ class SettingsVm : Vm() { val feedbackSubject: String, val autoBackupTimeString: String, val privacyEmoji: String?, - val todayOnHomeScreen: Boolean, ) { val headerTitle = "timeto.me" @@ -53,14 +51,12 @@ class SettingsVm : Vm() { val whatsNewNote: String = WhatsNewVm.historyItemsUi.first().timeAgoText - val todayOnHomeScreenText = "Today on Home Screen" - val supportTheDeveloperHeader = "SUPPORT THE DEVELOPER" val supportTheDeveloperReviewEmoji = prayEmoji val supportTheDeveloperGitHubTitle = "Star on GitHub" val dayStartNote: String = dayStartSecondsToString(dayStartSeconds) - val dayStartListItems = (-8..8).map { hour -> + val dayStartListItems = (-10..10).map { hour -> DayStartOffsetListItem( seconds = hour * 3_600, note = dayStartSecondsToString(hour * 3_600) @@ -89,7 +85,6 @@ class SettingsVm : Vm() { feedbackSubject = DEFAULT_FEEDBACK_SUBJECT, autoBackupTimeString = prepAutoBackupTimeString(AutoBackup.lastTimeCache.value), privacyEmoji = KvDb.KEY.IS_SENDING_REPORTS.selectOrNullCached().privacyEmojiOrNull(), - todayOnHomeScreen = KvDb.KEY.TODAY_ON_HOME_SCREEN.selectOrNullCached().todayOnHomeScreen(), ) ) @@ -102,20 +97,16 @@ class SettingsVm : Vm() { NoteDb.selectAscFlow(), KvDb.KEY.DAY_START_OFFSET_SECONDS.selectOrNullFlow(), KvDb.KEY.IS_SENDING_REPORTS.selectOrNullFlow(), - KvDb.KEY.TODAY_ON_HOME_SCREEN.selectOrNullFlow(), AutoBackup.lastTimeCache, KvDb.KEY.FEEDBACK_SUBJECT.selectStringOrNullFlow(), - ) { - activitiesDb: List, - checklistsDb: List, - shortcutsDb: List, - notesDb: List, - dayStartOffsetSeconds: KvDb?, - isSendingReports: KvDb?, - todayOnHomeScreen: KvDb?, - autoBackupLastTime: UnixTime?, - feedbackSubject: String?, - -> + ) { activitiesDb: List, + checklistsDb: List, + shortcutsDb: List, + notesDb: List, + dayStartOffsetSeconds: KvDb?, + isSendingReports: KvDb?, + autoBackupLastTime: UnixTime?, + feedbackSubject: String? -> state.update { it.copy( activitiesUi = ActivityUi.buildList(activitiesDb), @@ -124,7 +115,6 @@ class SettingsVm : Vm() { notesDb = notesDb, dayStartSeconds = dayStartOffsetSeconds.asDayStartOffsetSeconds(), privacyEmoji = isSendingReports.privacyEmojiOrNull(), - todayOnHomeScreen = todayOnHomeScreen.todayOnHomeScreen(), autoBackupTimeString = prepAutoBackupTimeString(autoBackupLastTime), feedbackSubject = feedbackSubject ?: DEFAULT_FEEDBACK_SUBJECT, ) @@ -138,12 +128,6 @@ class SettingsVm : Vm() { } } - fun setTodayOnHomeScreen(isOn: Boolean) { - launchExIo { - KvDb.KEY.TODAY_ON_HOME_SCREEN.upsertBoolean(isOn) - } - } - fun setDayStartOffsetSeconds(seconds: Int) { launchExIo { KvDb.KEY.DAY_START_OFFSET_SECONDS.upsertInt(seconds) diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/vm/symbol/SymbolLetterPickerUtils.kt b/shared/src/commonMain/kotlin/me/timeto/shared/vm/symbol/SymbolLetterPickerUtils.kt new file mode 100644 index 000000000..74c5c822e --- /dev/null +++ b/shared/src/commonMain/kotlin/me/timeto/shared/vm/symbol/SymbolLetterPickerUtils.kt @@ -0,0 +1,21 @@ +package me.timeto.shared.vm.symbol + +import me.timeto.shared.DialogsManager +import me.timeto.shared.Symbol + +object SymbolLetterPickerUtils { + + fun validateLetter( + letter: String, + dialogsManager: DialogsManager, + onSuccess: (Symbol.Letter) -> Unit, + ) { + val letter: String = + letter.trim() + if (letter.isBlank()) { + dialogsManager.alert("Empty Symbol") + return + } + onSuccess(Symbol.Letter(letter)) + } +} diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/vm/symbol/SymbolPickerVm.kt b/shared/src/commonMain/kotlin/me/timeto/shared/vm/symbol/SymbolPickerVm.kt new file mode 100644 index 000000000..4a5bcd1f9 --- /dev/null +++ b/shared/src/commonMain/kotlin/me/timeto/shared/vm/symbol/SymbolPickerVm.kt @@ -0,0 +1,20 @@ +package me.timeto.shared.vm.symbol + +import kotlinx.coroutines.flow.MutableStateFlow +import me.timeto.shared.Symbol +import me.timeto.shared.vm.Vm + +class SymbolPickerVm : Vm() { + + data class State( + val symbolChunks: List>, + ) + + override val state = MutableStateFlow( + State( + symbolChunks = Symbol.Icon.IconEnum.entries + .map { it.toIcon() } + .chunked(8), + ) + ) +} diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/vm/tasks/folders/TaskFolderFormVm.kt b/shared/src/commonMain/kotlin/me/timeto/shared/vm/task_folder/TaskFolderFormVm.kt similarity index 79% rename from shared/src/commonMain/kotlin/me/timeto/shared/vm/tasks/folders/TaskFolderFormVm.kt rename to shared/src/commonMain/kotlin/me/timeto/shared/vm/task_folder/TaskFolderFormVm.kt index d97ee46cf..a0de4afcd 100644 --- a/shared/src/commonMain/kotlin/me/timeto/shared/vm/tasks/folders/TaskFolderFormVm.kt +++ b/shared/src/commonMain/kotlin/me/timeto/shared/vm/task_folder/TaskFolderFormVm.kt @@ -1,14 +1,15 @@ -package me.timeto.shared.vm.tasks.folders +package me.timeto.shared.vm.task_folder import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update import me.timeto.shared.Cache -import me.timeto.shared.db.TaskDb -import me.timeto.shared.db.TaskFolderDb -import me.timeto.shared.launchExIo import me.timeto.shared.DialogsManager +import me.timeto.shared.Symbol import me.timeto.shared.UiException import me.timeto.shared.db.ActivityDb +import me.timeto.shared.db.TaskDb +import me.timeto.shared.db.TaskFolderDb +import me.timeto.shared.launchExIo import me.timeto.shared.textFeatures import me.timeto.shared.vm.Vm @@ -20,6 +21,7 @@ class TaskFolderFormVm( val folderDb: TaskFolderDb?, val activityDb: ActivityDb?, val name: String, + val symbol: Symbol?, ) { val title: String = @@ -32,9 +34,11 @@ class TaskFolderFormVm( val isSaveEnabled: Boolean = name.isNotBlank() + val iconTitle = "Icon" + val isActivityAvailable: Boolean = when { folderDb == null -> true - folderDb.isToday || folderDb.isTmrw -> false + folderDb.isToday || folderDb.isTomorrow || folderDb.isSomeday -> false else -> true } @@ -53,6 +57,7 @@ class TaskFolderFormVm( folderDb = folderDb, activityDb = folderDb?.selectActivityDbOrNullCached(), name = folderDb?.name ?: "", + symbol = folderDb?.symbolOrDefault(), ) ) @@ -60,6 +65,10 @@ class TaskFolderFormVm( state.update { it.copy(name = name) } } + fun setSymbol(symbol: Symbol) { + state.update { it.copy(symbol = symbol) } + } + fun setActivity(activityDb: ActivityDb?) { state.update { it.copy(activityDb = activityDb) } } @@ -73,6 +82,10 @@ class TaskFolderFormVm( state.value.activityDb val name: String = state.value.name + val symbol: Symbol = state.value.symbol ?: run { + dialogsManager.alert("No Symbol") + return@launchExIo + } val folderDb: TaskFolderDb? = state.value.folderDb if (folderDb != null) @@ -80,11 +93,13 @@ class TaskFolderFormVm( sort = folderDb.sort, activityDb = activityDb, rawName = name, + symbol = symbol, ) else TaskFolderDb.insertWithValidation( rawName = name, activityDb = activityDb, + symbol = symbol, ) onUi { onSuccess() } } catch (e: UiException) { @@ -103,6 +118,16 @@ class TaskFolderFormVm( return@launchExIo } + if (folderDb.isTomorrow) { + dialogsManager.alert("It's impossible to delete \"Tomorrow\" folder") + return@launchExIo + } + + if (folderDb.isSomeday) { + dialogsManager.alert("It's impossible to delete \"Someday\" folder") + return@launchExIo + } + if (TaskDb.selectAsc().any { it.folder_id == folderDb.id }) { dialogsManager.alert("The folder must be empty before deletion") return@launchExIo diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/vm/tasks/folders/TaskFoldersFormVm.kt b/shared/src/commonMain/kotlin/me/timeto/shared/vm/task_folders/TaskFoldersFormVm.kt similarity index 76% rename from shared/src/commonMain/kotlin/me/timeto/shared/vm/tasks/folders/TaskFoldersFormVm.kt rename to shared/src/commonMain/kotlin/me/timeto/shared/vm/task_folders/TaskFoldersFormVm.kt index 91e884c4a..60d97cfe6 100644 --- a/shared/src/commonMain/kotlin/me/timeto/shared/vm/tasks/folders/TaskFoldersFormVm.kt +++ b/shared/src/commonMain/kotlin/me/timeto/shared/vm/task_folders/TaskFoldersFormVm.kt @@ -1,15 +1,14 @@ -package me.timeto.shared.vm.tasks.folders +package me.timeto.shared.vm.task_folders import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update import me.timeto.shared.Cache +import me.timeto.shared.db.ActivityDb import me.timeto.shared.db.TaskFolderDb import me.timeto.shared.launchExIo -import me.timeto.shared.onEachExIn -import me.timeto.shared.DialogsManager -import me.timeto.shared.db.ActivityDb import me.timeto.shared.moveUiListAndroid import me.timeto.shared.moveUiListIos +import me.timeto.shared.onEachExIn import me.timeto.shared.textFeatures import me.timeto.shared.vm.Vm @@ -21,9 +20,6 @@ class TaskFoldersFormVm : Vm() { val title = "Folders" - val tmrwButtonUi: TmrwButtonUi? = - if (foldersDb.any { it.isTmrw }) null else TmrwButtonUi() - val foldersUi: List = foldersDb.map { TaskFolderUi(it) } } @@ -75,19 +71,4 @@ class TaskFoldersFormVm : Vm() { taskFolderDb.name } } - - class TmrwButtonUi { - - val text = "Add \"Tomorrow\" Folder" - - fun add( - dialogsManager: DialogsManager, - ): Unit = launchExIo { - if (TaskFolderDb.selectAllSorted().any { it.isTmrw }) { - dialogsManager.alert("Tmrw already exists") - return@launchExIo - } - TaskFolderDb.insertTmrw() - } - } } diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/vm/task_form/TaskFormStrategy.kt b/shared/src/commonMain/kotlin/me/timeto/shared/vm/task_form/TaskFormStrategy.kt index 739942d0a..7f8797f70 100644 --- a/shared/src/commonMain/kotlin/me/timeto/shared/vm/task_form/TaskFormStrategy.kt +++ b/shared/src/commonMain/kotlin/me/timeto/shared/vm/task_form/TaskFormStrategy.kt @@ -1,11 +1,13 @@ package me.timeto.shared.vm.task_form +import me.timeto.shared.db.ActivityDb import me.timeto.shared.db.TaskDb import me.timeto.shared.db.TaskFolderDb sealed class TaskFormStrategy { class NewTask( + val activityDb: ActivityDb, val taskFolderDb: TaskFolderDb, ) : TaskFormStrategy() diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/vm/task_form/TaskFormVm.kt b/shared/src/commonMain/kotlin/me/timeto/shared/vm/task_form/TaskFormVm.kt index 987234781..2b95dfc4d 100644 --- a/shared/src/commonMain/kotlin/me/timeto/shared/vm/task_form/TaskFormVm.kt +++ b/shared/src/commonMain/kotlin/me/timeto/shared/vm/task_form/TaskFormVm.kt @@ -2,6 +2,7 @@ package me.timeto.shared.vm.task_form import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update +import me.timeto.shared.ActivityUi import me.timeto.shared.Cache import me.timeto.shared.TextFeatures import me.timeto.shared.db.ChecklistDb @@ -11,8 +12,11 @@ import me.timeto.shared.launchExIo import me.timeto.shared.textFeatures import me.timeto.shared.toTimerHintNote import me.timeto.shared.DialogsManager +import me.timeto.shared.HomeButtonSort +import me.timeto.shared.TaskFolderUi import me.timeto.shared.UiException import me.timeto.shared.db.ActivityDb +import me.timeto.shared.db.TaskFolderDb import me.timeto.shared.vm.Vm class TaskFormVm( @@ -23,18 +27,15 @@ class TaskFormVm( val title: String, val doneText: String, val textFeatures: TextFeatures, + val settingsLogic: SettingsLogic, ) { val text: String = textFeatures.textNoFeatures val textPlaceholder = "Text" - val activityDb: ActivityDb? = textFeatures.activityDb - val activityTitle = "Activity" - val activityNote: String = - activityDb?.name?.textFeatures()?.textNoFeatures ?: "Not Selected" - val activitiesUi: List = - Cache.activitiesDb.map { ActivityUi(it) } + val activityDb: ActivityDb? = + textFeatures.activityDb val timerSeconds: Int? = when (val timerType = textFeatures.timerType) { is TextFeatures.TimerType.Timer -> timerType.seconds @@ -47,20 +48,22 @@ class TaskFormVm( val timerNote: String = timerSeconds?.toTimerHintNote(isShort = false) ?: "Not Selected" - val checklistsDb: List = textFeatures.checklistsDb + val checklistsDb: List = + textFeatures.checklistsDb val checklistsTitle = "Checklists" val checklistsNote: String = if (checklistsDb.isEmpty()) "None" else checklistsDb.joinToString(", ") { it.name } - val shortcutsDb: List = textFeatures.shortcutsDb + val shortcutsDb: List = + textFeatures.shortcutsDb val shortcutsTitle = "Shortcuts" val shortcutsNote: String = if (shortcutsDb.isEmpty()) "None" else shortcutsDb.joinToString(", ") { it.name } } - override val state = MutableStateFlow( + override val state: MutableStateFlow = MutableStateFlow( State( title = when (strategy) { is TaskFormStrategy.NewTask -> "New Task" @@ -69,9 +72,35 @@ class TaskFormVm( doneText = "Save", textFeatures = when (strategy) { is TaskFormStrategy.NewTask -> "".textFeatures().copy( - activityDb = strategy.taskFolderDb.selectActivityDbOrNullCached(), + activityDb = strategy.activityDb, ) - is TaskFormStrategy.EditTask -> strategy.taskDb.text.textFeatures() + is TaskFormStrategy.EditTask -> + strategy.taskDb.text.textFeatures() + }, + settingsLogic = run { + val taskFolderDb: TaskFolderDb = when (strategy) { + is TaskFormStrategy.NewTask -> strategy.taskFolderDb + is TaskFormStrategy.EditTask -> strategy.taskDb.selectTaskFolderDbCached() + } + val taskFolderActivityDb: ActivityDb? = + taskFolderDb.selectActivityDbOrNullCached() + if (taskFolderActivityDb != null) { + SettingsLogic.FixedTaskFolderUi( + taskFolderDb = taskFolderDb, + selectedHintUi = when { + taskFolderDb.isToday -> + SettingsLogic.FixedTaskFolderUi.TaskFolderHintUi.today + taskFolderDb.isTomorrow -> + SettingsLogic.FixedTaskFolderUi.TaskFolderHintUi.tomorrow + else -> + null + }, + ) + } else { + SettingsLogic.ActivitiesUi( + activitiesUi = Cache.activitiesDb.activitiesUiSorted(), + ) + } }, ) ) @@ -88,6 +117,10 @@ class TaskFormVm( } } + fun setSessionLogic(sessionLogic: SettingsLogic) { + state.update { it.copy(settingsLogic = sessionLogic) } + } + fun setTimer(seconds: Int) { state.update { it.copy( @@ -115,23 +148,43 @@ class TaskFormVm( onSuccess: () -> Unit, ): Unit = launchExIo { try { - val tf: TextFeatures = state.value.textFeatures + val tf: TextFeatures = + state.value.textFeatures if (tf.textNoFeatures.isBlank()) throw UiException("Empty text") val textWithFeatures: String = tf.textWithFeatures() + val settingsLogic: SettingsLogic = + state.value.settingsLogic when (strategy) { is TaskFormStrategy.NewTask -> { + val taskFolderDb: TaskFolderDb = when (settingsLogic) { + is SettingsLogic.FixedTaskFolderUi -> + settingsLogic.selectedHintUi?.taskFolderUi?.taskFolderDb ?: settingsLogic.taskFolderDb + is SettingsLogic.ActivitiesUi -> + strategy.taskFolderDb + } TaskDb.insertWithValidation( text = textWithFeatures, - onHomeActivity = true, - folder = strategy.taskFolderDb, + folder = taskFolderDb, ) } is TaskFormStrategy.EditTask -> { - strategy.taskDb.updateTextWithValidation( + val taskDb: TaskDb = strategy.taskDb + taskDb.updateTextWithValidation( newText = textWithFeatures, ) + if (settingsLogic is SettingsLogic.FixedTaskFolderUi) { + val hintTaskFolderDb: TaskFolderDb? = + settingsLogic.selectedHintUi?.taskFolderUi?.taskFolderDb + if (hintTaskFolderDb != null && hintTaskFolderDb.id != taskDb.id) { + taskDb.updateFolder( + taskFolderDb = hintTaskFolderDb, + updateFolderActivity = true, // No matter for today/tomorrow + replaceIfTmrw = true, + ) + } + } } } onUi { @@ -165,10 +218,54 @@ class TaskFormVm( /// - data class ActivityUi( - val activityDb: ActivityDb, - ) { - val title: String = - activityDb.name.textFeatures().textNoFeatures + sealed class SettingsLogic { + + data class FixedTaskFolderUi( + val taskFolderDb: TaskFolderDb, + val selectedHintUi: TaskFolderHintUi?, + ) : SettingsLogic() { + + val title: String = run { + val activityDb: ActivityDb? = + taskFolderDb.selectActivityDbOrNullCached() + if (activityDb != null) + return@run activityDb.name.textFeatures().textNoFeatures + taskFolderDb.name + } + + val taskFolderHintsUi: List = listOf( + TaskFolderHintUi.today, + TaskFolderHintUi.tomorrow, + ) + + fun buildWithNewHint(hintUi: TaskFolderHintUi): FixedTaskFolderUi = + copy( + selectedHintUi = + if (hintUi.taskFolderUi.taskFolderDb.id == selectedHintUi?.taskFolderUi?.taskFolderDb?.id) null + else hintUi + ) + + /// + + enum class TaskFolderHintUi( + val taskFolderUi: TaskFolderUi, + ) { + today(TaskFolderUi(Cache.todayTaskFolderDb, null)), + tomorrow(TaskFolderUi(Cache.tomorrowTaskFolderDb, null)) + } + } + + data class ActivitiesUi( + val activitiesUi: List, + ) : SettingsLogic() } } + +private fun List.activitiesUiSorted(): List = this + .map { + it to (HomeButtonSort.parseOrNull(it.home_button_sort) ?: HomeButtonSort(0, 0, 0)) + } + .sortedWith( + compareBy({ it.second.rowIdx }, { it.second.cellIdx }) + ) + .map { ActivityUi(it.first) } diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/vm/tasks/TaskTimerVm.kt b/shared/src/commonMain/kotlin/me/timeto/shared/vm/task_timer/TaskTimerVm.kt similarity index 98% rename from shared/src/commonMain/kotlin/me/timeto/shared/vm/tasks/TaskTimerVm.kt rename to shared/src/commonMain/kotlin/me/timeto/shared/vm/task_timer/TaskTimerVm.kt index 6b3fb543a..5ab1e3610 100644 --- a/shared/src/commonMain/kotlin/me/timeto/shared/vm/tasks/TaskTimerVm.kt +++ b/shared/src/commonMain/kotlin/me/timeto/shared/vm/task_timer/TaskTimerVm.kt @@ -1,4 +1,4 @@ -package me.timeto.shared.vm.tasks +package me.timeto.shared.vm.task_timer import kotlinx.coroutines.flow.MutableStateFlow import me.timeto.shared.Cache diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/vm/tasks/tab/TasksTabVm.kt b/shared/src/commonMain/kotlin/me/timeto/shared/vm/tasks/tab/TasksTabVm.kt deleted file mode 100644 index aec8a5ce4..000000000 --- a/shared/src/commonMain/kotlin/me/timeto/shared/vm/tasks/tab/TasksTabVm.kt +++ /dev/null @@ -1,45 +0,0 @@ -package me.timeto.shared.vm.tasks.tab - -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.update -import me.timeto.shared.Cache -import me.timeto.shared.db.TaskFolderDb -import me.timeto.shared.onEachExIn -import me.timeto.shared.vm.Vm - -class TasksTabVm : Vm() { - - data class State( - val taskFoldersUi: List, - val initFolder: TaskFolderDb, - ) - - override val state = MutableStateFlow( - State( - taskFoldersUi = - Cache.taskFoldersDbSorted.map { TaskFolderUi(it) }, - initFolder = Cache.getTodayFolderDb(), - ) - ) - - init { - val scopeVm = scopeVm() - TaskFolderDb.selectAllSortedFlow().onEachExIn(scopeVm) { folders -> - state.update { - it.copy(taskFoldersUi = folders.map { TaskFolderUi(it) }) - } - } - } - - /// - - data class TaskFolderUi( - val taskFolderDb: TaskFolderDb, - ) { - val tabText: String = - taskFolderDb.name.toTabText() - } -} - -private fun String.toTabText(): String = - this.uppercase().split("").joinToString("\n").trim() diff --git a/shared/src/commonMain/kotlin/me/timeto/shared/vm/tasks/tab/tasks/TasksTabTasksVm.kt b/shared/src/commonMain/kotlin/me/timeto/shared/vm/tasks/tab/tasks/TasksTabTasksVm.kt deleted file mode 100644 index ca360d206..000000000 --- a/shared/src/commonMain/kotlin/me/timeto/shared/vm/tasks/tab/tasks/TasksTabTasksVm.kt +++ /dev/null @@ -1,251 +0,0 @@ -package me.timeto.shared.vm.tasks.tab.tasks - -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.update -import me.timeto.shared.Cache -import me.timeto.shared.DaytimeUi -import me.timeto.shared.TextFeatures -import me.timeto.shared.UnixTime -import me.timeto.shared.db.EventDb -import me.timeto.shared.db.RepeatingDb -import me.timeto.shared.db.TaskDb -import me.timeto.shared.db.TaskFolderDb -import me.timeto.shared.launchExIo -import me.timeto.shared.ColorEnum -import me.timeto.shared.DayStartOffsetUtils -import me.timeto.shared.TaskUi -import me.timeto.shared.TimeFlows -import me.timeto.shared.ioScope -import me.timeto.shared.launchEx -import me.timeto.shared.sortedUi -import me.timeto.shared.textFeatures -import me.timeto.shared.vm.Vm - -class TasksTabTasksVm( - val taskFolderDb: TaskFolderDb, -) : Vm() { - - data class State( - val tasksVmUi: List, - val tmrwUi: TmrwUi?, - ) - - override val state = MutableStateFlow( - State( - tasksVmUi = Cache.tasksDb.toUiList(taskFolderDb), - tmrwUi = if (taskFolderDb.isTmrw) - prepTmrwUi( - allRepeatingsDb = Cache.repeatingsDb, - allEventsDb = Cache.eventsDb, - ) else null, - ) - ) - - init { - val scopeVm = scopeVm() - combine( - TaskDb.selectAscFlow(), - TimeFlows.eachMinuteSecondsFlow, // Update daytime badges - ) { tasksDb, _ -> - state.update { it.copy(tasksVmUi = tasksDb.toUiList(taskFolderDb)) } - }.launchIn(scopeVm) - } - - /// - - class TmrwUi( - val tasksUi: List, - val curTimeString: String, - ) - - class TmrwTaskUi( - val taskDb: TaskDb - ) { - - val textFeatures: TextFeatures = taskDb.text.textFeatures() - val text: String = textFeatures.textUi() - - val timeUi: TmrwTimeUi? = textFeatures.calcTimeData()?.let { timeData -> - val text = timeData.unixTime.getStringByComponents( - UnixTime.StringComponent.dayOfMonth, - UnixTime.StringComponent.space, - UnixTime.StringComponent.month3, - UnixTime.StringComponent.comma, - UnixTime.StringComponent.space, - UnixTime.StringComponent.hhmm24, - ) - val textColorEnum: ColorEnum = if (timeData.type.isEvent()) - ColorEnum.blue else ColorEnum.secondaryText - TmrwTimeUi( - text = text, - textColorEnum = textColorEnum, - ) - } - - class TmrwTimeUi( - val text: String, - val textColorEnum: ColorEnum, - ) - } - - class TaskVmUi( - val taskUi: TaskUi, - val taskFolderDb: TaskFolderDb, - ) { - val tf: TextFeatures = taskUi.tf - - val text: String = tf.textUi( - withActivityEmoji = taskFolderDb.activity_id == null, - withPausedEmoji = true, - ) - - val timeUi: TimeUi? = tf.calcTimeData()?.let { timeData -> - val unixTime = timeData.unixTime - val isHighlight = timeData.type.isEvent() || tf.isImportant - - val timeLeftText = timeData.timeLeftText() - val textColorEnum: ColorEnum = when (timeData.status) { - TextFeatures.TimeData.STATUS.IN -> ColorEnum.secondaryText - TextFeatures.TimeData.STATUS.SOON -> ColorEnum.blue - TextFeatures.TimeData.STATUS.OVERDUE -> ColorEnum.red - } - - if (isHighlight) { - val backgroundColorEnum: ColorEnum = - if (timeData.status.isOverdue()) ColorEnum.red else ColorEnum.blue - return@let TimeUi.HighlightUi( - timeData = timeData, - title = timeData.timeText(), - backgroundColorEnum = backgroundColorEnum, - timeLeftText = timeLeftText, - timeLeftColorEnum = textColorEnum, - ) - } - - val daytimeText: String = - DaytimeUi.byDaytime(unixTime.time - unixTime.localDayStartTime()).text - TimeUi.RegularUi( - timeData = timeData, - text = "$daytimeText $timeLeftText", - textColorEnum = textColorEnum, - ) - } - - fun toggleOnHomeActivity() { - ioScope().launchEx { - taskUi.taskDb.toggleOnHomeActivity() - } - } - - fun upFolder(newFolder: TaskFolderDb) { - launchExIo { - taskUi.taskDb.updateFolder( - taskFolderDb = newFolder, - updateFolderActivity = true, - replaceIfTmrw = true, - ) - } - } - - // - By manual removing; - // - After adding to calendar; - // - By starting from activity sheet; - fun delete() { - launchExIo { - taskUi.taskDb.delete() - } - } - - sealed class TimeUi( - val timeData: TextFeatures.TimeData, - ) { - - class HighlightUi( - timeData: TextFeatures.TimeData, - val title: String, - val backgroundColorEnum: ColorEnum, - val timeLeftText: String, - val timeLeftColorEnum: ColorEnum, - ) : TimeUi(timeData) - - class RegularUi( - timeData: TextFeatures.TimeData, - val text: String, - val textColorEnum: ColorEnum, - ) : TimeUi(timeData) - } - } -} - -private fun prepTmrwUi( - allRepeatingsDb: List, - allEventsDb: List, -): TasksTabTasksVm.TmrwUi { - - val tasksDb = mutableListOf() - val unixTmrwDS = UnixTime(utcOffset = DayStartOffsetUtils.getLocalUtcOffsetCached()).inDays(1) - val tmrwDSDay = unixTmrwDS.localDay - var lastFakeTaskId = unixTmrwDS.localDayStartTime() - - // Repeatings - allRepeatingsDb - .filter { it.getNextDay() == tmrwDSDay } - .forEach { repeatingDb -> - tasksDb.add( - TaskDb( - id = ++lastFakeTaskId, - folder_id = TaskFolderDb.ID_TODAY, - onHomeActivity = true, - text = repeatingDb.prepTextForTask(tmrwDSDay), - ) - ) - } - - // Events - allEventsDb - .filter { it.getLocalTime().localDay == tmrwDSDay } - .forEach { eventDb -> - tasksDb.add( - TaskDb( - id = ++lastFakeTaskId, - folder_id = TaskFolderDb.ID_TODAY, - onHomeActivity = true, - text = eventDb.prepTextForTask(), - ) - ) - } - - val resTasks: List = tasksDb - .map { TaskUi(it) } - .sortedUi(isToday = true) - .map { TasksTabTasksVm.TmrwTaskUi(it.taskDb) } - - val curTimeString: String = unixTmrwDS.getStringByComponents( - UnixTime.StringComponent.dayOfMonth, - UnixTime.StringComponent.space, - UnixTime.StringComponent.month, - UnixTime.StringComponent.comma, - UnixTime.StringComponent.space, - UnixTime.StringComponent.dayOfWeek, - ) - - return TasksTabTasksVm.TmrwUi( - tasksUi = resTasks, - curTimeString = "Tomorrow, $curTimeString", - ) -} - -private fun List.toUiList( - taskFolderDb: TaskFolderDb, -): List = this - .filter { it.folder_id == taskFolderDb.id } - .map { TaskUi(it) } - .sortedUi(isToday = taskFolderDb.isToday) - .map { - TasksTabTasksVm.TaskVmUi( - taskUi = it, - taskFolderDb = taskFolderDb, - ) - } diff --git a/shared/src/commonMain/sqldelight/dbsq/22.sqm b/shared/src/commonMain/sqldelight/dbsq/22.sqm new file mode 100644 index 000000000..55b25e203 --- /dev/null +++ b/shared/src/commonMain/sqldelight/dbsq/22.sqm @@ -0,0 +1,15 @@ +import kotlin.Int; + +ALTER TABLE TaskSq RENAME TO temp; + +CREATE TABLE TaskSq ( + id INTEGER AS Int NOT NULL PRIMARY KEY, + folder_id INTEGER AS Int NOT NULL, + text TEXT NOT NULL +); + +INSERT INTO TaskSq +SELECT id, folder_id, text +FROM temp; + +DROP TABLE temp; diff --git a/shared/src/commonMain/sqldelight/dbsq/23.sqm b/shared/src/commonMain/sqldelight/dbsq/23.sqm new file mode 100644 index 000000000..a19e690e6 --- /dev/null +++ b/shared/src/commonMain/sqldelight/dbsq/23.sqm @@ -0,0 +1,30 @@ +import kotlin.Int; + +ALTER TABLE ActivitySq +RENAME TO temp; + +CREATE TABLE ActivitySq ( + id INTEGER AS Int NOT NULL PRIMARY KEY, + parent_id INTEGER AS Int, -- Nullable + type_id INTEGER AS Int NOT NULL, + name TEXT NOT NULL, + goal_json TEXT, -- Nullable + timer INTEGER AS Int NOT NULL, + period_json TEXT NOT NULL, + symbol_raw TEXT NOT NULL, + home_button_sort TEXT NOT NULL, + color_rgba TEXT NOT NULL, + keep_screen_on INTEGER AS Int NOT NULL, + pomodoro_timer INTEGER AS Int NOT NULL, + checklist_hint INTEGER AS Int NOT NULL, + timer_hints TEXT NOT NULL +); + +INSERT INTO ActivitySq +SELECT +id, parent_id, type_id, name, goal_json, timer, period_json, "", +home_button_sort, color_rgba, keep_screen_on, pomodoro_timer, +checklist_hint, timer_hints +FROM temp; + +DROP TABLE temp; diff --git a/shared/src/commonMain/sqldelight/dbsq/24.sqm b/shared/src/commonMain/sqldelight/dbsq/24.sqm new file mode 100644 index 000000000..e95e321a3 --- /dev/null +++ b/shared/src/commonMain/sqldelight/dbsq/24.sqm @@ -0,0 +1,19 @@ +import kotlin.Int; + +ALTER TABLE TaskFolderSq +RENAME TO temp; + +CREATE TABLE TaskFolderSq ( + id INTEGER AS Int NOT NULL PRIMARY KEY, + sort INTEGER AS Int NOT NULL, + activity_id INTEGER AS Int, -- Nullable + name TEXT NOT NULL, + symbol_raw TEXT NOT NULL +); + +INSERT INTO TaskFolderSq +SELECT +id, sort, activity_id, name, "" +FROM temp; + +DROP TABLE temp; diff --git a/shared/src/commonMain/sqldelight/dbsq/Activity.sq b/shared/src/commonMain/sqldelight/dbsq/Activity.sq index 680e1f460..1ae9054a3 100644 --- a/shared/src/commonMain/sqldelight/dbsq/Activity.sq +++ b/shared/src/commonMain/sqldelight/dbsq/Activity.sq @@ -8,7 +8,7 @@ CREATE TABLE ActivitySq ( goal_json TEXT, -- Nullable timer INTEGER AS Int NOT NULL, period_json TEXT NOT NULL, - emoji TEXT NOT NULL, + symbol_raw TEXT NOT NULL, home_button_sort TEXT NOT NULL, color_rgba TEXT NOT NULL, keep_screen_on INTEGER AS Int NOT NULL, @@ -47,7 +47,7 @@ WHERE id=?; updateById: UPDATE ActivitySq SET parent_id=?, type_id=?, name=?, goal_json=?, - timer=?, period_json=?, emoji=?, + timer=?, period_json=?, symbol_raw=?, home_button_sort=?, color_rgba=?, keep_screen_on=?, pomodoro_timer=?, checklist_hint=?, timer_hints=? diff --git a/shared/src/commonMain/sqldelight/dbsq/Task.sq b/shared/src/commonMain/sqldelight/dbsq/Task.sq index 898017f84..2ff60390e 100644 --- a/shared/src/commonMain/sqldelight/dbsq/Task.sq +++ b/shared/src/commonMain/sqldelight/dbsq/Task.sq @@ -3,7 +3,6 @@ import kotlin.Int; CREATE TABLE TaskSq ( id INTEGER AS Int NOT NULL PRIMARY KEY, folder_id INTEGER AS Int NOT NULL, - on_home_activity INTEGER AS Int NOT NULL, text TEXT NOT NULL ); @@ -37,9 +36,9 @@ LIMIT 1; insert: INSERT INTO TaskSq -(id, folder_id, on_home_activity, text) +(id, folder_id, text) VALUES -(?, ?, ?, ?); +(?, ?, ?); -- -- Update @@ -51,7 +50,7 @@ WHERE id=:oldId; updateById: UPDATE TaskSq -SET folder_id=?, on_home_activity=?, text=? +SET folder_id=?, text=? WHERE id=?; updateTextById: @@ -64,10 +63,10 @@ UPDATE TaskSq SET folder_id=? WHERE id=?; -updateOnHomeActivityById: +updateFolderIdTodoRemove: UPDATE TaskSq -SET on_home_activity=? -WHERE id=?; +SET folder_id=:newFolderId +WHERE folder_id=:oldFolderId; updateTextTodoRemove: UPDATE TaskSq diff --git a/shared/src/commonMain/sqldelight/dbsq/TaskFolder.sq b/shared/src/commonMain/sqldelight/dbsq/TaskFolder.sq index 81387dfbe..fd315bc0d 100644 --- a/shared/src/commonMain/sqldelight/dbsq/TaskFolder.sq +++ b/shared/src/commonMain/sqldelight/dbsq/TaskFolder.sq @@ -4,7 +4,8 @@ CREATE TABLE TaskFolderSq ( id INTEGER AS Int NOT NULL PRIMARY KEY, sort INTEGER AS Int NOT NULL, activity_id INTEGER AS Int, -- Nullable - name TEXT NOT NULL + name TEXT NOT NULL, + symbol_raw TEXT NOT NULL ); -- @@ -25,16 +26,16 @@ ORDER BY sort ASC; insert: INSERT INTO TaskFolderSq -(id, sort, activity_id, name) +(id, sort, activity_id, name, symbol_raw) VALUES -(?, ?, ?, ?); +(?, ?, ?, ?, ?); -- -- Update updateById: UPDATE TaskFolderSq -SET sort=?, activity_id=?, name=? +SET sort=?, activity_id=?, name=?, symbol_raw=? WHERE id=?; updateSortById: @@ -42,6 +43,11 @@ UPDATE TaskFolderSq SET sort=? WHERE id=?; +updateIdTodoRemove: +UPDATE TaskFolderSq +SET id=:newId +WHERE id=:oldId; + -- -- Delete