From 4ecb7fe6015db682324236b75a816ee40c262b92 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Thu, 24 Apr 2025 21:03:08 -0300 Subject: [PATCH 01/59] UI for reminder. Initial checks for reminderAt value. --- .../core/util/PlatformDatePicker.android.kt | 70 +++++ .../core/util/PlatformTimePicker.android.kt | 51 ++++ .../composeResources/drawable/ic_close.png | Bin 0 -> 787 bytes .../composeResources/drawable/ic_date.png | Bin 0 -> 890 bytes .../composeResources/drawable/ic_time.png | Bin 0 -> 1840 bytes .../replyradar/app/ReplyRadarApp.kt | 5 +- .../replyradar/core/AppConstants.kt | 3 + .../core/common/clock/LocalClock.kt | 8 + .../replyradar/core/common/strings/Strings.kt | 14 + .../core/common/strings/StringsEn.kt | 18 ++ .../core/common/strings/StringsPt.kt | 18 ++ .../replyradar/core/common/ui/Dimens.kt | 6 + .../ui/components/ReplyConfirmationDialog.kt | 18 +- .../common/ui/components/ReplyDateSelector.kt | 141 ++++++++++ .../ui/components/ReplyOutlinedButton.kt | 3 +- .../common/ui/components/ReplyTimeSelector.kt | 124 +++++++++ .../core/common/ui/theme/ColorScheme.kt | 6 +- .../common/ui/theme/ReplyRadarColorScheme.kt | 3 + .../core/common/ui/theme/ReplyRadarColors.kt | 3 +- .../replyradar/core/util/Int.kt | 6 + .../core/util/PlatformDatePicker.kt | 10 + .../core/util/PlatformTimePicker.kt | 10 + .../replyradar/core/util/Timestamp.kt | 5 +- .../presentation/ActivityLogScreen.kt | 18 +- .../features/reply/domain/model/Reply.kt | 3 +- .../presentation/replylist/ReplyListScreen.kt | 23 +- .../components/RepliesArchivedScreen.kt | 3 +- .../components/RepliesOnTheRadarScreen.kt | 3 +- .../components/RepliesResolvedScreen.kt | 3 +- .../replylist/components/ReplyList.kt | 7 +- .../replylist/components/reminder/Reminder.kt | 244 ++++++++++++++++++ .../replybottomsheet/ReplyBottomSheet.kt | 3 + .../ReplyBottomSheetContent.kt | 78 +++++- .../settings/presentation/SettingsScreen.kt | 51 +++- .../core/util/PlatformDatePicker.desktop.kt | 12 + .../core/util/PlatformTimePicker.desktop.kt | 12 + .../core/util/PlatformDatePicker.ios.kt | 12 + .../core/util/PlatformTimePicker.ios.kt | 12 + 38 files changed, 961 insertions(+), 45 deletions(-) create mode 100644 composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt create mode 100644 composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt create mode 100644 composeApp/src/commonMain/composeResources/drawable/ic_close.png create mode 100644 composeApp/src/commonMain/composeResources/drawable/ic_date.png create mode 100644 composeApp/src/commonMain/composeResources/drawable/ic_time.png create mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/clock/LocalClock.kt create mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyDateSelector.kt create mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyTimeSelector.kt create mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Int.kt create mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.kt create mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.kt create mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/reminder/Reminder.kt create mode 100644 composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.desktop.kt create mode 100644 composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.desktop.kt create mode 100644 composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.ios.kt create mode 100644 composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.ios.kt diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt new file mode 100644 index 0000000..7d2be8e --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt @@ -0,0 +1,70 @@ +package com.rafaelfelipeac.replyradar.core.util + +import androidx.compose.material3.DatePicker +import androidx.compose.material3.DatePickerDialog +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.rememberDatePickerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import kotlinx.datetime.Instant +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.atStartOfDayIn +import kotlinx.datetime.toLocalDateTime + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +actual fun PlatformDatePicker( + selectedDate: LocalDate?, + onDateSelected: (LocalDate) -> Unit +) { + val datePickerState = rememberDatePickerState( + initialSelectedDateMillis = selectedDate?.toEpochMillis() + ) + + var showDialog by remember { mutableStateOf(true) } + + if (showDialog) { + DatePickerDialog( + onDismissRequest = { showDialog = false }, + confirmButton = { + TextButton( + onClick = { + val millis = datePickerState.selectedDateMillis + if (millis != null) { + val pickedDate = millis.toLocalDate() + onDateSelected(pickedDate) + } + showDialog = false + } + ) { + Text("OK") + } + }, + dismissButton = { + TextButton( + onClick = { showDialog = false } + ) { + Text("Cancelar") + } + } + ) { + DatePicker(state = datePickerState) + } + } +} + +fun LocalDate.toEpochMillis(): Long { + return this.atStartOfDayIn(TimeZone.currentSystemDefault()).toEpochMilliseconds() +} + +fun Long.toLocalDate(): LocalDate { + return Instant.fromEpochMilliseconds(this) + .toLocalDateTime(TimeZone.currentSystemDefault()) + .date +} diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt new file mode 100644 index 0000000..85e736f --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt @@ -0,0 +1,51 @@ +package com.rafaelfelipeac.replyradar.core.util + +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TimePicker +import androidx.compose.material3.rememberTimePickerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import kotlinx.datetime.LocalTime + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +actual fun PlatformTimePicker( + selectedTime: LocalTime?, + onTimeSelected: (LocalTime) -> Unit +) { + val timePickerState = rememberTimePickerState( + initialHour = selectedTime?.hour ?: 12, + initialMinute = selectedTime?.minute ?: 0 + ) + + var showDialog by remember { mutableStateOf(true) } + + if (showDialog) { + AlertDialog( + onDismissRequest = { showDialog = false }, + confirmButton = { + TextButton(onClick = { + onTimeSelected(LocalTime(timePickerState.hour, timePickerState.minute)) + showDialog = false + }) { + Text("OK") + } + }, + dismissButton = { + TextButton(onClick = { showDialog = false }) { + Text("Cancelar") + } + }, + title = { Text("Selecionar horário") }, + text = { + TimePicker(state = timePickerState) + } + ) + } +} diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_close.png b/composeApp/src/commonMain/composeResources/drawable/ic_close.png new file mode 100644 index 0000000000000000000000000000000000000000..8822ac38ff134fda81ed9a675e084efc71dca0e0 GIT binary patch literal 787 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGojKx9jP7LeL$-HD>U^?sR;uum9 z_jXR8-(dq0mXo$oobT#2CfurKaa9V9UfTQiN9DK9r|g_E>un$VwniD%{@_+MSoo6f z{|1I10uc?Y2Us3dm>*zDU@F)noxo_oXmO8s1H%jknfGiF3_c8f<;*$^Y7EDBGm0?? zH$1+}z}>)k;A0+G+m8=yF?!Q0XDyq5`Q;bZ_gl68Rqd@i|M^d)-F#=sR=<4*;>+iH z9^$HxtzOb%!1(=xv1dh_yso0l0kJNppTS_tsr8|qD`4URmiiqGQB6`cE)1%Uf_7>N zTUbJWtY`=nn)2{5(<0Rsho=h&s46noNHXqKO=;9uT`;}H=fhJLnW+38MS9Gz4*;-tqJ@_v!Cz^^ca{e|KH-U)=$1gQfQOt?#~n7TB(Tzdmru=c;Jz>|d)CU*Jpq0P1Shz! zdwXI_mX;=?EyK4Ni@P2iJZQvJ)%4o(y5qjx)4e%57{m;p>z!?5+|l3>FtONt!@WH$ zq6SPCI(n3i3>cLU_MBNYwUI-iL13n;Q5&~{U^ek|aSW-5 zdpkF_=!$_r%TwtCzq7dWCf!k)!(zd?({l&s%B%Yh^ZH0eWL-~Pe|PaV_aLkDpC7;U zPd-_+(@;!fvZd&nzk(h zao48aTo!u&`~1G+k2i0%`m8c1=$E8ZMY`{uN%7Z9*S8;jxOl0WaKq=He-^+0%|ES$ z_xGPJ!+q|rn;GZ$EnmLw^}dCj_d5P2_k6St(&l+^uBG)!?lrO6UE&O%0t%QL)YPu; zt=_TInsF8LTILH!c5A!&X)vTXUMyg0&~n=*&ai1gh6SU8sPb)YhR6VuISdQBCcb5D z(9w|cV+c5U;v18Lmr!-}MoYDNX_t~PBxhA4;C-(Jj<$TeOU>UeP%3qxp6jy}4H z<_sB0TQ*#J(;eN$b>O~Y(<_JQwnd%Z(idNRUBQgaTw#U~!%ce`Ca}654&{IFtD*gA zgEQm5$?4q}pYAwa?C|cz$8YkxRKH8<$!@o9XuWLOz1(NdU;RU&+zBrmjt6m9|K>jM z=e^aMTiqvFZ!k67ygW5q?Q!*Tzmz+kIS4@)cfN#Tn9#m=qYcbLp(TKlkgR<>BlXRF5)oF)S0m8^!QqQW7I8!{zS0 zSlb4sZ=e6?cdog4NJ4mxl*7BD)xX2{iay*sfBx+Kzs~Ns^EM&Bp7Fx(hL`fY;(n{V zfr#@Z_$16~Q4hPCZSC-$Iq&G@-wcnHqw>Ue?H4q@7TYtM!D6uh1LFnmnJWz&^xRB1 zrL8@;D=aS1F=tHB%&Mnv2jenlS!HuqZ3mu(Dx-hX{|_rmKfzZPARH+`V59-E!h_Ooy}N#BE#Uib^}Zdab$q$;%d3p%G*}%N=7<*ft>-IX`yg#q@7K31RK_2enHW4> L{an^LB{Ts5+MtYQ literal 0 HcmV?d00001 diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_time.png b/composeApp/src/commonMain/composeResources/drawable/ic_time.png new file mode 100644 index 0000000000000000000000000000000000000000..32bb027d45d987c8ce3c51b6923a7f2d96d56f28 GIT binary patch literal 1840 zcmV-02haG4P)Px*>PbXFRCr$Po!xQVHV}Y`JD#5HN6nBYtAnTlTNOmAAXW!b1&%AQJFruMFBMp+ z!0F(m184LYJ?nlDLBrE97XrJB#ot-vD*+PRZ~yR1Q4Cj735cX`A6{Mz01_*~0AK)+ zSOSS(KUly3Ah84zzkaZQ0f1cs^ZESMcsxEEjYe;kQfEa`yh6xqrj+_)y763*S--J6Gpho`KmC<>i5xm0425 z+ctcm2KgA)5EMYuG&-HUL<;}DEXxx}!;#N27Sk}+>-Be~)EnfpKGt>pJF+afSTF!h z)66j+DTKILE|(|1w4EXWN*k-HI;K1uTQ&#)DWwjyV2^dRTD?vo_^xmWF7m#v>(^Mp zV#|34Af;5$%Y_hERaJe4%Ei3jG|i<_>J#+Nx~{#-%+FX0MX#&|mD5K|W#M&SfyX8<-I>B~Qw zgrhC5?_S7WG!W;UW1JHJ$n|hE0;7?O#bW+A!}UK1Tz3Gy+h*~u>9rGp5DkGozI#6v z0H~kfqIhHgw#k%OJ=|0Pv=RV4X7Z$t3jpI#gNp`GG=Ez*B>>$9n+KrEfCRv%{h|GD z4lZch2EY}OtbhujSNlU}J-b$t+5%cvWE^g%Sb-M+loz_VoE!nztbig9Gyo~(x4t>n z*xIjQknbA00qFui+s4A+yK}zB5cPD1)yn~ zFNU?6XjA)%0rW1Z)N4_zs=9!_8wJ3W_oi?^ImH0o7H%+R0l@k>NDHX;HnfJ>MvTuk|W0f31EAb`bUaVCV&Dd_H%Y>9V<0D4n&rPRfAI{j)>DiDAv|KDsj zS4T%jNtTx624JyRyca^~rH}u)Fo75YAe;O@$23&8$&#jnt0$ZRU;+%rpSzUF%>r!w z0Z2{*02dADsbfmj2LPS~3_WH5&`Jo>I7pDL_XhwS$)$ks(g5_PeO>L14dF!rXtNGz zNn{Zd!ixit8-$PmdS`u7jfCz7Noeh+E+5(wIP)c7Kfp8>fEnj-%_yk#Eb|r5j|vIl z_V)I2G#d5ot^vo(Kmew3j>on0;KQkc{fx+x&I24MY-pbN4Azz}v7B&Qx?%apgk%n86~0J%-dW8lPsVA{Na znF~Mz*rzzqg%bk;cfkV61=-wCF8u*ZUuxg<1#{)!E7LR zfnc#Pbc&9)fPD_LveanDk0qx6;RuF5G9wcDQ{T2dQ_{lfvK^m|x zt_TBZw@en>RCS|5G{i+9vFq4^xNCoi?1Hu*Z96|Ux|yVtpTtI2&|tAwa_SpxE0no++>!_oz2FCz`P$+K$klp|wg7TB>a_%QU>Gc*q zC+K<&!hPC^8FkFD$NWM6;Bl# z+Mfmez#cFj>d@I>0NR)UE}(tRhlRKqkH@DW58l)7YWptwP*(78@m4|DXM@@-H7J12 z2a|u`P?qI|FBq+x(OXGLe`$*0B7+0y#0Be7z<@uWZ8n>~4h{}x5EQN9%jNQ&5aL(H zFHE8hqeKJ1um&Ho7X6IaX>wEm9c#$p|6#jONK=VuqCmkU05SuSMDWkahcA-c=a`mw zbDwLdAI--@?N4d}eT{4Xa`Yj@&pemQ { + error("No Clock provided") +} diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/Strings.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/Strings.kt index af5ec81..7dfca19 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/Strings.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/Strings.kt @@ -3,8 +3,15 @@ package com.rafaelfelipeac.replyradar.core.common.strings interface Strings { val appName: String + val weekDaysShort: List + val months: List + val genericErrorMessage: String + val componentReplyTimeSelectorLabel: String + + val componentReplyDateSelectorLabel: String + val replyListActivityLog: String val replyListTabOnTheRadar: String val replyListTabResolved: String @@ -37,6 +44,13 @@ interface Strings { val replyListSnackbarReopened: String val replyListSnackbarResolved: String val replyListSnackbarUnarchived: String + val replyListReminder: String + val replyListReminderSet: String + val replyListReminderToday: String + val replyListReminderTomorrow: String + val replyListReminderTimeIconContentDescription: String + val replyListReminderDateIconContentDescription: String + val replyListReminderCloseIconContentDescription: String val settingsTitle: String val settingsBackButton: String diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsEn.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsEn.kt index 15311ad..0aa95d3 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsEn.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsEn.kt @@ -4,8 +4,19 @@ import com.rafaelfelipeac.replyradar.core.util.getAppVersion object StringsEn : Strings { override val appName = "Reply Radar" + + override val weekDaysShort = listOf("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun") + override val months = listOf( + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" + ) + override val genericErrorMessage = "An unexpected error occurred." + override val componentReplyTimeSelectorLabel = "Set the time:" + + override val componentReplyDateSelectorLabel = "Set the day:" + override val replyListActivityLog = "Activity Log" override val replyListTabOnTheRadar = "On the Radar" override val replyListTabResolved = "Resolved" @@ -41,6 +52,13 @@ object StringsEn : Strings { override val replyListSnackbarReopened = "Item reopened and back on the radar." override val replyListSnackbarResolved = "Item marked as resolved." override val replyListSnackbarUnarchived = "Item successfully unarchived." + override val replyListReminder = "Reminder" + override val replyListReminderSet = "Reminder set for:" + override val replyListReminderToday = "today" + override val replyListReminderTomorrow = "tomorrow" + override val replyListReminderTimeIconContentDescription = "Time" + override val replyListReminderDateIconContentDescription = "Date" + override val replyListReminderCloseIconContentDescription = "Close" override val settingsTitle = "Settings" override val settingsBackButton = "Back" diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsPt.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsPt.kt index 03b2ec5..4a084d3 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsPt.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsPt.kt @@ -4,8 +4,19 @@ import com.rafaelfelipeac.replyradar.core.util.getAppVersion object StringsPt : Strings { override val appName = "Reply Radar" + + override val weekDaysShort = listOf("Seg", "Ter", "Qua", "Qui", "Sex", "Sáb", "Dom") + override val months = listOf( + "Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", + "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro" + ) + override val genericErrorMessage = "Ocorreu um erro inesperado." + override val componentReplyTimeSelectorLabel = "Definir horário:" + + override val componentReplyDateSelectorLabel = "Definir dia:" + override val replyListActivityLog = "Atividades" override val replyListTabOnTheRadar = "No Radar" override val replyListTabResolved = "Resolvidos" @@ -41,6 +52,13 @@ object StringsPt : Strings { override val replyListSnackbarReopened = "Item reaberto e voltou para o radar." override val replyListSnackbarResolved = "Item marcado como resolvido." override val replyListSnackbarUnarchived = "Item desarquivado com sucesso." + override val replyListReminder = "Lembrete" + override val replyListReminderSet = "Lembrete definido para:" + override val replyListReminderToday = "hoje" + override val replyListReminderTomorrow = "amanhã" + override val replyListReminderTimeIconContentDescription = "Horário" + override val replyListReminderDateIconContentDescription = "Data" + override val replyListReminderCloseIconContentDescription = "Remover" override val settingsTitle = "Configurações" override val settingsBackButton = "Voltar" diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/Dimens.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/Dimens.kt index e5dc861..54f85d1 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/Dimens.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/Dimens.kt @@ -27,6 +27,8 @@ val textSizeLarge = 20.sp val iconSize = 22.dp val iconSizeLarge = 28.dp +val iconButtonSize = 36.dp + val buttonHeight = 34.dp val buttonCornerRadius = 8.dp val buttonBorderWidth = 1.dp @@ -41,3 +43,7 @@ val settingsAppVersionOffset = 64.dp val radioButtonSize = 24.dp val dialogElevation = 8.dp + +val replyDateSelectorMinTonalElevation = 1.dp +val replyDateSelectorMaxTonalElevation = 4.dp +val replyDateItemWidth = 12.dp diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyConfirmationDialog.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyConfirmationDialog.kt index 3d39c23..03d25ff 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyConfirmationDialog.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyConfirmationDialog.kt @@ -48,7 +48,10 @@ fun ReplyConfirmationDialog( style = typography.titleLarge ) - Spacer(modifier = Modifier.height(spacerSmall)) + Spacer( + modifier = Modifier + .height(spacerSmall) + ) Text( text = description, @@ -56,10 +59,14 @@ fun ReplyConfirmationDialog( color = colorScheme.onSurfaceVariant ) - Spacer(modifier = Modifier.height(spacerLarge)) + Spacer( + modifier = Modifier + .height(spacerLarge) + ) Row( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth(), horizontalArrangement = End ) { TextButton( @@ -68,7 +75,10 @@ fun ReplyConfirmationDialog( Text(dismiss) } - Spacer(modifier = Modifier.width(spacerSmall)) + Spacer( + modifier = Modifier + .width(spacerSmall) + ) TextButton( onClick = { diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyDateSelector.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyDateSelector.kt new file mode 100644 index 0000000..deae078 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyDateSelector.kt @@ -0,0 +1,141 @@ +package com.rafaelfelipeac.replyradar.core.common.ui.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.MaterialTheme.shapes +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import com.rafaelfelipeac.replyradar.core.common.clock.LocalClock +import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.common.ui.empty +import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall +import com.rafaelfelipeac.replyradar.core.common.ui.paddingXSmall +import com.rafaelfelipeac.replyradar.core.common.ui.replyDateItemWidth +import com.rafaelfelipeac.replyradar.core.common.ui.replyDateSelectorMaxTonalElevation +import com.rafaelfelipeac.replyradar.core.common.ui.replyDateSelectorMinTonalElevation +import kotlinx.datetime.DateTimeUnit.Companion.DAY +import kotlinx.datetime.Instant +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.plus +import kotlinx.datetime.toLocalDateTime + +@Composable +fun ReplyDateSelector( + modifier: Modifier = Modifier, + selectedDate: LocalDate?, + onDateSelected: (LocalDate) -> Unit +) { + val clock = LocalClock.current + + val today = remember { + Instant.fromEpochMilliseconds(clock.now()) + .toLocalDateTime(TimeZone.currentSystemDefault()) + .date + } + val dates = remember { + (0..CALENDAR_RANGE).map { offset -> today.plus(offset, DAY) } + } + + val listState = rememberLazyListState() + val visibleIndex by remember { + derivedStateOf { listState.firstVisibleItemIndex } + } + + val currentDate = dates.getOrNull(visibleIndex) + val currentMonthText = currentDate?.let { + "${LocalReplyRadarStrings.current.months[currentDate.monthNumber - 1]} ${it.year}" + } + + if (selectedDate == null && currentDate != null) { + onDateSelected(currentDate) + } + + Column( + modifier = modifier + ) { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(start = paddingSmall, end = paddingSmall), + text = LocalReplyRadarStrings.current.componentReplyDateSelectorLabel, + style = typography.bodySmall + ) + + if (currentMonthText != null) { + Surface( + tonalElevation = replyDateSelectorMinTonalElevation, + color = colorScheme.surface, + modifier = Modifier + .fillMaxWidth() + .padding(start = paddingSmall, end = paddingSmall, top = paddingSmall), + ) { + Text( + text = currentMonthText, + style = typography.titleSmall, + modifier = Modifier + .padding(horizontal = paddingSmall, vertical = paddingSmall) + ) + } + } + + LazyRow( + state = listState, + horizontalArrangement = spacedBy(paddingSmall), + modifier = Modifier + .fillMaxWidth() + .padding(vertical = paddingSmall, horizontal = paddingSmall) + ) { + itemsIndexed( + items = dates, + key = { _, item -> item.toString() } + ) { index, date -> + val isSelected = selectedDate == date + + Surface( + color = if (isSelected) colorScheme.primary else colorScheme.surfaceVariant, + shape = shapes.small, + tonalElevation = if (isSelected) replyDateSelectorMaxTonalElevation else replyDateSelectorMinTonalElevation, + modifier = Modifier + .padding(end = if (index < dates.lastIndex) paddingXSmall else empty) + .clickable { onDateSelected(date) } + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .padding( + vertical = paddingSmall, + horizontal = replyDateItemWidth + ) + ) { + Text( + text = date.dayOfMonth.toString(), + style = typography.bodyLarge + ) + Text( + text = LocalReplyRadarStrings.current.weekDaysShort[date.dayOfWeek.ordinal] + .replaceFirstChar { it.uppercase() }, + style = typography.labelSmall + ) + } + } + } + } + } +} + +private const val CALENDAR_RANGE = 90 // 3 month range diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyOutlinedButton.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyOutlinedButton.kt index 81e117f..347ff6c 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyOutlinedButton.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyOutlinedButton.kt @@ -39,7 +39,8 @@ fun ReplyOutlinedButton(text: String, icon: DrawableResource? = null, onClick: ( contentPadding = PaddingValues(horizontal = paddingSmall, vertical = paddingXSmall) ) { Text( - modifier = Modifier.padding(horizontal = paddingXSmall), + modifier = Modifier + .padding(horizontal = paddingXSmall), text = text ) icon?.let { icon -> diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyTimeSelector.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyTimeSelector.kt new file mode 100644 index 0000000..747545d --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyTimeSelector.kt @@ -0,0 +1,124 @@ +package com.rafaelfelipeac.replyradar.core.common.ui.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall +import com.rafaelfelipeac.replyradar.core.util.toTwoDigitString +import kotlinx.datetime.LocalTime + +@Composable +fun ReplyTimeSelector( + modifier: Modifier = Modifier, + selectedTime: LocalTime?, + onTimeSelected: (LocalTime) -> Unit +) { + val hours = (0..23) + val minutes = listOf(0, 15, 30, 45) + + val defaultHour = 12 + val defaultMinute = 0 + + var expandedHour by remember { mutableStateOf(false) } + var expandedMinute by remember { mutableStateOf(false) } + var selectedHour by remember { mutableStateOf(selectedTime?.hour ?: defaultHour) } + var selectedMinute by remember { mutableStateOf(selectedTime?.minute ?: defaultMinute) } + + if (selectedTime == null) { + onTimeSelected(LocalTime(defaultHour, defaultMinute)) + } + + Column( + modifier = modifier + ) { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = paddingSmall), + text = LocalReplyRadarStrings.current.componentReplyTimeSelectorLabel, + style = typography.bodySmall + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = paddingSmall) + ) { + Box { + Text( + text = selectedHour.toTwoDigitString(), + style = typography.titleMedium, + color = colorScheme.primary, + modifier = Modifier + .clickable { expandedHour = true } + ) + + DropdownMenu( + expanded = expandedHour, + onDismissRequest = { expandedHour = false } + ) { + hours.forEach { hour -> + DropdownMenuItem( + text = { Text(hour.toString()) }, + onClick = { + selectedHour = hour + expandedHour = false + onTimeSelected(LocalTime(hour, selectedMinute)) + } + ) + } + } + } + + Text( + text = TIME_SEPARATOR, + style = typography.bodyLarge, + modifier = Modifier + .alignByBaseline() + ) + + Box { + Text( + text = selectedMinute.toTwoDigitString(), + style = typography.titleMedium, + color = colorScheme.primary, + modifier = Modifier + .clickable { expandedMinute = true } + ) + + DropdownMenu( + expanded = expandedMinute, + onDismissRequest = { expandedMinute = false } + ) { + minutes.forEach { minute -> + DropdownMenuItem( + text = { Text(minute.toString()) }, + onClick = { + selectedMinute = minute + expandedMinute = false + onTimeSelected(LocalTime(selectedHour, minute)) + } + ) + } + } + } + } + } +} + +private const val TIME_SEPARATOR = ":" diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ColorScheme.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ColorScheme.kt index cfe6881..c5a17f8 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ColorScheme.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ColorScheme.kt @@ -21,7 +21,8 @@ val LightExtraColors = ReplyRadarColors( horizontalDividerColor = Color(0xFFE0E0E0), unselectedTabColor = Color(0xFF1E1E1E).copy(alpha = UnselectedTabAlpha), toolbarIconsColor = Color(0xFF464152), - snackbarBackgroundColor = Color(0xFFECECEC) + snackbarBackgroundColor = Color(0xFFECECEC), + replyBottomSheetIconColor = Color(0xFF888888) ) val DarkColorScheme = darkColorScheme( @@ -39,5 +40,6 @@ val DarkExtraColors = ReplyRadarColors( horizontalDividerColor = Color(0xFF333333), unselectedTabColor = Color(0xFFEDEDED).copy(alpha = UnselectedTabAlpha), toolbarIconsColor = Color(0xFFB3B8EF), - snackbarBackgroundColor = Color(0xFF2C2C2C) + snackbarBackgroundColor = Color(0xFF2C2C2C), + replyBottomSheetIconColor = Color(0xFFAAAAAA) ) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ReplyRadarColorScheme.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ReplyRadarColorScheme.kt index 130c44d..9d8f692 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ReplyRadarColorScheme.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ReplyRadarColorScheme.kt @@ -22,3 +22,6 @@ val ColorScheme.toolbarIconsColor: Color val ColorScheme.snackbarBackgroundColor: Color @Composable get() = colors.snackbarBackgroundColor + +val ColorScheme.replyBottomSheetIconColor: Color + @Composable get() = colors.replyBottomSheetIconColor diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ReplyRadarColors.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ReplyRadarColors.kt index af95864..fa5a905 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ReplyRadarColors.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ReplyRadarColors.kt @@ -11,7 +11,8 @@ data class ReplyRadarColors( val horizontalDividerColor: Color, val unselectedTabColor: Color, val toolbarIconsColor: Color, - val snackbarBackgroundColor: Color + val snackbarBackgroundColor: Color, + val replyBottomSheetIconColor: Color ) val LocalReplyRadarColors = staticCompositionLocalOf { diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Int.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Int.kt new file mode 100644 index 0000000..266b579 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Int.kt @@ -0,0 +1,6 @@ +package com.rafaelfelipeac.replyradar.core.util + +const val PAD_CHAR = '0' +const val PAD_LENGTH = 2 + +fun Int.toTwoDigitString(): String = toString().padStart(PAD_LENGTH, PAD_CHAR) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.kt new file mode 100644 index 0000000..9572257 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.kt @@ -0,0 +1,10 @@ +package com.rafaelfelipeac.replyradar.core.util + +import androidx.compose.runtime.Composable +import kotlinx.datetime.LocalDate + +@Composable +expect fun PlatformDatePicker( + selectedDate: LocalDate?, + onDateSelected: (LocalDate) -> Unit +) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.kt new file mode 100644 index 0000000..49024b9 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.kt @@ -0,0 +1,10 @@ +package com.rafaelfelipeac.replyradar.core.util + +import androidx.compose.runtime.Composable +import kotlinx.datetime.LocalTime + +@Composable +expect fun PlatformTimePicker( + selectedTime: LocalTime?, + onTimeSelected: (LocalTime) -> Unit +) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Timestamp.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Timestamp.kt index a289f24..e152480 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Timestamp.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Timestamp.kt @@ -9,8 +9,5 @@ fun formatTimestamp(timestampMillis: Long): String { val localDateTime = instant.toLocalDateTime(TimeZone.currentSystemDefault()) return "${localDateTime.dayOfMonth}/${localDateTime.monthNumber}/${localDateTime.year} " + - "${localDateTime.hour}:${localDateTime.minute.toString().padStart(LENGTH, PAD_CHAR)}" + "${localDateTime.hour}:${localDateTime.minute.toTwoDigitString()}" } - -private const val LENGTH = 2 -private const val PAD_CHAR = '0' diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/activitylog/presentation/ActivityLogScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/activitylog/presentation/ActivityLogScreen.kt index af5e6a6..14e8ab0 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/activitylog/presentation/ActivityLogScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/activitylog/presentation/ActivityLogScreen.kt @@ -91,7 +91,7 @@ fun ActivityLogScreen(viewModel: ActivityLogViewModel = koinViewModel(), onBackC Icon( imageVector = Icons.Default.ArrowBack, contentDescription = - LocalReplyRadarStrings.current.activityLogBackButton + LocalReplyRadarStrings.current.activityLogBackButton ) } } @@ -124,7 +124,8 @@ fun ActivityLogScreen(viewModel: ActivityLogViewModel = koinViewModel(), onBackC @Composable private fun Loading() { Box( - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .fillMaxSize(), contentAlignment = Alignment.Center ) { ReplyProgress() @@ -134,7 +135,8 @@ private fun Loading() { @Composable private fun Error(state: ActivityLogState) { Box( - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .fillMaxSize(), contentAlignment = Alignment.Center ) { ReplyRadarError( @@ -146,7 +148,8 @@ private fun Error(state: ActivityLogState) { @Composable private fun Placeholder() { Box( - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .fillMaxSize(), contentAlignment = Alignment.Center ) { ReplyRadarPlaceholder( @@ -162,7 +165,10 @@ private fun ActivityLogList(state: ActivityLogState) { state.activityLogItems, key = { _, item -> item.id } ) { index, userAction -> - Column(modifier = Modifier.fillMaxWidth()) { + Column( + modifier = Modifier + .fillMaxWidth() + ) { ActivityLogListItem(userAction = userAction) if (index < state.activityLogItems.lastIndex) { @@ -209,7 +215,7 @@ fun ActivityLogListItem(userAction: UserAction) { ), tint = colorScheme.primary, contentDescription = - LocalReplyRadarStrings.current.activityLogItemContentDescription + LocalReplyRadarStrings.current.activityLogItemContentDescription ) } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/domain/model/Reply.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/domain/model/Reply.kt index d62185e..82a7e0c 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/domain/model/Reply.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/domain/model/Reply.kt @@ -9,5 +9,6 @@ data class Reply( val createdAt: Long = 0, val updatedAt: Long = 0, val resolvedAt: Long = 0, - val archivedAt: Long = 0 + val archivedAt: Long = 0, + val reminderAt: Long = 0 ) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt index b0ddd3f..d4dd703 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt @@ -17,6 +17,7 @@ import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material3.ColorScheme +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -31,6 +32,7 @@ import androidx.compose.material3.TabRow import androidx.compose.material3.TabRowDefaults import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -91,6 +93,7 @@ fun ReplyListScreenRoot( ) } +@OptIn(ExperimentalMaterial3Api::class) @Composable fun ReplyListScreen( state: ReplyListState, @@ -102,6 +105,9 @@ fun ReplyListScreen( val pagerState = rememberPagerState { PAGER_PAGE_COUNT } val snackbarHostState = remember { SnackbarHostState() } + val sheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = true + ) LaunchedEffect(pagerState.currentPage) { onIntent(OnTabSelected(pagerState.currentPage)) @@ -162,28 +168,34 @@ fun ReplyListScreen( } ) { ReplyTab( - modifier = Modifier.weight(WEIGHT), + modifier = Modifier + .weight(WEIGHT), selected = state.selectedTabIndex == ON_THE_RADAR_INDEX, onClick = { onIntent(OnTabSelected(ON_THE_RADAR_INDEX)) }, text = LocalReplyRadarStrings.current.replyListTabOnTheRadar ) ReplyTab( - modifier = Modifier.weight(WEIGHT), + modifier = Modifier + .weight(WEIGHT), selected = state.selectedTabIndex == RESOLVED_INDEX, onClick = { onIntent(OnTabSelected(RESOLVED_INDEX)) }, text = LocalReplyRadarStrings.current.replyListTabResolved ) ReplyTab( - modifier = Modifier.weight(WEIGHT), + modifier = Modifier + .weight(WEIGHT), selected = state.selectedTabIndex == ARCHIVED_INDEX, onClick = { onIntent(OnTabSelected(ARCHIVED_INDEX)) }, text = LocalReplyRadarStrings.current.replyListTabArchived ) } - Spacer(modifier = Modifier.height(spacerXSmall)) + Spacer( + modifier = Modifier + .height(spacerXSmall) + ) HorizontalPager( modifier = Modifier @@ -199,6 +211,7 @@ fun ReplyListScreen( if (state.replyBottomSheetState != null) { ReplyBottomSheet( + sheetState = sheetState, onIntent = onIntent, replyBottomSheetState = state.replyBottomSheetState ) @@ -215,7 +228,7 @@ private fun FAB(onIntent: (ReplyListScreenIntent) -> Unit, colorScheme: ColorSch Icon( imageVector = Icons.Filled.Add, contentDescription = - LocalReplyRadarStrings.current.replyListFabContentDescription, + LocalReplyRadarStrings.current.replyListFabContentDescription, tint = colorScheme.background ) } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesArchivedScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesArchivedScreen.kt index 2ec1ebd..9327cd9 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesArchivedScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesArchivedScreen.kt @@ -16,7 +16,8 @@ fun RepliesArchivedScreen(state: ReplyListState, onIntent: (ReplyListScreenInten ReplyRadarPlaceholder(message = LocalReplyRadarStrings.current.replyListPlaceholderArchived) } else { ReplyList( - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .fillMaxSize(), replies = state.archivedReplies, onReplyClick = { onIntent(OnReplyClick(it)) }, onReplyToggle = { onIntent(OnReplyToggle(it)) } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesOnTheRadarScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesOnTheRadarScreen.kt index 39a8d22..decc73e 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesOnTheRadarScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesOnTheRadarScreen.kt @@ -31,7 +31,8 @@ fun RepliesOnTheRadarScreen(state: ReplyListState, onIntent: (ReplyListScreenInt else -> { ReplyList( - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .fillMaxSize(), replies = state.replies, onReplyClick = { onIntent(OnReplyClick(it)) }, onReplyToggle = { onIntent(OnReplyToggle(it)) } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesResolvedScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesResolvedScreen.kt index 5ffe9e9..3c54648 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesResolvedScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesResolvedScreen.kt @@ -15,7 +15,8 @@ fun RepliesResolvedScreen(state: ReplyListState, onIntent: (ReplyListScreenInten ReplyRadarPlaceholder(message = LocalReplyRadarStrings.current.replyListPlaceholderResolved) } else { ReplyList( - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .fillMaxSize(), replies = state.resolvedReplies, onReplyClick = { onIntent(OnReplyClick(it)) } ) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/ReplyList.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/ReplyList.kt index 03bd2f2..9077b3e 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/ReplyList.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/ReplyList.kt @@ -29,8 +29,11 @@ fun ReplyList( .padding(top = paddingMedium), horizontalAlignment = CenterHorizontally ) { - itemsIndexed(replies, key = { _, item -> item.id }) { index, reply -> - Column(modifier = Modifier.fillMaxWidth()) { + itemsIndexed(items = replies, key = { _, item -> item.id }) { index, reply -> + Column( + modifier = Modifier + .fillMaxWidth() + ) { ReplyListItem( modifier = Modifier .fillMaxWidth(), diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/reminder/Reminder.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/reminder/Reminder.kt new file mode 100644 index 0000000..918aebd --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/reminder/Reminder.kt @@ -0,0 +1,244 @@ +package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.reminder + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_DEFAULT_HOUR +import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_DEFAULT_MINUTE +import com.rafaelfelipeac.replyradar.core.common.clock.LocalClock +import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.common.ui.iconButtonSize +import com.rafaelfelipeac.replyradar.core.common.ui.iconSize +import com.rafaelfelipeac.replyradar.core.common.ui.listDividerThickness +import com.rafaelfelipeac.replyradar.core.common.ui.paddingLarge +import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall +import com.rafaelfelipeac.replyradar.core.common.ui.paddingXSmall +import com.rafaelfelipeac.replyradar.core.common.ui.theme.horizontalDividerColor +import com.rafaelfelipeac.replyradar.core.common.ui.theme.textFieldPlaceholderColor +import com.rafaelfelipeac.replyradar.core.common.ui.theme.toolbarIconsColor +import com.rafaelfelipeac.replyradar.core.util.PlatformDatePicker +import com.rafaelfelipeac.replyradar.core.util.PlatformTimePicker +import com.rafaelfelipeac.replyradar.core.util.toTwoDigitString +import kotlinx.datetime.Instant +import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.LocalTime +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import org.jetbrains.compose.resources.painterResource +import replyradar.composeapp.generated.resources.Res.drawable +import replyradar.composeapp.generated.resources.ic_close +import replyradar.composeapp.generated.resources.ic_date +import replyradar.composeapp.generated.resources.ic_time + +@Composable +fun Reminder( + selectedTime: LocalTime?, + selectedDate: LocalDate?, + onSelectedTimeChange: (LocalTime?) -> Unit, + onSelectedDateChange: (LocalDate?) -> Unit, + closeKeyboard: () -> Unit? +) { + var showTimePicker by remember { mutableStateOf(false) } + var showDatePicker by remember { mutableStateOf(false) } + val reminderText = formatReminder(selectedDate = selectedDate, selectedTime = selectedTime) + + ReminderText( + reminderText = reminderText, + onDeleteClick = { + onSelectedTimeChange(null) + onSelectedDateChange(null) + showTimePicker = false + showDatePicker = false + } + ) + + HorizontalDivider( + modifier = Modifier + .padding(top = paddingSmall, start = paddingXSmall, end = paddingXSmall), + thickness = listDividerThickness, + color = colorScheme.horizontalDividerColor + ) + +// if (showTimePicker || showDatePicker) { +// Text( +// text = LocalReplyRadarStrings.current.replyListReminder, +// modifier = Modifier +// .fillMaxWidth() +// .padding(start = paddingSmall, top = paddingSmall), +// style = typography.bodyMedium, +// color = colorScheme.textFieldPlaceholderColor +// ) +// } + + if (showTimePicker) { +// ReplyTimeSelector( +// modifier = Modifier +// .padding(top = paddingSmall), +// selectedTime = selectedTime, +// onTimeSelected = { onSelectedTimeChange(it) } +// ) + + PlatformTimePicker( + selectedTime = selectedTime, + onTimeSelected = { onSelectedTimeChange(it) } + ) + } + + if (showDatePicker) { +// ReplyDateSelector( +// modifier = Modifier +// .padding(top = paddingSmall), +// selectedDate = selectedDate, +// onDateSelected = { onSelectedDateChange(it) } +// ) + + PlatformDatePicker( + selectedDate = selectedDate, + onDateSelected = { onSelectedDateChange(it) } + ) + } + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = paddingSmall), + horizontalArrangement = Arrangement.Start + ) { + IconButton( + onClick = { + showTimePicker = !showTimePicker + closeKeyboard() + }, + modifier = Modifier + .size(iconButtonSize) + ) { + Icon( + modifier = Modifier + .size(iconSize), + painter = painterResource(drawable.ic_time), + contentDescription = LocalReplyRadarStrings.current.replyListReminderTimeIconContentDescription, + tint = colorScheme.primary //if (showTimePicker) colorScheme.primary else colorScheme.replyBottomSheetIconColor + ) + } + + IconButton( + onClick = { + showDatePicker = !showDatePicker + closeKeyboard() + }, + modifier = Modifier + .size(iconButtonSize) + ) { + Icon( + modifier = Modifier + .size(iconSize), + painter = painterResource(drawable.ic_date), + contentDescription = LocalReplyRadarStrings.current.replyListReminderDateIconContentDescription, + tint = colorScheme.primary //if (showDatePicker) colorScheme.primary else colorScheme.replyBottomSheetIconColor + ) + } + } + + HorizontalDivider( + modifier = Modifier + .padding(vertical = paddingSmall, horizontal = paddingXSmall), + thickness = listDividerThickness, + color = colorScheme.horizontalDividerColor + ) +} + +@Composable +private fun ReminderText( + reminderText: String?, + onDeleteClick: () -> Unit +) { + if (reminderText != null) { + Row( + modifier = Modifier + .fillMaxWidth(), + horizontalArrangement = Arrangement.Start + ) { + Text( + text = reminderText, + modifier = Modifier + .padding(start = paddingSmall, top = paddingSmall), + style = typography.bodyMedium, + color = colorScheme.textFieldPlaceholderColor + ) + + IconButton( + modifier = Modifier + .padding(start = paddingLarge) + .size(iconButtonSize), + onClick = { onDeleteClick() }, + ) { + Icon( + modifier = Modifier + .size(iconSize), + painter = painterResource(drawable.ic_close), + contentDescription = LocalReplyRadarStrings.current.replyListReminderCloseIconContentDescription, + tint = colorScheme.toolbarIconsColor + ) + } + } + } +} + +@Composable +fun formatReminder(selectedDate: LocalDate?, selectedTime: LocalTime?): String? { + if (selectedDate == null && selectedTime == null) return null + + val timeZone = TimeZone.currentSystemDefault() + val now = Instant.fromEpochMilliseconds(LocalClock.current.now()).toLocalDateTime(timeZone) + val defaultTime = LocalTime(REMINDER_DEFAULT_HOUR, REMINDER_DEFAULT_MINUTE) + + val datePart = when { + selectedDate != null -> { + val day = selectedDate.dayOfMonth.toTwoDigitString() + val month = selectedDate.monthNumber.toTwoDigitString() + val year = selectedDate.year.toString() + "$day/$month/$year" + } + + selectedTime != null -> { + val reminderTimeToday = LocalDateTime(now.date, selectedTime) + if (reminderTimeToday > now) { + LocalReplyRadarStrings.current.replyListReminderToday + } else { + LocalReplyRadarStrings.current.replyListReminderTomorrow + } + } + + else -> null + } + + val timePart = when { + selectedTime != null -> "${selectedTime.hour.toTwoDigitString()}:${selectedTime.minute.toTwoDigitString()}" + selectedDate != null -> "${defaultTime.hour.toTwoDigitString()}:${defaultTime.minute.toTwoDigitString()}" + else -> null + } + + + return "${LocalReplyRadarStrings.current.replyListReminderSet} ${ + when { + datePart != null && timePart != null -> "$datePart $timePart" + datePart != null -> datePart + else -> timePart + } + }" +} diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt index ed56728..6d5f09e 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt @@ -3,6 +3,7 @@ package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.comp import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.SheetState import androidx.compose.runtime.Composable import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyRoundedCorner import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply @@ -19,10 +20,12 @@ import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.compo @OptIn(ExperimentalMaterial3Api::class) @Composable fun ReplyBottomSheet( + sheetState: SheetState, onIntent: (ReplyListScreenIntent) -> Unit, replyBottomSheetState: ReplyBottomSheetState ) { ModalBottomSheet( + sheetState = sheetState, onDismissRequest = { onIntent(OnDismissBottomSheet) }, containerColor = colorScheme.background, dragHandle = null, diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt index 1e79fff..0acc2f6 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt @@ -19,12 +19,17 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.CenterHorizontally +import androidx.compose.ui.Alignment.Companion.Start import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.platform.LocalSoftwareKeyboardController import com.rafaelfelipeac.replyradar.core.AppConstants.EMPTY import com.rafaelfelipeac.replyradar.core.AppConstants.INITIAL_DATE_LONG +import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_DEFAULT_HOUR +import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_DEFAULT_MINUTE +import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_TOMORROW_OFFSET +import com.rafaelfelipeac.replyradar.core.common.clock.LocalClock import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyButton import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyConfirmationDialog @@ -36,7 +41,17 @@ import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall import com.rafaelfelipeac.replyradar.core.util.format import com.rafaelfelipeac.replyradar.core.util.formatTimestamp import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.reminder.Reminder import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetMode.EDIT +import kotlinx.datetime.DateTimeUnit.Companion.DAY +import kotlinx.datetime.Instant +import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.LocalTime +import kotlinx.datetime.TimeZone +import kotlinx.datetime.plus +import kotlinx.datetime.toInstant +import kotlinx.datetime.toLocalDateTime import replyradar.composeapp.generated.resources.Res.drawable import replyradar.composeapp.generated.resources.ic_archive import replyradar.composeapp.generated.resources.ic_check @@ -57,6 +72,8 @@ fun ReplyBottomSheetContent( replyBottomSheetState?.let { state -> var name by remember { mutableStateOf(state.reply?.name ?: EMPTY) } var subject by remember { mutableStateOf(state.reply?.subject ?: EMPTY) } + var selectedTime by remember { mutableStateOf(null) } + var selectedDate by remember { mutableStateOf(null) } Column( modifier = Modifier @@ -93,16 +110,24 @@ fun ReplyBottomSheetContent( Text( modifier = Modifier .padding(start = paddingSmall, top = paddingSmall) - .align(Alignment.Start), + .align(Start), text = getTimestamp(state.reply), style = typography.bodySmall ) } + Reminder( + selectedTime = selectedTime, + selectedDate = selectedDate, + onSelectedTimeChange = { selectedTime = it }, + onSelectedDateChange = { selectedDate = it }, + closeKeyboard = { keyboardController?.hide() } + ) + Row( modifier = Modifier .fillMaxWidth() - .padding(top = paddingSmall, bottom = paddingMedium), + .padding(top = paddingSmall, bottom = paddingMedium, end = paddingSmall), horizontalArrangement = if (isEditMode(state)) spacedBy(paddingSmall) else End, verticalAlignment = Alignment.CenterVertically ) { @@ -140,6 +165,12 @@ fun ReplyBottomSheetContent( } } } + + val reminderAt = getReminderTimestamp( + selectedDate = selectedDate, + selectedTime = selectedTime + ) + ReplyButton( modifier = Modifier .wrapContentWidth() @@ -154,14 +185,16 @@ fun ReplyBottomSheetContent( onComplete( state.reply.copy( name = name, - subject = subject + subject = subject, + reminderAt = reminderAt ) ) } else { onComplete( Reply( name = name, - subject = subject + subject = subject, + reminderAt = reminderAt ) ) } @@ -278,3 +311,40 @@ private fun getTimestamp(reply: Reply): String { } } } + +@Composable +fun getReminderTimestamp( + selectedDate: LocalDate?, + selectedTime: LocalTime? +): Long { + val timeZone = TimeZone.currentSystemDefault() + val nowDateTime = + Instant.fromEpochMilliseconds(LocalClock.current.now()).toLocalDateTime(timeZone) + + val finalDate = when { + selectedDate != null -> selectedDate + selectedTime != null -> { + val timeToday = LocalDateTime( + date = nowDateTime.date, + time = selectedTime + ) + + if (timeToday > nowDateTime) { + nowDateTime.date + } else { + nowDateTime.date.plus( + REMINDER_TOMORROW_OFFSET, + DAY + ) + } + } + + else -> return INITIAL_DATE_LONG + } + + val finalTime = selectedTime ?: LocalTime(REMINDER_DEFAULT_HOUR, REMINDER_DEFAULT_MINUTE) + + val finalDateTime = LocalDateTime(finalDate, finalTime) + + return finalDateTime.toInstant(timeZone).toEpochMilliseconds() +} diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsScreen.kt index 2826981..a545aee 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsScreen.kt @@ -100,11 +100,20 @@ fun SettingsScreen( .padding(bottom = settingsAppVersionOffset) ) { ActivityLog(onActivityLogClick = onActivityLogClick) - HorizontalDivider(modifier = Modifier.padding(vertical = paddingMedium)) + HorizontalDivider( + modifier = Modifier + .padding(vertical = paddingMedium) + ) Theme(state = state, onIntent = { viewModel.onIntent(it) }) - HorizontalDivider(modifier = Modifier.padding(vertical = paddingMedium)) + HorizontalDivider( + modifier = Modifier + .padding(vertical = paddingMedium) + ) Language(state = state, onIntent = { viewModel.onIntent(it) }) - HorizontalDivider(modifier = Modifier.padding(vertical = paddingMedium)) + HorizontalDivider( + modifier = Modifier + .padding(vertical = paddingMedium) + ) App(onIntent = { viewModel.onIntent(it) }) } @@ -138,7 +147,10 @@ fun App(onIntent: (SettingsIntent) -> Unit) { } ) - Spacer(modifier = Modifier.height(paddingSmall)) + Spacer( + modifier = Modifier + .height(paddingSmall) + ) SettingsItem( text = strings.settingsRateTitle, @@ -170,7 +182,10 @@ private fun Theme(state: SettingsState, onIntent: (SettingsIntent) -> Unit) { color = colorScheme.primary ) - Spacer(modifier = Modifier.height(paddingXSmall)) + Spacer( + modifier = Modifier + .height(paddingXSmall) + ) ThemeOptions( state = state, @@ -186,7 +201,10 @@ private fun Language(state: SettingsState, onIntent: (SettingsIntent) -> Unit) { color = colorScheme.primary ) - Spacer(modifier = Modifier.height(paddingXSmall)) + Spacer( + modifier = Modifier + .height(paddingXSmall) + ) LanguageOptions( state = state, @@ -227,12 +245,16 @@ private fun ThemeOption( .clickable { onThemeSelected(theme) } ) { RadioButton( - modifier = Modifier.size(radioButtonSize), + modifier = Modifier + .size(radioButtonSize), selected = theme == selectedTheme, onClick = { onThemeSelected(theme) } ) - Spacer(modifier = Modifier.width(paddingSmall)) + Spacer( + modifier = Modifier + .width(paddingSmall) + ) Text(text = getThemeOptionLabel(theme)) } @@ -266,12 +288,16 @@ private fun LanguageOption( .clickable { onLanguageSelected(language) } ) { RadioButton( - modifier = Modifier.size(radioButtonSize), + modifier = Modifier + .size(radioButtonSize), selected = language == selectedLanguage, onClick = { onLanguageSelected(language) } ) - Spacer(modifier = Modifier.width(paddingSmall)) + Spacer( + modifier = Modifier + .width(paddingSmall) + ) Text(text = getLanguageLabel(language)) } @@ -324,7 +350,10 @@ fun AppVersionFooter(modifier: Modifier = Modifier) { .fillMaxWidth() .background(colorScheme.background) ) { - HorizontalDivider(modifier = Modifier.padding(bottom = paddingMedium)) + HorizontalDivider( + modifier = Modifier + .padding(bottom = paddingMedium) + ) Text( text = "${LocalReplyRadarStrings.current.settingsAppVersion} ${getAppVersion()}", diff --git a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.desktop.kt b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.desktop.kt new file mode 100644 index 0000000..e11c2a5 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.desktop.kt @@ -0,0 +1,12 @@ +package com.rafaelfelipeac.replyradar.core.util + +import androidx.compose.runtime.Composable +import kotlinx.datetime.LocalDate + +@Composable +actual fun PlatformDatePicker( + selectedDate: LocalDate?, + onDateSelected: (LocalDate) -> Unit +) { + TODO("Not yet implemented for this platform.") +} diff --git a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.desktop.kt b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.desktop.kt new file mode 100644 index 0000000..381f007 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.desktop.kt @@ -0,0 +1,12 @@ +package com.rafaelfelipeac.replyradar.core.util + +import androidx.compose.runtime.Composable +import kotlinx.datetime.LocalTime + +@Composable +actual fun PlatformTimePicker( + selectedTime: LocalTime?, + onTimeSelected: (LocalTime) -> Unit +) { + TODO("Not yet implemented for this platform.") +} diff --git a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.ios.kt b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.ios.kt new file mode 100644 index 0000000..e11c2a5 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.ios.kt @@ -0,0 +1,12 @@ +package com.rafaelfelipeac.replyradar.core.util + +import androidx.compose.runtime.Composable +import kotlinx.datetime.LocalDate + +@Composable +actual fun PlatformDatePicker( + selectedDate: LocalDate?, + onDateSelected: (LocalDate) -> Unit +) { + TODO("Not yet implemented for this platform.") +} diff --git a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.ios.kt b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.ios.kt new file mode 100644 index 0000000..381f007 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.ios.kt @@ -0,0 +1,12 @@ +package com.rafaelfelipeac.replyradar.core.util + +import androidx.compose.runtime.Composable +import kotlinx.datetime.LocalTime + +@Composable +actual fun PlatformTimePicker( + selectedTime: LocalTime?, + onTimeSelected: (LocalTime) -> Unit +) { + TODO("Not yet implemented for this platform.") +} From 6ff5fa7bb27f010497b43a6774a132b07a417f63 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Sat, 26 Apr 2025 19:20:08 -0300 Subject: [PATCH 02/59] Improvements on Date and Time pickers. --- .../common/ui/components/ReplyDateSelector.kt | 141 ------------------ .../common/ui/components/ReplyTimeSelector.kt | 124 --------------- .../core/util/PlatformDatePicker.kt | 5 +- .../core/util/PlatformTimePicker.kt | 2 + .../core/util/PlatformDatePicker.desktop.kt | 5 +- .../core/util/PlatformTimePicker.desktop.kt | 2 + .../core/util/PlatformDatePicker.ios.kt | 5 +- .../core/util/PlatformTimePicker.ios.kt | 2 + 8 files changed, 18 insertions(+), 268 deletions(-) delete mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyDateSelector.kt delete mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyTimeSelector.kt diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyDateSelector.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyDateSelector.kt deleted file mode 100644 index deae078..0000000 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyDateSelector.kt +++ /dev/null @@ -1,141 +0,0 @@ -package com.rafaelfelipeac.replyradar.core.common.ui.components - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material3.MaterialTheme.colorScheme -import androidx.compose.material3.MaterialTheme.shapes -import androidx.compose.material3.MaterialTheme.typography -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import com.rafaelfelipeac.replyradar.core.common.clock.LocalClock -import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings -import com.rafaelfelipeac.replyradar.core.common.ui.empty -import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall -import com.rafaelfelipeac.replyradar.core.common.ui.paddingXSmall -import com.rafaelfelipeac.replyradar.core.common.ui.replyDateItemWidth -import com.rafaelfelipeac.replyradar.core.common.ui.replyDateSelectorMaxTonalElevation -import com.rafaelfelipeac.replyradar.core.common.ui.replyDateSelectorMinTonalElevation -import kotlinx.datetime.DateTimeUnit.Companion.DAY -import kotlinx.datetime.Instant -import kotlinx.datetime.LocalDate -import kotlinx.datetime.TimeZone -import kotlinx.datetime.plus -import kotlinx.datetime.toLocalDateTime - -@Composable -fun ReplyDateSelector( - modifier: Modifier = Modifier, - selectedDate: LocalDate?, - onDateSelected: (LocalDate) -> Unit -) { - val clock = LocalClock.current - - val today = remember { - Instant.fromEpochMilliseconds(clock.now()) - .toLocalDateTime(TimeZone.currentSystemDefault()) - .date - } - val dates = remember { - (0..CALENDAR_RANGE).map { offset -> today.plus(offset, DAY) } - } - - val listState = rememberLazyListState() - val visibleIndex by remember { - derivedStateOf { listState.firstVisibleItemIndex } - } - - val currentDate = dates.getOrNull(visibleIndex) - val currentMonthText = currentDate?.let { - "${LocalReplyRadarStrings.current.months[currentDate.monthNumber - 1]} ${it.year}" - } - - if (selectedDate == null && currentDate != null) { - onDateSelected(currentDate) - } - - Column( - modifier = modifier - ) { - Text( - modifier = Modifier - .fillMaxWidth() - .padding(start = paddingSmall, end = paddingSmall), - text = LocalReplyRadarStrings.current.componentReplyDateSelectorLabel, - style = typography.bodySmall - ) - - if (currentMonthText != null) { - Surface( - tonalElevation = replyDateSelectorMinTonalElevation, - color = colorScheme.surface, - modifier = Modifier - .fillMaxWidth() - .padding(start = paddingSmall, end = paddingSmall, top = paddingSmall), - ) { - Text( - text = currentMonthText, - style = typography.titleSmall, - modifier = Modifier - .padding(horizontal = paddingSmall, vertical = paddingSmall) - ) - } - } - - LazyRow( - state = listState, - horizontalArrangement = spacedBy(paddingSmall), - modifier = Modifier - .fillMaxWidth() - .padding(vertical = paddingSmall, horizontal = paddingSmall) - ) { - itemsIndexed( - items = dates, - key = { _, item -> item.toString() } - ) { index, date -> - val isSelected = selectedDate == date - - Surface( - color = if (isSelected) colorScheme.primary else colorScheme.surfaceVariant, - shape = shapes.small, - tonalElevation = if (isSelected) replyDateSelectorMaxTonalElevation else replyDateSelectorMinTonalElevation, - modifier = Modifier - .padding(end = if (index < dates.lastIndex) paddingXSmall else empty) - .clickable { onDateSelected(date) } - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier - .padding( - vertical = paddingSmall, - horizontal = replyDateItemWidth - ) - ) { - Text( - text = date.dayOfMonth.toString(), - style = typography.bodyLarge - ) - Text( - text = LocalReplyRadarStrings.current.weekDaysShort[date.dayOfWeek.ordinal] - .replaceFirstChar { it.uppercase() }, - style = typography.labelSmall - ) - } - } - } - } - } -} - -private const val CALENDAR_RANGE = 90 // 3 month range diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyTimeSelector.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyTimeSelector.kt deleted file mode 100644 index 747545d..0000000 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyTimeSelector.kt +++ /dev/null @@ -1,124 +0,0 @@ -package com.rafaelfelipeac.replyradar.core.common.ui.components - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.MaterialTheme.colorScheme -import androidx.compose.material3.MaterialTheme.typography -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings -import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall -import com.rafaelfelipeac.replyradar.core.util.toTwoDigitString -import kotlinx.datetime.LocalTime - -@Composable -fun ReplyTimeSelector( - modifier: Modifier = Modifier, - selectedTime: LocalTime?, - onTimeSelected: (LocalTime) -> Unit -) { - val hours = (0..23) - val minutes = listOf(0, 15, 30, 45) - - val defaultHour = 12 - val defaultMinute = 0 - - var expandedHour by remember { mutableStateOf(false) } - var expandedMinute by remember { mutableStateOf(false) } - var selectedHour by remember { mutableStateOf(selectedTime?.hour ?: defaultHour) } - var selectedMinute by remember { mutableStateOf(selectedTime?.minute ?: defaultMinute) } - - if (selectedTime == null) { - onTimeSelected(LocalTime(defaultHour, defaultMinute)) - } - - Column( - modifier = modifier - ) { - Text( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = paddingSmall), - text = LocalReplyRadarStrings.current.componentReplyTimeSelectorLabel, - style = typography.bodySmall - ) - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = paddingSmall) - ) { - Box { - Text( - text = selectedHour.toTwoDigitString(), - style = typography.titleMedium, - color = colorScheme.primary, - modifier = Modifier - .clickable { expandedHour = true } - ) - - DropdownMenu( - expanded = expandedHour, - onDismissRequest = { expandedHour = false } - ) { - hours.forEach { hour -> - DropdownMenuItem( - text = { Text(hour.toString()) }, - onClick = { - selectedHour = hour - expandedHour = false - onTimeSelected(LocalTime(hour, selectedMinute)) - } - ) - } - } - } - - Text( - text = TIME_SEPARATOR, - style = typography.bodyLarge, - modifier = Modifier - .alignByBaseline() - ) - - Box { - Text( - text = selectedMinute.toTwoDigitString(), - style = typography.titleMedium, - color = colorScheme.primary, - modifier = Modifier - .clickable { expandedMinute = true } - ) - - DropdownMenu( - expanded = expandedMinute, - onDismissRequest = { expandedMinute = false } - ) { - minutes.forEach { minute -> - DropdownMenuItem( - text = { Text(minute.toString()) }, - onClick = { - selectedMinute = minute - expandedMinute = false - onTimeSelected(LocalTime(selectedHour, minute)) - } - ) - } - } - } - } - } -} - -private const val TIME_SEPARATOR = ":" diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.kt index 9572257..d250c2c 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.kt @@ -2,9 +2,12 @@ package com.rafaelfelipeac.replyradar.core.util import androidx.compose.runtime.Composable import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalTime @Composable expect fun PlatformDatePicker( selectedDate: LocalDate?, - onDateSelected: (LocalDate) -> Unit + selectedTime: LocalTime?, + onDateSelected: (LocalDate) -> Unit, + onTimeInvalidated: () -> Unit ) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.kt index 49024b9..240d4c6 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.kt @@ -1,10 +1,12 @@ package com.rafaelfelipeac.replyradar.core.util import androidx.compose.runtime.Composable +import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalTime @Composable expect fun PlatformTimePicker( selectedTime: LocalTime?, + selectedDate: LocalDate?, onTimeSelected: (LocalTime) -> Unit ) diff --git a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.desktop.kt b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.desktop.kt index e11c2a5..a0a2474 100644 --- a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.desktop.kt +++ b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.desktop.kt @@ -2,11 +2,14 @@ package com.rafaelfelipeac.replyradar.core.util import androidx.compose.runtime.Composable import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalTime @Composable actual fun PlatformDatePicker( selectedDate: LocalDate?, - onDateSelected: (LocalDate) -> Unit + selectedTime: LocalTime?, + onDateSelected: (LocalDate) -> Unit, + onTimeInvalidated: () -> Unit ) { TODO("Not yet implemented for this platform.") } diff --git a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.desktop.kt b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.desktop.kt index 381f007..c0af029 100644 --- a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.desktop.kt +++ b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.desktop.kt @@ -1,11 +1,13 @@ package com.rafaelfelipeac.replyradar.core.util import androidx.compose.runtime.Composable +import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalTime @Composable actual fun PlatformTimePicker( selectedTime: LocalTime?, + selectedDate: LocalDate?, onTimeSelected: (LocalTime) -> Unit ) { TODO("Not yet implemented for this platform.") diff --git a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.ios.kt b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.ios.kt index e11c2a5..a0a2474 100644 --- a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.ios.kt @@ -2,11 +2,14 @@ package com.rafaelfelipeac.replyradar.core.util import androidx.compose.runtime.Composable import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalTime @Composable actual fun PlatformDatePicker( selectedDate: LocalDate?, - onDateSelected: (LocalDate) -> Unit + selectedTime: LocalTime?, + onDateSelected: (LocalDate) -> Unit, + onTimeInvalidated: () -> Unit ) { TODO("Not yet implemented for this platform.") } diff --git a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.ios.kt b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.ios.kt index 381f007..c0af029 100644 --- a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.ios.kt @@ -1,11 +1,13 @@ package com.rafaelfelipeac.replyradar.core.util import androidx.compose.runtime.Composable +import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalTime @Composable actual fun PlatformTimePicker( selectedTime: LocalTime?, + selectedDate: LocalDate?, onTimeSelected: (LocalTime) -> Unit ) { TODO("Not yet implemented for this platform.") From 5f7f49522dbfbbac5dea239253b3bbc0a2420212 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Sat, 26 Apr 2025 19:49:10 -0300 Subject: [PATCH 03/59] Improvements on Date and Time pickers. --- .../core/util/PlatformDatePicker.android.kt | 48 +++++-- .../core/util/PlatformTimePicker.android.kt | 19 ++- .../replyradar/core/util/datetime/Datetime.kt | 18 +++ .../components/reminder/DatetimeExt.kt | 117 ++++++++++++++++++ .../replylist/components/reminder/Reminder.kt | 80 ++---------- 5 files changed, 193 insertions(+), 89 deletions(-) create mode 100644 composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt create mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/reminder/DatetimeExt.kt diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt index 7d2be8e..2d85dc6 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt @@ -3,6 +3,7 @@ package com.rafaelfelipeac.replyradar.core.util import androidx.compose.material3.DatePicker import androidx.compose.material3.DatePickerDialog import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.SelectableDates import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.rememberDatePickerState @@ -11,20 +12,39 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import com.rafaelfelipeac.replyradar.core.util.datetime.toEpochMillis +import com.rafaelfelipeac.replyradar.core.util.datetime.toLocalDate +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.reminder.isDateTimeValid +import kotlinx.datetime.Clock import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalTime import kotlinx.datetime.TimeZone -import kotlinx.datetime.atStartOfDayIn +import kotlinx.datetime.TimeZone.Companion.UTC import kotlinx.datetime.toLocalDateTime @OptIn(ExperimentalMaterial3Api::class) @Composable actual fun PlatformDatePicker( selectedDate: LocalDate?, - onDateSelected: (LocalDate) -> Unit + selectedTime: LocalTime?, + onDateSelected: (LocalDate) -> Unit, + onTimeInvalidated: () -> Unit ) { + val now = Clock.System.now() + .toLocalDateTime(TimeZone.currentSystemDefault()) + val datePickerState = rememberDatePickerState( - initialSelectedDateMillis = selectedDate?.toEpochMillis() + initialSelectedDateMillis = selectedDate?.toEpochMillis(), + selectableDates = object : SelectableDates { + override fun isSelectableDate(utcTimeMillis: Long): Boolean { + val candidateDate = Instant.fromEpochMilliseconds(utcTimeMillis) + .toLocalDateTime(UTC) + .date + + return candidateDate >= now.date + } + } ) var showDialog by remember { mutableStateOf(true) } @@ -38,7 +58,19 @@ actual fun PlatformDatePicker( val millis = datePickerState.selectedDateMillis if (millis != null) { val pickedDate = millis.toLocalDate() + onDateSelected(pickedDate) + selectedTime?.let { + val isStillValid = isDateTimeValid( + date = pickedDate, + time = it, + now = now + ) + + if (!isStillValid) { + onTimeInvalidated() + } + } ?: onTimeInvalidated() } showDialog = false } @@ -58,13 +90,3 @@ actual fun PlatformDatePicker( } } } - -fun LocalDate.toEpochMillis(): Long { - return this.atStartOfDayIn(TimeZone.currentSystemDefault()).toEpochMilliseconds() -} - -fun Long.toLocalDate(): LocalDate { - return Instant.fromEpochMilliseconds(this) - .toLocalDateTime(TimeZone.currentSystemDefault()) - .date -} diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt index 85e736f..6ce42c4 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt @@ -11,12 +11,15 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.reminder.isTimeValid +import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalTime @OptIn(ExperimentalMaterial3Api::class) @Composable actual fun PlatformTimePicker( selectedTime: LocalTime?, + selectedDate: LocalDate?, onTimeSelected: (LocalTime) -> Unit ) { val timePickerState = rememberTimePickerState( @@ -27,13 +30,21 @@ actual fun PlatformTimePicker( var showDialog by remember { mutableStateOf(true) } if (showDialog) { + val pickedTime = LocalTime(timePickerState.hour, timePickerState.minute) + val isValid = isTimeValid(selectedDate, pickedTime) + AlertDialog( onDismissRequest = { showDialog = false }, confirmButton = { - TextButton(onClick = { - onTimeSelected(LocalTime(timePickerState.hour, timePickerState.minute)) - showDialog = false - }) { + TextButton( + onClick = { + if (isValid) { + onTimeSelected(pickedTime) + showDialog = false + } + }, + enabled = isValid + ) { Text("OK") } }, diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt new file mode 100644 index 0000000..b114330 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt @@ -0,0 +1,18 @@ +package com.rafaelfelipeac.replyradar.core.util.datetime + +import kotlinx.datetime.Instant +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.TimeZone.Companion.UTC +import kotlinx.datetime.atStartOfDayIn +import kotlinx.datetime.toLocalDateTime + +fun LocalDate.toEpochMillis(): Long { + return this.atStartOfDayIn(TimeZone.currentSystemDefault()).toEpochMilliseconds() +} + +fun Long.toLocalDate(): LocalDate { + return Instant.fromEpochMilliseconds(this) + .toLocalDateTime(UTC) + .date +} diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/reminder/DatetimeExt.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/reminder/DatetimeExt.kt new file mode 100644 index 0000000..0c5c494 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/reminder/DatetimeExt.kt @@ -0,0 +1,117 @@ +package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.reminder + +import androidx.compose.runtime.Composable +import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_DEFAULT_HOUR +import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_DEFAULT_MINUTE +import com.rafaelfelipeac.replyradar.core.common.clock.LocalClock +import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.util.toTwoDigitString +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.LocalTime +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime + +fun isDateTimeValid(date: LocalDate?, time: LocalTime, now: LocalDateTime): Boolean { + return when { + date == null -> { + val todayTime = LocalDateTime(now.date, time) + todayTime > now + } + + date == now.date -> { + val selectedDateTime = LocalDateTime(date, time) + selectedDateTime > now + } + + date > now.date -> true + else -> false + } +} + +@Composable +fun getDefaultTime(selectedDate: LocalDate?, selectedTime: LocalTime?): LocalTime { + val clock = LocalClock.current + + val now = Instant.fromEpochMilliseconds(clock.now()) + .toLocalDateTime(TimeZone.currentSystemDefault()) + + val eightAM = LocalTime(REMINDER_DEFAULT_HOUR, REMINDER_DEFAULT_MINUTE) + + return if (selectedDate != null && isDateTimeValid( + date = selectedDate, + time = eightAM, + now = now + ) + ) { + selectedTime ?: eightAM + } else { + val nextHour = now.hour + if (now.minute > 0) 1 else 0 + LocalTime(hour = nextHour % 24, minute = 0) + } +} + +@Composable +fun formatReminder(selectedDate: LocalDate?, selectedTime: LocalTime?): String? { + if (selectedDate == null && selectedTime == null) return null + + val timeZone = TimeZone.currentSystemDefault() + val now = Instant.fromEpochMilliseconds(LocalClock.current.now()).toLocalDateTime(timeZone) + val defaultTime = getDefaultTime(selectedDate, selectedTime) + + val datePart = when { + selectedDate != null -> { + val day = selectedDate.dayOfMonth.toTwoDigitString() + val month = selectedDate.monthNumber.toTwoDigitString() + val year = selectedDate.year.toString() + "$day/$month/$year" + } + + selectedTime != null -> { + val reminderTimeToday = LocalDateTime(now.date, selectedTime) + if (reminderTimeToday > now) { + LocalReplyRadarStrings.current.replyListReminderToday + } else { + LocalReplyRadarStrings.current.replyListReminderTomorrow + } + } + + else -> null + } + + val timePart = when { + selectedTime != null -> "${selectedTime.hour.toTwoDigitString()}:${selectedTime.minute.toTwoDigitString()}" + selectedDate != null -> "${defaultTime.hour.toTwoDigitString()}:${defaultTime.minute.toTwoDigitString()}" + else -> null + } + + + return "${LocalReplyRadarStrings.current.replyListReminderSet} ${ + when { + datePart != null && timePart != null -> "$datePart $timePart" + datePart != null -> datePart + else -> timePart + } + }" +} + +fun isTimeValid(date: LocalDate?, time: LocalTime): Boolean { + val now = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) + + return when { + date == null -> { + val todayTime = LocalDateTime(now.date, time) + todayTime > now + } + + date == now.date -> { + val selectedDateTime = LocalDateTime(date, time) + selectedDateTime > now + } + + date > now.date -> true + else -> false + } +} diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/reminder/Reminder.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/reminder/Reminder.kt index 918aebd..d48f6e6 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/reminder/Reminder.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/reminder/Reminder.kt @@ -74,42 +74,22 @@ fun Reminder( color = colorScheme.horizontalDividerColor ) -// if (showTimePicker || showDatePicker) { -// Text( -// text = LocalReplyRadarStrings.current.replyListReminder, -// modifier = Modifier -// .fillMaxWidth() -// .padding(start = paddingSmall, top = paddingSmall), -// style = typography.bodyMedium, -// color = colorScheme.textFieldPlaceholderColor -// ) -// } - if (showTimePicker) { -// ReplyTimeSelector( -// modifier = Modifier -// .padding(top = paddingSmall), -// selectedTime = selectedTime, -// onTimeSelected = { onSelectedTimeChange(it) } -// ) - PlatformTimePicker( selectedTime = selectedTime, + selectedDate = selectedDate, onTimeSelected = { onSelectedTimeChange(it) } ) } if (showDatePicker) { -// ReplyDateSelector( -// modifier = Modifier -// .padding(top = paddingSmall), -// selectedDate = selectedDate, -// onDateSelected = { onSelectedDateChange(it) } -// ) - PlatformDatePicker( selectedDate = selectedDate, - onDateSelected = { onSelectedDateChange(it) } + selectedTime = selectedTime, + onDateSelected = { onSelectedDateChange(it) }, + onTimeInvalidated = { + onSelectedTimeChange(null) + } ) } @@ -132,7 +112,7 @@ fun Reminder( .size(iconSize), painter = painterResource(drawable.ic_time), contentDescription = LocalReplyRadarStrings.current.replyListReminderTimeIconContentDescription, - tint = colorScheme.primary //if (showTimePicker) colorScheme.primary else colorScheme.replyBottomSheetIconColor + tint = colorScheme.primary ) } @@ -149,7 +129,7 @@ fun Reminder( .size(iconSize), painter = painterResource(drawable.ic_date), contentDescription = LocalReplyRadarStrings.current.replyListReminderDateIconContentDescription, - tint = colorScheme.primary //if (showDatePicker) colorScheme.primary else colorScheme.replyBottomSheetIconColor + tint = colorScheme.primary ) } } @@ -198,47 +178,3 @@ private fun ReminderText( } } } - -@Composable -fun formatReminder(selectedDate: LocalDate?, selectedTime: LocalTime?): String? { - if (selectedDate == null && selectedTime == null) return null - - val timeZone = TimeZone.currentSystemDefault() - val now = Instant.fromEpochMilliseconds(LocalClock.current.now()).toLocalDateTime(timeZone) - val defaultTime = LocalTime(REMINDER_DEFAULT_HOUR, REMINDER_DEFAULT_MINUTE) - - val datePart = when { - selectedDate != null -> { - val day = selectedDate.dayOfMonth.toTwoDigitString() - val month = selectedDate.monthNumber.toTwoDigitString() - val year = selectedDate.year.toString() - "$day/$month/$year" - } - - selectedTime != null -> { - val reminderTimeToday = LocalDateTime(now.date, selectedTime) - if (reminderTimeToday > now) { - LocalReplyRadarStrings.current.replyListReminderToday - } else { - LocalReplyRadarStrings.current.replyListReminderTomorrow - } - } - - else -> null - } - - val timePart = when { - selectedTime != null -> "${selectedTime.hour.toTwoDigitString()}:${selectedTime.minute.toTwoDigitString()}" - selectedDate != null -> "${defaultTime.hour.toTwoDigitString()}:${defaultTime.minute.toTwoDigitString()}" - else -> null - } - - - return "${LocalReplyRadarStrings.current.replyListReminderSet} ${ - when { - datePart != null && timePart != null -> "$datePart $timePart" - datePart != null -> datePart - else -> timePart - } - }" -} From 4c7b8f3729307370fb19787f135276f9344df57f Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Sat, 26 Apr 2025 19:50:50 -0300 Subject: [PATCH 04/59] Improvements on Date and Time pickers. --- .../replyradar/core/util/PlatformDatePicker.android.kt | 2 +- .../replyradar/core/util/PlatformTimePicker.android.kt | 2 +- .../reminder => core/util/datetime}/DatetimeExt.kt | 2 +- .../replylist/components/reminder/Reminder.kt | 9 +-------- 4 files changed, 4 insertions(+), 11 deletions(-) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/{features/reply/presentation/replylist/components/reminder => core/util/datetime}/DatetimeExt.kt (97%) diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt index 2d85dc6..d991eff 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt @@ -14,7 +14,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import com.rafaelfelipeac.replyradar.core.util.datetime.toEpochMillis import com.rafaelfelipeac.replyradar.core.util.datetime.toLocalDate -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.reminder.isDateTimeValid +import com.rafaelfelipeac.replyradar.core.util.datetime.isDateTimeValid import kotlinx.datetime.Clock import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt index 6ce42c4..854e9df 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt @@ -11,7 +11,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.reminder.isTimeValid +import com.rafaelfelipeac.replyradar.core.util.datetime.isTimeValid import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalTime diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/reminder/DatetimeExt.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/DatetimeExt.kt similarity index 97% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/reminder/DatetimeExt.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/DatetimeExt.kt index 0c5c494..6776233 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/reminder/DatetimeExt.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/DatetimeExt.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.reminder +package com.rafaelfelipeac.replyradar.core.util.datetime import androidx.compose.runtime.Composable import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_DEFAULT_HOUR diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/reminder/Reminder.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/reminder/Reminder.kt index d48f6e6..da2207c 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/reminder/Reminder.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/reminder/Reminder.kt @@ -17,9 +17,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_DEFAULT_HOUR -import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_DEFAULT_MINUTE -import com.rafaelfelipeac.replyradar.core.common.clock.LocalClock import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.iconButtonSize import com.rafaelfelipeac.replyradar.core.common.ui.iconSize @@ -32,13 +29,9 @@ import com.rafaelfelipeac.replyradar.core.common.ui.theme.textFieldPlaceholderCo import com.rafaelfelipeac.replyradar.core.common.ui.theme.toolbarIconsColor import com.rafaelfelipeac.replyradar.core.util.PlatformDatePicker import com.rafaelfelipeac.replyradar.core.util.PlatformTimePicker -import com.rafaelfelipeac.replyradar.core.util.toTwoDigitString -import kotlinx.datetime.Instant +import com.rafaelfelipeac.replyradar.core.util.datetime.formatReminder import kotlinx.datetime.LocalDate -import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalTime -import kotlinx.datetime.TimeZone -import kotlinx.datetime.toLocalDateTime import org.jetbrains.compose.resources.painterResource import replyradar.composeapp.generated.resources.Res.drawable import replyradar.composeapp.generated.resources.ic_close From 6457d894d41d9f1847b8dcd28c41b7feb286ac4d Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Sat, 26 Apr 2025 19:51:15 -0300 Subject: [PATCH 05/59] Improvements on Date and Time pickers. --- .../replyradar/core/util/datetime/{DatetimeExt.kt => Datetime.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/{DatetimeExt.kt => Datetime.kt} (100%) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/DatetimeExt.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt similarity index 100% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/DatetimeExt.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt From 77ed82c7be609a6d1b2af55edab02ee7ea1178d8 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Sat, 26 Apr 2025 19:52:54 -0300 Subject: [PATCH 06/59] Improvements on Date and Time pickers. --- .../replyradar/core/util/datetime/{Datetime.kt => DatetimeExt.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/{Datetime.kt => DatetimeExt.kt} (100%) diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/DatetimeExt.kt similarity index 100% rename from composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt rename to composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/DatetimeExt.kt From d4ec264c71424586c123e2a40de94e0050e90a5a Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Sat, 26 Apr 2025 19:58:19 -0300 Subject: [PATCH 07/59] Moving ReplyReminder to Common. --- .../Reminder.kt => core/common/ui/components/ReplyReminder.kt} | 2 +- .../components/replybottomsheet/ReplyBottomSheetContent.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/{features/reply/presentation/replylist/components/reminder/Reminder.kt => core/common/ui/components/ReplyReminder.kt} (98%) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/reminder/Reminder.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt similarity index 98% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/reminder/Reminder.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt index da2207c..d3a71a0 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/reminder/Reminder.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.reminder +package com.rafaelfelipeac.replyradar.core.common.ui.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt index 0acc2f6..7746798 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt @@ -41,7 +41,7 @@ import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall import com.rafaelfelipeac.replyradar.core.util.format import com.rafaelfelipeac.replyradar.core.util.formatTimestamp import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.reminder.Reminder +import com.rafaelfelipeac.replyradar.core.common.ui.components.Reminder import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetMode.EDIT import kotlinx.datetime.DateTimeUnit.Companion.DAY import kotlinx.datetime.Instant From 2063f04ae41af0acc5e69b35a718468c5200e769 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Tue, 29 Apr 2025 17:26:38 -0300 Subject: [PATCH 08/59] Renaming ReplyReminder. --- .../replyradar/core/common/ui/components/ReplyReminder.kt | 2 +- .../components/replybottomsheet/ReplyBottomSheetContent.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt index d3a71a0..6e814d3 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt @@ -39,7 +39,7 @@ import replyradar.composeapp.generated.resources.ic_date import replyradar.composeapp.generated.resources.ic_time @Composable -fun Reminder( +fun ReplyReminder( selectedTime: LocalTime?, selectedDate: LocalDate?, onSelectedTimeChange: (LocalTime?) -> Unit, diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt index 7746798..af442a8 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt @@ -41,7 +41,7 @@ import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall import com.rafaelfelipeac.replyradar.core.util.format import com.rafaelfelipeac.replyradar.core.util.formatTimestamp import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply -import com.rafaelfelipeac.replyradar.core.common.ui.components.Reminder +import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyReminder import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetMode.EDIT import kotlinx.datetime.DateTimeUnit.Companion.DAY import kotlinx.datetime.Instant @@ -116,7 +116,7 @@ fun ReplyBottomSheetContent( ) } - Reminder( + ReplyReminder( selectedTime = selectedTime, selectedDate = selectedDate, onSelectedTimeChange = { selectedTime = it }, From d3c6793fca17d072ae70038585da9d905e0cdd7f Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Tue, 29 Apr 2025 18:15:41 -0300 Subject: [PATCH 09/59] Added dismiss calls to datetime pickers. --- .../core/util/PlatformDatePicker.android.kt | 14 ++++++++++--- .../core/util/PlatformTimePicker.android.kt | 14 ++++++++++--- .../common/ui/components/ReplyReminder.kt | 20 +++++++++++++++---- .../core/util/PlatformDatePicker.kt | 3 ++- .../core/util/PlatformTimePicker.kt | 3 ++- .../core/util/PlatformDatePicker.desktop.kt | 3 ++- .../core/util/PlatformTimePicker.desktop.kt | 3 ++- .../core/util/PlatformDatePicker.ios.kt | 3 ++- .../core/util/PlatformTimePicker.ios.kt | 3 ++- 9 files changed, 50 insertions(+), 16 deletions(-) diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt index d991eff..be1eea6 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt @@ -29,7 +29,8 @@ actual fun PlatformDatePicker( selectedDate: LocalDate?, selectedTime: LocalTime?, onDateSelected: (LocalDate) -> Unit, - onTimeInvalidated: () -> Unit + onTimeInvalidated: () -> Unit, + onDismiss: () -> Unit ) { val now = Clock.System.now() .toLocalDateTime(TimeZone.currentSystemDefault()) @@ -51,7 +52,10 @@ actual fun PlatformDatePicker( if (showDialog) { DatePickerDialog( - onDismissRequest = { showDialog = false }, + onDismissRequest = { + onDismiss() + showDialog = false + }, confirmButton = { TextButton( onClick = { @@ -72,6 +76,7 @@ actual fun PlatformDatePicker( } } ?: onTimeInvalidated() } + onDismiss() showDialog = false } ) { @@ -80,7 +85,10 @@ actual fun PlatformDatePicker( }, dismissButton = { TextButton( - onClick = { showDialog = false } + onClick = { + onDismiss() + showDialog = false + } ) { Text("Cancelar") } diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt index 854e9df..d29f3d8 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt @@ -20,7 +20,8 @@ import kotlinx.datetime.LocalTime actual fun PlatformTimePicker( selectedTime: LocalTime?, selectedDate: LocalDate?, - onTimeSelected: (LocalTime) -> Unit + onTimeSelected: (LocalTime) -> Unit, + onDismiss: () -> Unit ) { val timePickerState = rememberTimePickerState( initialHour = selectedTime?.hour ?: 12, @@ -34,12 +35,16 @@ actual fun PlatformTimePicker( val isValid = isTimeValid(selectedDate, pickedTime) AlertDialog( - onDismissRequest = { showDialog = false }, + onDismissRequest = { + onDismiss() + showDialog = false + }, confirmButton = { TextButton( onClick = { if (isValid) { onTimeSelected(pickedTime) + onDismiss() showDialog = false } }, @@ -49,7 +54,10 @@ actual fun PlatformTimePicker( } }, dismissButton = { - TextButton(onClick = { showDialog = false }) { + TextButton(onClick = { + onDismiss() + showDialog = false + }) { Text("Cancelar") } }, diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt index 6e814d3..9e5755d 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt @@ -71,7 +71,13 @@ fun ReplyReminder( PlatformTimePicker( selectedTime = selectedTime, selectedDate = selectedDate, - onTimeSelected = { onSelectedTimeChange(it) } + onTimeSelected = { + onSelectedTimeChange(it) + showTimePicker = false + }, + onDismiss = { + showTimePicker = false + } ) } @@ -79,9 +85,15 @@ fun ReplyReminder( PlatformDatePicker( selectedDate = selectedDate, selectedTime = selectedTime, - onDateSelected = { onSelectedDateChange(it) }, + onDateSelected = { + onSelectedDateChange(it) + showDatePicker = false + }, onTimeInvalidated = { onSelectedTimeChange(null) + }, + onDismiss = { + showDatePicker = false } ) } @@ -94,7 +106,7 @@ fun ReplyReminder( ) { IconButton( onClick = { - showTimePicker = !showTimePicker + showTimePicker = true closeKeyboard() }, modifier = Modifier @@ -111,7 +123,7 @@ fun ReplyReminder( IconButton( onClick = { - showDatePicker = !showDatePicker + showDatePicker = true closeKeyboard() }, modifier = Modifier diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.kt index d250c2c..bf06b7f 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.kt @@ -9,5 +9,6 @@ expect fun PlatformDatePicker( selectedDate: LocalDate?, selectedTime: LocalTime?, onDateSelected: (LocalDate) -> Unit, - onTimeInvalidated: () -> Unit + onTimeInvalidated: () -> Unit, + onDismiss: () -> Unit ) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.kt index 240d4c6..1862411 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.kt @@ -8,5 +8,6 @@ import kotlinx.datetime.LocalTime expect fun PlatformTimePicker( selectedTime: LocalTime?, selectedDate: LocalDate?, - onTimeSelected: (LocalTime) -> Unit + onTimeSelected: (LocalTime) -> Unit, + onDismiss: () -> Unit ) diff --git a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.desktop.kt b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.desktop.kt index a0a2474..8a373cd 100644 --- a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.desktop.kt +++ b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.desktop.kt @@ -9,7 +9,8 @@ actual fun PlatformDatePicker( selectedDate: LocalDate?, selectedTime: LocalTime?, onDateSelected: (LocalDate) -> Unit, - onTimeInvalidated: () -> Unit + onTimeInvalidated: () -> Unit, + onDismiss: () -> Unit ) { TODO("Not yet implemented for this platform.") } diff --git a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.desktop.kt b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.desktop.kt index c0af029..b026c0f 100644 --- a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.desktop.kt +++ b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.desktop.kt @@ -8,7 +8,8 @@ import kotlinx.datetime.LocalTime actual fun PlatformTimePicker( selectedTime: LocalTime?, selectedDate: LocalDate?, - onTimeSelected: (LocalTime) -> Unit + onTimeSelected: (LocalTime) -> Unit, + onDismiss: () -> Unit ) { TODO("Not yet implemented for this platform.") } diff --git a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.ios.kt b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.ios.kt index a0a2474..8a373cd 100644 --- a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.ios.kt @@ -9,7 +9,8 @@ actual fun PlatformDatePicker( selectedDate: LocalDate?, selectedTime: LocalTime?, onDateSelected: (LocalDate) -> Unit, - onTimeInvalidated: () -> Unit + onTimeInvalidated: () -> Unit, + onDismiss: () -> Unit ) { TODO("Not yet implemented for this platform.") } diff --git a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.ios.kt b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.ios.kt index c0af029..b026c0f 100644 --- a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.ios.kt @@ -8,7 +8,8 @@ import kotlinx.datetime.LocalTime actual fun PlatformTimePicker( selectedTime: LocalTime?, selectedDate: LocalDate?, - onTimeSelected: (LocalTime) -> Unit + onTimeSelected: (LocalTime) -> Unit, + onDismiss: () -> Unit ) { TODO("Not yet implemented for this platform.") } From c101856de858b682cef2815d682d91f51cec998d Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Tue, 29 Apr 2025 18:34:17 -0300 Subject: [PATCH 10/59] Fixing datetime issues. --- .../common/ui/components/ReplyReminder.kt | 6 ++++- .../replyradar/core/util/datetime/Datetime.kt | 25 +++++++++++++------ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt index 9e5755d..74cc88e 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt @@ -48,7 +48,11 @@ fun ReplyReminder( ) { var showTimePicker by remember { mutableStateOf(false) } var showDatePicker by remember { mutableStateOf(false) } - val reminderText = formatReminder(selectedDate = selectedDate, selectedTime = selectedTime) + val reminderText = formatReminder( + selectedDate = selectedDate, + selectedTime = selectedTime, + onTimeSelected = { onSelectedTimeChange(it) } + ) ReminderText( reminderText = reminderText, diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt index 6776233..5821907 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt @@ -40,26 +40,35 @@ fun getDefaultTime(selectedDate: LocalDate?, selectedTime: LocalTime?): LocalTim val eightAM = LocalTime(REMINDER_DEFAULT_HOUR, REMINDER_DEFAULT_MINUTE) - return if (selectedDate != null && isDateTimeValid( + if (selectedDate != null && selectedTime != null && + isDateTimeValid( date = selectedDate, - time = eightAM, + time = selectedTime, now = now ) ) { - selectedTime ?: eightAM - } else { - val nextHour = now.hour + if (now.minute > 0) 1 else 0 - LocalTime(hour = nextHour % 24, minute = 0) + return selectedTime } + + if (selectedDate != null && isDateTimeValid(date = selectedDate, time = eightAM, now = now)) { + return eightAM + } + + val nextHour = now.hour + if (now.minute > 0) 1 else 0 + return LocalTime(hour = nextHour % 24, minute = 0) } @Composable -fun formatReminder(selectedDate: LocalDate?, selectedTime: LocalTime?): String? { +fun formatReminder( + selectedDate: LocalDate?, + selectedTime: LocalTime?, + onTimeSelected: (LocalTime) -> Unit, +): String? { if (selectedDate == null && selectedTime == null) return null val timeZone = TimeZone.currentSystemDefault() val now = Instant.fromEpochMilliseconds(LocalClock.current.now()).toLocalDateTime(timeZone) - val defaultTime = getDefaultTime(selectedDate, selectedTime) + val defaultTime = getDefaultTime(selectedDate, selectedTime).also { onTimeSelected(it) } val datePart = when { selectedDate != null -> { From fc5f778d85fefb74fb04a5059ce464aa7b87f38c Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Tue, 29 Apr 2025 18:52:39 -0300 Subject: [PATCH 11/59] Fixing datetime issues. --- .../replyradar/core/util/PlatformDatePicker.android.kt | 2 +- .../rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt index be1eea6..28eea25 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt @@ -12,9 +12,9 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import com.rafaelfelipeac.replyradar.core.util.datetime.isDateTimeValid import com.rafaelfelipeac.replyradar.core.util.datetime.toEpochMillis import com.rafaelfelipeac.replyradar.core.util.datetime.toLocalDate -import com.rafaelfelipeac.replyradar.core.util.datetime.isDateTimeValid import kotlinx.datetime.Clock import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt index 5821907..35deeda 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt @@ -40,8 +40,7 @@ fun getDefaultTime(selectedDate: LocalDate?, selectedTime: LocalTime?): LocalTim val eightAM = LocalTime(REMINDER_DEFAULT_HOUR, REMINDER_DEFAULT_MINUTE) - if (selectedDate != null && selectedTime != null && - isDateTimeValid( + if (selectedTime != null && isDateTimeValid( date = selectedDate, time = selectedTime, now = now @@ -50,7 +49,7 @@ fun getDefaultTime(selectedDate: LocalDate?, selectedTime: LocalTime?): LocalTim return selectedTime } - if (selectedDate != null && isDateTimeValid(date = selectedDate, time = eightAM, now = now)) { + if (isDateTimeValid(date = selectedDate, time = eightAM, now = now)) { return eightAM } From 0836ed0fee7bc17bcb43fadc87b3c60b52b47d26 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Tue, 29 Apr 2025 19:26:43 -0300 Subject: [PATCH 12/59] Reading reminderAt. --- .../replybottomsheet/ReplyBottomSheetContent.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt index af442a8..47ae0c8 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt @@ -72,8 +72,14 @@ fun ReplyBottomSheetContent( replyBottomSheetState?.let { state -> var name by remember { mutableStateOf(state.reply?.name ?: EMPTY) } var subject by remember { mutableStateOf(state.reply?.subject ?: EMPTY) } - var selectedTime by remember { mutableStateOf(null) } - var selectedDate by remember { mutableStateOf(null) } + val reminderAt = state.reply?.reminderAt + ?.takeIf { it != 0L } + ?.let { millis -> + Instant.fromEpochMilliseconds(millis) + .toLocalDateTime(TimeZone.currentSystemDefault()) + } + var selectedTime by remember(reminderAt) { mutableStateOf(reminderAt?.time) } + var selectedDate by remember(reminderAt) { mutableStateOf(reminderAt?.date) } Column( modifier = Modifier From 66f397d98d1a108c0154b57554c35cfa006bc6a8 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Tue, 29 Apr 2025 21:02:44 -0300 Subject: [PATCH 13/59] Database migration for reminderAt. --- .../1.json | 12 +- .../2.json | 125 ++++++++++++++++++ .../core/database/DatabaseFactory.kt | 5 +- .../core/database/ReplyRadarMigrations.kt | 21 +++ .../core/database/ReplyRadarDatabase.kt | 2 +- .../reply/data/database/entity/ReplyEntity.kt | 3 +- .../features/reply/data/mapper/ReplyMapper.kt | 6 +- 7 files changed, 165 insertions(+), 9 deletions(-) create mode 100644 composeApp/schemas/com.rafaelfelipeac.replyradar.core.database.ReplyRadarDatabase/2.json create mode 100644 composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/database/ReplyRadarMigrations.kt diff --git a/composeApp/schemas/com.rafaelfelipeac.replyradar.core.database.ReplyRadarDatabase/1.json b/composeApp/schemas/com.rafaelfelipeac.replyradar.core.database.ReplyRadarDatabase/1.json index 3cfa686..cfaa152 100644 --- a/composeApp/schemas/com.rafaelfelipeac.replyradar.core.database.ReplyRadarDatabase/1.json +++ b/composeApp/schemas/com.rafaelfelipeac.replyradar.core.database.ReplyRadarDatabase/1.json @@ -2,11 +2,11 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "6fc3470e59a8628f2c278dc0df38ab28", + "identityHash": "5607ce6deeaf96cb63ae56e6e3177625", "entities": [ { "tableName": "replies", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `subject` TEXT NOT NULL, `isResolved` INTEGER NOT NULL, `isArchived` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `resolvedAt` INTEGER NOT NULL, `archivedAt` INTEGER NOT NULL)", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `subject` TEXT NOT NULL, `isResolved` INTEGER NOT NULL, `isArchived` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `resolvedAt` INTEGER NOT NULL, `archivedAt` INTEGER NOT NULL, `reminderAt` INTEGER NOT NULL)", "fields": [ { "fieldPath": "id", @@ -61,6 +61,12 @@ "columnName": "archivedAt", "affinity": "INTEGER", "notNull": true + }, + { + "fieldPath": "reminderAt", + "columnName": "reminderAt", + "affinity": "INTEGER", + "notNull": true } ], "primaryKey": { @@ -113,7 +119,7 @@ ], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6fc3470e59a8628f2c278dc0df38ab28')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5607ce6deeaf96cb63ae56e6e3177625')" ] } } \ No newline at end of file diff --git a/composeApp/schemas/com.rafaelfelipeac.replyradar.core.database.ReplyRadarDatabase/2.json b/composeApp/schemas/com.rafaelfelipeac.replyradar.core.database.ReplyRadarDatabase/2.json new file mode 100644 index 0000000..6faaeb6 --- /dev/null +++ b/composeApp/schemas/com.rafaelfelipeac.replyradar.core.database.ReplyRadarDatabase/2.json @@ -0,0 +1,125 @@ +{ + "formatVersion": 1, + "database": { + "version": 2, + "identityHash": "5607ce6deeaf96cb63ae56e6e3177625", + "entities": [ + { + "tableName": "replies", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `subject` TEXT NOT NULL, `isResolved` INTEGER NOT NULL, `isArchived` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `resolvedAt` INTEGER NOT NULL, `archivedAt` INTEGER NOT NULL, `reminderAt` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isResolved", + "columnName": "isResolved", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isArchived", + "columnName": "isArchived", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "resolvedAt", + "columnName": "resolvedAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "archivedAt", + "columnName": "archivedAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "reminderAt", + "columnName": "reminderAt", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "user_actions", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `actionType` TEXT NOT NULL, `targetType` TEXT, `targetId` INTEGER, `createdAt` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "actionType", + "columnName": "actionType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "targetType", + "columnName": "targetType", + "affinity": "TEXT" + }, + { + "fieldPath": "targetId", + "columnName": "targetId", + "affinity": "INTEGER" + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5607ce6deeaf96cb63ae56e6e3177625')" + ] + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/database/DatabaseFactory.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/database/DatabaseFactory.kt index 85358fa..89d8654 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/database/DatabaseFactory.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/database/DatabaseFactory.kt @@ -4,6 +4,7 @@ import android.content.Context import androidx.room.Room import androidx.room.RoomDatabase import com.rafaelfelipeac.replyradar.core.AppConstants.DB_NAME +import com.rafaelfelipeac.replyradar.core.database.ReplyRadarMigrations.ALL_MIGRATIONS actual class DatabaseFactory( private val context: Context @@ -12,9 +13,9 @@ actual class DatabaseFactory( val appContext = context.applicationContext val dbFile = appContext.getDatabasePath(DB_NAME) - return Room.databaseBuilder( + return Room.databaseBuilder( context = appContext, name = dbFile.absolutePath - ) + ).addMigrations(*ALL_MIGRATIONS) } } diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/database/ReplyRadarMigrations.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/database/ReplyRadarMigrations.kt new file mode 100644 index 0000000..5f61984 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/database/ReplyRadarMigrations.kt @@ -0,0 +1,21 @@ +package com.rafaelfelipeac.replyradar.core.database + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase +import androidx.sqlite.SQLiteConnection +import androidx.sqlite.execSQL + +object ReplyRadarMigrations { + val ALL_MIGRATIONS = arrayOf(MIGRATION_1_2) +} + +val MIGRATION_1_2 = object : Migration(1, 2) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE replies ADD COLUMN reminderAt INTEGER NOT NULL DEFAULT 0") + } + + override fun migrate(connection: SQLiteConnection) { + connection.execSQL("ALTER TABLE replies ADD COLUMN reminderAt INTEGER NOT NULL DEFAULT 0") + } +} + diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/database/ReplyRadarDatabase.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/database/ReplyRadarDatabase.kt index 6d114b0..93494f3 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/database/ReplyRadarDatabase.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/database/ReplyRadarDatabase.kt @@ -15,4 +15,4 @@ abstract class ReplyRadarDatabase : RoomDatabase() { abstract val userActionDao: UserActionDao } -private const val DATABASE_VERSION = 1 +private const val DATABASE_VERSION = 2 diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/database/entity/ReplyEntity.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/database/entity/ReplyEntity.kt index c2a0d41..d1ec1f5 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/database/entity/ReplyEntity.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/database/entity/ReplyEntity.kt @@ -14,5 +14,6 @@ data class ReplyEntity( val createdAt: Long = INITIAL_DATE_LONG, val updatedAt: Long = INITIAL_DATE_LONG, val resolvedAt: Long = INITIAL_DATE_LONG, - val archivedAt: Long = INITIAL_DATE_LONG + val archivedAt: Long = INITIAL_DATE_LONG, + val reminderAt: Long = INITIAL_DATE_LONG ) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/mapper/ReplyMapper.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/mapper/ReplyMapper.kt index 63101d3..9c2d621 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/mapper/ReplyMapper.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/mapper/ReplyMapper.kt @@ -12,7 +12,8 @@ fun Reply.toReplyEntity() = ReplyEntity( createdAt = createdAt, updatedAt = updatedAt, resolvedAt = resolvedAt, - archivedAt = archivedAt + archivedAt = archivedAt, + reminderAt = reminderAt ) fun ReplyEntity.toReply() = Reply( @@ -24,5 +25,6 @@ fun ReplyEntity.toReply() = Reply( createdAt = createdAt, updatedAt = updatedAt, resolvedAt = resolvedAt, - archivedAt = archivedAt + archivedAt = archivedAt, + reminderAt = reminderAt ) From 645e97a85f489b763b0e3d84f740c8a23efb5100 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Tue, 6 May 2025 18:13:04 -0300 Subject: [PATCH 14/59] Added ReminderScheduler. --- composeApp/build.gradle.kts | 1 + .../src/androidMain/AndroidManifest.xml | 1 + .../replyradar/ReplyRadarApplication.kt | 20 +++++++++ .../core/util/reminder/NotificationUtils.kt | 29 ++++++++++++ .../util/reminder/ReminderSchedulerImpl.kt | 44 +++++++++++++++++++ .../core/util/reminder/ReminderWorker.kt | 20 +++++++++ .../replyradar/di/Modules.android.kt | 4 ++ .../core/util/reminder/ReminderScheduler.kt | 12 +++++ .../features/reply/di/ReplyModule.kt | 3 +- .../replylist/ReplyListViewModel.kt | 11 +++++ gradle/libs.versions.toml | 2 + 11 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/NotificationUtils.kt create mode 100644 composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderSchedulerImpl.kt create mode 100644 composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderWorker.kt create mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderScheduler.kt diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 04940ec..f1c0160 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -216,6 +216,7 @@ tasks.register("detektFormat") { dependencies { implementation(libs.androidx.ui.android) + implementation(libs.androidx.work.runtime.ktx) debugImplementation(compose.uiTooling) add("kspAndroid", libs.androidx.room.compiler) } diff --git a/composeApp/src/androidMain/AndroidManifest.xml b/composeApp/src/androidMain/AndroidManifest.xml index 290daea..3d57d63 100644 --- a/composeApp/src/androidMain/AndroidManifest.xml +++ b/composeApp/src/androidMain/AndroidManifest.xml @@ -2,6 +2,7 @@ + = Build.VERSION_CODES.O) { + val channelId = "reminder_channel" + val name = "Reminder Notifications" + val descriptionText = "Notifications for scheduled reminders" + val importance = NotificationManager.IMPORTANCE_HIGH + val channel = NotificationChannel(channelId, name, importance).apply { + description = descriptionText + } + val notificationManager: NotificationManager = + getSystemService(NotificationManager::class.java) + notificationManager.createNotificationChannel(channel) + } } companion object { diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/NotificationUtils.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/NotificationUtils.kt new file mode 100644 index 0000000..e326e56 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/NotificationUtils.kt @@ -0,0 +1,29 @@ +package com.rafaelfelipeac.replyradar.core.util.reminder + +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager +import androidx.core.app.ActivityCompat +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import com.rafaelfelipeac.replyradar.R + +object NotificationUtils { + fun showReminderNotification(context: Context, name: String, subject: String) { + val notification = NotificationCompat.Builder(context, "reminder_channel") + .setSmallIcon(R.drawable.ic_launcher_background) + .setContentTitle("Reminder: $name") + .setContentText(subject) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .build() + + if (ActivityCompat.checkSelfPermission( + context, + Manifest.permission.POST_NOTIFICATIONS + ) != PackageManager.PERMISSION_GRANTED + ) { + return + } + NotificationManagerCompat.from(context).notify(name.hashCode(), notification) + } +} diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderSchedulerImpl.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderSchedulerImpl.kt new file mode 100644 index 0000000..236a7dd --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderSchedulerImpl.kt @@ -0,0 +1,44 @@ +package com.rafaelfelipeac.replyradar.core.util.reminder + +import android.content.Context +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.workDataOf +import java.util.concurrent.TimeUnit + +class ReminderSchedulerImpl( + private val context: Context +) : ReminderScheduler { + + override fun scheduleReminder( + reminderAtMillis: Long, + name: String, + subject: String, + replyId: Long + ) { + val delay = reminderAtMillis - System.currentTimeMillis() + if (delay <= 0) return + + enqueueReminder(delay, name, subject, replyId) + } + + private fun enqueueReminder(delay: Long, name: String, subject: String, replyId: Long) { + val workRequest = OneTimeWorkRequestBuilder() + .setInitialDelay(delay, TimeUnit.MILLISECONDS) + .setInputData( + workDataOf( + "name" to name, + "subject" to subject, + "replyId" to replyId.toString() + ) + ) + .addTag("reminder-$replyId") + .build() + + WorkManager.getInstance(context).enqueue(workRequest) + } + + override fun cancelReminder(replyId: Long) { + WorkManager.getInstance(context).cancelAllWorkByTag("reminder-$replyId") + } +} diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderWorker.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderWorker.kt new file mode 100644 index 0000000..6960671 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderWorker.kt @@ -0,0 +1,20 @@ +package com.rafaelfelipeac.replyradar.core.util.reminder + +import android.content.Context +import androidx.work.Worker +import androidx.work.WorkerParameters + +class ReminderWorker( + appContext: Context, + workerParams: WorkerParameters +) : Worker(appContext, workerParams) { + + override fun doWork(): Result { + val name = inputData.getString("name") ?: return Result.failure() + val subject = inputData.getString("subject") ?: return Result.failure() + + NotificationUtils.showReminderNotification(applicationContext, name, subject) + + return Result.success() + } +} diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/di/Modules.android.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/di/Modules.android.kt index 21b392d..d1f1fb3 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/di/Modules.android.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/di/Modules.android.kt @@ -1,9 +1,12 @@ package com.rafaelfelipeac.replyradar.di +import android.content.Context import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import com.rafaelfelipeac.replyradar.core.database.DatabaseFactory import com.rafaelfelipeac.replyradar.core.preferences.CreateDataStore +import com.rafaelfelipeac.replyradar.core.util.reminder.ReminderScheduler +import com.rafaelfelipeac.replyradar.core.util.reminder.ReminderSchedulerImpl import io.ktor.client.engine.HttpClientEngine import io.ktor.client.engine.okhttp.OkHttp import org.koin.android.ext.koin.androidApplication @@ -15,4 +18,5 @@ actual val platformModule: Module single { OkHttp.create() } single { DatabaseFactory(androidApplication()) } single> { CreateDataStore(androidApplication()) } + single { ReminderSchedulerImpl(androidApplication() as Context) } } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderScheduler.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderScheduler.kt new file mode 100644 index 0000000..9f82e99 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderScheduler.kt @@ -0,0 +1,12 @@ +package com.rafaelfelipeac.replyradar.core.util.reminder + +interface ReminderScheduler { + fun scheduleReminder( + reminderAtMillis: Long, + name: String, + subject: String, + replyId: Long + ) + + fun cancelReminder(replyId: Long) +} diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/di/ReplyModule.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/di/ReplyModule.kt index ba05b4c..ee6fc67 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/di/ReplyModule.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/di/ReplyModule.kt @@ -31,7 +31,8 @@ val replyModule = module { deleteReplyUseCase = get(), getRepliesUseCase = get(), logUserActionUseCase = get(), - dispatcher = get() + dispatcher = get(), + reminderScheduler = get() ) } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt index 6ed3443..571594c 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt @@ -2,6 +2,7 @@ package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.rafaelfelipeac.replyradar.core.util.reminder.ReminderScheduler import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.domain.usecase.DeleteReplyUseCase import com.rafaelfelipeac.replyradar.features.reply.domain.usecase.GetRepliesUseCase @@ -58,6 +59,7 @@ class ReplyListViewModel( private val deleteReplyUseCase: DeleteReplyUseCase, private val getRepliesUseCase: GetRepliesUseCase, private val logUserActionUseCase: LogUserActionUseCase, + private val reminderScheduler: ReminderScheduler, private val dispatcher: CoroutineDispatcher = Dispatchers.Main ) : ViewModel() { @@ -185,6 +187,15 @@ class ReplyListViewModel( val replyId = upsertReplyUseCase.upsertReply(reply) logUserAction(actionType = actionType, targetId = replyId) + + if (reply.reminderAt != 0L) { + reminderScheduler.scheduleReminder( + reminderAtMillis = reply.reminderAt, + name = reply.name, + subject = reply.subject, + replyId = replyId + ) + } } private fun onToggleArchiveReply(reply: Reply) = viewModelScope.launch { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ec6a85c..d720d65 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,6 +23,7 @@ datastore = "1.1.1" assertk = "0.28.1" turbine = "1.1.0" ksp = "2.1.20-1.0.31" +workRuntimeKtx = "2.10.1" [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } @@ -69,6 +70,7 @@ datastore-preferences = { module = "androidx.datastore:datastore-preferences", v assertk = { module = "com.willowtreeapps.assertk:assertk", version.ref = "assertk" } turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" } +androidx-work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "workRuntimeKtx" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } From 9076842332ff99692c5cfe68018e634f5fbfc0ff Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Tue, 6 May 2025 19:09:23 -0300 Subject: [PATCH 15/59] Added ReminderScheduler. --- .../rafaelfelipeac/replyradar/AndroidApp.kt | 4 +- .../com/rafaelfelipeac/replyradar/Previews.kt | 6 +- .../rememberNotificationPermissionManager.kt | 71 +++++++++++++++++++ .../replyradar/app/ReplyRadarApp.kt | 9 ++- .../replyradar/core/navigation/AppNavHost.kt | 9 ++- .../NotificationPermissionManager.kt | 5 ++ .../presentation/replylist/ReplyListScreen.kt | 13 ++-- .../replybottomsheet/ReplyBottomSheet.kt | 16 +++-- .../ReplyBottomSheetContent.kt | 45 +++++++----- .../com/rafaelfelipeac/replyradar/Main.kt | 2 +- .../replyradar/MainViewController.kt | 2 +- 11 files changed, 148 insertions(+), 34 deletions(-) create mode 100644 composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt create mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/NotificationPermissionManager.kt diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/AndroidApp.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/AndroidApp.kt index ff8b976..7d086cd 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/AndroidApp.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/AndroidApp.kt @@ -9,6 +9,7 @@ import androidx.compose.ui.graphics.Color.Companion.Black import androidx.compose.ui.tooling.preview.Preview import com.rafaelfelipeac.replyradar.app.ReplyRadarApp import com.rafaelfelipeac.replyradar.core.ConfigureSystemBars +import rememberNotificationPermissionManager @Composable @Preview @@ -22,6 +23,7 @@ fun AndroidApp() { onSystemBarsConfigured = { dark, bgColor -> isDark = dark backgroundColor = bgColor - } + }, + notificationPermissionManager = rememberNotificationPermissionManager() ) } diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/Previews.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/Previews.kt index f2020a3..0b8fd37 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/Previews.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/Previews.kt @@ -2,6 +2,7 @@ package com.rafaelfelipeac.replyradar import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview +import com.rafaelfelipeac.replyradar.core.notification.NotificationPermissionManager import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreen import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListState @@ -24,6 +25,9 @@ private fun ReplyListScreenPreview() { ), onIntent = {}, onSettingsClick = {}, - onActivityLogClick = {} + onActivityLogClick = {}, + notificationPermissionManager = object : NotificationPermissionManager { + override suspend fun ensureNotificationPermission() = false + } ) } diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt new file mode 100644 index 0000000..34ef58f --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt @@ -0,0 +1,71 @@ +package com.rafaelfelipeac.replyradar.core.notification + +import android.Manifest +import android.content.pm.PackageManager +import android.os.Build +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.platform.LocalContext +import androidx.core.content.ContextCompat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.resume + +@Composable +fun rememberNotificationPermissionManager(): NotificationPermissionManager { + val context = LocalContext.current + val permissionResultState = remember { mutableStateOf(null) } + + val permissionLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.RequestPermission() + ) { isGranted -> + permissionResultState.value = isGranted + } + + return remember { + object : NotificationPermissionManager { + override suspend fun ensureNotificationPermission(): Boolean { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + return true + } + + val granted = ContextCompat.checkSelfPermission( + context, + Manifest.permission.POST_NOTIFICATIONS + ) == PackageManager.PERMISSION_GRANTED + + if (granted) { + return true + } + + // Launch and suspend for result + return suspendCancellableCoroutine { cont -> + permissionResultState.value = null + permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + + val scope = CoroutineScope(Dispatchers.Main.immediate) + + val job = scope.launch { + snapshotFlow { permissionResultState.value } + .filterNotNull() + .first() + .let { result -> + cont.resume(result) + } + } + + cont.invokeOnCancellation { job.cancel() } + } + } + } + } +} + diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt index 32f6201..0482014 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt @@ -17,13 +17,15 @@ import com.rafaelfelipeac.replyradar.core.common.ui.theme.ReplyRadarTheme import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme.DARK import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme.SYSTEM import com.rafaelfelipeac.replyradar.core.navigation.AppNavHost +import com.rafaelfelipeac.replyradar.core.notification.NotificationPermissionManager import com.rafaelfelipeac.replyradar.core.util.getClock import com.rafaelfelipeac.replyradar.features.app.settings.AppSettingsViewModel import org.koin.compose.viewmodel.koinViewModel @Composable fun ReplyRadarApp( - onSystemBarsConfigured: ((isDark: Boolean, backgroundColor: Color) -> Unit)? = null + onSystemBarsConfigured: ((isDark: Boolean, backgroundColor: Color) -> Unit)? = null, + notificationPermissionManager: NotificationPermissionManager ) { val navController = rememberNavController() val appSettingsViewModel = koinViewModel() @@ -46,7 +48,10 @@ fun ReplyRadarApp( LocalClock provides getClock() ) { ReplyRadarTheme(darkTheme = isDark) { - AppNavHost(navController = navController) + AppNavHost( + navController = navController, + notificationPermissionManager = notificationPermissionManager + ) } } } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/navigation/AppNavHost.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/navigation/AppNavHost.kt index 419c580..bbebe86 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/navigation/AppNavHost.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/navigation/AppNavHost.kt @@ -13,6 +13,7 @@ import com.rafaelfelipeac.replyradar.core.navigation.Route.ActivityLog import com.rafaelfelipeac.replyradar.core.navigation.Route.ReplyGraph import com.rafaelfelipeac.replyradar.core.navigation.Route.ReplyList import com.rafaelfelipeac.replyradar.core.navigation.Route.Settings +import com.rafaelfelipeac.replyradar.core.notification.NotificationPermissionManager import com.rafaelfelipeac.replyradar.features.activitylog.presentation.ActivityLogScreen import com.rafaelfelipeac.replyradar.features.activitylog.presentation.ActivityLogViewModel import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenRoot @@ -22,7 +23,10 @@ import com.rafaelfelipeac.replyradar.features.settings.presentation.SettingsView import org.koin.compose.viewmodel.koinViewModel @Composable -fun AppNavHost(navController: NavHostController) { +fun AppNavHost( + navController: NavHostController, + notificationPermissionManager: NotificationPermissionManager +) { NavHost( navController = navController, startDestination = ReplyGraph @@ -39,7 +43,8 @@ fun AppNavHost(navController: NavHostController) { ReplyListScreenRoot( viewModel = viewModel, onSettingsClick = { navController.navigate(Settings) }, - onActivityLogClick = { navController.navigate(ActivityLog) } + onActivityLogClick = { navController.navigate(ActivityLog) }, + notificationPermissionManager = notificationPermissionManager ) } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/NotificationPermissionManager.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/NotificationPermissionManager.kt new file mode 100644 index 0000000..f16e202 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/NotificationPermissionManager.kt @@ -0,0 +1,5 @@ +package com.rafaelfelipeac.replyradar.core.notification + +interface NotificationPermissionManager { + suspend fun ensureNotificationPermission(): Boolean +} diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt index d4dd703..01fbbcd 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt @@ -52,6 +52,7 @@ import com.rafaelfelipeac.replyradar.core.common.ui.spacerXSmall import com.rafaelfelipeac.replyradar.core.common.ui.tabRowTopPadding import com.rafaelfelipeac.replyradar.core.common.ui.theme.snackbarBackgroundColor import com.rafaelfelipeac.replyradar.core.common.ui.theme.toolbarIconsColor +import com.rafaelfelipeac.replyradar.core.notification.NotificationPermissionManager import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.ClearSnackbarState import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnAddReplyClick import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnTabSelected @@ -79,7 +80,8 @@ private const val ARCHIVED_INDEX = 2 fun ReplyListScreenRoot( viewModel: ReplyListViewModel = koinViewModel(), onSettingsClick: () -> Unit, - onActivityLogClick: () -> Unit + onActivityLogClick: () -> Unit, + notificationPermissionManager: NotificationPermissionManager ) { val state by viewModel.state.collectAsStateWithLifecycle() @@ -89,7 +91,8 @@ fun ReplyListScreenRoot( viewModel.onIntent(intent) }, onSettingsClick = onSettingsClick, - onActivityLogClick = onActivityLogClick + onActivityLogClick = onActivityLogClick, + notificationPermissionManager = notificationPermissionManager ) } @@ -99,7 +102,8 @@ fun ReplyListScreen( state: ReplyListState, onIntent: (ReplyListScreenIntent) -> Unit, onSettingsClick: () -> Unit, - onActivityLogClick: () -> Unit + onActivityLogClick: () -> Unit, + notificationPermissionManager: NotificationPermissionManager ) { val strings = LocalReplyRadarStrings.current @@ -213,7 +217,8 @@ fun ReplyListScreen( ReplyBottomSheet( sheetState = sheetState, onIntent = onIntent, - replyBottomSheetState = state.replyBottomSheetState + replyBottomSheetState = state.replyBottomSheetState, + notificationPermissionManager = notificationPermissionManager ) } } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt index 6d5f09e..c677921 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt @@ -6,6 +6,7 @@ import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.SheetState import androidx.compose.runtime.Composable import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyRoundedCorner +import com.rafaelfelipeac.replyradar.core.notification.NotificationPermissionManager import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnAddReply @@ -22,7 +23,8 @@ import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.compo fun ReplyBottomSheet( sheetState: SheetState, onIntent: (ReplyListScreenIntent) -> Unit, - replyBottomSheetState: ReplyBottomSheetState + replyBottomSheetState: ReplyBottomSheetState, + notificationPermissionManager: NotificationPermissionManager ) { ModalBottomSheet( sheetState = sheetState, @@ -36,7 +38,8 @@ fun ReplyBottomSheet( BottomSheetContent( state = ReplyBottomSheetState(CREATE), onComplete = { onIntent(OnAddReply(it)) }, - onIntent = onIntent + onIntent = onIntent, + notificationPermissionManager = notificationPermissionManager ) } @@ -48,7 +51,8 @@ fun ReplyBottomSheet( reply = replyBottomSheetState.reply ), onComplete = { onIntent(OnEditReply(it)) }, - onIntent = onIntent + onIntent = onIntent, + notificationPermissionManager = notificationPermissionManager ) } } @@ -60,13 +64,15 @@ fun ReplyBottomSheet( private fun BottomSheetContent( state: ReplyBottomSheetState, onComplete: (Reply) -> Unit, - onIntent: (ReplyListScreenIntent) -> Unit + onIntent: (ReplyListScreenIntent) -> Unit, + notificationPermissionManager: NotificationPermissionManager ) { ReplyBottomSheetContent( replyBottomSheetState = state, onComplete = onComplete, onResolve = { onIntent(OnToggleResolve(it)) }, onArchive = { onIntent(OnToggleArchive(it)) }, - onDelete = { onIntent(OnDeleteReply(it)) } + onDelete = { onIntent(OnDeleteReply(it)) }, + notificationPermissionManager = notificationPermissionManager ) } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt index 47ae0c8..cbe7caf 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt @@ -16,6 +16,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.CenterHorizontally @@ -42,7 +43,9 @@ import com.rafaelfelipeac.replyradar.core.util.format import com.rafaelfelipeac.replyradar.core.util.formatTimestamp import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyReminder +import com.rafaelfelipeac.replyradar.core.notification.NotificationPermissionManager import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetMode.EDIT +import kotlinx.coroutines.launch import kotlinx.datetime.DateTimeUnit.Companion.DAY import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate @@ -67,7 +70,8 @@ fun ReplyBottomSheetContent( onComplete: (Reply) -> Unit, onResolve: (Reply) -> Unit, onArchive: (Reply) -> Unit, - onDelete: (Reply) -> Unit + onDelete: (Reply) -> Unit, + notificationPermissionManager: NotificationPermissionManager ) { replyBottomSheetState?.let { state -> var name by remember { mutableStateOf(state.reply?.name ?: EMPTY) } @@ -177,6 +181,8 @@ fun ReplyBottomSheetContent( selectedTime = selectedTime ) + val coroutineScope = rememberCoroutineScope() + ReplyButton( modifier = Modifier .wrapContentWidth() @@ -187,22 +193,27 @@ fun ReplyBottomSheetContent( LocalReplyRadarStrings.current.replyListBottomSheetSave }, onClick = { - if (state.reply != null) { - onComplete( - state.reply.copy( - name = name, - subject = subject, - reminderAt = reminderAt - ) - ) - } else { - onComplete( - Reply( - name = name, - subject = subject, - reminderAt = reminderAt - ) - ) + coroutineScope.launch { + val granted = notificationPermissionManager.ensureNotificationPermission() + if (granted) { + val replyToSave = if (state.reply != null) { + state.reply.copy( + name = name, + subject = subject, + reminderAt = reminderAt + ) + } else { + Reply( + name = name, + subject = subject, + reminderAt = reminderAt + ) + } + + onComplete(replyToSave) + } else { + val x = "" + } } }, enabled = name.isNotBlank() diff --git a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/Main.kt b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/Main.kt index 182a09e..488b025 100644 --- a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/Main.kt +++ b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/Main.kt @@ -12,7 +12,7 @@ fun main() { onCloseRequest = ::exitApplication, title = "Reply Radar" ) { - ReplyRadarApp() + ReplyRadarApp(notificationPermissionManager = notificationPermissionManager) } } } diff --git a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/MainViewController.kt b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/MainViewController.kt index 306db96..9075086 100644 --- a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/MainViewController.kt +++ b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/MainViewController.kt @@ -9,4 +9,4 @@ fun MainViewController() = ComposeUIViewController( configure = { initKoin() } -) { ReplyRadarApp() } +) { ReplyRadarApp(notificationPermissionManager = notificationPermissionManager) } From ccadfa4117aeecfffd245f047764da54ef286fd4 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Tue, 6 May 2025 19:46:38 -0300 Subject: [PATCH 16/59] Dealing with NotificationPermissionManager. --- .../rafaelfelipeac/replyradar/AndroidApp.kt | 19 ++++++++++++------- .../com/rafaelfelipeac/replyradar/Previews.kt | 6 +----- .../rememberNotificationPermissionManager.kt | 19 +++++++++++++++++-- .../replyradar/app/ReplyRadarApp.kt | 7 ++----- .../replyradar/core/navigation/AppNavHost.kt | 7 ++----- .../LocalNotificationPermissionManager.kt | 7 +++++++ .../presentation/replylist/ReplyListScreen.kt | 12 ++++-------- .../replybottomsheet/ReplyBottomSheet.kt | 16 +++++----------- .../ReplyBottomSheetContent.kt | 12 +++++++----- 9 files changed, 57 insertions(+), 48 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/LocalNotificationPermissionManager.kt diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/AndroidApp.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/AndroidApp.kt index 7d086cd..d570530 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/AndroidApp.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/AndroidApp.kt @@ -1,6 +1,7 @@ package com.rafaelfelipeac.replyradar import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -9,6 +10,7 @@ import androidx.compose.ui.graphics.Color.Companion.Black import androidx.compose.ui.tooling.preview.Preview import com.rafaelfelipeac.replyradar.app.ReplyRadarApp import com.rafaelfelipeac.replyradar.core.ConfigureSystemBars +import com.rafaelfelipeac.replyradar.core.notification.LocalNotificationPermissionManager import rememberNotificationPermissionManager @Composable @@ -19,11 +21,14 @@ fun AndroidApp() { ConfigureSystemBars(darkTheme = isDark, backgroundColor = backgroundColor) - ReplyRadarApp( - onSystemBarsConfigured = { dark, bgColor -> - isDark = dark - backgroundColor = bgColor - }, - notificationPermissionManager = rememberNotificationPermissionManager() - ) + CompositionLocalProvider( + LocalNotificationPermissionManager provides rememberNotificationPermissionManager() + ) { + ReplyRadarApp( + onSystemBarsConfigured = { dark, bgColor -> + isDark = dark + backgroundColor = bgColor + } + ) + } } diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/Previews.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/Previews.kt index 0b8fd37..f2020a3 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/Previews.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/Previews.kt @@ -2,7 +2,6 @@ package com.rafaelfelipeac.replyradar import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview -import com.rafaelfelipeac.replyradar.core.notification.NotificationPermissionManager import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreen import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListState @@ -25,9 +24,6 @@ private fun ReplyListScreenPreview() { ), onIntent = {}, onSettingsClick = {}, - onActivityLogClick = {}, - notificationPermissionManager = object : NotificationPermissionManager { - override suspend fun ensureNotificationPermission() = false - } + onActivityLogClick = {} ) } diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt index 34ef58f..f483e80 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt @@ -1,6 +1,7 @@ package com.rafaelfelipeac.replyradar.core.notification import android.Manifest +import android.app.Activity import android.content.pm.PackageManager import android.os.Build import androidx.activity.compose.rememberLauncherForActivityResult @@ -10,6 +11,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.platform.LocalContext +import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -46,7 +48,6 @@ fun rememberNotificationPermissionManager(): NotificationPermissionManager { return true } - // Launch and suspend for result return suspendCancellableCoroutine { cont -> permissionResultState.value = null permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) @@ -58,6 +59,21 @@ fun rememberNotificationPermissionManager(): NotificationPermissionManager { .filterNotNull() .first() .let { result -> + if (!result) { + val activity = context as? Activity + val shouldShowRationale = + activity?.let { + ActivityCompat.shouldShowRequestPermissionRationale( + it, + Manifest.permission.POST_NOTIFICATIONS + ) + } ?: true + + if (!shouldShowRationale) { + + } + } + cont.resume(result) } } @@ -68,4 +84,3 @@ fun rememberNotificationPermissionManager(): NotificationPermissionManager { } } } - diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt index 0482014..705ec0b 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt @@ -17,15 +17,13 @@ import com.rafaelfelipeac.replyradar.core.common.ui.theme.ReplyRadarTheme import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme.DARK import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme.SYSTEM import com.rafaelfelipeac.replyradar.core.navigation.AppNavHost -import com.rafaelfelipeac.replyradar.core.notification.NotificationPermissionManager import com.rafaelfelipeac.replyradar.core.util.getClock import com.rafaelfelipeac.replyradar.features.app.settings.AppSettingsViewModel import org.koin.compose.viewmodel.koinViewModel @Composable fun ReplyRadarApp( - onSystemBarsConfigured: ((isDark: Boolean, backgroundColor: Color) -> Unit)? = null, - notificationPermissionManager: NotificationPermissionManager + onSystemBarsConfigured: ((isDark: Boolean, backgroundColor: Color) -> Unit)? = null ) { val navController = rememberNavController() val appSettingsViewModel = koinViewModel() @@ -49,8 +47,7 @@ fun ReplyRadarApp( ) { ReplyRadarTheme(darkTheme = isDark) { AppNavHost( - navController = navController, - notificationPermissionManager = notificationPermissionManager + navController = navController ) } } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/navigation/AppNavHost.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/navigation/AppNavHost.kt index bbebe86..f7ed921 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/navigation/AppNavHost.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/navigation/AppNavHost.kt @@ -13,7 +13,6 @@ import com.rafaelfelipeac.replyradar.core.navigation.Route.ActivityLog import com.rafaelfelipeac.replyradar.core.navigation.Route.ReplyGraph import com.rafaelfelipeac.replyradar.core.navigation.Route.ReplyList import com.rafaelfelipeac.replyradar.core.navigation.Route.Settings -import com.rafaelfelipeac.replyradar.core.notification.NotificationPermissionManager import com.rafaelfelipeac.replyradar.features.activitylog.presentation.ActivityLogScreen import com.rafaelfelipeac.replyradar.features.activitylog.presentation.ActivityLogViewModel import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenRoot @@ -24,8 +23,7 @@ import org.koin.compose.viewmodel.koinViewModel @Composable fun AppNavHost( - navController: NavHostController, - notificationPermissionManager: NotificationPermissionManager + navController: NavHostController ) { NavHost( navController = navController, @@ -43,8 +41,7 @@ fun AppNavHost( ReplyListScreenRoot( viewModel = viewModel, onSettingsClick = { navController.navigate(Settings) }, - onActivityLogClick = { navController.navigate(ActivityLog) }, - notificationPermissionManager = notificationPermissionManager + onActivityLogClick = { navController.navigate(ActivityLog) } ) } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/LocalNotificationPermissionManager.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/LocalNotificationPermissionManager.kt new file mode 100644 index 0000000..482fefe --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/LocalNotificationPermissionManager.kt @@ -0,0 +1,7 @@ +package com.rafaelfelipeac.replyradar.core.notification + +import androidx.compose.runtime.compositionLocalOf + +val LocalNotificationPermissionManager = compositionLocalOf { + error("No NotificationPermissionManager provided") +} diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt index 01fbbcd..917c415 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt @@ -80,8 +80,7 @@ private const val ARCHIVED_INDEX = 2 fun ReplyListScreenRoot( viewModel: ReplyListViewModel = koinViewModel(), onSettingsClick: () -> Unit, - onActivityLogClick: () -> Unit, - notificationPermissionManager: NotificationPermissionManager + onActivityLogClick: () -> Unit ) { val state by viewModel.state.collectAsStateWithLifecycle() @@ -91,8 +90,7 @@ fun ReplyListScreenRoot( viewModel.onIntent(intent) }, onSettingsClick = onSettingsClick, - onActivityLogClick = onActivityLogClick, - notificationPermissionManager = notificationPermissionManager + onActivityLogClick = onActivityLogClick ) } @@ -102,8 +100,7 @@ fun ReplyListScreen( state: ReplyListState, onIntent: (ReplyListScreenIntent) -> Unit, onSettingsClick: () -> Unit, - onActivityLogClick: () -> Unit, - notificationPermissionManager: NotificationPermissionManager + onActivityLogClick: () -> Unit ) { val strings = LocalReplyRadarStrings.current @@ -217,8 +214,7 @@ fun ReplyListScreen( ReplyBottomSheet( sheetState = sheetState, onIntent = onIntent, - replyBottomSheetState = state.replyBottomSheetState, - notificationPermissionManager = notificationPermissionManager + replyBottomSheetState = state.replyBottomSheetState ) } } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt index c677921..6d5f09e 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt @@ -6,7 +6,6 @@ import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.SheetState import androidx.compose.runtime.Composable import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyRoundedCorner -import com.rafaelfelipeac.replyradar.core.notification.NotificationPermissionManager import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnAddReply @@ -23,8 +22,7 @@ import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.compo fun ReplyBottomSheet( sheetState: SheetState, onIntent: (ReplyListScreenIntent) -> Unit, - replyBottomSheetState: ReplyBottomSheetState, - notificationPermissionManager: NotificationPermissionManager + replyBottomSheetState: ReplyBottomSheetState ) { ModalBottomSheet( sheetState = sheetState, @@ -38,8 +36,7 @@ fun ReplyBottomSheet( BottomSheetContent( state = ReplyBottomSheetState(CREATE), onComplete = { onIntent(OnAddReply(it)) }, - onIntent = onIntent, - notificationPermissionManager = notificationPermissionManager + onIntent = onIntent ) } @@ -51,8 +48,7 @@ fun ReplyBottomSheet( reply = replyBottomSheetState.reply ), onComplete = { onIntent(OnEditReply(it)) }, - onIntent = onIntent, - notificationPermissionManager = notificationPermissionManager + onIntent = onIntent ) } } @@ -64,15 +60,13 @@ fun ReplyBottomSheet( private fun BottomSheetContent( state: ReplyBottomSheetState, onComplete: (Reply) -> Unit, - onIntent: (ReplyListScreenIntent) -> Unit, - notificationPermissionManager: NotificationPermissionManager + onIntent: (ReplyListScreenIntent) -> Unit ) { ReplyBottomSheetContent( replyBottomSheetState = state, onComplete = onComplete, onResolve = { onIntent(OnToggleResolve(it)) }, onArchive = { onIntent(OnToggleArchive(it)) }, - onDelete = { onIntent(OnDeleteReply(it)) }, - notificationPermissionManager = notificationPermissionManager + onDelete = { onIntent(OnDeleteReply(it)) } ) } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt index cbe7caf..58437f7 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt @@ -35,15 +35,15 @@ import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyButton import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyConfirmationDialog import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyOutlinedButton +import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyReminder import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyTextField import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyTextFieldSize.Large import com.rafaelfelipeac.replyradar.core.common.ui.paddingMedium import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall +import com.rafaelfelipeac.replyradar.core.notification.LocalNotificationPermissionManager import com.rafaelfelipeac.replyradar.core.util.format import com.rafaelfelipeac.replyradar.core.util.formatTimestamp import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply -import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyReminder -import com.rafaelfelipeac.replyradar.core.notification.NotificationPermissionManager import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetMode.EDIT import kotlinx.coroutines.launch import kotlinx.datetime.DateTimeUnit.Companion.DAY @@ -70,8 +70,7 @@ fun ReplyBottomSheetContent( onComplete: (Reply) -> Unit, onResolve: (Reply) -> Unit, onArchive: (Reply) -> Unit, - onDelete: (Reply) -> Unit, - notificationPermissionManager: NotificationPermissionManager + onDelete: (Reply) -> Unit ) { replyBottomSheetState?.let { state -> var name by remember { mutableStateOf(state.reply?.name ?: EMPTY) } @@ -182,6 +181,7 @@ fun ReplyBottomSheetContent( ) val coroutineScope = rememberCoroutineScope() + val notificationPermissionManager = LocalNotificationPermissionManager.current ReplyButton( modifier = Modifier @@ -196,6 +196,8 @@ fun ReplyBottomSheetContent( coroutineScope.launch { val granted = notificationPermissionManager.ensureNotificationPermission() if (granted) { + val x = "Permission Granted" + val replyToSave = if (state.reply != null) { state.reply.copy( name = name, @@ -212,7 +214,7 @@ fun ReplyBottomSheetContent( onComplete(replyToSave) } else { - val x = "" + val x = "Permission denied" } } }, From 27c6f95638b1c872ae44f83749f2445d43683c4b Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Tue, 6 May 2025 19:57:39 -0300 Subject: [PATCH 17/59] Improvements on notificationPermissionManager. --- .../rafaelfelipeac/replyradar/AndroidApp.kt | 21 +++++++------------ .../replyradar/app/ReplyRadarApp.kt | 8 +++++-- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/AndroidApp.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/AndroidApp.kt index d570530..feb62bc 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/AndroidApp.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/AndroidApp.kt @@ -1,7 +1,6 @@ package com.rafaelfelipeac.replyradar import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -10,8 +9,7 @@ import androidx.compose.ui.graphics.Color.Companion.Black import androidx.compose.ui.tooling.preview.Preview import com.rafaelfelipeac.replyradar.app.ReplyRadarApp import com.rafaelfelipeac.replyradar.core.ConfigureSystemBars -import com.rafaelfelipeac.replyradar.core.notification.LocalNotificationPermissionManager -import rememberNotificationPermissionManager +import com.rafaelfelipeac.replyradar.core.notification.rememberNotificationPermissionManager @Composable @Preview @@ -21,14 +19,11 @@ fun AndroidApp() { ConfigureSystemBars(darkTheme = isDark, backgroundColor = backgroundColor) - CompositionLocalProvider( - LocalNotificationPermissionManager provides rememberNotificationPermissionManager() - ) { - ReplyRadarApp( - onSystemBarsConfigured = { dark, bgColor -> - isDark = dark - backgroundColor = bgColor - } - ) - } + ReplyRadarApp( + onSystemBarsConfigured = { dark, bgColor -> + isDark = dark + backgroundColor = bgColor + }, + notificationPermissionManager = rememberNotificationPermissionManager() + ) } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt index 705ec0b..b5c583e 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt @@ -17,13 +17,16 @@ import com.rafaelfelipeac.replyradar.core.common.ui.theme.ReplyRadarTheme import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme.DARK import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme.SYSTEM import com.rafaelfelipeac.replyradar.core.navigation.AppNavHost +import com.rafaelfelipeac.replyradar.core.notification.LocalNotificationPermissionManager +import com.rafaelfelipeac.replyradar.core.notification.NotificationPermissionManager import com.rafaelfelipeac.replyradar.core.util.getClock import com.rafaelfelipeac.replyradar.features.app.settings.AppSettingsViewModel import org.koin.compose.viewmodel.koinViewModel @Composable fun ReplyRadarApp( - onSystemBarsConfigured: ((isDark: Boolean, backgroundColor: Color) -> Unit)? = null + onSystemBarsConfigured: ((isDark: Boolean, backgroundColor: Color) -> Unit)? = null, + notificationPermissionManager: NotificationPermissionManager ) { val navController = rememberNavController() val appSettingsViewModel = koinViewModel() @@ -43,7 +46,8 @@ fun ReplyRadarApp( CompositionLocalProvider( LocalReplyRadarStrings provides strings, - LocalClock provides getClock() + LocalClock provides getClock(), + LocalNotificationPermissionManager provides notificationPermissionManager ) { ReplyRadarTheme(darkTheme = isDark) { AppNavHost( From 4e2d940952b5787c708f694c8615cef9060b9d87 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Tue, 6 May 2025 23:09:06 -0300 Subject: [PATCH 18/59] Improvements. --- .../replyradar/ReplyRadarApplication.kt | 19 ++++++++++--------- .../replyradar/app/ReplyRadarApp.kt | 4 +--- .../replyradar/core/common/strings/Strings.kt | 7 ------- .../core/common/strings/StringsEn.kt | 10 ---------- .../core/common/strings/StringsPt.kt | 9 --------- .../replyradar/core/common/ui/Dimens.kt | 4 ---- composeApp/src/main/res/values/strings.xml | 4 ++++ gradle/libs.versions.toml | 2 +- 8 files changed, 16 insertions(+), 43 deletions(-) diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/ReplyRadarApplication.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/ReplyRadarApplication.kt index ba00c36..92bcf30 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/ReplyRadarApplication.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/ReplyRadarApplication.kt @@ -3,6 +3,7 @@ package com.rafaelfelipeac.replyradar import android.app.Application import android.app.NotificationChannel import android.app.NotificationManager +import android.app.NotificationManager.IMPORTANCE_HIGH import android.os.Build import com.rafaelfelipeac.replyradar.di.initKoin import org.koin.android.ext.koin.androidContext @@ -17,20 +18,20 @@ class ReplyRadarApplication : Application() { androidContext(this@ReplyRadarApplication) } - createNotificationChannels() + createNotificationChannel() } - private fun createNotificationChannels() { + private fun createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val channelId = "reminder_channel" - val name = "Reminder Notifications" - val descriptionText = "Notifications for scheduled reminders" - val importance = NotificationManager.IMPORTANCE_HIGH - val channel = NotificationChannel(channelId, name, importance).apply { + val id = getString(R.string.notification_channel_id) + val name = getString(R.string.notification_channel_name) + val descriptionText = getString(R.string.notifications_channel_description) + val importance = IMPORTANCE_HIGH + val channel = NotificationChannel(id, name, importance).apply { description = descriptionText } - val notificationManager: NotificationManager = - getSystemService(NotificationManager::class.java) + val notificationManager = getSystemService(NotificationManager::class.java) + notificationManager.createNotificationChannel(channel) } } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt index b5c583e..1157e63 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt @@ -50,9 +50,7 @@ fun ReplyRadarApp( LocalNotificationPermissionManager provides notificationPermissionManager ) { ReplyRadarTheme(darkTheme = isDark) { - AppNavHost( - navController = navController - ) + AppNavHost(navController = navController) } } } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/Strings.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/Strings.kt index 7dfca19..5a94c06 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/Strings.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/Strings.kt @@ -3,15 +3,8 @@ package com.rafaelfelipeac.replyradar.core.common.strings interface Strings { val appName: String - val weekDaysShort: List - val months: List - val genericErrorMessage: String - val componentReplyTimeSelectorLabel: String - - val componentReplyDateSelectorLabel: String - val replyListActivityLog: String val replyListTabOnTheRadar: String val replyListTabResolved: String diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsEn.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsEn.kt index 0aa95d3..d19bfc1 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsEn.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsEn.kt @@ -5,18 +5,8 @@ import com.rafaelfelipeac.replyradar.core.util.getAppVersion object StringsEn : Strings { override val appName = "Reply Radar" - override val weekDaysShort = listOf("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun") - override val months = listOf( - "January", "February", "March", "April", "May", "June", - "July", "August", "September", "October", "November", "December" - ) - override val genericErrorMessage = "An unexpected error occurred." - override val componentReplyTimeSelectorLabel = "Set the time:" - - override val componentReplyDateSelectorLabel = "Set the day:" - override val replyListActivityLog = "Activity Log" override val replyListTabOnTheRadar = "On the Radar" override val replyListTabResolved = "Resolved" diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsPt.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsPt.kt index 4a084d3..9848c2e 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsPt.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsPt.kt @@ -5,18 +5,9 @@ import com.rafaelfelipeac.replyradar.core.util.getAppVersion object StringsPt : Strings { override val appName = "Reply Radar" - override val weekDaysShort = listOf("Seg", "Ter", "Qua", "Qui", "Sex", "Sáb", "Dom") - override val months = listOf( - "Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", - "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro" - ) override val genericErrorMessage = "Ocorreu um erro inesperado." - override val componentReplyTimeSelectorLabel = "Definir horário:" - - override val componentReplyDateSelectorLabel = "Definir dia:" - override val replyListActivityLog = "Atividades" override val replyListTabOnTheRadar = "No Radar" override val replyListTabResolved = "Resolvidos" diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/Dimens.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/Dimens.kt index 54f85d1..f410726 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/Dimens.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/Dimens.kt @@ -43,7 +43,3 @@ val settingsAppVersionOffset = 64.dp val radioButtonSize = 24.dp val dialogElevation = 8.dp - -val replyDateSelectorMinTonalElevation = 1.dp -val replyDateSelectorMaxTonalElevation = 4.dp -val replyDateItemWidth = 12.dp diff --git a/composeApp/src/main/res/values/strings.xml b/composeApp/src/main/res/values/strings.xml index 549dea4..6c24c38 100644 --- a/composeApp/src/main/res/values/strings.xml +++ b/composeApp/src/main/res/values/strings.xml @@ -1,4 +1,8 @@ No email app found. + + reminder_channel + Reminder Notifications + Notifications for scheduled reminders \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d720d65..fdfe0d4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,6 +33,7 @@ androidx-activity-compose = { module = "androidx.activity:activity-compose", ver androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" } androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } androidx-ui-android = { group = "androidx.compose.ui", name = "ui-android", version.ref = "uiAndroid" } +androidx-work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "workRuntimeKtx" } accompanist-systemuicontroller = { group = "com.google.accompanist", name = "accompanist-systemuicontroller", version.ref = "accompanist" } jetbrains-compose-navigation = { group = "org.jetbrains.androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" } @@ -70,7 +71,6 @@ datastore-preferences = { module = "androidx.datastore:datastore-preferences", v assertk = { module = "com.willowtreeapps.assertk:assertk", version.ref = "assertk" } turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" } -androidx-work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "workRuntimeKtx" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } From 25bc3098d7b61b19a24e6221685f352a853e4498 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Tue, 6 May 2025 23:31:13 -0300 Subject: [PATCH 19/59] Improvements. --- .../replyradar/ReplyRadarApplication.kt | 9 ++++++--- .../core/util/reminder/NotificationUtils.kt | 15 ++++++++++----- .../core/util/reminder/ReminderSchedulerImpl.kt | 16 +++++++++++----- .../core/util/reminder/ReminderWorker.kt | 6 ++++-- .../replyradar/core/common/strings/StringsPt.kt | 1 - .../replyradar/core/navigation/AppNavHost.kt | 4 +--- composeApp/src/main/res/values/strings.xml | 5 +++++ 7 files changed, 37 insertions(+), 19 deletions(-) diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/ReplyRadarApplication.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/ReplyRadarApplication.kt index 92bcf30..5bc7794 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/ReplyRadarApplication.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/ReplyRadarApplication.kt @@ -5,6 +5,9 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.app.NotificationManager.IMPORTANCE_HIGH import android.os.Build +import com.rafaelfelipeac.replyradar.R.string.notification_channel_id +import com.rafaelfelipeac.replyradar.R.string.notification_channel_name +import com.rafaelfelipeac.replyradar.R.string.notifications_channel_description import com.rafaelfelipeac.replyradar.di.initKoin import org.koin.android.ext.koin.androidContext @@ -23,9 +26,9 @@ class ReplyRadarApplication : Application() { private fun createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val id = getString(R.string.notification_channel_id) - val name = getString(R.string.notification_channel_name) - val descriptionText = getString(R.string.notifications_channel_description) + val id = getString(notification_channel_id) + val name = getString(notification_channel_name) + val descriptionText = getString(notifications_channel_description) val importance = IMPORTANCE_HIGH val channel = NotificationChannel(id, name, importance).apply { description = descriptionText diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/NotificationUtils.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/NotificationUtils.kt index e326e56..d6b7301 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/NotificationUtils.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/NotificationUtils.kt @@ -1,16 +1,20 @@ package com.rafaelfelipeac.replyradar.core.util.reminder -import android.Manifest +import android.Manifest.permission.POST_NOTIFICATIONS import android.content.Context -import android.content.pm.PackageManager +import android.content.pm.PackageManager.PERMISSION_GRANTED import androidx.core.app.ActivityCompat import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import com.rafaelfelipeac.replyradar.R +import com.rafaelfelipeac.replyradar.R.string.notification_channel_id object NotificationUtils { fun showReminderNotification(context: Context, name: String, subject: String) { - val notification = NotificationCompat.Builder(context, "reminder_channel") + val notification = NotificationCompat.Builder( + context, + context.getString(notification_channel_id) + ) .setSmallIcon(R.drawable.ic_launcher_background) .setContentTitle("Reminder: $name") .setContentText(subject) @@ -19,11 +23,12 @@ object NotificationUtils { if (ActivityCompat.checkSelfPermission( context, - Manifest.permission.POST_NOTIFICATIONS - ) != PackageManager.PERMISSION_GRANTED + POST_NOTIFICATIONS + ) != PERMISSION_GRANTED ) { return } + NotificationManagerCompat.from(context).notify(name.hashCode(), notification) } } diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderSchedulerImpl.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderSchedulerImpl.kt index 236a7dd..5f2d9ba 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderSchedulerImpl.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderSchedulerImpl.kt @@ -4,6 +4,10 @@ import android.content.Context import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkManager import androidx.work.workDataOf +import com.rafaelfelipeac.replyradar.R.string.reminder_name +import com.rafaelfelipeac.replyradar.R.string.reminder_reply_id +import com.rafaelfelipeac.replyradar.R.string.reminder_subject +import com.rafaelfelipeac.replyradar.R.string.reminder_tag import java.util.concurrent.TimeUnit class ReminderSchedulerImpl( @@ -27,18 +31,20 @@ class ReminderSchedulerImpl( .setInitialDelay(delay, TimeUnit.MILLISECONDS) .setInputData( workDataOf( - "name" to name, - "subject" to subject, - "replyId" to replyId.toString() + context.getString(reminder_name) to name, + context.getString(reminder_subject) to subject, + context.getString(reminder_reply_id) to replyId.toString() ) ) - .addTag("reminder-$replyId") + .addTag(getTag(replyId)) .build() WorkManager.getInstance(context).enqueue(workRequest) } override fun cancelReminder(replyId: Long) { - WorkManager.getInstance(context).cancelAllWorkByTag("reminder-$replyId") + WorkManager.getInstance(context).cancelAllWorkByTag(getTag(replyId)) } + + private fun getTag(replyId: Long) = context.getString(reminder_tag, replyId) } diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderWorker.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderWorker.kt index 6960671..bfa130d 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderWorker.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderWorker.kt @@ -3,6 +3,8 @@ package com.rafaelfelipeac.replyradar.core.util.reminder import android.content.Context import androidx.work.Worker import androidx.work.WorkerParameters +import com.rafaelfelipeac.replyradar.R.string.reminder_name +import com.rafaelfelipeac.replyradar.R.string.reminder_subject class ReminderWorker( appContext: Context, @@ -10,8 +12,8 @@ class ReminderWorker( ) : Worker(appContext, workerParams) { override fun doWork(): Result { - val name = inputData.getString("name") ?: return Result.failure() - val subject = inputData.getString("subject") ?: return Result.failure() + val name = inputData.getString(applicationContext.getString(reminder_name)) ?: return Result.failure() + val subject = inputData.getString(applicationContext.getString(reminder_subject)) ?: return Result.failure() NotificationUtils.showReminderNotification(applicationContext, name, subject) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsPt.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsPt.kt index 9848c2e..6557c42 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsPt.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsPt.kt @@ -5,7 +5,6 @@ import com.rafaelfelipeac.replyradar.core.util.getAppVersion object StringsPt : Strings { override val appName = "Reply Radar" - override val genericErrorMessage = "Ocorreu um erro inesperado." override val replyListActivityLog = "Atividades" diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/navigation/AppNavHost.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/navigation/AppNavHost.kt index f7ed921..419c580 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/navigation/AppNavHost.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/navigation/AppNavHost.kt @@ -22,9 +22,7 @@ import com.rafaelfelipeac.replyradar.features.settings.presentation.SettingsView import org.koin.compose.viewmodel.koinViewModel @Composable -fun AppNavHost( - navController: NavHostController -) { +fun AppNavHost(navController: NavHostController) { NavHost( navController = navController, startDestination = ReplyGraph diff --git a/composeApp/src/main/res/values/strings.xml b/composeApp/src/main/res/values/strings.xml index 6c24c38..105396a 100644 --- a/composeApp/src/main/res/values/strings.xml +++ b/composeApp/src/main/res/values/strings.xml @@ -5,4 +5,9 @@ reminder_channel Reminder Notifications Notifications for scheduled reminders + + name + subject + replyId + reminder-%1$d \ No newline at end of file From 1e2d9e801fe975486afe94b0c453d3a4146274da Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Thu, 8 May 2025 20:32:30 -0300 Subject: [PATCH 20/59] Improvements. --- .../rememberNotificationPermissionManager.kt | 12 ++++++------ .../core/util/PlatformTimePicker.android.kt | 7 +++++-- .../core/util/reminder/NotificationUtils.kt | 6 ++++-- .../core/util/reminder/ReminderSchedulerImpl.kt | 4 +++- .../replyradar/core/AppConstants.kt | 2 +- .../replyradar/core/util/datetime/Datetime.kt | 11 +++++++++-- .../reply/data/database/entity/ReplyEntity.kt | 12 ++++++------ .../reply/data/repository/ReplyRepositoryImpl.kt | 8 ++++---- .../features/reply/domain/model/Reply.kt | 12 +++++++----- .../presentation/replylist/ReplyListViewModel.kt | 3 ++- .../replybottomsheet/ReplyBottomSheetContent.kt | 15 ++++++++------- .../kotlin/com/rafaelfelipeac/replyradar/Main.kt | 9 ++++++++- .../replyradar/MainViewController.kt | 11 ++++++++++- composeApp/src/main/res/values/strings.xml | 3 ++- 14 files changed, 75 insertions(+), 40 deletions(-) diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt index f483e80..2bfd211 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt @@ -1,8 +1,8 @@ package com.rafaelfelipeac.replyradar.core.notification -import android.Manifest +import android.Manifest.permission.POST_NOTIFICATIONS import android.app.Activity -import android.content.pm.PackageManager +import android.content.pm.PackageManager.PERMISSION_GRANTED import android.os.Build import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts @@ -41,8 +41,8 @@ fun rememberNotificationPermissionManager(): NotificationPermissionManager { val granted = ContextCompat.checkSelfPermission( context, - Manifest.permission.POST_NOTIFICATIONS - ) == PackageManager.PERMISSION_GRANTED + POST_NOTIFICATIONS + ) == PERMISSION_GRANTED if (granted) { return true @@ -50,7 +50,7 @@ fun rememberNotificationPermissionManager(): NotificationPermissionManager { return suspendCancellableCoroutine { cont -> permissionResultState.value = null - permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + permissionLauncher.launch(POST_NOTIFICATIONS) val scope = CoroutineScope(Dispatchers.Main.immediate) @@ -65,7 +65,7 @@ fun rememberNotificationPermissionManager(): NotificationPermissionManager { activity?.let { ActivityCompat.shouldShowRequestPermissionRationale( it, - Manifest.permission.POST_NOTIFICATIONS + POST_NOTIFICATIONS ) } ?: true diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt index d29f3d8..1daa425 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt @@ -15,6 +15,9 @@ import com.rafaelfelipeac.replyradar.core.util.datetime.isTimeValid import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalTime +private const val HOUR_DEFAULT_INITIAL_HOUR = 12 +private const val HOUR_DEFAULT_INITIAL_MINUTE = 12 + @OptIn(ExperimentalMaterial3Api::class) @Composable actual fun PlatformTimePicker( @@ -24,8 +27,8 @@ actual fun PlatformTimePicker( onDismiss: () -> Unit ) { val timePickerState = rememberTimePickerState( - initialHour = selectedTime?.hour ?: 12, - initialMinute = selectedTime?.minute ?: 0 + initialHour = selectedTime?.hour ?: HOUR_DEFAULT_INITIAL_HOUR, + initialMinute = selectedTime?.minute ?: HOUR_DEFAULT_INITIAL_MINUTE ) var showDialog by remember { mutableStateOf(true) } diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/NotificationUtils.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/NotificationUtils.kt index d6b7301..a8b7048 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/NotificationUtils.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/NotificationUtils.kt @@ -5,9 +5,11 @@ import android.content.Context import android.content.pm.PackageManager.PERMISSION_GRANTED import androidx.core.app.ActivityCompat import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationCompat.PRIORITY_HIGH import androidx.core.app.NotificationManagerCompat import com.rafaelfelipeac.replyradar.R import com.rafaelfelipeac.replyradar.R.string.notification_channel_id +import com.rafaelfelipeac.replyradar.R.string.notification_content_reminder_title object NotificationUtils { fun showReminderNotification(context: Context, name: String, subject: String) { @@ -16,9 +18,9 @@ object NotificationUtils { context.getString(notification_channel_id) ) .setSmallIcon(R.drawable.ic_launcher_background) - .setContentTitle("Reminder: $name") + .setContentTitle(context.getString(notification_content_reminder_title, name)) .setContentText(subject) - .setPriority(NotificationCompat.PRIORITY_HIGH) + .setPriority(PRIORITY_HIGH) .build() if (ActivityCompat.checkSelfPermission( diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderSchedulerImpl.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderSchedulerImpl.kt index 5f2d9ba..29cee13 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderSchedulerImpl.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderSchedulerImpl.kt @@ -10,6 +10,8 @@ import com.rafaelfelipeac.replyradar.R.string.reminder_subject import com.rafaelfelipeac.replyradar.R.string.reminder_tag import java.util.concurrent.TimeUnit +private const val INVALID_DELAY = 0 + class ReminderSchedulerImpl( private val context: Context ) : ReminderScheduler { @@ -21,7 +23,7 @@ class ReminderSchedulerImpl( replyId: Long ) { val delay = reminderAtMillis - System.currentTimeMillis() - if (delay <= 0) return + if (delay <= INVALID_DELAY) return enqueueReminder(delay, name, subject, replyId) } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/AppConstants.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/AppConstants.kt index d2c9d58..2785370 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/AppConstants.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/AppConstants.kt @@ -5,7 +5,7 @@ object AppConstants { const val DB_NAME = "replyradar.db" const val PACKAGE_NAME = "com.rafaelfelipeac.replyradar" const val EMAIL = "rafaelfelipeac@gmail.com" - const val INITIAL_DATE_LONG = 0L + const val INITIAL_DATE = 0L const val REMINDER_DEFAULT_HOUR = 8 const val REMINDER_DEFAULT_MINUTE = 0 const val REMINDER_TOMORROW_OFFSET = 1 diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt index 35deeda..d6ffc33 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt @@ -14,6 +14,12 @@ import kotlinx.datetime.LocalTime import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime +const val LOCAL_TIME_HOUR_DEFAULT = 24 +const val LOCAL_TIME_MINUTE_DEFAULT = 0 +const val HOUR_OFFSET = 1 +const val HOUR_OFFSET_DEFAULT = 0 +const val MINUTE_EMPTY = 0 + fun isDateTimeValid(date: LocalDate?, time: LocalTime, now: LocalDateTime): Boolean { return when { date == null -> { @@ -53,8 +59,9 @@ fun getDefaultTime(selectedDate: LocalDate?, selectedTime: LocalTime?): LocalTim return eightAM } - val nextHour = now.hour + if (now.minute > 0) 1 else 0 - return LocalTime(hour = nextHour % 24, minute = 0) + val nextHour = now.hour + if (now.minute > MINUTE_EMPTY) HOUR_OFFSET else HOUR_OFFSET_DEFAULT + + return LocalTime(hour = nextHour % LOCAL_TIME_HOUR_DEFAULT, minute = LOCAL_TIME_MINUTE_DEFAULT) } @Composable diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/database/entity/ReplyEntity.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/database/entity/ReplyEntity.kt index d1ec1f5..477b5cf 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/database/entity/ReplyEntity.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/database/entity/ReplyEntity.kt @@ -2,7 +2,7 @@ package com.rafaelfelipeac.replyradar.features.reply.data.database.entity import androidx.room.Entity import androidx.room.PrimaryKey -import com.rafaelfelipeac.replyradar.core.AppConstants.INITIAL_DATE_LONG +import com.rafaelfelipeac.replyradar.core.AppConstants.INITIAL_DATE @Entity(tableName = "replies") data class ReplyEntity( @@ -11,9 +11,9 @@ data class ReplyEntity( val subject: String, val isResolved: Boolean = false, val isArchived: Boolean = false, - val createdAt: Long = INITIAL_DATE_LONG, - val updatedAt: Long = INITIAL_DATE_LONG, - val resolvedAt: Long = INITIAL_DATE_LONG, - val archivedAt: Long = INITIAL_DATE_LONG, - val reminderAt: Long = INITIAL_DATE_LONG + val createdAt: Long = INITIAL_DATE, + val updatedAt: Long = INITIAL_DATE, + val resolvedAt: Long = INITIAL_DATE, + val archivedAt: Long = INITIAL_DATE, + val reminderAt: Long = INITIAL_DATE ) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/repository/ReplyRepositoryImpl.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/repository/ReplyRepositoryImpl.kt index c499e4c..f4e3d37 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/repository/ReplyRepositoryImpl.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/repository/ReplyRepositoryImpl.kt @@ -1,6 +1,6 @@ package com.rafaelfelipeac.replyradar.features.reply.data.repository -import com.rafaelfelipeac.replyradar.core.AppConstants.INITIAL_DATE_LONG +import com.rafaelfelipeac.replyradar.core.AppConstants.INITIAL_DATE import com.rafaelfelipeac.replyradar.core.util.Clock import com.rafaelfelipeac.replyradar.features.reply.data.database.dao.ReplyDao import com.rafaelfelipeac.replyradar.features.reply.data.mapper.toReply @@ -19,7 +19,7 @@ class ReplyRepositoryImpl( val now = clock.now() val replyEntity = reply.toReplyEntity() - val entityToSave = if (reply.id == INITIAL_DATE_LONG) { + val entityToSave = if (reply.id == INITIAL_DATE) { replyEntity.copy(createdAt = now, updatedAt = now) } else { replyEntity.copy(updatedAt = now) @@ -31,7 +31,7 @@ class ReplyRepositoryImpl( override suspend fun toggleReplyResolve(reply: Reply) { replyDao.update( reply.toReplyEntity().copy( - resolvedAt = if (!reply.isResolved) clock.now() else INITIAL_DATE_LONG, + resolvedAt = if (!reply.isResolved) clock.now() else INITIAL_DATE, isResolved = !reply.isResolved ) ) @@ -40,7 +40,7 @@ class ReplyRepositoryImpl( override suspend fun toggleReplyArchive(reply: Reply) { replyDao.update( reply.toReplyEntity().copy( - archivedAt = if (!reply.isArchived) clock.now() else INITIAL_DATE_LONG, + archivedAt = if (!reply.isArchived) clock.now() else INITIAL_DATE, isArchived = !reply.isArchived ) ) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/domain/model/Reply.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/domain/model/Reply.kt index 82a7e0c..b1335b2 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/domain/model/Reply.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/domain/model/Reply.kt @@ -1,14 +1,16 @@ package com.rafaelfelipeac.replyradar.features.reply.domain.model +import com.rafaelfelipeac.replyradar.core.AppConstants.INITIAL_DATE + data class Reply( val id: Long = 0, val name: String, val subject: String, val isResolved: Boolean = false, val isArchived: Boolean = false, - val createdAt: Long = 0, - val updatedAt: Long = 0, - val resolvedAt: Long = 0, - val archivedAt: Long = 0, - val reminderAt: Long = 0 + val createdAt: Long = INITIAL_DATE, + val updatedAt: Long = INITIAL_DATE, + val resolvedAt: Long = INITIAL_DATE, + val archivedAt: Long = INITIAL_DATE, + val reminderAt: Long = INITIAL_DATE ) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt index 571594c..6697591 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt @@ -2,6 +2,7 @@ package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.rafaelfelipeac.replyradar.core.AppConstants.INITIAL_DATE import com.rafaelfelipeac.replyradar.core.util.reminder.ReminderScheduler import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.domain.usecase.DeleteReplyUseCase @@ -188,7 +189,7 @@ class ReplyListViewModel( logUserAction(actionType = actionType, targetId = replyId) - if (reply.reminderAt != 0L) { + if (reply.reminderAt != INITIAL_DATE) { reminderScheduler.scheduleReminder( reminderAtMillis = reply.reminderAt, name = reply.name, diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt index 58437f7..48870b7 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt @@ -26,7 +26,7 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.platform.LocalSoftwareKeyboardController import com.rafaelfelipeac.replyradar.core.AppConstants.EMPTY -import com.rafaelfelipeac.replyradar.core.AppConstants.INITIAL_DATE_LONG +import com.rafaelfelipeac.replyradar.core.AppConstants.INITIAL_DATE import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_DEFAULT_HOUR import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_DEFAULT_MINUTE import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_TOMORROW_OFFSET @@ -76,7 +76,7 @@ fun ReplyBottomSheetContent( var name by remember { mutableStateOf(state.reply?.name ?: EMPTY) } var subject by remember { mutableStateOf(state.reply?.subject ?: EMPTY) } val reminderAt = state.reply?.reminderAt - ?.takeIf { it != 0L } + ?.takeIf { it != INITIAL_DATE } ?.let { millis -> Instant.fromEpochMilliseconds(millis) .toLocalDateTime(TimeZone.currentSystemDefault()) @@ -194,7 +194,8 @@ fun ReplyBottomSheetContent( }, onClick = { coroutineScope.launch { - val granted = notificationPermissionManager.ensureNotificationPermission() + val granted = + notificationPermissionManager.ensureNotificationPermission() if (granted) { val x = "Permission Granted" @@ -304,18 +305,18 @@ private fun ArchivedStateButton( private fun getTimestamp(reply: Reply): String { with(reply) { return when { - archivedAt != INITIAL_DATE_LONG -> format( + archivedAt != INITIAL_DATE -> format( LocalReplyRadarStrings.current.replyListItemArchivedAt, formatTimestamp(archivedAt) ) - resolvedAt != INITIAL_DATE_LONG -> format( + resolvedAt != INITIAL_DATE -> format( LocalReplyRadarStrings.current.replyListItemResolvedAt, formatTimestamp(resolvedAt) ) else -> { - if (updatedAt != INITIAL_DATE_LONG && updatedAt != createdAt) { + if (updatedAt != INITIAL_DATE && updatedAt != createdAt) { format( LocalReplyRadarStrings.current.replyListItemUpdatedAt, formatTimestamp(updatedAt) @@ -358,7 +359,7 @@ fun getReminderTimestamp( } } - else -> return INITIAL_DATE_LONG + else -> return INITIAL_DATE } val finalTime = selectedTime ?: LocalTime(REMINDER_DEFAULT_HOUR, REMINDER_DEFAULT_MINUTE) diff --git a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/Main.kt b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/Main.kt index 488b025..59b72ae 100644 --- a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/Main.kt +++ b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/Main.kt @@ -3,6 +3,7 @@ package com.rafaelfelipeac.replyradar import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import com.rafaelfelipeac.replyradar.app.ReplyRadarApp +import com.rafaelfelipeac.replyradar.core.notification.NotificationPermissionManager import com.rafaelfelipeac.replyradar.di.initKoin fun main() { @@ -12,7 +13,13 @@ fun main() { onCloseRequest = ::exitApplication, title = "Reply Radar" ) { - ReplyRadarApp(notificationPermissionManager = notificationPermissionManager) + ReplyRadarApp( + notificationPermissionManager = object : NotificationPermissionManager { + override suspend fun ensureNotificationPermission(): Boolean { + TODO("Not yet implemented for this platform.") + } + } + ) } } } diff --git a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/MainViewController.kt b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/MainViewController.kt index 9075086..cf06585 100644 --- a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/MainViewController.kt +++ b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/MainViewController.kt @@ -2,6 +2,7 @@ package com.rafaelfelipeac.replyradar import androidx.compose.ui.window.ComposeUIViewController import com.rafaelfelipeac.replyradar.app.ReplyRadarApp +import com.rafaelfelipeac.replyradar.core.notification.NotificationPermissionManager import com.rafaelfelipeac.replyradar.di.initKoin @Suppress("FunctionNaming") @@ -9,4 +10,12 @@ fun MainViewController() = ComposeUIViewController( configure = { initKoin() } -) { ReplyRadarApp(notificationPermissionManager = notificationPermissionManager) } +) { + ReplyRadarApp( + notificationPermissionManager = object : NotificationPermissionManager { + override suspend fun ensureNotificationPermission(): Boolean { + TODO("Not yet implemented for this platform.") + } + } + ) +} diff --git a/composeApp/src/main/res/values/strings.xml b/composeApp/src/main/res/values/strings.xml index 105396a..4c03390 100644 --- a/composeApp/src/main/res/values/strings.xml +++ b/composeApp/src/main/res/values/strings.xml @@ -4,7 +4,8 @@ reminder_channel Reminder Notifications - Notifications for scheduled reminders + Notifications for scheduled reminders + Reminder: %1$s name subject From 35bb40f39207648f62ac3be09d7d5af062e0663a Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Tue, 13 May 2025 19:13:28 -0300 Subject: [PATCH 21/59] Added translated strings to Time and Date pickers. --- .../replyradar/ReplyRadarApplication.kt | 4 ++-- .../core/util/PlatformDatePicker.android.kt | 8 +++++--- .../core/util/PlatformTimePicker.android.kt | 11 +++++++---- .../replyradar/core/common/strings/Strings.kt | 5 +++++ .../replyradar/core/common/strings/StringsEn.kt | 5 +++++ .../replyradar/core/common/strings/StringsPt.kt | 5 +++++ .../core/common/ui/components/ReplyReminder.kt | 9 +++++++-- .../replyradar/core/util/PlatformDatePicker.kt | 4 +++- .../replyradar/core/util/PlatformTimePicker.kt | 5 ++++- .../core/util/PlatformDatePicker.desktop.kt | 4 +++- .../core/util/PlatformTimePicker.desktop.kt | 5 ++++- .../replyradar/core/util/PlatformDatePicker.ios.kt | 4 +++- .../replyradar/core/util/PlatformTimePicker.ios.kt | 5 ++++- 13 files changed, 57 insertions(+), 17 deletions(-) diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/ReplyRadarApplication.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/ReplyRadarApplication.kt index 5bc7794..c40ba54 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/ReplyRadarApplication.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/ReplyRadarApplication.kt @@ -7,7 +7,7 @@ import android.app.NotificationManager.IMPORTANCE_HIGH import android.os.Build import com.rafaelfelipeac.replyradar.R.string.notification_channel_id import com.rafaelfelipeac.replyradar.R.string.notification_channel_name -import com.rafaelfelipeac.replyradar.R.string.notifications_channel_description +import com.rafaelfelipeac.replyradar.R.string.notification_channel_description import com.rafaelfelipeac.replyradar.di.initKoin import org.koin.android.ext.koin.androidContext @@ -28,7 +28,7 @@ class ReplyRadarApplication : Application() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val id = getString(notification_channel_id) val name = getString(notification_channel_name) - val descriptionText = getString(notifications_channel_description) + val descriptionText = getString(notification_channel_description) val importance = IMPORTANCE_HIGH val channel = NotificationChannel(id, name, importance).apply { description = descriptionText diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt index 28eea25..982a803 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt @@ -30,7 +30,9 @@ actual fun PlatformDatePicker( selectedTime: LocalTime?, onDateSelected: (LocalDate) -> Unit, onTimeInvalidated: () -> Unit, - onDismiss: () -> Unit + onDismiss: () -> Unit, + confirmButtonText: String, + dismissButtonText: String ) { val now = Clock.System.now() .toLocalDateTime(TimeZone.currentSystemDefault()) @@ -80,7 +82,7 @@ actual fun PlatformDatePicker( showDialog = false } ) { - Text("OK") + Text(confirmButtonText) } }, dismissButton = { @@ -90,7 +92,7 @@ actual fun PlatformDatePicker( showDialog = false } ) { - Text("Cancelar") + Text(dismissButtonText) } } ) { diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt index 1daa425..74849a9 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt @@ -24,7 +24,10 @@ actual fun PlatformTimePicker( selectedTime: LocalTime?, selectedDate: LocalDate?, onTimeSelected: (LocalTime) -> Unit, - onDismiss: () -> Unit + onDismiss: () -> Unit, + confirmButtonText: String, + dismissButtonText: String, + pickerTimeTitle: String ) { val timePickerState = rememberTimePickerState( initialHour = selectedTime?.hour ?: HOUR_DEFAULT_INITIAL_HOUR, @@ -53,7 +56,7 @@ actual fun PlatformTimePicker( }, enabled = isValid ) { - Text("OK") + Text(confirmButtonText) } }, dismissButton = { @@ -61,10 +64,10 @@ actual fun PlatformTimePicker( onDismiss() showDialog = false }) { - Text("Cancelar") + Text(dismissButtonText) } }, - title = { Text("Selecionar horário") }, + title = { Text(pickerTimeTitle) }, text = { TimePicker(state = timePickerState) } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/Strings.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/Strings.kt index 5a94c06..fe4f1e8 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/Strings.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/Strings.kt @@ -44,6 +44,11 @@ interface Strings { val replyListReminderTimeIconContentDescription: String val replyListReminderDateIconContentDescription: String val replyListReminderCloseIconContentDescription: String + val replyListReminderTimePickerTitle: String + val replyListReminderTimePickerConfirmButton: String + val replyListReminderTimePickerDismissButton: String + val replyListReminderDatePickerConfirmButton: String + val replyListReminderDatePickerDismissButton: String val settingsTitle: String val settingsBackButton: String diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsEn.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsEn.kt index d19bfc1..053652d 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsEn.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsEn.kt @@ -49,6 +49,11 @@ object StringsEn : Strings { override val replyListReminderTimeIconContentDescription = "Time" override val replyListReminderDateIconContentDescription = "Date" override val replyListReminderCloseIconContentDescription = "Close" + override val replyListReminderTimePickerTitle = "Select time" + override val replyListReminderTimePickerConfirmButton = "OK" + override val replyListReminderTimePickerDismissButton = "Cancel" + override val replyListReminderDatePickerConfirmButton = "OK" + override val replyListReminderDatePickerDismissButton = "Cancel" override val settingsTitle = "Settings" override val settingsBackButton = "Back" diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsPt.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsPt.kt index 6557c42..630b24c 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsPt.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsPt.kt @@ -49,6 +49,11 @@ object StringsPt : Strings { override val replyListReminderTimeIconContentDescription = "Horário" override val replyListReminderDateIconContentDescription = "Data" override val replyListReminderCloseIconContentDescription = "Remover" + override val replyListReminderTimePickerTitle = "Selecionar horário" + override val replyListReminderTimePickerConfirmButton = "OK" + override val replyListReminderTimePickerDismissButton = "Cancelar" + override val replyListReminderDatePickerConfirmButton = "OK" + override val replyListReminderDatePickerDismissButton = "Cancelar" override val settingsTitle = "Configurações" override val settingsBackButton = "Voltar" diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt index 74cc88e..746581d 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt @@ -81,7 +81,10 @@ fun ReplyReminder( }, onDismiss = { showTimePicker = false - } + }, + confirmButtonText = LocalReplyRadarStrings.current.replyListReminderTimePickerConfirmButton, + dismissButtonText = LocalReplyRadarStrings.current.replyListReminderTimePickerDismissButton, + pickerTimeTitle = LocalReplyRadarStrings.current.replyListReminderTimePickerTitle, ) } @@ -98,7 +101,9 @@ fun ReplyReminder( }, onDismiss = { showDatePicker = false - } + }, + confirmButtonText = LocalReplyRadarStrings.current.replyListReminderDatePickerConfirmButton, + dismissButtonText = LocalReplyRadarStrings.current.replyListReminderDatePickerDismissButton, ) } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.kt index bf06b7f..1997424 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.kt @@ -10,5 +10,7 @@ expect fun PlatformDatePicker( selectedTime: LocalTime?, onDateSelected: (LocalDate) -> Unit, onTimeInvalidated: () -> Unit, - onDismiss: () -> Unit + onDismiss: () -> Unit, + confirmButtonText: String, + dismissButtonText: String ) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.kt index 1862411..77056ba 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.kt @@ -9,5 +9,8 @@ expect fun PlatformTimePicker( selectedTime: LocalTime?, selectedDate: LocalDate?, onTimeSelected: (LocalTime) -> Unit, - onDismiss: () -> Unit + onDismiss: () -> Unit, + confirmButtonText: String, + dismissButtonText: String, + pickerTimeTitle: String ) diff --git a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.desktop.kt b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.desktop.kt index 8a373cd..e21e37f 100644 --- a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.desktop.kt +++ b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.desktop.kt @@ -10,7 +10,9 @@ actual fun PlatformDatePicker( selectedTime: LocalTime?, onDateSelected: (LocalDate) -> Unit, onTimeInvalidated: () -> Unit, - onDismiss: () -> Unit + onDismiss: () -> Unit, + confirmButtonText: String, + dismissButtonText: String ) { TODO("Not yet implemented for this platform.") } diff --git a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.desktop.kt b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.desktop.kt index b026c0f..c9c65ea 100644 --- a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.desktop.kt +++ b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.desktop.kt @@ -9,7 +9,10 @@ actual fun PlatformTimePicker( selectedTime: LocalTime?, selectedDate: LocalDate?, onTimeSelected: (LocalTime) -> Unit, - onDismiss: () -> Unit + onDismiss: () -> Unit, + confirmButtonText: String, + dismissButtonText: String, + pickerTimeTitle: String ) { TODO("Not yet implemented for this platform.") } diff --git a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.ios.kt b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.ios.kt index 8a373cd..e21e37f 100644 --- a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.ios.kt @@ -10,7 +10,9 @@ actual fun PlatformDatePicker( selectedTime: LocalTime?, onDateSelected: (LocalDate) -> Unit, onTimeInvalidated: () -> Unit, - onDismiss: () -> Unit + onDismiss: () -> Unit, + confirmButtonText: String, + dismissButtonText: String ) { TODO("Not yet implemented for this platform.") } diff --git a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.ios.kt b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.ios.kt index b026c0f..c9c65ea 100644 --- a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.ios.kt @@ -9,7 +9,10 @@ actual fun PlatformTimePicker( selectedTime: LocalTime?, selectedDate: LocalDate?, onTimeSelected: (LocalTime) -> Unit, - onDismiss: () -> Unit + onDismiss: () -> Unit, + confirmButtonText: String, + dismissButtonText: String, + pickerTimeTitle: String ) { TODO("Not yet implemented for this platform.") } From 0ab9dc6709a9fc8a8445f1b066c1ff8332be89e8 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Tue, 13 May 2025 19:46:08 -0300 Subject: [PATCH 22/59] Method to open app settings. --- .../rememberNotificationPermissionManager.kt | 16 +++++++++ .../replyradar/core/common/strings/Strings.kt | 5 +++ .../core/common/strings/StringsEn.kt | 5 +++ .../core/common/strings/StringsPt.kt | 5 +++ .../ReplyNotificationPermissionDialog.kt | 35 +++++++++++++++++++ .../NotificationPermissionManager.kt | 3 ++ .../ReplyBottomSheetContent.kt | 19 ++++++++-- 7 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyNotificationPermissionDialog.kt diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt index 2bfd211..c737f58 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt @@ -2,8 +2,11 @@ package com.rafaelfelipeac.replyradar.core.notification import android.Manifest.permission.POST_NOTIFICATIONS import android.app.Activity +import android.content.Intent import android.content.pm.PackageManager.PERMISSION_GRANTED +import android.net.Uri import android.os.Build +import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.Composable @@ -21,6 +24,8 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.resume +private const val PACKAGE = "package" + @Composable fun rememberNotificationPermissionManager(): NotificationPermissionManager { val context = LocalContext.current @@ -81,6 +86,17 @@ fun rememberNotificationPermissionManager(): NotificationPermissionManager { cont.invokeOnCancellation { job.cancel() } } } + + override suspend fun goToAppSettings() { + val activity = context as? Activity ?: return + + val intent = Intent( + ACTION_APPLICATION_DETAILS_SETTINGS, + Uri.fromParts(PACKAGE, context.packageName, null) + ) + + activity.startActivity(intent) + } } } } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/Strings.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/Strings.kt index fe4f1e8..2eb379a 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/Strings.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/Strings.kt @@ -88,4 +88,9 @@ interface Strings { val activityLogUserActionLanguage: String val activityLogUserActionFeedback: String val activityLogUserActionRate: String + + val notificationPermissionDialogTitle: String + val notificationPermissionDialogDescription: String + val notificationPermissionDialogConfirmButton: String + val notificationPermissionDialogDismissButton: String } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsEn.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsEn.kt index 053652d..e6eaf4c 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsEn.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsEn.kt @@ -107,4 +107,9 @@ App version: ${getAppVersion()} override val activityLogUserActionLanguage = "You changed the app language." override val activityLogUserActionFeedback = "You gave feedback about the app." override val activityLogUserActionRate = "You rated the app." + + override val notificationPermissionDialogTitle = "Notification Permission" + override val notificationPermissionDialogDescription = "To remind you to reply to your messages, we need permission to send notifications. \n\nYou can enable it in the app settings." + override val notificationPermissionDialogConfirmButton = "Open Settings" + override val notificationPermissionDialogDismissButton = "Got it" } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsPt.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsPt.kt index 630b24c..a4fe109 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsPt.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsPt.kt @@ -107,4 +107,9 @@ Versão do app: ${getAppVersion()} override val activityLogUserActionLanguage = "Você mudou o idioma do app." override val activityLogUserActionFeedback = "Você enviou um feedback sobre o app." override val activityLogUserActionRate = "Você avaliou o app." + + override val notificationPermissionDialogTitle = "Permissão de notificações" + override val notificationPermissionDialogDescription = "Para que possamos te lembrar de responder às suas mensagens, precisamos que você permita o envio de notificações. \n\nVocê pode ativar isso nas configurações do app." + override val notificationPermissionDialogConfirmButton = "Abrir Configurações" + override val notificationPermissionDialogDismissButton = "Entendido" } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyNotificationPermissionDialog.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyNotificationPermissionDialog.kt new file mode 100644 index 0000000..ccb7332 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyNotificationPermissionDialog.kt @@ -0,0 +1,35 @@ +package com.rafaelfelipeac.replyradar.core.common.ui.components + +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings + +@Composable +fun NotificationPermissionDialog( + onDismiss: () -> Unit, + onGoToSettings: () -> Unit +) { + AlertDialog( + onDismissRequest = onDismiss, + title = { + Text(text = LocalReplyRadarStrings.current.notificationPermissionDialogTitle) + }, + text = { + Text( + text = LocalReplyRadarStrings.current.notificationPermissionDialogDescription + ) + }, + confirmButton = { + TextButton(onClick = onGoToSettings) { + Text(LocalReplyRadarStrings.current.notificationPermissionDialogConfirmButton) + } + }, + dismissButton = { + TextButton(onClick = onDismiss) { + Text(LocalReplyRadarStrings.current.notificationPermissionDialogDismissButton) + } + } + ) +} diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/NotificationPermissionManager.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/NotificationPermissionManager.kt index f16e202..1197928 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/NotificationPermissionManager.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/NotificationPermissionManager.kt @@ -1,5 +1,8 @@ package com.rafaelfelipeac.replyradar.core.notification interface NotificationPermissionManager { + suspend fun ensureNotificationPermission(): Boolean + + suspend fun goToAppSettings() } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt index 48870b7..bc697dc 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt @@ -32,6 +32,7 @@ import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_DEFAULT_MINUTE import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_TOMORROW_OFFSET import com.rafaelfelipeac.replyradar.core.common.clock.LocalClock import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.common.ui.components.NotificationPermissionDialog import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyButton import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyConfirmationDialog import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyOutlinedButton @@ -182,6 +183,7 @@ fun ReplyBottomSheetContent( val coroutineScope = rememberCoroutineScope() val notificationPermissionManager = LocalNotificationPermissionManager.current + var showPermissionDialog by remember { mutableStateOf(false) } ReplyButton( modifier = Modifier @@ -196,9 +198,8 @@ fun ReplyBottomSheetContent( coroutineScope.launch { val granted = notificationPermissionManager.ensureNotificationPermission() - if (granted) { - val x = "Permission Granted" + if (granted) { val replyToSave = if (state.reply != null) { state.reply.copy( name = name, @@ -215,12 +216,24 @@ fun ReplyBottomSheetContent( onComplete(replyToSave) } else { - val x = "Permission denied" + showPermissionDialog = true } } }, enabled = name.isNotBlank() ) + + if (showPermissionDialog) { + NotificationPermissionDialog( + onDismiss = { showPermissionDialog = false }, + onGoToSettings = { + showPermissionDialog = false + coroutineScope.launch { + notificationPermissionManager.goToAppSettings() + } + } + ) + } } } } From eb39d6c19f0bdcb5ed4182951e146b7806a7e239 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Tue, 13 May 2025 19:51:41 -0300 Subject: [PATCH 23/59] Removing unused code in ensureNotificationPermission. --- .../rememberNotificationPermissionManager.kt | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt index c737f58..758163d 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt @@ -66,17 +66,12 @@ fun rememberNotificationPermissionManager(): NotificationPermissionManager { .let { result -> if (!result) { val activity = context as? Activity - val shouldShowRationale = - activity?.let { - ActivityCompat.shouldShowRequestPermissionRationale( - it, - POST_NOTIFICATIONS - ) - } ?: true - - if (!shouldShowRationale) { - - } + activity?.let { + ActivityCompat.shouldShowRequestPermissionRationale( + it, + POST_NOTIFICATIONS + ) + } ?: true } cont.resume(result) From 64f892bb67a836844fca4a5304765aebaa691522 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Sat, 21 Jun 2025 19:56:30 -0300 Subject: [PATCH 24/59] Improving ensureNotificationPermission uses. --- .../com/rafaelfelipeac/replyradar/Previews.kt | 4 +- .../presentation/replylist/ReplyListEffect.kt | 14 ++ .../presentation/replylist/ReplyListScreen.kt | 78 ++++-- .../replylist/ReplyListScreenIntent.kt | 10 +- .../presentation/replylist/ReplyListState.kt | 1 + .../replylist/ReplyListViewModel.kt | 49 +++- .../presentation/replylist/SnackbarState.kt | 9 - .../replybottomsheet/ReplyBottomSheet.kt | 35 ++- .../ReplyBottomSheetContent.kt | 238 ++++++++++-------- 9 files changed, 278 insertions(+), 160 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListEffect.kt delete mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/SnackbarState.kt diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/Previews.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/Previews.kt index f2020a3..846e459 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/Previews.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/Previews.kt @@ -5,6 +5,7 @@ import androidx.compose.ui.tooling.preview.Preview import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreen import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListState +import kotlinx.coroutines.flow.flowOf private val replies = (1L..10L).map { Reply( @@ -24,6 +25,7 @@ private fun ReplyListScreenPreview() { ), onIntent = {}, onSettingsClick = {}, - onActivityLogClick = {} + onActivityLogClick = {}, + effect = flowOf() ) } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListEffect.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListEffect.kt new file mode 100644 index 0000000..a86b5b3 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListEffect.kt @@ -0,0 +1,14 @@ +package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist + +sealed interface ReplyListEffect { + + data object RequestNotificationPermission: ReplyListEffect + + sealed interface SnackbarState : ReplyListEffect { + data object Resolved : SnackbarState + data object Reopened : SnackbarState + data object Removed : SnackbarState + data object Archived : SnackbarState + data object Unarchived : SnackbarState + } +} diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt index 917c415..1a273cc 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt @@ -36,14 +36,16 @@ import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings -import com.rafaelfelipeac.replyradar.core.common.strings.Strings +import com.rafaelfelipeac.replyradar.core.common.ui.components.NotificationPermissionDialog import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyTab import com.rafaelfelipeac.replyradar.core.common.ui.fontSizeLarge import com.rafaelfelipeac.replyradar.core.common.ui.iconSize @@ -52,19 +54,23 @@ import com.rafaelfelipeac.replyradar.core.common.ui.spacerXSmall import com.rafaelfelipeac.replyradar.core.common.ui.tabRowTopPadding import com.rafaelfelipeac.replyradar.core.common.ui.theme.snackbarBackgroundColor import com.rafaelfelipeac.replyradar.core.common.ui.theme.toolbarIconsColor -import com.rafaelfelipeac.replyradar.core.notification.NotificationPermissionManager +import com.rafaelfelipeac.replyradar.core.notification.LocalNotificationPermissionManager +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.RequestNotificationPermission +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Archived +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Removed +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Reopened +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Resolved +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Unarchived +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnGoToSettings import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.ClearSnackbarState import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnAddReplyClick import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnTabSelected -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.SnackbarState.Archived -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.SnackbarState.Removed -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.SnackbarState.Reopened -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.SnackbarState.Resolved -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.SnackbarState.Unarchived import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.RepliesArchivedScreen import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.RepliesOnTheRadarScreen import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.RepliesResolvedScreen import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheet +import kotlinx.coroutines.flow.Flow import org.jetbrains.compose.resources.painterResource import org.koin.compose.viewmodel.koinViewModel import replyradar.composeapp.generated.resources.Res.drawable @@ -83,9 +89,11 @@ fun ReplyListScreenRoot( onActivityLogClick: () -> Unit ) { val state by viewModel.state.collectAsStateWithLifecycle() + val effect = viewModel.effect ReplyListScreen( state = state, + effect = effect, onIntent = { intent -> viewModel.onIntent(intent) }, @@ -98,17 +106,20 @@ fun ReplyListScreenRoot( @Composable fun ReplyListScreen( state: ReplyListState, + effect: Flow, onIntent: (ReplyListScreenIntent) -> Unit, onSettingsClick: () -> Unit, onActivityLogClick: () -> Unit ) { val strings = LocalReplyRadarStrings.current + val notificationPermissionManager = LocalNotificationPermissionManager.current val pagerState = rememberPagerState { PAGER_PAGE_COUNT } val snackbarHostState = remember { SnackbarHostState() } val sheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true ) + var showPermissionDialog by remember { mutableStateOf(false) } LaunchedEffect(pagerState.currentPage) { onIntent(OnTabSelected(pagerState.currentPage)) @@ -118,18 +129,40 @@ fun ReplyListScreen( pagerState.animateScrollToPage(state.selectedTabIndex) } - LaunchedEffect(state.snackbarState) { - state.snackbarState?.let { - snackbarHostState.showSnackbar( - getSnackbarMessage( - snackbarState = state.snackbarState, - strings = strings - ) - ) - onIntent(ClearSnackbarState) + LaunchedEffect(Unit) { + effect.collect { effect -> + when (effect) { + RequestNotificationPermission -> { + showPermissionDialog = true + } + + is SnackbarState -> { + snackbarHostState.showSnackbar( + when (effect) { + Archived -> strings.replyListSnackbarArchived + Removed -> strings.replyListSnackbarRemoved + Reopened -> strings.replyListSnackbarReopened + Resolved -> strings.replyListSnackbarResolved + Unarchived -> strings.replyListSnackbarUnarchived + } + ) + + onIntent(ClearSnackbarState) + } + } } } + if (showPermissionDialog) { + NotificationPermissionDialog( + onDismiss = { showPermissionDialog = false }, + onGoToSettings = { + showPermissionDialog = false + onIntent(OnGoToSettings(notificationPermissionManager = notificationPermissionManager)) + } + ) + } + Scaffold( containerColor = colorScheme.background, snackbarHost = { Snackbar(snackbarHostState) }, @@ -214,7 +247,9 @@ fun ReplyListScreen( ReplyBottomSheet( sheetState = sheetState, onIntent = onIntent, - replyBottomSheetState = state.replyBottomSheetState + replyBottomSheetState = state.replyBottomSheetState, + showPermissionDialog = showPermissionDialog, + onShowPermissionDialog = { value -> showPermissionDialog = value } ) } } @@ -335,12 +370,3 @@ private fun RepliesScreen( } } } - -private fun getSnackbarMessage(snackbarState: SnackbarState, strings: Strings) = - when (snackbarState) { - Archived -> strings.replyListSnackbarArchived - Removed -> strings.replyListSnackbarRemoved - Reopened -> strings.replyListSnackbarReopened - Resolved -> strings.replyListSnackbarResolved - Unarchived -> strings.replyListSnackbarUnarchived - } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt index f153138..c54a9e4 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt @@ -1,5 +1,6 @@ package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist +import com.rafaelfelipeac.replyradar.core.notification.NotificationPermissionManager import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply sealed interface ReplyListScreenIntent { @@ -13,11 +14,16 @@ sealed interface ReplyListScreenIntent { } sealed interface ReplyBottomSheetIntent : ReplyListScreenIntent { - data class OnAddReply(val reply: Reply) : ReplyBottomSheetIntent - data class OnEditReply(val reply: Reply) : ReplyBottomSheetIntent + data class OnAddOrEditReply( + val reply: Reply, + val notificationPermissionManager: NotificationPermissionManager, + ) : ReplyBottomSheetIntent data class OnDeleteReply(val reply: Reply) : ReplyBottomSheetIntent data class OnToggleArchive(val reply: Reply) : ReplyBottomSheetIntent data class OnToggleResolve(val reply: Reply) : ReplyBottomSheetIntent data object OnDismissBottomSheet : ReplyBottomSheetIntent + data class OnGoToSettings( + val notificationPermissionManager: NotificationPermissionManager + ) : ReplyBottomSheetIntent } } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListState.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListState.kt index 3d3a5e1..5ad0b30 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListState.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListState.kt @@ -1,6 +1,7 @@ package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetState data class ReplyListState( diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt index 6697591..e8b49ad 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt @@ -3,6 +3,7 @@ package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.rafaelfelipeac.replyradar.core.AppConstants.INITIAL_DATE +import com.rafaelfelipeac.replyradar.core.notification.NotificationPermissionManager import com.rafaelfelipeac.replyradar.core.util.reminder.ReminderScheduler import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.domain.usecase.DeleteReplyUseCase @@ -10,11 +11,17 @@ import com.rafaelfelipeac.replyradar.features.reply.domain.usecase.GetRepliesUse import com.rafaelfelipeac.replyradar.features.reply.domain.usecase.ToggleArchiveReplyUseCase import com.rafaelfelipeac.replyradar.features.reply.domain.usecase.ToggleResolveReplyUseCase import com.rafaelfelipeac.replyradar.features.reply.domain.usecase.UpsertReplyUseCase +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.RequestNotificationPermission +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Archived +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Removed +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Reopened +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Resolved +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Unarchived import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnAddReply +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnAddOrEditReply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnDeleteReply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnDismissBottomSheet -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnEditReply +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnGoToSettings import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnToggleArchive import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnToggleResolve import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent @@ -23,11 +30,6 @@ import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnReplyClick import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnReplyToggle import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnTabSelected -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.SnackbarState.Archived -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.SnackbarState.Removed -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.SnackbarState.Reopened -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.SnackbarState.Resolved -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.SnackbarState.Unarchived import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetMode.CREATE import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetMode.EDIT import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetState @@ -36,7 +38,6 @@ import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActio import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Archive import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Create import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Delete -import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Edit import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Reopen import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Resolve import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Unarchive @@ -45,6 +46,7 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed import kotlinx.coroutines.flow.onStart @@ -77,6 +79,9 @@ class ReplyListViewModel( _state.value ) + private val _effect = MutableSharedFlow() + val effect = _effect + fun onIntent(intent: ReplyListScreenIntent) { when (intent) { is ReplyListIntent -> handleReplyListIntent(intent) @@ -127,11 +132,19 @@ class ReplyListViewModel( private fun handleReplyBottomSheetIntent(intent: ReplyBottomSheetIntent) { when (intent) { - is OnAddReply -> onUpsertReply(reply = intent.reply, actionType = Create) - is OnEditReply -> onUpsertReply(reply = intent.reply, actionType = Edit) + is OnAddOrEditReply -> checkForNotificationPermission( + reply = intent.reply, + notificationPermissionManager = intent.notificationPermissionManager, + actionType = Create + ) is OnDeleteReply -> deleteReply(reply = intent.reply) is OnToggleArchive -> onToggleArchiveReply(reply = intent.reply) is OnToggleResolve -> onToggleResolveReply(reply = intent.reply) + is OnGoToSettings -> { + viewModelScope.launch { + intent.notificationPermissionManager.goToAppSettings() + } + } OnDismissBottomSheet -> dismissBottomSheet() } @@ -199,6 +212,22 @@ class ReplyListViewModel( } } + private fun checkForNotificationPermission( + reply: Reply, + notificationPermissionManager: NotificationPermissionManager, + actionType: UserActionType + ) = viewModelScope.launch { + if (reply.reminderAt != 0L) { + if (notificationPermissionManager.ensureNotificationPermission()) { + onUpsertReply(reply = reply, actionType = actionType) + } else { + _effect.emit(RequestNotificationPermission) + } + } else { + onUpsertReply(reply = reply, actionType = actionType) + } + } + private fun onToggleArchiveReply(reply: Reply) = viewModelScope.launch { val isArchived = toggleArchiveReplyUseCase.toggleArchiveReply(reply) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/SnackbarState.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/SnackbarState.kt deleted file mode 100644 index 7988d21..0000000 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/SnackbarState.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist - -sealed interface SnackbarState { - data object Resolved : SnackbarState - data object Reopened : SnackbarState - data object Removed : SnackbarState - data object Archived : SnackbarState - data object Unarchived : SnackbarState -} diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt index 6d5f09e..08df849 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt @@ -6,12 +6,11 @@ import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.SheetState import androidx.compose.runtime.Composable import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyRoundedCorner -import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnAddReply +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnAddOrEditReply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnDeleteReply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnDismissBottomSheet -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnEditReply +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnGoToSettings import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnToggleArchive import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnToggleResolve import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetMode.CREATE @@ -22,7 +21,9 @@ import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.compo fun ReplyBottomSheet( sheetState: SheetState, onIntent: (ReplyListScreenIntent) -> Unit, - replyBottomSheetState: ReplyBottomSheetState + replyBottomSheetState: ReplyBottomSheetState, + showPermissionDialog: Boolean, + onShowPermissionDialog: (Boolean) -> Unit ) { ModalBottomSheet( sheetState = sheetState, @@ -35,8 +36,9 @@ fun ReplyBottomSheet( CREATE -> { BottomSheetContent( state = ReplyBottomSheetState(CREATE), - onComplete = { onIntent(OnAddReply(it)) }, - onIntent = onIntent + onIntent = onIntent, + showPermissionDialog = showPermissionDialog, + onShowPermissionDialog = onShowPermissionDialog ) } @@ -47,8 +49,9 @@ fun ReplyBottomSheet( EDIT, reply = replyBottomSheetState.reply ), - onComplete = { onIntent(OnEditReply(it)) }, - onIntent = onIntent + onIntent = onIntent, + showPermissionDialog = showPermissionDialog, + onShowPermissionDialog = onShowPermissionDialog, ) } } @@ -59,14 +62,22 @@ fun ReplyBottomSheet( @Composable private fun BottomSheetContent( state: ReplyBottomSheetState, - onComplete: (Reply) -> Unit, - onIntent: (ReplyListScreenIntent) -> Unit + onIntent: (ReplyListScreenIntent) -> Unit, + showPermissionDialog: Boolean, + onShowPermissionDialog: (Boolean) -> Unit ) { ReplyBottomSheetContent( replyBottomSheetState = state, - onComplete = onComplete, onResolve = { onIntent(OnToggleResolve(it)) }, onArchive = { onIntent(OnToggleArchive(it)) }, - onDelete = { onIntent(OnDeleteReply(it)) } + onDelete = { onIntent(OnDeleteReply(it)) }, + onComplete = { reply, notificationPermissionManager -> + onIntent(OnAddOrEditReply(reply, notificationPermissionManager)) + }, + onGoToSettings = { notificationPermissionManager -> + onIntent(OnGoToSettings(notificationPermissionManager)) + }, + showPermissionDialog = showPermissionDialog, + onShowPermissionDialog = onShowPermissionDialog, ) } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt index bc697dc..45bf04e 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Arrangement.End import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentWidth @@ -16,7 +17,6 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.CenterHorizontally @@ -42,11 +42,11 @@ import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyTextFieldSiz import com.rafaelfelipeac.replyradar.core.common.ui.paddingMedium import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall import com.rafaelfelipeac.replyradar.core.notification.LocalNotificationPermissionManager +import com.rafaelfelipeac.replyradar.core.notification.NotificationPermissionManager import com.rafaelfelipeac.replyradar.core.util.format import com.rafaelfelipeac.replyradar.core.util.formatTimestamp import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetMode.EDIT -import kotlinx.coroutines.launch import kotlinx.datetime.DateTimeUnit.Companion.DAY import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate @@ -68,10 +68,13 @@ private const val WEIGHT = 1f @Composable fun ReplyBottomSheetContent( replyBottomSheetState: ReplyBottomSheetState? = null, - onComplete: (Reply) -> Unit, + onComplete: (Reply, NotificationPermissionManager) -> Unit, onResolve: (Reply) -> Unit, onArchive: (Reply) -> Unit, - onDelete: (Reply) -> Unit + onDelete: (Reply) -> Unit, + onGoToSettings: (NotificationPermissionManager) -> Unit, + showPermissionDialog: Boolean, + onShowPermissionDialog: (Boolean) -> Unit ) { replyBottomSheetState?.let { state -> var name by remember { mutableStateOf(state.reply?.name ?: EMPTY) } @@ -116,12 +119,12 @@ fun ReplyBottomSheetContent( onValueChange = { subject = it } ) - state.reply?.let { + state.reply?.let { reply -> Text( modifier = Modifier .padding(start = paddingSmall, top = paddingSmall) .align(Start), - text = getTimestamp(state.reply), + text = getTimestamp(reply), style = typography.bodySmall ) } @@ -134,105 +137,140 @@ fun ReplyBottomSheetContent( closeKeyboard = { keyboardController?.hide() } ) - Row( - modifier = Modifier - .fillMaxWidth() - .padding(top = paddingSmall, bottom = paddingMedium, end = paddingSmall), - horizontalArrangement = if (isEditMode(state)) spacedBy(paddingSmall) else End, - verticalAlignment = Alignment.CenterVertically - ) { - if (state.reply != null && isEditMode(state)) { - Row( - modifier = Modifier - .weight(WEIGHT) - ) { - with(state.reply) { - when { - !isArchived && !isResolved -> { - ActiveStateButtons( - reply = state.reply, - onArchive = onArchive, - onResolve = onResolve - ) - } - - isResolved && !isArchived -> { - ResolvedStateButtons( - reply = state.reply, - onArchive = onArchive, - onResolve = onResolve - ) - } - - isArchived -> { - ArchivedStateButton( - reply = state.reply, - onArchive = onArchive, - onDelete = onDelete - ) - } - } - } - } - } + Buttons( + state = replyBottomSheetState, + onArchive = onArchive, + onResolve = onResolve, + onComplete = onComplete, + onDelete = onDelete, + onGoToSettings = onGoToSettings, + selectedDate = selectedDate, + selectedTime = selectedTime, + reply = state.reply, + name = name, + subject = subject, + onShowPermissionDialog = onShowPermissionDialog, + showPermissionDialog = showPermissionDialog, + ) + } + } +} - val reminderAt = getReminderTimestamp( - selectedDate = selectedDate, - selectedTime = selectedTime - ) +@Composable +private fun Buttons( + state: ReplyBottomSheetState, + onArchive: (Reply) -> Unit, + onResolve: (Reply) -> Unit, + onDelete: (Reply) -> Unit, + onComplete: (Reply, NotificationPermissionManager) -> Unit, + onGoToSettings: (NotificationPermissionManager) -> Unit, + selectedDate: LocalDate?, + selectedTime: LocalTime?, + reply: Reply?, + name: String, + subject: String, + showPermissionDialog: Boolean, + onShowPermissionDialog: (Boolean) -> Unit, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = paddingSmall, bottom = paddingMedium, end = paddingSmall), + horizontalArrangement = if (isEditMode(state)) spacedBy(paddingSmall) else End, + verticalAlignment = Alignment.CenterVertically + ) { + StateButtons( + state = state, + onArchive = onArchive, + onResolve = onResolve, + onDelete = onDelete + ) - val coroutineScope = rememberCoroutineScope() - val notificationPermissionManager = LocalNotificationPermissionManager.current - var showPermissionDialog by remember { mutableStateOf(false) } + val reminderAtTimestamp = getReminderTimestamp( + selectedDate = selectedDate, + selectedTime = selectedTime + ) - ReplyButton( - modifier = Modifier - .wrapContentWidth() - .align(Alignment.CenterVertically), - text = if (state.reply == null) { - LocalReplyRadarStrings.current.replyListBottomSheetAdd - } else { - LocalReplyRadarStrings.current.replyListBottomSheetSave - }, - onClick = { - coroutineScope.launch { - val granted = - notificationPermissionManager.ensureNotificationPermission() - - if (granted) { - val replyToSave = if (state.reply != null) { - state.reply.copy( - name = name, - subject = subject, - reminderAt = reminderAt - ) - } else { - Reply( - name = name, - subject = subject, - reminderAt = reminderAt - ) - } - - onComplete(replyToSave) - } else { - showPermissionDialog = true - } - } - }, - enabled = name.isNotBlank() - ) + val notificationPermissionManager = LocalNotificationPermissionManager.current - if (showPermissionDialog) { - NotificationPermissionDialog( - onDismiss = { showPermissionDialog = false }, - onGoToSettings = { - showPermissionDialog = false - coroutineScope.launch { - notificationPermissionManager.goToAppSettings() - } - } + ReplyButton( + modifier = Modifier + .wrapContentWidth() + .align(Alignment.CenterVertically), + text = if (reply == null) { + LocalReplyRadarStrings.current.replyListBottomSheetAdd + } else { + LocalReplyRadarStrings.current.replyListBottomSheetSave + }, + onClick = { + val replyToSave = if (state.reply != null) { + state.reply.copy( + name = name, + subject = subject, + reminderAt = reminderAtTimestamp ) + } else { + Reply( + name = name, + subject = subject, + reminderAt = reminderAtTimestamp + ) + } + + onComplete(replyToSave, notificationPermissionManager) + }, + enabled = name.isNotBlank() + ) + + if (showPermissionDialog) { + NotificationPermissionDialog( + onDismiss = { onShowPermissionDialog(false) }, + onGoToSettings = { + onShowPermissionDialog(false) + onGoToSettings(notificationPermissionManager) + } + ) + } + } +} + +@Composable +private fun RowScope.StateButtons( + state: ReplyBottomSheetState, + onArchive: (Reply) -> Unit, + onResolve: (Reply) -> Unit, + onDelete: (Reply) -> Unit +) { + if (state.reply != null && isEditMode(state)) { + Row( + modifier = Modifier.Companion + .weight(WEIGHT) + ) { + with(state.reply) { + when { + !isArchived && !isResolved -> { + ActiveStateButtons( + reply = state.reply, + onArchive = onArchive, + onResolve = onResolve + ) + } + + isResolved && !isArchived -> { + ResolvedStateButtons( + reply = state.reply, + onArchive = onArchive, + onResolve = onResolve + ) + } + + isArchived -> { + ArchivedStateButton( + reply = state.reply, + onArchive = onArchive, + onDelete = onDelete + ) + } } } } From 58ccf430be9731d986ed8ecd8d9212a141a01e46 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Mon, 23 Jun 2025 10:09:30 -0300 Subject: [PATCH 25/59] Improving ensureNotificationPermission uses. --- .../presentation/replylist/ReplyListEffect.kt | 6 +++ .../presentation/replylist/ReplyListScreen.kt | 34 ++++++++++++- .../replylist/ReplyListScreenIntent.kt | 11 ++--- .../replylist/ReplyListViewModel.kt | 49 ++++++++----------- .../replybottomsheet/ReplyBottomSheet.kt | 49 +++++++++++-------- .../ReplyBottomSheetContent.kt | 14 +++--- 6 files changed, 97 insertions(+), 66 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListEffect.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListEffect.kt index a86b5b3..795ed69 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListEffect.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListEffect.kt @@ -1,9 +1,15 @@ package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist +import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply + sealed interface ReplyListEffect { + data class CheckNotificationPermission(val reply: Reply): ReplyListEffect + data object RequestNotificationPermission: ReplyListEffect + data object GoToSettings : ReplyListEffect + sealed interface SnackbarState : ReplyListEffect { data object Resolved : SnackbarState data object Reopened : SnackbarState diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt index 1a273cc..2260691 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt @@ -55,6 +55,7 @@ import com.rafaelfelipeac.replyradar.core.common.ui.tabRowTopPadding import com.rafaelfelipeac.replyradar.core.common.ui.theme.snackbarBackgroundColor import com.rafaelfelipeac.replyradar.core.common.ui.theme.toolbarIconsColor import com.rafaelfelipeac.replyradar.core.notification.LocalNotificationPermissionManager +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.GoToSettings import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.RequestNotificationPermission import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Archived @@ -62,7 +63,13 @@ import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Reopened import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Resolved import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Unarchived +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.CheckNotificationPermission +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnAddOrEditReply +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnDeleteReply +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnDismissBottomSheet import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnGoToSettings +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnToggleArchive +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnToggleResolve import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.ClearSnackbarState import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnAddReplyClick import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnTabSelected @@ -149,6 +156,18 @@ fun ReplyListScreen( onIntent(ClearSnackbarState) } + + GoToSettings -> { + notificationPermissionManager.goToAppSettings() + } + + is ReplyListEffect.CheckNotificationPermission -> { + if (notificationPermissionManager.ensureNotificationPermission()) { + onIntent(OnAddOrEditReply(effect.reply)) + } else { + onIntent(ReplyListScreenIntent.ReplyBottomSheetIntent.RequestNotificationPermission) + } + } } } } @@ -158,7 +177,7 @@ fun ReplyListScreen( onDismiss = { showPermissionDialog = false }, onGoToSettings = { showPermissionDialog = false - onIntent(OnGoToSettings(notificationPermissionManager = notificationPermissionManager)) + onIntent(OnGoToSettings) } ) } @@ -246,7 +265,18 @@ fun ReplyListScreen( if (state.replyBottomSheetState != null) { ReplyBottomSheet( sheetState = sheetState, - onIntent = onIntent, + onResolve = { onIntent(OnToggleResolve(it)) }, + onArchive = { onIntent(OnToggleArchive(it)) }, + onDelete = { onIntent(OnDeleteReply(it)) }, + onComplete = { reply -> + if (reply.reminderAt != 0L) { + onIntent(CheckNotificationPermission(reply)) + } else { + onIntent(OnAddOrEditReply(reply)) + } + }, + onGoToSettings = { onIntent(OnGoToSettings) }, + onDismiss = { onIntent(OnDismissBottomSheet) }, replyBottomSheetState = state.replyBottomSheetState, showPermissionDialog = showPermissionDialog, onShowPermissionDialog = { value -> showPermissionDialog = value } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt index c54a9e4..e128ce3 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt @@ -14,16 +14,13 @@ sealed interface ReplyListScreenIntent { } sealed interface ReplyBottomSheetIntent : ReplyListScreenIntent { - data class OnAddOrEditReply( - val reply: Reply, - val notificationPermissionManager: NotificationPermissionManager, - ) : ReplyBottomSheetIntent + data class OnAddOrEditReply(val reply: Reply) : ReplyBottomSheetIntent data class OnDeleteReply(val reply: Reply) : ReplyBottomSheetIntent data class OnToggleArchive(val reply: Reply) : ReplyBottomSheetIntent data class OnToggleResolve(val reply: Reply) : ReplyBottomSheetIntent data object OnDismissBottomSheet : ReplyBottomSheetIntent - data class OnGoToSettings( - val notificationPermissionManager: NotificationPermissionManager - ) : ReplyBottomSheetIntent + data object RequestNotificationPermission : ReplyBottomSheetIntent + data class CheckNotificationPermission(val reply: Reply) : ReplyBottomSheetIntent + data object OnGoToSettings : ReplyBottomSheetIntent } } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt index e8b49ad..0901aa4 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt @@ -3,7 +3,6 @@ package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.rafaelfelipeac.replyradar.core.AppConstants.INITIAL_DATE -import com.rafaelfelipeac.replyradar.core.notification.NotificationPermissionManager import com.rafaelfelipeac.replyradar.core.util.reminder.ReminderScheduler import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.domain.usecase.DeleteReplyUseCase @@ -11,6 +10,7 @@ import com.rafaelfelipeac.replyradar.features.reply.domain.usecase.GetRepliesUse import com.rafaelfelipeac.replyradar.features.reply.domain.usecase.ToggleArchiveReplyUseCase import com.rafaelfelipeac.replyradar.features.reply.domain.usecase.ToggleResolveReplyUseCase import com.rafaelfelipeac.replyradar.features.reply.domain.usecase.UpsertReplyUseCase +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.GoToSettings import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.RequestNotificationPermission import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Archived import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Removed @@ -18,6 +18,7 @@ import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Resolved import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Unarchived import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.CheckNotificationPermission import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnAddOrEditReply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnDeleteReply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnDismissBottomSheet @@ -38,6 +39,7 @@ import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActio import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Archive import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Create import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Delete +import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Edit import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Reopen import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Resolve import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Unarchive @@ -132,20 +134,14 @@ class ReplyListViewModel( private fun handleReplyBottomSheetIntent(intent: ReplyBottomSheetIntent) { when (intent) { - is OnAddOrEditReply -> checkForNotificationPermission( - reply = intent.reply, - notificationPermissionManager = intent.notificationPermissionManager, - actionType = Create - ) + is OnAddOrEditReply -> onUpsertReply(reply = intent.reply) is OnDeleteReply -> deleteReply(reply = intent.reply) is OnToggleArchive -> onToggleArchiveReply(reply = intent.reply) is OnToggleResolve -> onToggleResolveReply(reply = intent.reply) - is OnGoToSettings -> { - viewModelScope.launch { - intent.notificationPermissionManager.goToAppSettings() - } - } + is OnGoToSettings -> goToSettings() + is CheckNotificationPermission -> checkNotificationPermission(intent.reply) OnDismissBottomSheet -> dismissBottomSheet() + ReplyBottomSheetIntent.RequestNotificationPermission -> requestNotificationPermission() } dismissBottomSheet() @@ -197,7 +193,8 @@ class ReplyListViewModel( } } - private fun onUpsertReply(reply: Reply, actionType: UserActionType) = viewModelScope.launch { + private fun onUpsertReply(reply: Reply) = viewModelScope.launch { + val actionType = if (reply.id == 0L) Create else Edit val replyId = upsertReplyUseCase.upsertReply(reply) logUserAction(actionType = actionType, targetId = replyId) @@ -212,22 +209,6 @@ class ReplyListViewModel( } } - private fun checkForNotificationPermission( - reply: Reply, - notificationPermissionManager: NotificationPermissionManager, - actionType: UserActionType - ) = viewModelScope.launch { - if (reply.reminderAt != 0L) { - if (notificationPermissionManager.ensureNotificationPermission()) { - onUpsertReply(reply = reply, actionType = actionType) - } else { - _effect.emit(RequestNotificationPermission) - } - } else { - onUpsertReply(reply = reply, actionType = actionType) - } - } - private fun onToggleArchiveReply(reply: Reply) = viewModelScope.launch { val isArchived = toggleArchiveReplyUseCase.toggleArchiveReply(reply) @@ -256,6 +237,18 @@ class ReplyListViewModel( updateState { copy(replyBottomSheetState = null) } } + private fun requestNotificationPermission() = viewModelScope.launch { + _effect.emit(RequestNotificationPermission) + } + + private fun checkNotificationPermission(reply: Reply) = viewModelScope.launch { + _effect.emit(ReplyListEffect.CheckNotificationPermission(reply)) + } + + private fun goToSettings() = viewModelScope.launch { + _effect.emit(GoToSettings) + } + private fun updateState(update: ReplyListState.() -> ReplyListState) { _state.update { it.update() } } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt index 08df849..a00c64b 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt @@ -6,13 +6,7 @@ import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.SheetState import androidx.compose.runtime.Composable import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyRoundedCorner -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnAddOrEditReply -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnDeleteReply -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnDismissBottomSheet -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnGoToSettings -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnToggleArchive -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnToggleResolve +import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetMode.CREATE import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetMode.EDIT @@ -20,14 +14,19 @@ import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.compo @Composable fun ReplyBottomSheet( sheetState: SheetState, - onIntent: (ReplyListScreenIntent) -> Unit, + onComplete: (Reply) -> Unit, + onResolve: (Reply) -> Unit, + onArchive: (Reply) -> Unit, + onDelete: (Reply) -> Unit, + onGoToSettings: () -> Unit, + onDismiss: () -> Unit, replyBottomSheetState: ReplyBottomSheetState, showPermissionDialog: Boolean, onShowPermissionDialog: (Boolean) -> Unit ) { ModalBottomSheet( sheetState = sheetState, - onDismissRequest = { onIntent(OnDismissBottomSheet) }, + onDismissRequest = onDismiss, containerColor = colorScheme.background, dragHandle = null, shape = ReplyRoundedCorner(onlyTopCorners = true) @@ -36,7 +35,11 @@ fun ReplyBottomSheet( CREATE -> { BottomSheetContent( state = ReplyBottomSheetState(CREATE), - onIntent = onIntent, + onComplete = onComplete, + onResolve = onResolve, + onArchive = onArchive, + onDelete = onDelete, + onGoToSettings = onGoToSettings, showPermissionDialog = showPermissionDialog, onShowPermissionDialog = onShowPermissionDialog ) @@ -49,7 +52,11 @@ fun ReplyBottomSheet( EDIT, reply = replyBottomSheetState.reply ), - onIntent = onIntent, + onComplete = onComplete, + onResolve = onResolve, + onArchive = onArchive, + onDelete = onDelete, + onGoToSettings = onGoToSettings, showPermissionDialog = showPermissionDialog, onShowPermissionDialog = onShowPermissionDialog, ) @@ -62,21 +69,21 @@ fun ReplyBottomSheet( @Composable private fun BottomSheetContent( state: ReplyBottomSheetState, - onIntent: (ReplyListScreenIntent) -> Unit, + onComplete: (Reply) -> Unit, + onResolve: (Reply) -> Unit, + onArchive: (Reply) -> Unit, + onDelete: (Reply) -> Unit, + onGoToSettings: () -> Unit, showPermissionDialog: Boolean, onShowPermissionDialog: (Boolean) -> Unit ) { ReplyBottomSheetContent( replyBottomSheetState = state, - onResolve = { onIntent(OnToggleResolve(it)) }, - onArchive = { onIntent(OnToggleArchive(it)) }, - onDelete = { onIntent(OnDeleteReply(it)) }, - onComplete = { reply, notificationPermissionManager -> - onIntent(OnAddOrEditReply(reply, notificationPermissionManager)) - }, - onGoToSettings = { notificationPermissionManager -> - onIntent(OnGoToSettings(notificationPermissionManager)) - }, + onResolve = onResolve, + onArchive = onArchive, + onDelete = onDelete, + onComplete = onComplete, + onGoToSettings = onGoToSettings, showPermissionDialog = showPermissionDialog, onShowPermissionDialog = onShowPermissionDialog, ) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt index 45bf04e..80142d1 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt @@ -68,11 +68,11 @@ private const val WEIGHT = 1f @Composable fun ReplyBottomSheetContent( replyBottomSheetState: ReplyBottomSheetState? = null, - onComplete: (Reply, NotificationPermissionManager) -> Unit, + onComplete: (Reply) -> Unit, onResolve: (Reply) -> Unit, onArchive: (Reply) -> Unit, onDelete: (Reply) -> Unit, - onGoToSettings: (NotificationPermissionManager) -> Unit, + onGoToSettings: () -> Unit, showPermissionDialog: Boolean, onShowPermissionDialog: (Boolean) -> Unit ) { @@ -162,8 +162,8 @@ private fun Buttons( onArchive: (Reply) -> Unit, onResolve: (Reply) -> Unit, onDelete: (Reply) -> Unit, - onComplete: (Reply, NotificationPermissionManager) -> Unit, - onGoToSettings: (NotificationPermissionManager) -> Unit, + onComplete: (Reply) -> Unit, + onGoToSettings: () -> Unit, selectedDate: LocalDate?, selectedTime: LocalTime?, reply: Reply?, @@ -191,8 +191,6 @@ private fun Buttons( selectedTime = selectedTime ) - val notificationPermissionManager = LocalNotificationPermissionManager.current - ReplyButton( modifier = Modifier .wrapContentWidth() @@ -217,7 +215,7 @@ private fun Buttons( ) } - onComplete(replyToSave, notificationPermissionManager) + onComplete(replyToSave) }, enabled = name.isNotBlank() ) @@ -227,7 +225,7 @@ private fun Buttons( onDismiss = { onShowPermissionDialog(false) }, onGoToSettings = { onShowPermissionDialog(false) - onGoToSettings(notificationPermissionManager) + onGoToSettings() } ) } From f81c578f2f3a810680e2bbb61d343192edcd959d Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Mon, 23 Jun 2025 10:16:04 -0300 Subject: [PATCH 26/59] Improving ensureNotificationPermission uses. --- .../presentation/replylist/ReplyListScreen.kt | 9 +++++---- .../replylist/ReplyListScreenIntent.kt | 10 ++++++---- .../replylist/ReplyListViewModel.kt | 18 +++++++++++++----- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt index 2260691..4cd3ba6 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt @@ -63,11 +63,12 @@ import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Reopened import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Resolved import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Unarchived -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.CheckNotificationPermission +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.NotificationPermissionIntent.OnCheckNotificationPermission +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.NotificationPermissionIntent.OnGoToSettings +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.NotificationPermissionIntent.OnRequestNotificationPermission import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnAddOrEditReply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnDeleteReply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnDismissBottomSheet -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnGoToSettings import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnToggleArchive import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnToggleResolve import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.ClearSnackbarState @@ -165,7 +166,7 @@ fun ReplyListScreen( if (notificationPermissionManager.ensureNotificationPermission()) { onIntent(OnAddOrEditReply(effect.reply)) } else { - onIntent(ReplyListScreenIntent.ReplyBottomSheetIntent.RequestNotificationPermission) + onIntent(OnRequestNotificationPermission) } } } @@ -270,7 +271,7 @@ fun ReplyListScreen( onDelete = { onIntent(OnDeleteReply(it)) }, onComplete = { reply -> if (reply.reminderAt != 0L) { - onIntent(CheckNotificationPermission(reply)) + onIntent(OnCheckNotificationPermission(reply)) } else { onIntent(OnAddOrEditReply(reply)) } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt index e128ce3..df7838e 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt @@ -1,6 +1,5 @@ package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist -import com.rafaelfelipeac.replyradar.core.notification.NotificationPermissionManager import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply sealed interface ReplyListScreenIntent { @@ -19,8 +18,11 @@ sealed interface ReplyListScreenIntent { data class OnToggleArchive(val reply: Reply) : ReplyBottomSheetIntent data class OnToggleResolve(val reply: Reply) : ReplyBottomSheetIntent data object OnDismissBottomSheet : ReplyBottomSheetIntent - data object RequestNotificationPermission : ReplyBottomSheetIntent - data class CheckNotificationPermission(val reply: Reply) : ReplyBottomSheetIntent - data object OnGoToSettings : ReplyBottomSheetIntent + } + + sealed interface NotificationPermissionIntent : ReplyListScreenIntent { + data object OnRequestNotificationPermission : NotificationPermissionIntent + data class OnCheckNotificationPermission(val reply: Reply) : NotificationPermissionIntent + data object OnGoToSettings : NotificationPermissionIntent } } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt index 0901aa4..b982e75 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt @@ -17,12 +17,14 @@ import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Reopened import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Resolved import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Unarchived +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.NotificationPermissionIntent +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.NotificationPermissionIntent.OnCheckNotificationPermission +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.NotificationPermissionIntent.OnGoToSettings +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.NotificationPermissionIntent.OnRequestNotificationPermission import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.CheckNotificationPermission import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnAddOrEditReply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnDeleteReply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnDismissBottomSheet -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnGoToSettings import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnToggleArchive import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnToggleResolve import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent @@ -88,6 +90,7 @@ class ReplyListViewModel( when (intent) { is ReplyListIntent -> handleReplyListIntent(intent) is ReplyBottomSheetIntent -> handleReplyBottomSheetIntent(intent) + is NotificationPermissionIntent -> handleNotificationPermissionIntent(intent) } } @@ -138,15 +141,20 @@ class ReplyListViewModel( is OnDeleteReply -> deleteReply(reply = intent.reply) is OnToggleArchive -> onToggleArchiveReply(reply = intent.reply) is OnToggleResolve -> onToggleResolveReply(reply = intent.reply) - is OnGoToSettings -> goToSettings() - is CheckNotificationPermission -> checkNotificationPermission(intent.reply) OnDismissBottomSheet -> dismissBottomSheet() - ReplyBottomSheetIntent.RequestNotificationPermission -> requestNotificationPermission() } dismissBottomSheet() } + private fun handleNotificationPermissionIntent(intent: NotificationPermissionIntent) { + when (intent) { + is OnCheckNotificationPermission -> checkNotificationPermission(intent.reply) + OnGoToSettings -> goToSettings() + OnRequestNotificationPermission -> requestNotificationPermission() + } + } + private fun getReplies() = viewModelScope.launch { updateState { copy(isLoading = true) } From 8d28da05fcfd4c786255da1e3c8d6a010b3d937d Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Mon, 23 Jun 2025 10:32:07 -0300 Subject: [PATCH 27/59] Improvements. --- .../presentation/replylist/ReplyListScreen.kt | 2 +- .../replybottomsheet/ReplyBottomSheet.kt | 10 ++--- .../ReplyBottomSheetContent.kt | 40 +++++++++++-------- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt index 4cd3ba6..135bdd0 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt @@ -269,7 +269,7 @@ fun ReplyListScreen( onResolve = { onIntent(OnToggleResolve(it)) }, onArchive = { onIntent(OnToggleArchive(it)) }, onDelete = { onIntent(OnDeleteReply(it)) }, - onComplete = { reply -> + onSave = { reply -> if (reply.reminderAt != 0L) { onIntent(OnCheckNotificationPermission(reply)) } else { diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt index a00c64b..55b1a00 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt @@ -14,7 +14,7 @@ import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.compo @Composable fun ReplyBottomSheet( sheetState: SheetState, - onComplete: (Reply) -> Unit, + onSave: (Reply) -> Unit, onResolve: (Reply) -> Unit, onArchive: (Reply) -> Unit, onDelete: (Reply) -> Unit, @@ -35,7 +35,7 @@ fun ReplyBottomSheet( CREATE -> { BottomSheetContent( state = ReplyBottomSheetState(CREATE), - onComplete = onComplete, + onSave = onSave, onResolve = onResolve, onArchive = onArchive, onDelete = onDelete, @@ -52,7 +52,7 @@ fun ReplyBottomSheet( EDIT, reply = replyBottomSheetState.reply ), - onComplete = onComplete, + onSave = onSave, onResolve = onResolve, onArchive = onArchive, onDelete = onDelete, @@ -69,7 +69,7 @@ fun ReplyBottomSheet( @Composable private fun BottomSheetContent( state: ReplyBottomSheetState, - onComplete: (Reply) -> Unit, + onSave: (Reply) -> Unit, onResolve: (Reply) -> Unit, onArchive: (Reply) -> Unit, onDelete: (Reply) -> Unit, @@ -82,7 +82,7 @@ private fun BottomSheetContent( onResolve = onResolve, onArchive = onArchive, onDelete = onDelete, - onComplete = onComplete, + onSave = onSave, onGoToSettings = onGoToSettings, showPermissionDialog = showPermissionDialog, onShowPermissionDialog = onShowPermissionDialog, diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt index 80142d1..3afbc9c 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt @@ -41,8 +41,6 @@ import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyTextField import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyTextFieldSize.Large import com.rafaelfelipeac.replyradar.core.common.ui.paddingMedium import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall -import com.rafaelfelipeac.replyradar.core.notification.LocalNotificationPermissionManager -import com.rafaelfelipeac.replyradar.core.notification.NotificationPermissionManager import com.rafaelfelipeac.replyradar.core.util.format import com.rafaelfelipeac.replyradar.core.util.formatTimestamp import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply @@ -68,7 +66,7 @@ private const val WEIGHT = 1f @Composable fun ReplyBottomSheetContent( replyBottomSheetState: ReplyBottomSheetState? = null, - onComplete: (Reply) -> Unit, + onSave: (Reply) -> Unit, onResolve: (Reply) -> Unit, onArchive: (Reply) -> Unit, onDelete: (Reply) -> Unit, @@ -141,7 +139,7 @@ fun ReplyBottomSheetContent( state = replyBottomSheetState, onArchive = onArchive, onResolve = onResolve, - onComplete = onComplete, + onSave = onSave, onDelete = onDelete, onGoToSettings = onGoToSettings, selectedDate = selectedDate, @@ -162,7 +160,7 @@ private fun Buttons( onArchive: (Reply) -> Unit, onResolve: (Reply) -> Unit, onDelete: (Reply) -> Unit, - onComplete: (Reply) -> Unit, + onSave: (Reply) -> Unit, onGoToSettings: () -> Unit, selectedDate: LocalDate?, selectedTime: LocalTime?, @@ -201,21 +199,14 @@ private fun Buttons( LocalReplyRadarStrings.current.replyListBottomSheetSave }, onClick = { - val replyToSave = if (state.reply != null) { - state.reply.copy( + onSave( + getReplyToSave( + stateReply = state.reply, name = name, subject = subject, - reminderAt = reminderAtTimestamp + reminderAtTimestamp = reminderAtTimestamp ) - } else { - Reply( - name = name, - subject = subject, - reminderAt = reminderAtTimestamp - ) - } - - onComplete(replyToSave) + ) }, enabled = name.isNotBlank() ) @@ -232,6 +223,21 @@ private fun Buttons( } } +private fun getReplyToSave( + stateReply: Reply?, + name: String, + subject: String, + reminderAtTimestamp: Long +) = stateReply?.copy( + name = name, + subject = subject, + reminderAt = reminderAtTimestamp +) ?: Reply( + name = name, + subject = subject, + reminderAt = reminderAtTimestamp +) + @Composable private fun RowScope.StateButtons( state: ReplyBottomSheetState, From 8b124a6c15a6268be1805a11b85bf8f1cd08850b Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Mon, 23 Jun 2025 11:25:43 -0300 Subject: [PATCH 28/59] Improvements. --- .../common/ui/components/ReplySnackbar.kt | 22 ++ .../replyradar/core/util/Timestamp.kt | 47 +++ .../presentation/replylist/ReplyListScreen.kt | 172 +++------ .../presentation/replylist/ReplyListState.kt | 4 +- .../replylist/ReplyListViewModel.kt | 6 +- .../presentation/replylist/components/FAB.kt | 26 ++ .../replylist/components/RepliesScreen.kt | 59 ++++ .../components/ReplyTimestampInfo.kt | 63 ++++ .../replylist/components/TopBar.kt | 68 ++++ .../replybottomsheet/ReplyBottomSheet.kt | 25 +- .../ReplyBottomSheetActions.kt | 227 ++++++++++++ .../ReplyBottomSheetContent.kt | 326 +----------------- 12 files changed, 566 insertions(+), 479 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplySnackbar.kt create mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/FAB.kt create mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesScreen.kt create mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/ReplyTimestampInfo.kt create mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/TopBar.kt create mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetActions.kt diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplySnackbar.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplySnackbar.kt new file mode 100644 index 0000000..8955e21 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplySnackbar.kt @@ -0,0 +1,22 @@ +package com.rafaelfelipeac.replyradar.core.common.ui.components + +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.Snackbar +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.runtime.Composable +import com.rafaelfelipeac.replyradar.core.common.ui.theme.snackbarBackgroundColor + +@Composable +fun ReplySnackbar(snackbarHostState: SnackbarHostState) { + SnackbarHost( + hostState = snackbarHostState, + snackbar = { snackbarData -> + Snackbar( + snackbarData = snackbarData, + containerColor = colorScheme.snackbarBackgroundColor, + contentColor = colorScheme.onSurface + ) + } + ) +} diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Timestamp.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Timestamp.kt index e152480..1e63af0 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Timestamp.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Timestamp.kt @@ -1,7 +1,17 @@ package com.rafaelfelipeac.replyradar.core.util +import com.rafaelfelipeac.replyradar.core.AppConstants.INITIAL_DATE +import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_DEFAULT_HOUR +import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_DEFAULT_MINUTE +import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_TOMORROW_OFFSET +import kotlinx.datetime.DateTimeUnit.Companion.DAY import kotlinx.datetime.Instant +import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.LocalTime import kotlinx.datetime.TimeZone +import kotlinx.datetime.plus +import kotlinx.datetime.toInstant import kotlinx.datetime.toLocalDateTime fun formatTimestamp(timestampMillis: Long): String { @@ -11,3 +21,40 @@ fun formatTimestamp(timestampMillis: Long): String { return "${localDateTime.dayOfMonth}/${localDateTime.monthNumber}/${localDateTime.year} " + "${localDateTime.hour}:${localDateTime.minute.toTwoDigitString()}" } + +fun getReminderTimestamp( + now: Long, + selectedDate: LocalDate?, + selectedTime: LocalTime? +): Long { + val timeZone = TimeZone.currentSystemDefault() + val nowDateTime = + Instant.fromEpochMilliseconds(now).toLocalDateTime(timeZone) + + val finalDate = when { + selectedDate != null -> selectedDate + selectedTime != null -> { + val timeToday = LocalDateTime( + date = nowDateTime.date, + time = selectedTime + ) + + if (timeToday > nowDateTime) { + nowDateTime.date + } else { + nowDateTime.date.plus( + REMINDER_TOMORROW_OFFSET, + DAY + ) + } + } + + else -> return INITIAL_DATE + } + + val finalTime = selectedTime ?: LocalTime(REMINDER_DEFAULT_HOUR, REMINDER_DEFAULT_MINUTE) + + val finalDateTime = LocalDateTime(finalDate, finalTime) + + return finalDateTime.toInstant(timeZone).toEpochMilliseconds() +} diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt index 135bdd0..53fea64 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt @@ -1,37 +1,22 @@ package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add -import androidx.compose.material3.ColorScheme import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.FloatingActionButton -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme.colorScheme -import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.Scaffold -import androidx.compose.material3.Snackbar -import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Surface import androidx.compose.material3.TabRow import androidx.compose.material3.TabRowDefaults import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset -import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -39,22 +24,20 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextAlign import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.rafaelfelipeac.replyradar.core.AppConstants.INITIAL_DATE import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.common.strings.Strings import com.rafaelfelipeac.replyradar.core.common.ui.components.NotificationPermissionDialog +import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplySnackbar import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyTab -import com.rafaelfelipeac.replyradar.core.common.ui.fontSizeLarge -import com.rafaelfelipeac.replyradar.core.common.ui.iconSize import com.rafaelfelipeac.replyradar.core.common.ui.paddingMedium import com.rafaelfelipeac.replyradar.core.common.ui.spacerXSmall import com.rafaelfelipeac.replyradar.core.common.ui.tabRowTopPadding -import com.rafaelfelipeac.replyradar.core.common.ui.theme.snackbarBackgroundColor -import com.rafaelfelipeac.replyradar.core.common.ui.theme.toolbarIconsColor import com.rafaelfelipeac.replyradar.core.notification.LocalNotificationPermissionManager +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.CheckNotificationPermission import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.GoToSettings import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.RequestNotificationPermission import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState @@ -72,23 +55,19 @@ import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnToggleArchive import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnToggleResolve import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.ClearSnackbarState -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnAddReplyClick import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnTabSelected -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.RepliesArchivedScreen -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.RepliesOnTheRadarScreen -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.RepliesResolvedScreen +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.FAB +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.RepliesScreen +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.TopBar import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheet import kotlinx.coroutines.flow.Flow -import org.jetbrains.compose.resources.painterResource import org.koin.compose.viewmodel.koinViewModel -import replyradar.composeapp.generated.resources.Res.drawable -import replyradar.composeapp.generated.resources.ic_settings private const val WEIGHT = 1f private const val PAGER_PAGE_COUNT = 3 -private const val ON_THE_RADAR_INDEX = 0 -private const val RESOLVED_INDEX = 1 -private const val ARCHIVED_INDEX = 2 +const val ON_THE_RADAR_INDEX = 0 +const val RESOLVED_INDEX = 1 +const val ARCHIVED_INDEX = 2 @Composable fun ReplyListScreenRoot( @@ -137,36 +116,28 @@ fun ReplyListScreen( pagerState.animateScrollToPage(state.selectedTabIndex) } +// onIntent(ClearSnackbarState) // necessary? + LaunchedEffect(Unit) { effect.collect { effect -> when (effect) { - RequestNotificationPermission -> { - showPermissionDialog = true - } - is SnackbarState -> { - snackbarHostState.showSnackbar( - when (effect) { - Archived -> strings.replyListSnackbarArchived - Removed -> strings.replyListSnackbarRemoved - Reopened -> strings.replyListSnackbarReopened - Resolved -> strings.replyListSnackbarResolved - Unarchived -> strings.replyListSnackbarUnarchived - } - ) - + snackbarHostState.showSnackbar(getSnackbarMessage(effect, strings)) onIntent(ClearSnackbarState) } - GoToSettings -> { - notificationPermissionManager.goToAppSettings() - } + RequestNotificationPermission -> showPermissionDialog = true + + + GoToSettings -> notificationPermissionManager.goToAppSettings() + + is CheckNotificationPermission -> { + when { + notificationPermissionManager.ensureNotificationPermission() -> { + onIntent(OnAddOrEditReply(effect.reply)) + } - is ReplyListEffect.CheckNotificationPermission -> { - if (notificationPermissionManager.ensureNotificationPermission()) { - onIntent(OnAddOrEditReply(effect.reply)) - } else { - onIntent(OnRequestNotificationPermission) + else -> onIntent(OnRequestNotificationPermission) } } } @@ -185,7 +156,7 @@ fun ReplyListScreen( Scaffold( containerColor = colorScheme.background, - snackbarHost = { Snackbar(snackbarHostState) }, + snackbarHost = { ReplySnackbar(snackbarHostState) }, floatingActionButton = { FAB(onIntent, colorScheme) } ) { paddingValues -> Column( @@ -270,93 +241,30 @@ fun ReplyListScreen( onArchive = { onIntent(OnToggleArchive(it)) }, onDelete = { onIntent(OnDeleteReply(it)) }, onSave = { reply -> - if (reply.reminderAt != 0L) { - onIntent(OnCheckNotificationPermission(reply)) - } else { - onIntent(OnAddOrEditReply(reply)) + when { + reply.reminderAt != INITIAL_DATE -> { + onIntent(OnCheckNotificationPermission(reply)) + } + + else -> onIntent(OnAddOrEditReply(reply)) } }, - onGoToSettings = { onIntent(OnGoToSettings) }, onDismiss = { onIntent(OnDismissBottomSheet) }, replyBottomSheetState = state.replyBottomSheetState, - showPermissionDialog = showPermissionDialog, - onShowPermissionDialog = { value -> showPermissionDialog = value } ) } } } -@Composable -private fun FAB(onIntent: (ReplyListScreenIntent) -> Unit, colorScheme: ColorScheme) { - FloatingActionButton( - onClick = { onIntent(OnAddReplyClick) }, - containerColor = colorScheme.secondary - ) { - Icon( - imageVector = Icons.Filled.Add, - contentDescription = - LocalReplyRadarStrings.current.replyListFabContentDescription, - tint = colorScheme.background - ) - } -} - -@Composable -private fun Snackbar(snackbarHostState: SnackbarHostState) { - SnackbarHost( - hostState = snackbarHostState, - snackbar = { snackbarData -> - Snackbar( - snackbarData = snackbarData, - containerColor = colorScheme.snackbarBackgroundColor, - contentColor = colorScheme.onSurface - ) - } - ) -} - -@Composable -private fun TopBar(onActivityLogClick: () -> Unit, onSettingsClick: () -> Unit) { - Box( - modifier = Modifier - .fillMaxWidth() - ) { - Text( - modifier = Modifier - .padding(top = paddingMedium, start = paddingMedium) - .align(Alignment.CenterStart) - .clickable { onActivityLogClick() }, - textAlign = TextAlign.Center, - text = LocalReplyRadarStrings.current.replyListActivityLog, - style = typography.bodySmall, - color = colorScheme.toolbarIconsColor - ) - - Text( - modifier = Modifier - .padding(top = paddingMedium) - .align(Alignment.Center), - textAlign = TextAlign.Center, - text = LocalReplyRadarStrings.current.appName, - style = typography.titleLarge.copy(fontSize = fontSizeLarge), - color = colorScheme.onBackground - ) - - IconButton( - onClick = { onSettingsClick() }, - modifier = Modifier - .align(Alignment.CenterEnd) - .padding(top = paddingMedium, end = paddingMedium) - ) { - Icon( - modifier = Modifier - .size(iconSize), - painter = painterResource(drawable.ic_settings), - contentDescription = LocalReplyRadarStrings.current.settingsTitle, - tint = colorScheme.toolbarIconsColor - ) - } - } +private fun getSnackbarMessage( + effect: SnackbarState, + strings: Strings +) = when (effect) { + Archived -> strings.replyListSnackbarArchived + Removed -> strings.replyListSnackbarRemoved + Reopened -> strings.replyListSnackbarReopened + Resolved -> strings.replyListSnackbarResolved + Unarchived -> strings.replyListSnackbarUnarchived } @Composable diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListState.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListState.kt index 5ad0b30..4049572 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListState.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListState.kt @@ -1,7 +1,6 @@ package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetState data class ReplyListState( @@ -11,6 +10,5 @@ data class ReplyListState( val isLoading: Boolean = true, val errorMessage: String? = null, val selectedTabIndex: Int = 0, - val replyBottomSheetState: ReplyBottomSheetState? = null, - val snackbarState: SnackbarState? = null + val replyBottomSheetState: ReplyBottomSheetState? = null ) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt index b982e75..9e18a76 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt @@ -222,7 +222,7 @@ class ReplyListViewModel( logUserAction(actionType = if (isArchived) Archive else Unarchive, targetId = reply.id) - updateState { copy(snackbarState = if (isArchived) Archived else Unarchived) } + _effect.emit(if (isArchived) Archived else Unarchived) } private fun onToggleResolveReply(reply: Reply) = viewModelScope.launch { @@ -230,7 +230,7 @@ class ReplyListViewModel( logUserAction(actionType = if (isResolved) Resolve else Reopen, targetId = reply.id) - updateState { copy(snackbarState = if (isResolved) Resolved else Reopened) } + _effect.emit(if (isResolved) Resolved else Reopened) } private fun deleteReply(reply: Reply) = viewModelScope.launch { @@ -238,7 +238,7 @@ class ReplyListViewModel( logUserAction(actionType = Delete, targetId = reply.id) - updateState { copy(snackbarState = Removed) } + _effect.emit(Removed) } private fun dismissBottomSheet() { diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/FAB.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/FAB.kt new file mode 100644 index 0000000..18f75f0 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/FAB.kt @@ -0,0 +1,26 @@ +package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material3.ColorScheme +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnAddReplyClick + +@Composable +fun FAB(onIntent: (ReplyListScreenIntent) -> Unit, colorScheme: ColorScheme) { + FloatingActionButton( + onClick = { onIntent(OnAddReplyClick) }, + containerColor = colorScheme.secondary + ) { + Icon( + imageVector = Icons.Filled.Add, + contentDescription = + LocalReplyRadarStrings.current.replyListFabContentDescription, + tint = colorScheme.background + ) + } +} diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesScreen.kt new file mode 100644 index 0000000..d048080 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesScreen.kt @@ -0,0 +1,59 @@ +package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ARCHIVED_INDEX +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ON_THE_RADAR_INDEX +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.RESOLVED_INDEX +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListState + +@Composable +fun RepliesScreen( + pageIndex: Int, + state: ReplyListState, + onIntent: (ReplyListScreenIntent) -> Unit +) { + Box( + modifier = Modifier + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Column( + modifier = Modifier + .fillMaxSize() + .background(colorScheme.background), + verticalArrangement = Arrangement.Center + ) { + Box( + modifier = Modifier + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + when (pageIndex) { + ON_THE_RADAR_INDEX -> RepliesOnTheRadarScreen( + state = state, + onIntent = onIntent + ) + + RESOLVED_INDEX -> RepliesResolvedScreen( + state = state, + onIntent = onIntent + ) + + ARCHIVED_INDEX -> RepliesArchivedScreen( + state = state, + onIntent = onIntent + ) + } + } + } + } +} diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/ReplyTimestampInfo.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/ReplyTimestampInfo.kt new file mode 100644 index 0000000..c082272 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/ReplyTimestampInfo.kt @@ -0,0 +1,63 @@ +package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components + +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment.Companion.Start +import androidx.compose.ui.Modifier +import com.rafaelfelipeac.replyradar.core.AppConstants.INITIAL_DATE +import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall +import com.rafaelfelipeac.replyradar.core.util.format +import com.rafaelfelipeac.replyradar.core.util.formatTimestamp +import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetState + + +@Composable +fun ColumnScope.ReplyTimestampInfo( + state: ReplyBottomSheetState +) { + state.reply?.let { reply -> + Text( + modifier = Modifier + .padding(start = paddingSmall, top = paddingSmall) + .align(Start), + text = getTimestampInfo(reply), + style = typography.bodySmall + ) + } +} + +@Composable +private fun getTimestampInfo(reply: Reply): String { + with(reply) { + return when { + archivedAt != INITIAL_DATE -> format( + LocalReplyRadarStrings.current.replyListItemArchivedAt, + formatTimestamp(archivedAt) + ) + + resolvedAt != INITIAL_DATE -> format( + LocalReplyRadarStrings.current.replyListItemResolvedAt, + formatTimestamp(resolvedAt) + ) + + else -> { + if (updatedAt != INITIAL_DATE && updatedAt != createdAt) { + format( + LocalReplyRadarStrings.current.replyListItemUpdatedAt, + formatTimestamp(updatedAt) + ) + } else { + format( + LocalReplyRadarStrings.current.replyListItemCreatedAt, + formatTimestamp(createdAt) + ) + } + } + } + } +} diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/TopBar.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/TopBar.kt new file mode 100644 index 0000000..6aa295e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/TopBar.kt @@ -0,0 +1,68 @@ +package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.common.ui.fontSizeLarge +import com.rafaelfelipeac.replyradar.core.common.ui.iconSize +import com.rafaelfelipeac.replyradar.core.common.ui.paddingMedium +import com.rafaelfelipeac.replyradar.core.common.ui.theme.toolbarIconsColor +import org.jetbrains.compose.resources.painterResource +import replyradar.composeapp.generated.resources.Res.drawable +import replyradar.composeapp.generated.resources.ic_settings + +@Composable +fun TopBar(onActivityLogClick: () -> Unit, onSettingsClick: () -> Unit) { + Box( + modifier = Modifier + .fillMaxWidth() + ) { + Text( + modifier = Modifier + .padding(top = paddingMedium, start = paddingMedium) + .align(Alignment.CenterStart) + .clickable { onActivityLogClick() }, + textAlign = TextAlign.Center, + text = LocalReplyRadarStrings.current.replyListActivityLog, + style = typography.bodySmall, + color = colorScheme.toolbarIconsColor + ) + + Text( + modifier = Modifier + .padding(top = paddingMedium) + .align(Alignment.Center), + textAlign = TextAlign.Center, + text = LocalReplyRadarStrings.current.appName, + style = typography.titleLarge.copy(fontSize = fontSizeLarge), + color = colorScheme.onBackground + ) + + IconButton( + onClick = { onSettingsClick() }, + modifier = Modifier + .align(Alignment.CenterEnd) + .padding(top = paddingMedium, end = paddingMedium) + ) { + Icon( + modifier = Modifier + .size(iconSize), + painter = painterResource(drawable.ic_settings), + contentDescription = LocalReplyRadarStrings.current.settingsTitle, + tint = colorScheme.toolbarIconsColor + ) + } + } +} diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt index 55b1a00..3d34236 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt @@ -18,11 +18,8 @@ fun ReplyBottomSheet( onResolve: (Reply) -> Unit, onArchive: (Reply) -> Unit, onDelete: (Reply) -> Unit, - onGoToSettings: () -> Unit, onDismiss: () -> Unit, - replyBottomSheetState: ReplyBottomSheetState, - showPermissionDialog: Boolean, - onShowPermissionDialog: (Boolean) -> Unit + replyBottomSheetState: ReplyBottomSheetState ) { ModalBottomSheet( sheetState = sheetState, @@ -38,10 +35,7 @@ fun ReplyBottomSheet( onSave = onSave, onResolve = onResolve, onArchive = onArchive, - onDelete = onDelete, - onGoToSettings = onGoToSettings, - showPermissionDialog = showPermissionDialog, - onShowPermissionDialog = onShowPermissionDialog + onDelete = onDelete ) } @@ -55,10 +49,7 @@ fun ReplyBottomSheet( onSave = onSave, onResolve = onResolve, onArchive = onArchive, - onDelete = onDelete, - onGoToSettings = onGoToSettings, - showPermissionDialog = showPermissionDialog, - onShowPermissionDialog = onShowPermissionDialog, + onDelete = onDelete ) } } @@ -72,19 +63,13 @@ private fun BottomSheetContent( onSave: (Reply) -> Unit, onResolve: (Reply) -> Unit, onArchive: (Reply) -> Unit, - onDelete: (Reply) -> Unit, - onGoToSettings: () -> Unit, - showPermissionDialog: Boolean, - onShowPermissionDialog: (Boolean) -> Unit + onDelete: (Reply) -> Unit ) { ReplyBottomSheetContent( replyBottomSheetState = state, onResolve = onResolve, onArchive = onArchive, onDelete = onDelete, - onSave = onSave, - onGoToSettings = onGoToSettings, - showPermissionDialog = showPermissionDialog, - onShowPermissionDialog = onShowPermissionDialog, + onSave = onSave ) } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetActions.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetActions.kt new file mode 100644 index 0000000..cb1e902 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetActions.kt @@ -0,0 +1,227 @@ +package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet + +import androidx.compose.foundation.layout.Arrangement.End +import androidx.compose.foundation.layout.Arrangement.spacedBy +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import com.rafaelfelipeac.replyradar.core.common.clock.LocalClock +import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyButton +import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyConfirmationDialog +import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyOutlinedButton +import com.rafaelfelipeac.replyradar.core.common.ui.paddingMedium +import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall +import com.rafaelfelipeac.replyradar.core.util.format +import com.rafaelfelipeac.replyradar.core.util.getReminderTimestamp +import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetMode.EDIT +import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalTime +import replyradar.composeapp.generated.resources.Res.drawable +import replyradar.composeapp.generated.resources.ic_archive +import replyradar.composeapp.generated.resources.ic_check +import replyradar.composeapp.generated.resources.ic_delete +import replyradar.composeapp.generated.resources.ic_reopen +import replyradar.composeapp.generated.resources.ic_unarchive + +private const val WEIGHT = 1f + +@Composable +fun ReplyBottomSheetActions( + state: ReplyBottomSheetState, + onArchive: (Reply) -> Unit, + onResolve: (Reply) -> Unit, + onDelete: (Reply) -> Unit, + onSave: (Reply) -> Unit, + selectedDate: LocalDate?, + selectedTime: LocalTime?, + reply: Reply?, + name: String, + subject: String, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = paddingSmall, bottom = paddingMedium, end = paddingSmall), + horizontalArrangement = if (isEditMode(state)) spacedBy(paddingSmall) else End, + verticalAlignment = Alignment.CenterVertically + ) { + StateButtons( + state = state, + onArchive = onArchive, + onResolve = onResolve, + onDelete = onDelete + ) + + val reminderAtTimestamp = getReminderTimestamp( + now = LocalClock.current.now(), + selectedDate = selectedDate, + selectedTime = selectedTime + ) + + ReplyButton( + modifier = Modifier + .wrapContentWidth() + .align(Alignment.CenterVertically), + text = if (reply == null) { + LocalReplyRadarStrings.current.replyListBottomSheetAdd + } else { + LocalReplyRadarStrings.current.replyListBottomSheetSave + }, + onClick = { + onSave( + getReplyToSave( + stateReply = state.reply, + name = name, + subject = subject, + reminderAtTimestamp = reminderAtTimestamp + ) + ) + }, + enabled = name.isNotBlank() + ) + } +} + +@Composable +private fun RowScope.StateButtons( + state: ReplyBottomSheetState, + onArchive: (Reply) -> Unit, + onResolve: (Reply) -> Unit, + onDelete: (Reply) -> Unit +) { + if (state.reply != null && isEditMode(state)) { + Row( + modifier = Modifier.Companion + .weight(WEIGHT) + ) { + with(state.reply) { + when { + !isArchived && !isResolved -> { + ActiveStateButtons( + reply = state.reply, + onArchive = onArchive, + onResolve = onResolve + ) + } + + isResolved && !isArchived -> { + ResolvedStateButtons( + reply = state.reply, + onArchive = onArchive, + onResolve = onResolve + ) + } + + isArchived -> { + ArchivedStateButton( + reply = state.reply, + onArchive = onArchive, + onDelete = onDelete + ) + } + } + } + } + } +} + +@Composable +private fun ActiveStateButtons( + reply: Reply, + onArchive: (Reply) -> Unit, + onResolve: (Reply) -> Unit +) { + ReplyOutlinedButton( + text = LocalReplyRadarStrings.current.replyListBottomSheetResolve, + icon = drawable.ic_check, + onClick = { onResolve(reply) } + ) + + ReplyOutlinedButton( + text = LocalReplyRadarStrings.current.replyListBottomSheetArchive, + icon = drawable.ic_archive, + onClick = { onArchive(reply) } + ) +} + +@Composable +private fun ResolvedStateButtons( + reply: Reply, + onArchive: (Reply) -> Unit, + onResolve: (Reply) -> Unit +) { + ReplyOutlinedButton( + text = LocalReplyRadarStrings.current.replyListBottomSheetReopen, + icon = drawable.ic_reopen, + onClick = { onResolve(reply) } + ) + + ReplyOutlinedButton( + text = LocalReplyRadarStrings.current.replyListBottomSheetArchive, + icon = drawable.ic_archive, + onClick = { onArchive(reply) } + ) +} + +@Composable +private fun ArchivedStateButton( + reply: Reply, + onArchive: (Reply) -> Unit, + onDelete: (Reply) -> Unit +) { + var showDeleteDialog by remember { mutableStateOf(false) } + + ReplyOutlinedButton( + text = LocalReplyRadarStrings.current.replyListBottomSheetUnarchive, + icon = drawable.ic_unarchive, + onClick = { onArchive(reply) } + ) + + ReplyOutlinedButton( + text = LocalReplyRadarStrings.current.replyListBottomSheetDelete, + icon = drawable.ic_delete, + onClick = { showDeleteDialog = true } + ) + + if (showDeleteDialog) { + ReplyConfirmationDialog( + title = LocalReplyRadarStrings.current.replyListDeleteDialogTitle, + description = format( + LocalReplyRadarStrings.current.replyListDeleteDialogDescription, + reply.name + ), + confirm = LocalReplyRadarStrings.current.replyListDeleteDialogConfirm, + dismiss = LocalReplyRadarStrings.current.replyListDeleteDialogDismiss, + onDismiss = { showDeleteDialog = false }, + onConfirm = { onDelete(reply) } + ) + } +} + +private fun isEditMode(state: ReplyBottomSheetState) = state.replyBottomSheetMode == EDIT + +private fun getReplyToSave( + stateReply: Reply?, + name: String, + subject: String, + reminderAtTimestamp: Long +) = stateReply?.copy( + name = name, + subject = subject, + reminderAt = reminderAtTimestamp +) ?: Reply( + name = name, + subject = subject, + reminderAt = reminderAtTimestamp +) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt index 3afbc9c..6168948 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt @@ -1,67 +1,35 @@ package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement.End -import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.CenterHorizontally -import androidx.compose.ui.Alignment.Companion.Start import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.platform.LocalSoftwareKeyboardController import com.rafaelfelipeac.replyradar.core.AppConstants.EMPTY import com.rafaelfelipeac.replyradar.core.AppConstants.INITIAL_DATE -import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_DEFAULT_HOUR -import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_DEFAULT_MINUTE -import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_TOMORROW_OFFSET -import com.rafaelfelipeac.replyradar.core.common.clock.LocalClock import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings -import com.rafaelfelipeac.replyradar.core.common.ui.components.NotificationPermissionDialog -import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyButton -import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyConfirmationDialog -import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyOutlinedButton import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyReminder import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyTextField import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyTextFieldSize.Large import com.rafaelfelipeac.replyradar.core.common.ui.paddingMedium import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall -import com.rafaelfelipeac.replyradar.core.util.format -import com.rafaelfelipeac.replyradar.core.util.formatTimestamp import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetMode.EDIT -import kotlinx.datetime.DateTimeUnit.Companion.DAY +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.ReplyTimestampInfo import kotlinx.datetime.Instant -import kotlinx.datetime.LocalDate -import kotlinx.datetime.LocalDateTime -import kotlinx.datetime.LocalTime import kotlinx.datetime.TimeZone -import kotlinx.datetime.plus -import kotlinx.datetime.toInstant import kotlinx.datetime.toLocalDateTime -import replyradar.composeapp.generated.resources.Res.drawable -import replyradar.composeapp.generated.resources.ic_archive -import replyradar.composeapp.generated.resources.ic_check -import replyradar.composeapp.generated.resources.ic_delete -import replyradar.composeapp.generated.resources.ic_reopen -import replyradar.composeapp.generated.resources.ic_unarchive - -private const val WEIGHT = 1f @Composable fun ReplyBottomSheetContent( @@ -69,10 +37,7 @@ fun ReplyBottomSheetContent( onSave: (Reply) -> Unit, onResolve: (Reply) -> Unit, onArchive: (Reply) -> Unit, - onDelete: (Reply) -> Unit, - onGoToSettings: () -> Unit, - showPermissionDialog: Boolean, - onShowPermissionDialog: (Boolean) -> Unit + onDelete: (Reply) -> Unit ) { replyBottomSheetState?.let { state -> var name by remember { mutableStateOf(state.reply?.name ?: EMPTY) } @@ -117,15 +82,7 @@ fun ReplyBottomSheetContent( onValueChange = { subject = it } ) - state.reply?.let { reply -> - Text( - modifier = Modifier - .padding(start = paddingSmall, top = paddingSmall) - .align(Start), - text = getTimestamp(reply), - style = typography.bodySmall - ) - } + ReplyTimestampInfo(state) ReplyReminder( selectedTime = selectedTime, @@ -135,291 +92,18 @@ fun ReplyBottomSheetContent( closeKeyboard = { keyboardController?.hide() } ) - Buttons( + ReplyBottomSheetActions( state = replyBottomSheetState, onArchive = onArchive, onResolve = onResolve, - onSave = onSave, onDelete = onDelete, - onGoToSettings = onGoToSettings, + onSave = onSave, selectedDate = selectedDate, selectedTime = selectedTime, reply = state.reply, name = name, subject = subject, - onShowPermissionDialog = onShowPermissionDialog, - showPermissionDialog = showPermissionDialog, - ) - } - } -} - -@Composable -private fun Buttons( - state: ReplyBottomSheetState, - onArchive: (Reply) -> Unit, - onResolve: (Reply) -> Unit, - onDelete: (Reply) -> Unit, - onSave: (Reply) -> Unit, - onGoToSettings: () -> Unit, - selectedDate: LocalDate?, - selectedTime: LocalTime?, - reply: Reply?, - name: String, - subject: String, - showPermissionDialog: Boolean, - onShowPermissionDialog: (Boolean) -> Unit, -) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(top = paddingSmall, bottom = paddingMedium, end = paddingSmall), - horizontalArrangement = if (isEditMode(state)) spacedBy(paddingSmall) else End, - verticalAlignment = Alignment.CenterVertically - ) { - StateButtons( - state = state, - onArchive = onArchive, - onResolve = onResolve, - onDelete = onDelete - ) - - val reminderAtTimestamp = getReminderTimestamp( - selectedDate = selectedDate, - selectedTime = selectedTime - ) - - ReplyButton( - modifier = Modifier - .wrapContentWidth() - .align(Alignment.CenterVertically), - text = if (reply == null) { - LocalReplyRadarStrings.current.replyListBottomSheetAdd - } else { - LocalReplyRadarStrings.current.replyListBottomSheetSave - }, - onClick = { - onSave( - getReplyToSave( - stateReply = state.reply, - name = name, - subject = subject, - reminderAtTimestamp = reminderAtTimestamp - ) - ) - }, - enabled = name.isNotBlank() - ) - - if (showPermissionDialog) { - NotificationPermissionDialog( - onDismiss = { onShowPermissionDialog(false) }, - onGoToSettings = { - onShowPermissionDialog(false) - onGoToSettings() - } ) } } } - -private fun getReplyToSave( - stateReply: Reply?, - name: String, - subject: String, - reminderAtTimestamp: Long -) = stateReply?.copy( - name = name, - subject = subject, - reminderAt = reminderAtTimestamp -) ?: Reply( - name = name, - subject = subject, - reminderAt = reminderAtTimestamp -) - -@Composable -private fun RowScope.StateButtons( - state: ReplyBottomSheetState, - onArchive: (Reply) -> Unit, - onResolve: (Reply) -> Unit, - onDelete: (Reply) -> Unit -) { - if (state.reply != null && isEditMode(state)) { - Row( - modifier = Modifier.Companion - .weight(WEIGHT) - ) { - with(state.reply) { - when { - !isArchived && !isResolved -> { - ActiveStateButtons( - reply = state.reply, - onArchive = onArchive, - onResolve = onResolve - ) - } - - isResolved && !isArchived -> { - ResolvedStateButtons( - reply = state.reply, - onArchive = onArchive, - onResolve = onResolve - ) - } - - isArchived -> { - ArchivedStateButton( - reply = state.reply, - onArchive = onArchive, - onDelete = onDelete - ) - } - } - } - } - } -} - -private fun isEditMode(state: ReplyBottomSheetState) = state.replyBottomSheetMode == EDIT - -@Composable -private fun ActiveStateButtons( - reply: Reply, - onArchive: (Reply) -> Unit, - onResolve: (Reply) -> Unit -) { - ReplyOutlinedButton( - text = LocalReplyRadarStrings.current.replyListBottomSheetResolve, - icon = drawable.ic_check, - onClick = { onResolve(reply) } - ) - - ReplyOutlinedButton( - text = LocalReplyRadarStrings.current.replyListBottomSheetArchive, - icon = drawable.ic_archive, - onClick = { onArchive(reply) } - ) -} - -@Composable -private fun ResolvedStateButtons( - reply: Reply, - onArchive: (Reply) -> Unit, - onResolve: (Reply) -> Unit -) { - ReplyOutlinedButton( - text = LocalReplyRadarStrings.current.replyListBottomSheetReopen, - icon = drawable.ic_reopen, - onClick = { onResolve(reply) } - ) - - ReplyOutlinedButton( - text = LocalReplyRadarStrings.current.replyListBottomSheetArchive, - icon = drawable.ic_archive, - onClick = { onArchive(reply) } - ) -} - -@Composable -private fun ArchivedStateButton( - reply: Reply, - onArchive: (Reply) -> Unit, - onDelete: (Reply) -> Unit -) { - var showDeleteDialog by remember { mutableStateOf(false) } - - ReplyOutlinedButton( - text = LocalReplyRadarStrings.current.replyListBottomSheetUnarchive, - icon = drawable.ic_unarchive, - onClick = { onArchive(reply) } - ) - - ReplyOutlinedButton( - text = LocalReplyRadarStrings.current.replyListBottomSheetDelete, - icon = drawable.ic_delete, - onClick = { showDeleteDialog = true } - ) - - if (showDeleteDialog) { - ReplyConfirmationDialog( - title = LocalReplyRadarStrings.current.replyListDeleteDialogTitle, - description = format( - LocalReplyRadarStrings.current.replyListDeleteDialogDescription, - reply.name - ), - confirm = LocalReplyRadarStrings.current.replyListDeleteDialogConfirm, - dismiss = LocalReplyRadarStrings.current.replyListDeleteDialogDismiss, - onDismiss = { showDeleteDialog = false }, - onConfirm = { onDelete(reply) } - ) - } -} - -@Composable -private fun getTimestamp(reply: Reply): String { - with(reply) { - return when { - archivedAt != INITIAL_DATE -> format( - LocalReplyRadarStrings.current.replyListItemArchivedAt, - formatTimestamp(archivedAt) - ) - - resolvedAt != INITIAL_DATE -> format( - LocalReplyRadarStrings.current.replyListItemResolvedAt, - formatTimestamp(resolvedAt) - ) - - else -> { - if (updatedAt != INITIAL_DATE && updatedAt != createdAt) { - format( - LocalReplyRadarStrings.current.replyListItemUpdatedAt, - formatTimestamp(updatedAt) - ) - } else { - format( - LocalReplyRadarStrings.current.replyListItemCreatedAt, - formatTimestamp(createdAt) - ) - } - } - } - } -} - -@Composable -fun getReminderTimestamp( - selectedDate: LocalDate?, - selectedTime: LocalTime? -): Long { - val timeZone = TimeZone.currentSystemDefault() - val nowDateTime = - Instant.fromEpochMilliseconds(LocalClock.current.now()).toLocalDateTime(timeZone) - - val finalDate = when { - selectedDate != null -> selectedDate - selectedTime != null -> { - val timeToday = LocalDateTime( - date = nowDateTime.date, - time = selectedTime - ) - - if (timeToday > nowDateTime) { - nowDateTime.date - } else { - nowDateTime.date.plus( - REMINDER_TOMORROW_OFFSET, - DAY - ) - } - } - - else -> return INITIAL_DATE - } - - val finalTime = selectedTime ?: LocalTime(REMINDER_DEFAULT_HOUR, REMINDER_DEFAULT_MINUTE) - - val finalDateTime = LocalDateTime(finalDate, finalTime) - - return finalDateTime.toInstant(timeZone).toEpochMilliseconds() -} From 69faa9b05d18250f24f875aea014d2824bfaa315 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Mon, 23 Jun 2025 11:26:09 -0300 Subject: [PATCH 29/59] Improvements. --- .../presentation/replylist/ReplyListScreen.kt | 43 ------------------- 1 file changed, 43 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt index 53fea64..6be1363 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt @@ -266,46 +266,3 @@ private fun getSnackbarMessage( Resolved -> strings.replyListSnackbarResolved Unarchived -> strings.replyListSnackbarUnarchived } - -@Composable -private fun RepliesScreen( - pageIndex: Int, - state: ReplyListState, - onIntent: (ReplyListScreenIntent) -> Unit -) { - Box( - modifier = Modifier - .fillMaxSize(), - contentAlignment = Alignment.Center - ) { - Column( - modifier = Modifier - .fillMaxSize() - .background(colorScheme.background), - verticalArrangement = Arrangement.Center - ) { - Box( - modifier = Modifier - .fillMaxSize(), - contentAlignment = Alignment.Center - ) { - when (pageIndex) { - ON_THE_RADAR_INDEX -> RepliesOnTheRadarScreen( - state = state, - onIntent = onIntent - ) - - RESOLVED_INDEX -> RepliesResolvedScreen( - state = state, - onIntent = onIntent - ) - - ARCHIVED_INDEX -> RepliesArchivedScreen( - state = state, - onIntent = onIntent - ) - } - } - } - } -} From f3ef69a34fe4b7566ad2df522c6f658b7e5f8e16 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Mon, 23 Jun 2025 11:27:27 -0300 Subject: [PATCH 30/59] Improvements. --- .../reply/presentation/replylist/ReplyListScreen.kt | 12 ++++++------ .../presentation/replylist/ReplyListScreenIntent.kt | 1 - .../presentation/replylist/ReplyListViewModel.kt | 6 ------ 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt index 6be1363..1502951 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt @@ -116,15 +116,15 @@ fun ReplyListScreen( pagerState.animateScrollToPage(state.selectedTabIndex) } -// onIntent(ClearSnackbarState) // necessary? - LaunchedEffect(Unit) { effect.collect { effect -> when (effect) { - is SnackbarState -> { - snackbarHostState.showSnackbar(getSnackbarMessage(effect, strings)) - onIntent(ClearSnackbarState) - } + is SnackbarState -> snackbarHostState.showSnackbar( + getSnackbarMessage( + effect, + strings + ) + ) RequestNotificationPermission -> showPermissionDialog = true diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt index df7838e..9335d5c 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt @@ -9,7 +9,6 @@ sealed interface ReplyListScreenIntent { data class OnTabSelected(val index: Int) : ReplyListIntent data class OnReplyClick(val reply: Reply) : ReplyListIntent data class OnReplyToggle(val reply: Reply) : ReplyListIntent - data object ClearSnackbarState : ReplyListIntent } sealed interface ReplyBottomSheetIntent : ReplyListScreenIntent { diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt index 9e18a76..e287295 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt @@ -126,12 +126,6 @@ class ReplyListViewModel( copy(selectedTabIndex = intent.index) } } - - ClearSnackbarState -> { - updateState { - copy(snackbarState = null) - } - } } } From 3b42433cea6c636641e356145196e9aa1bbcd407 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Mon, 23 Jun 2025 11:27:55 -0300 Subject: [PATCH 31/59] Improvements. --- .../features/reply/presentation/replylist/ReplyListScreen.kt | 1 - .../features/reply/presentation/replylist/ReplyListViewModel.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt index 1502951..60d6f96 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt @@ -54,7 +54,6 @@ import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnDismissBottomSheet import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnToggleArchive import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnToggleResolve -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.ClearSnackbarState import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnTabSelected import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.FAB import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.RepliesScreen diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt index e287295..178ad2b 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt @@ -28,7 +28,6 @@ import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnToggleArchive import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnToggleResolve import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.ClearSnackbarState import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnAddReplyClick import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnReplyClick import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnReplyToggle From 970305f0d5041b3b9d2ee9061daf928ba6d14fef Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Wed, 25 Jun 2025 20:48:40 -0300 Subject: [PATCH 32/59] Fixed and improvements. --- .../rememberNotificationPermissionManager.kt | 2 +- .../core/util/PlatformDatePicker.android.kt | 13 ++++++++----- .../core/util/PlatformTimePicker.android.kt | 10 ++++++---- .../core/common/ui/components/ReplyReminder.kt | 3 +-- .../replyradar/core/util/PlatformDatePicker.kt | 6 +++--- .../core/util/reminder/ReminderScheduler.kt | 1 + .../core/util/PlatformDatePicker.desktop.kt | 6 +++--- .../replyradar/core/util/PlatformDatePicker.ios.kt | 6 +++--- 8 files changed, 26 insertions(+), 21 deletions(-) diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt index 758163d..1d3e442 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt @@ -15,7 +15,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.platform.LocalContext import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat +import androidx.core.content.ContextCompat.checkSelfPermission import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.filterNotNull diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt index 982a803..1467a05 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt @@ -61,15 +61,17 @@ actual fun PlatformDatePicker( confirmButton = { TextButton( onClick = { - val millis = datePickerState.selectedDateMillis - if (millis != null) { - val pickedDate = millis.toLocalDate() + val dateInMillis = datePickerState.selectedDateMillis + + if (dateInMillis != null) { + val pickedDate = dateInMillis.toLocalDate() onDateSelected(pickedDate) - selectedTime?.let { + + selectedTime?.let { selectedTime -> val isStillValid = isDateTimeValid( date = pickedDate, - time = it, + time = selectedTime, now = now ) @@ -78,6 +80,7 @@ actual fun PlatformDatePicker( } } ?: onTimeInvalidated() } + onDismiss() showDialog = false } diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt index 74849a9..4c0d0ca 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt @@ -60,10 +60,12 @@ actual fun PlatformTimePicker( } }, dismissButton = { - TextButton(onClick = { - onDismiss() - showDialog = false - }) { + TextButton( + onClick = { + onDismiss() + showDialog = false + } + ) { Text(dismissButtonText) } }, diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt index 746581d..4e160ab 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt @@ -171,8 +171,7 @@ private fun ReminderText( text = reminderText, modifier = Modifier .padding(start = paddingSmall, top = paddingSmall), - style = typography.bodyMedium, - color = colorScheme.textFieldPlaceholderColor + style = typography.bodySmall ) IconButton( diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.kt index 1997424..fc9bfd4 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.kt @@ -9,8 +9,8 @@ expect fun PlatformDatePicker( selectedDate: LocalDate?, selectedTime: LocalTime?, onDateSelected: (LocalDate) -> Unit, - onTimeInvalidated: () -> Unit, - onDismiss: () -> Unit, confirmButtonText: String, - dismissButtonText: String + dismissButtonText: String, + onTimeInvalidated: () -> Unit, + onDismiss: () -> Unit ) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderScheduler.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderScheduler.kt index 9f82e99..5ff4c25 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderScheduler.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderScheduler.kt @@ -1,6 +1,7 @@ package com.rafaelfelipeac.replyradar.core.util.reminder interface ReminderScheduler { + fun scheduleReminder( reminderAtMillis: Long, name: String, diff --git a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.desktop.kt b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.desktop.kt index e21e37f..38b69b3 100644 --- a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.desktop.kt +++ b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.desktop.kt @@ -9,10 +9,10 @@ actual fun PlatformDatePicker( selectedDate: LocalDate?, selectedTime: LocalTime?, onDateSelected: (LocalDate) -> Unit, - onTimeInvalidated: () -> Unit, - onDismiss: () -> Unit, confirmButtonText: String, - dismissButtonText: String + dismissButtonText: String, + onTimeInvalidated: () -> Unit, + onDismiss: () -> Unit ) { TODO("Not yet implemented for this platform.") } diff --git a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.ios.kt b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.ios.kt index e21e37f..38b69b3 100644 --- a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.ios.kt @@ -9,10 +9,10 @@ actual fun PlatformDatePicker( selectedDate: LocalDate?, selectedTime: LocalTime?, onDateSelected: (LocalDate) -> Unit, - onTimeInvalidated: () -> Unit, - onDismiss: () -> Unit, confirmButtonText: String, - dismissButtonText: String + dismissButtonText: String, + onTimeInvalidated: () -> Unit, + onDismiss: () -> Unit ) { TODO("Not yet implemented for this platform.") } From 529cb09c2100f90bd69cf86ffffa9107e9b099b2 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Thu, 26 Jun 2025 11:39:32 -0300 Subject: [PATCH 33/59] Datetime, packages and Modifiers improvements. --- .../core/util/{ => datetime}/Clock.kt | 2 +- .../core/util/datetime/DatetimeExt.kt | 5 +- .../PlatformDatePicker.android.kt | 29 ++--- .../PlatformTimePicker.android.kt | 10 +- .../replyradar/app/ReplyRadarApp.kt | 2 +- .../core/common/clock/LocalClock.kt | 2 +- .../common/ui/components/ReplyReminder.kt | 37 +++--- .../core/common/ui/components/ReplyToggle.kt | 6 +- .../ui/components/util/FormatReminderText.kt | 70 +++++++++++ .../core/util/{ => datetime}/Clock.kt | 2 +- .../replyradar/core/util/datetime/Datetime.kt | 111 +++++------------- .../util/{ => datetime}/PlatformDatePicker.kt | 2 +- .../util/{ => datetime}/PlatformTimePicker.kt | 2 +- .../core/util/{ => datetime}/Timestamp.kt | 22 ++-- .../rafaelfelipeac/replyradar/di/Modules.kt | 4 +- .../presentation/ActivityLogScreen.kt | 2 +- .../data/repository/ReplyRepositoryImpl.kt | 2 +- .../presentation/replylist/ReplyListScreen.kt | 4 +- .../{FAB.kt => FloatingActionButton.kt} | 2 +- .../components/ReplyTimestampInfo.kt | 2 +- .../replylist/components/TopBar.kt | 4 +- .../ReplyBottomSheetActions.kt | 5 +- .../ReplyBottomSheetContent.kt | 12 +- .../settings/presentation/SettingsScreen.kt | 8 +- .../repository/UserActionRepositoryImpl.kt | 2 +- .../replyradar/fakes/core/util/FakeClock.kt | 2 +- .../com/rafaelfelipeac/replyradar/Main.kt | 4 + .../core/util/{ => datetime}/Clock.desktop.kt | 2 +- .../PlatformDatePicker.desktop.kt | 2 +- .../PlatformTimePicker.desktop.kt | 2 +- .../replyradar/MainViewController.kt | 4 + .../core/util/{ => datetime}/Clock.ios.kt | 2 +- .../{ => datetime}/PlatformDatePicker.ios.kt | 2 +- .../{ => datetime}/PlatformTimePicker.ios.kt | 2 +- 34 files changed, 191 insertions(+), 180 deletions(-) rename composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/{ => datetime}/Clock.kt (65%) rename composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/{ => datetime}/PlatformDatePicker.android.kt (78%) rename composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/{ => datetime}/PlatformTimePicker.android.kt (89%) create mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/util/FormatReminderText.kt rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/{ => datetime}/Clock.kt (55%) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/{ => datetime}/PlatformDatePicker.kt (87%) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/{ => datetime}/PlatformTimePicker.kt (86%) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/{ => datetime}/Timestamp.kt (70%) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/{FAB.kt => FloatingActionButton.kt} (91%) rename composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/{ => datetime}/Clock.desktop.kt (65%) rename composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/{ => datetime}/PlatformDatePicker.desktop.kt (88%) rename composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/{ => datetime}/PlatformTimePicker.desktop.kt (88%) rename composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/{ => datetime}/Clock.ios.kt (83%) rename composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/{ => datetime}/PlatformDatePicker.ios.kt (88%) rename composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/{ => datetime}/PlatformTimePicker.ios.kt (88%) diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Clock.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Clock.kt similarity index 65% rename from composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Clock.kt rename to composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Clock.kt index d6063c4..a83de67 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Clock.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Clock.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util +package com.rafaelfelipeac.replyradar.core.util.datetime actual fun getClock(): Clock = object : Clock { override fun now(): Long = System.currentTimeMillis() diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/DatetimeExt.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/DatetimeExt.kt index b114330..29fce45 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/DatetimeExt.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/DatetimeExt.kt @@ -3,7 +3,6 @@ package com.rafaelfelipeac.replyradar.core.util.datetime import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate import kotlinx.datetime.TimeZone -import kotlinx.datetime.TimeZone.Companion.UTC import kotlinx.datetime.atStartOfDayIn import kotlinx.datetime.toLocalDateTime @@ -11,8 +10,8 @@ fun LocalDate.toEpochMillis(): Long { return this.atStartOfDayIn(TimeZone.currentSystemDefault()).toEpochMilliseconds() } -fun Long.toLocalDate(): LocalDate { +fun Long.toLocalDate(timeZone: TimeZone = TimeZone.currentSystemDefault()): LocalDate { return Instant.fromEpochMilliseconds(this) - .toLocalDateTime(UTC) + .toLocalDateTime(timeZone) .date } diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformDatePicker.android.kt similarity index 78% rename from composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt rename to composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformDatePicker.android.kt index 1467a05..9130b37 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.android.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformDatePicker.android.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util +package com.rafaelfelipeac.replyradar.core.util.datetime import androidx.compose.material3.DatePicker import androidx.compose.material3.DatePickerDialog @@ -12,16 +12,10 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import com.rafaelfelipeac.replyradar.core.util.datetime.isDateTimeValid -import com.rafaelfelipeac.replyradar.core.util.datetime.toEpochMillis -import com.rafaelfelipeac.replyradar.core.util.datetime.toLocalDate -import kotlinx.datetime.Clock -import kotlinx.datetime.Instant +import com.rafaelfelipeac.replyradar.core.common.clock.LocalClock import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalTime -import kotlinx.datetime.TimeZone import kotlinx.datetime.TimeZone.Companion.UTC -import kotlinx.datetime.toLocalDateTime @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -29,23 +23,20 @@ actual fun PlatformDatePicker( selectedDate: LocalDate?, selectedTime: LocalTime?, onDateSelected: (LocalDate) -> Unit, - onTimeInvalidated: () -> Unit, - onDismiss: () -> Unit, confirmButtonText: String, - dismissButtonText: String + dismissButtonText: String, + onTimeInvalidated: () -> Unit, + onDismiss: () -> Unit ) { - val now = Clock.System.now() - .toLocalDateTime(TimeZone.currentSystemDefault()) + val dateTime = LocalClock.current.now().dateTime() val datePickerState = rememberDatePickerState( initialSelectedDateMillis = selectedDate?.toEpochMillis(), selectableDates = object : SelectableDates { override fun isSelectableDate(utcTimeMillis: Long): Boolean { - val candidateDate = Instant.fromEpochMilliseconds(utcTimeMillis) - .toLocalDateTime(UTC) - .date + val candidateDate = utcTimeMillis.toLocalDate(UTC) - return candidateDate >= now.date + return candidateDate >= dateTime.date } } ) @@ -64,7 +55,7 @@ actual fun PlatformDatePicker( val dateInMillis = datePickerState.selectedDateMillis if (dateInMillis != null) { - val pickedDate = dateInMillis.toLocalDate() + val pickedDate = dateInMillis.toLocalDate(UTC) onDateSelected(pickedDate) @@ -72,7 +63,7 @@ actual fun PlatformDatePicker( val isStillValid = isDateTimeValid( date = pickedDate, time = selectedTime, - now = now + dateTime = dateTime ) if (!isStillValid) { diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformTimePicker.android.kt similarity index 89% rename from composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt rename to composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformTimePicker.android.kt index 4c0d0ca..1a6ddf6 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.android.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformTimePicker.android.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util +package com.rafaelfelipeac.replyradar.core.util.datetime import androidx.compose.material3.AlertDialog import androidx.compose.material3.ExperimentalMaterial3Api @@ -11,7 +11,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import com.rafaelfelipeac.replyradar.core.util.datetime.isTimeValid +import com.rafaelfelipeac.replyradar.core.common.clock.LocalClock import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalTime @@ -38,7 +38,11 @@ actual fun PlatformTimePicker( if (showDialog) { val pickedTime = LocalTime(timePickerState.hour, timePickerState.minute) - val isValid = isTimeValid(selectedDate, pickedTime) + val isValid = isTimeValid( + dateTime = LocalClock.current.now().dateTime(), + date = selectedDate, + time = pickedTime + ) AlertDialog( onDismissRequest = { diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt index 1157e63..2d62154 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt @@ -19,7 +19,7 @@ import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme.SYSTEM import com.rafaelfelipeac.replyradar.core.navigation.AppNavHost import com.rafaelfelipeac.replyradar.core.notification.LocalNotificationPermissionManager import com.rafaelfelipeac.replyradar.core.notification.NotificationPermissionManager -import com.rafaelfelipeac.replyradar.core.util.getClock +import com.rafaelfelipeac.replyradar.core.util.datetime.getClock import com.rafaelfelipeac.replyradar.features.app.settings.AppSettingsViewModel import org.koin.compose.viewmodel.koinViewModel diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/clock/LocalClock.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/clock/LocalClock.kt index 1e2a697..a982749 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/clock/LocalClock.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/clock/LocalClock.kt @@ -1,7 +1,7 @@ package com.rafaelfelipeac.replyradar.core.common.clock import androidx.compose.runtime.staticCompositionLocalOf -import com.rafaelfelipeac.replyradar.core.util.Clock +import com.rafaelfelipeac.replyradar.core.util.datetime.Clock val LocalClock = staticCompositionLocalOf { error("No Clock provided") diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt index 4e160ab..0a4452f 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt @@ -18,6 +18,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.common.ui.components.util.formatReminderText import com.rafaelfelipeac.replyradar.core.common.ui.iconButtonSize import com.rafaelfelipeac.replyradar.core.common.ui.iconSize import com.rafaelfelipeac.replyradar.core.common.ui.listDividerThickness @@ -25,11 +26,9 @@ import com.rafaelfelipeac.replyradar.core.common.ui.paddingLarge import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall import com.rafaelfelipeac.replyradar.core.common.ui.paddingXSmall import com.rafaelfelipeac.replyradar.core.common.ui.theme.horizontalDividerColor -import com.rafaelfelipeac.replyradar.core.common.ui.theme.textFieldPlaceholderColor import com.rafaelfelipeac.replyradar.core.common.ui.theme.toolbarIconsColor -import com.rafaelfelipeac.replyradar.core.util.PlatformDatePicker -import com.rafaelfelipeac.replyradar.core.util.PlatformTimePicker -import com.rafaelfelipeac.replyradar.core.util.datetime.formatReminder +import com.rafaelfelipeac.replyradar.core.util.datetime.PlatformDatePicker +import com.rafaelfelipeac.replyradar.core.util.datetime.PlatformTimePicker import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalTime import org.jetbrains.compose.resources.painterResource @@ -48,7 +47,7 @@ fun ReplyReminder( ) { var showTimePicker by remember { mutableStateOf(false) } var showDatePicker by remember { mutableStateOf(false) } - val reminderText = formatReminder( + val reminderText = formatReminderText( selectedDate = selectedDate, selectedTime = selectedTime, onTimeSelected = { onSelectedTimeChange(it) } @@ -75,16 +74,16 @@ fun ReplyReminder( PlatformTimePicker( selectedTime = selectedTime, selectedDate = selectedDate, + confirmButtonText = LocalReplyRadarStrings.current.replyListReminderTimePickerConfirmButton, + dismissButtonText = LocalReplyRadarStrings.current.replyListReminderTimePickerDismissButton, + pickerTimeTitle = LocalReplyRadarStrings.current.replyListReminderTimePickerTitle, onTimeSelected = { onSelectedTimeChange(it) showTimePicker = false }, onDismiss = { showTimePicker = false - }, - confirmButtonText = LocalReplyRadarStrings.current.replyListReminderTimePickerConfirmButton, - dismissButtonText = LocalReplyRadarStrings.current.replyListReminderTimePickerDismissButton, - pickerTimeTitle = LocalReplyRadarStrings.current.replyListReminderTimePickerTitle, + } ) } @@ -92,6 +91,8 @@ fun ReplyReminder( PlatformDatePicker( selectedDate = selectedDate, selectedTime = selectedTime, + confirmButtonText = LocalReplyRadarStrings.current.replyListReminderDatePickerConfirmButton, + dismissButtonText = LocalReplyRadarStrings.current.replyListReminderDatePickerDismissButton, onDateSelected = { onSelectedDateChange(it) showDatePicker = false @@ -101,9 +102,7 @@ fun ReplyReminder( }, onDismiss = { showDatePicker = false - }, - confirmButtonText = LocalReplyRadarStrings.current.replyListReminderDatePickerConfirmButton, - dismissButtonText = LocalReplyRadarStrings.current.replyListReminderDatePickerDismissButton, + } ) } @@ -114,12 +113,12 @@ fun ReplyReminder( horizontalArrangement = Arrangement.Start ) { IconButton( + modifier = Modifier + .size(iconButtonSize), onClick = { showTimePicker = true closeKeyboard() - }, - modifier = Modifier - .size(iconButtonSize) + } ) { Icon( modifier = Modifier @@ -131,12 +130,12 @@ fun ReplyReminder( } IconButton( + modifier = Modifier + .size(iconButtonSize), onClick = { showDatePicker = true closeKeyboard() - }, - modifier = Modifier - .size(iconButtonSize) + } ) { Icon( modifier = Modifier @@ -168,9 +167,9 @@ private fun ReminderText( horizontalArrangement = Arrangement.Start ) { Text( - text = reminderText, modifier = Modifier .padding(start = paddingSmall, top = paddingSmall), + text = reminderText, style = typography.bodySmall ) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyToggle.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyToggle.kt index 1706bc2..6733279 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyToggle.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyToggle.kt @@ -72,11 +72,11 @@ fun ReplyToggle(modifier: Modifier = Modifier, isResolved: Boolean, onToggle: () contentAlignment = Alignment.Center ) { Icon( + modifier = Modifier + .size(iconSize), painter = painterResource(drawable.ic_check), contentDescription = LocalReplyRadarStrings.current.replyListPlaceholderResolved, - tint = colorScheme.primary.copy(alpha = checkIconAlpha), - modifier = Modifier - .size(iconSize) + tint = colorScheme.primary.copy(alpha = checkIconAlpha) ) } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/util/FormatReminderText.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/util/FormatReminderText.kt new file mode 100644 index 0000000..db17fb5 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/util/FormatReminderText.kt @@ -0,0 +1,70 @@ +package com.rafaelfelipeac.replyradar.core.common.ui.components.util + +import androidx.compose.runtime.Composable +import com.rafaelfelipeac.replyradar.core.common.clock.LocalClock +import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.util.datetime.dateTime +import com.rafaelfelipeac.replyradar.core.util.datetime.getDefaultTime +import com.rafaelfelipeac.replyradar.core.util.toTwoDigitString +import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.LocalTime + +@Composable +fun formatReminderText( + selectedDate: LocalDate?, + selectedTime: LocalTime?, + onTimeSelected: (LocalTime) -> Unit, +): String? { + if (selectedDate == null && selectedTime == null) return null + + val datetime = LocalClock.current.now().dateTime() + val defaultTime = + getDefaultTime(datetime, selectedDate, selectedTime).also { onTimeSelected(it) } + + val datePart = getDatePart(selectedDate, selectedTime, datetime) + val timePart = getTimePart(selectedTime, selectedDate, defaultTime) + + return "${LocalReplyRadarStrings.current.replyListReminderSet} ${ + when { + datePart != null && timePart != null -> "$datePart $timePart" + datePart != null -> datePart + else -> timePart + } + }" +} + +@Composable +private fun getDatePart( + selectedDate: LocalDate?, + selectedTime: LocalTime?, + dateTime: LocalDateTime +) = when { + selectedDate != null -> { + val day = selectedDate.dayOfMonth.toTwoDigitString() + val month = selectedDate.monthNumber.toTwoDigitString() + val year = selectedDate.year.toString() + "$day/$month/$year" + } + + selectedTime != null -> { + val reminderTimeToday = LocalDateTime(dateTime.date, selectedTime) + if (reminderTimeToday > dateTime) { + LocalReplyRadarStrings.current.replyListReminderToday + } else { + LocalReplyRadarStrings.current.replyListReminderTomorrow + } + } + + else -> null +} + +private fun getTimePart( + selectedTime: LocalTime?, + selectedDate: LocalDate?, + defaultTime: LocalTime +) = when { + selectedTime != null -> "${selectedTime.hour.toTwoDigitString()}:${selectedTime.minute.toTwoDigitString()}" + selectedDate != null -> "${defaultTime.hour.toTwoDigitString()}:${defaultTime.minute.toTwoDigitString()}" + else -> null +} diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Clock.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Clock.kt similarity index 55% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Clock.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Clock.kt index 3efc7f2..f3df169 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Clock.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Clock.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util +package com.rafaelfelipeac.replyradar.core.util.datetime interface Clock { fun now(): Long diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt index d6ffc33..1ba734f 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt @@ -1,12 +1,7 @@ package com.rafaelfelipeac.replyradar.core.util.datetime -import androidx.compose.runtime.Composable import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_DEFAULT_HOUR import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_DEFAULT_MINUTE -import com.rafaelfelipeac.replyradar.core.common.clock.LocalClock -import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings -import com.rafaelfelipeac.replyradar.core.util.toTwoDigitString -import kotlinx.datetime.Clock import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime @@ -20,113 +15,69 @@ const val HOUR_OFFSET = 1 const val HOUR_OFFSET_DEFAULT = 0 const val MINUTE_EMPTY = 0 -fun isDateTimeValid(date: LocalDate?, time: LocalTime, now: LocalDateTime): Boolean { +fun Long.dateTime(timeZone: TimeZone = TimeZone.currentSystemDefault()): LocalDateTime { + return Instant.fromEpochMilliseconds(this) + .toLocalDateTime(timeZone) +} + +fun isDateTimeValid(date: LocalDate?, time: LocalTime, dateTime: LocalDateTime): Boolean { return when { date == null -> { - val todayTime = LocalDateTime(now.date, time) - todayTime > now + val todayTime = LocalDateTime(dateTime.date, time) + + todayTime > dateTime } - date == now.date -> { + date == dateTime.date -> { val selectedDateTime = LocalDateTime(date, time) - selectedDateTime > now + + selectedDateTime > dateTime } - date > now.date -> true + date > dateTime.date -> true else -> false } } -@Composable -fun getDefaultTime(selectedDate: LocalDate?, selectedTime: LocalTime?): LocalTime { - val clock = LocalClock.current - - val now = Instant.fromEpochMilliseconds(clock.now()) - .toLocalDateTime(TimeZone.currentSystemDefault()) - - val eightAM = LocalTime(REMINDER_DEFAULT_HOUR, REMINDER_DEFAULT_MINUTE) - +fun getDefaultTime( + dateTime: LocalDateTime, + selectedDate: LocalDate?, + selectedTime: LocalTime? +): LocalTime { if (selectedTime != null && isDateTimeValid( date = selectedDate, time = selectedTime, - now = now + dateTime = dateTime ) ) { return selectedTime } - if (isDateTimeValid(date = selectedDate, time = eightAM, now = now)) { - return eightAM - } - - val nextHour = now.hour + if (now.minute > MINUTE_EMPTY) HOUR_OFFSET else HOUR_OFFSET_DEFAULT + val defaultTime = LocalTime(REMINDER_DEFAULT_HOUR, REMINDER_DEFAULT_MINUTE) - return LocalTime(hour = nextHour % LOCAL_TIME_HOUR_DEFAULT, minute = LOCAL_TIME_MINUTE_DEFAULT) -} - -@Composable -fun formatReminder( - selectedDate: LocalDate?, - selectedTime: LocalTime?, - onTimeSelected: (LocalTime) -> Unit, -): String? { - if (selectedDate == null && selectedTime == null) return null - - val timeZone = TimeZone.currentSystemDefault() - val now = Instant.fromEpochMilliseconds(LocalClock.current.now()).toLocalDateTime(timeZone) - val defaultTime = getDefaultTime(selectedDate, selectedTime).also { onTimeSelected(it) } - - val datePart = when { - selectedDate != null -> { - val day = selectedDate.dayOfMonth.toTwoDigitString() - val month = selectedDate.monthNumber.toTwoDigitString() - val year = selectedDate.year.toString() - "$day/$month/$year" - } - - selectedTime != null -> { - val reminderTimeToday = LocalDateTime(now.date, selectedTime) - if (reminderTimeToday > now) { - LocalReplyRadarStrings.current.replyListReminderToday - } else { - LocalReplyRadarStrings.current.replyListReminderTomorrow - } - } - - else -> null - } - - val timePart = when { - selectedTime != null -> "${selectedTime.hour.toTwoDigitString()}:${selectedTime.minute.toTwoDigitString()}" - selectedDate != null -> "${defaultTime.hour.toTwoDigitString()}:${defaultTime.minute.toTwoDigitString()}" - else -> null + if (isDateTimeValid(date = selectedDate, time = defaultTime, dateTime = dateTime)) { + return defaultTime } + val nextHour = + dateTime.hour + if (dateTime.minute > MINUTE_EMPTY) HOUR_OFFSET else HOUR_OFFSET_DEFAULT - return "${LocalReplyRadarStrings.current.replyListReminderSet} ${ - when { - datePart != null && timePart != null -> "$datePart $timePart" - datePart != null -> datePart - else -> timePart - } - }" + return LocalTime(hour = nextHour % LOCAL_TIME_HOUR_DEFAULT, minute = LOCAL_TIME_MINUTE_DEFAULT) } -fun isTimeValid(date: LocalDate?, time: LocalTime): Boolean { - val now = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) - +fun isTimeValid(dateTime: LocalDateTime, date: LocalDate?, time: LocalTime): Boolean { return when { date == null -> { - val todayTime = LocalDateTime(now.date, time) - todayTime > now + val todayTime = LocalDateTime(dateTime.date, time) + todayTime > dateTime } - date == now.date -> { + date == dateTime.date -> { val selectedDateTime = LocalDateTime(date, time) - selectedDateTime > now + selectedDateTime > dateTime } - date > now.date -> true + date > dateTime.date -> true else -> false } } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformDatePicker.kt similarity index 87% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformDatePicker.kt index fc9bfd4..bb735e2 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformDatePicker.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util +package com.rafaelfelipeac.replyradar.core.util.datetime import androidx.compose.runtime.Composable import kotlinx.datetime.LocalDate diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformTimePicker.kt similarity index 86% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformTimePicker.kt index 77056ba..e88ec5e 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformTimePicker.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util +package com.rafaelfelipeac.replyradar.core.util.datetime import androidx.compose.runtime.Composable import kotlinx.datetime.LocalDate diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Timestamp.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Timestamp.kt similarity index 70% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Timestamp.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Timestamp.kt index 1e63af0..12e051e 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Timestamp.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Timestamp.kt @@ -1,48 +1,44 @@ -package com.rafaelfelipeac.replyradar.core.util +package com.rafaelfelipeac.replyradar.core.util.datetime import com.rafaelfelipeac.replyradar.core.AppConstants.INITIAL_DATE import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_DEFAULT_HOUR import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_DEFAULT_MINUTE import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_TOMORROW_OFFSET +import com.rafaelfelipeac.replyradar.core.util.toTwoDigitString import kotlinx.datetime.DateTimeUnit.Companion.DAY -import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalTime import kotlinx.datetime.TimeZone import kotlinx.datetime.plus import kotlinx.datetime.toInstant -import kotlinx.datetime.toLocalDateTime fun formatTimestamp(timestampMillis: Long): String { - val instant = Instant.fromEpochMilliseconds(timestampMillis) - val localDateTime = instant.toLocalDateTime(TimeZone.currentSystemDefault()) + val localDateTime = timestampMillis.dateTime() return "${localDateTime.dayOfMonth}/${localDateTime.monthNumber}/${localDateTime.year} " + - "${localDateTime.hour}:${localDateTime.minute.toTwoDigitString()}" + "${localDateTime.hour}:${localDateTime.minute.toTwoDigitString()}" } fun getReminderTimestamp( - now: Long, + dateTime: LocalDateTime, selectedDate: LocalDate?, selectedTime: LocalTime? ): Long { val timeZone = TimeZone.currentSystemDefault() - val nowDateTime = - Instant.fromEpochMilliseconds(now).toLocalDateTime(timeZone) val finalDate = when { selectedDate != null -> selectedDate selectedTime != null -> { val timeToday = LocalDateTime( - date = nowDateTime.date, + date = dateTime.date, time = selectedTime ) - if (timeToday > nowDateTime) { - nowDateTime.date + if (timeToday > dateTime) { + dateTime.date } else { - nowDateTime.date.plus( + dateTime.date.plus( REMINDER_TOMORROW_OFFSET, DAY ) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/di/Modules.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/di/Modules.kt index 2808032..0e0386a 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/di/Modules.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/di/Modules.kt @@ -2,8 +2,8 @@ package com.rafaelfelipeac.replyradar.di import androidx.sqlite.driver.bundled.BundledSQLiteDriver import com.rafaelfelipeac.replyradar.core.database.DatabaseFactory -import com.rafaelfelipeac.replyradar.core.util.Clock -import com.rafaelfelipeac.replyradar.core.util.getClock +import com.rafaelfelipeac.replyradar.core.util.datetime.Clock +import com.rafaelfelipeac.replyradar.core.util.datetime.getClock import org.koin.core.module.Module import org.koin.dsl.module diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/activitylog/presentation/ActivityLogScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/activitylog/presentation/ActivityLogScreen.kt index 14e8ab0..b7c67fc 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/activitylog/presentation/ActivityLogScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/activitylog/presentation/ActivityLogScreen.kt @@ -43,7 +43,7 @@ import com.rafaelfelipeac.replyradar.core.common.ui.listDividerThickness import com.rafaelfelipeac.replyradar.core.common.ui.paddingMedium import com.rafaelfelipeac.replyradar.core.common.ui.theme.horizontalDividerColor import com.rafaelfelipeac.replyradar.core.util.format -import com.rafaelfelipeac.replyradar.core.util.formatTimestamp +import com.rafaelfelipeac.replyradar.core.util.datetime.formatTimestamp import com.rafaelfelipeac.replyradar.features.activitylog.presentation.ActivityLogViewModel.Companion.ERROR_GET_ACTIVITY_LOG import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserAction import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionTargetType diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/repository/ReplyRepositoryImpl.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/repository/ReplyRepositoryImpl.kt index f4e3d37..bd03f7d 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/repository/ReplyRepositoryImpl.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/repository/ReplyRepositoryImpl.kt @@ -1,7 +1,7 @@ package com.rafaelfelipeac.replyradar.features.reply.data.repository import com.rafaelfelipeac.replyradar.core.AppConstants.INITIAL_DATE -import com.rafaelfelipeac.replyradar.core.util.Clock +import com.rafaelfelipeac.replyradar.core.util.datetime.Clock import com.rafaelfelipeac.replyradar.features.reply.data.database.dao.ReplyDao import com.rafaelfelipeac.replyradar.features.reply.data.mapper.toReply import com.rafaelfelipeac.replyradar.features.reply.data.mapper.toReplyEntity diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt index 60d6f96..3402e51 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt @@ -55,7 +55,7 @@ import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnToggleArchive import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnToggleResolve import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnTabSelected -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.FAB +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.FloatingActionButton import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.RepliesScreen import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.TopBar import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheet @@ -156,7 +156,7 @@ fun ReplyListScreen( Scaffold( containerColor = colorScheme.background, snackbarHost = { ReplySnackbar(snackbarHostState) }, - floatingActionButton = { FAB(onIntent, colorScheme) } + floatingActionButton = { FloatingActionButton(onIntent, colorScheme) } ) { paddingValues -> Column( modifier = Modifier diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/FAB.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/FloatingActionButton.kt similarity index 91% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/FAB.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/FloatingActionButton.kt index 18f75f0..377353a 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/FAB.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/FloatingActionButton.kt @@ -11,7 +11,7 @@ import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnAddReplyClick @Composable -fun FAB(onIntent: (ReplyListScreenIntent) -> Unit, colorScheme: ColorScheme) { +fun FloatingActionButton(onIntent: (ReplyListScreenIntent) -> Unit, colorScheme: ColorScheme) { FloatingActionButton( onClick = { onIntent(OnAddReplyClick) }, containerColor = colorScheme.secondary diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/ReplyTimestampInfo.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/ReplyTimestampInfo.kt index c082272..8962932 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/ReplyTimestampInfo.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/ReplyTimestampInfo.kt @@ -11,7 +11,7 @@ import com.rafaelfelipeac.replyradar.core.AppConstants.INITIAL_DATE import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall import com.rafaelfelipeac.replyradar.core.util.format -import com.rafaelfelipeac.replyradar.core.util.formatTimestamp +import com.rafaelfelipeac.replyradar.core.util.datetime.formatTimestamp import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetState diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/TopBar.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/TopBar.kt index 6aa295e..5bff6d5 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/TopBar.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/TopBar.kt @@ -51,10 +51,10 @@ fun TopBar(onActivityLogClick: () -> Unit, onSettingsClick: () -> Unit) { ) IconButton( - onClick = { onSettingsClick() }, modifier = Modifier .align(Alignment.CenterEnd) - .padding(top = paddingMedium, end = paddingMedium) + .padding(top = paddingMedium, end = paddingMedium), + onClick = { onSettingsClick() } ) { Icon( modifier = Modifier diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetActions.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetActions.kt index cb1e902..66d3b20 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetActions.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetActions.kt @@ -21,8 +21,9 @@ import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyConfirmation import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyOutlinedButton import com.rafaelfelipeac.replyradar.core.common.ui.paddingMedium import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall +import com.rafaelfelipeac.replyradar.core.util.datetime.dateTime import com.rafaelfelipeac.replyradar.core.util.format -import com.rafaelfelipeac.replyradar.core.util.getReminderTimestamp +import com.rafaelfelipeac.replyradar.core.util.datetime.getReminderTimestamp import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetMode.EDIT import kotlinx.datetime.LocalDate @@ -64,7 +65,7 @@ fun ReplyBottomSheetActions( ) val reminderAtTimestamp = getReminderTimestamp( - now = LocalClock.current.now(), + dateTime = LocalClock.current.now().dateTime(), selectedDate = selectedDate, selectedTime = selectedTime ) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt index 6168948..1d2a52a 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme.colorScheme -import androidx.compose.material3.MaterialTheme.typography import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -25,11 +24,9 @@ import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyTextField import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyTextFieldSize.Large import com.rafaelfelipeac.replyradar.core.common.ui.paddingMedium import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall +import com.rafaelfelipeac.replyradar.core.util.datetime.dateTime import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.ReplyTimestampInfo -import kotlinx.datetime.Instant -import kotlinx.datetime.TimeZone -import kotlinx.datetime.toLocalDateTime @Composable fun ReplyBottomSheetContent( @@ -42,12 +39,7 @@ fun ReplyBottomSheetContent( replyBottomSheetState?.let { state -> var name by remember { mutableStateOf(state.reply?.name ?: EMPTY) } var subject by remember { mutableStateOf(state.reply?.subject ?: EMPTY) } - val reminderAt = state.reply?.reminderAt - ?.takeIf { it != INITIAL_DATE } - ?.let { millis -> - Instant.fromEpochMilliseconds(millis) - .toLocalDateTime(TimeZone.currentSystemDefault()) - } + val reminderAt = state.reply?.reminderAt?.takeIf { it != INITIAL_DATE }?.dateTime() var selectedTime by remember(reminderAt) { mutableStateOf(reminderAt?.time) } var selectedDate by remember(reminderAt) { mutableStateOf(reminderAt?.date) } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsScreen.kt index a545aee..55d93f7 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsScreen.kt @@ -238,11 +238,11 @@ private fun ThemeOption( onThemeSelected: (AppTheme) -> Unit ) { Row( - verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() .padding(paddingSmall) - .clickable { onThemeSelected(theme) } + .clickable { onThemeSelected(theme) }, + verticalAlignment = Alignment.CenterVertically ) { RadioButton( modifier = Modifier @@ -281,11 +281,11 @@ private fun LanguageOption( onLanguageSelected: (AppLanguage) -> Unit ) { Row( - verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() .padding(paddingSmall) - .clickable { onLanguageSelected(language) } + .clickable { onLanguageSelected(language) }, + verticalAlignment = Alignment.CenterVertically ) { RadioButton( modifier = Modifier diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/useractions/data/repository/UserActionRepositoryImpl.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/useractions/data/repository/UserActionRepositoryImpl.kt index 469487b..80aabe4 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/useractions/data/repository/UserActionRepositoryImpl.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/useractions/data/repository/UserActionRepositoryImpl.kt @@ -1,6 +1,6 @@ package com.rafaelfelipeac.replyradar.features.useractions.data.repository -import com.rafaelfelipeac.replyradar.core.util.Clock +import com.rafaelfelipeac.replyradar.core.util.datetime.Clock import com.rafaelfelipeac.replyradar.features.reply.data.database.dao.ReplyDao import com.rafaelfelipeac.replyradar.features.useractions.data.database.dao.UserActionDao import com.rafaelfelipeac.replyradar.features.useractions.data.database.entity.UserActionEntity diff --git a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/core/util/FakeClock.kt b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/core/util/FakeClock.kt index 4a906a2..1f64b74 100644 --- a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/core/util/FakeClock.kt +++ b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/core/util/FakeClock.kt @@ -1,6 +1,6 @@ package com.rafaelfelipeac.replyradar.fakes.core.util -import com.rafaelfelipeac.replyradar.core.util.Clock +import com.rafaelfelipeac.replyradar.core.util.datetime.Clock class FakeClock(private val fixedNow: Long) : Clock { override fun now(): Long = fixedNow diff --git a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/Main.kt b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/Main.kt index 59b72ae..2c9911f 100644 --- a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/Main.kt +++ b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/Main.kt @@ -18,6 +18,10 @@ fun main() { override suspend fun ensureNotificationPermission(): Boolean { TODO("Not yet implemented for this platform.") } + + override suspend fun goToAppSettings() { + TODO("Not yet implemented for this platform.") + } } ) } diff --git a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Clock.desktop.kt b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Clock.desktop.kt similarity index 65% rename from composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Clock.desktop.kt rename to composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Clock.desktop.kt index d6063c4..a83de67 100644 --- a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Clock.desktop.kt +++ b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Clock.desktop.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util +package com.rafaelfelipeac.replyradar.core.util.datetime actual fun getClock(): Clock = object : Clock { override fun now(): Long = System.currentTimeMillis() diff --git a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.desktop.kt b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformDatePicker.desktop.kt similarity index 88% rename from composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.desktop.kt rename to composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformDatePicker.desktop.kt index 38b69b3..a73796e 100644 --- a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.desktop.kt +++ b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformDatePicker.desktop.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util +package com.rafaelfelipeac.replyradar.core.util.datetime import androidx.compose.runtime.Composable import kotlinx.datetime.LocalDate diff --git a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.desktop.kt b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformTimePicker.desktop.kt similarity index 88% rename from composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.desktop.kt rename to composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformTimePicker.desktop.kt index c9c65ea..4d901ed 100644 --- a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.desktop.kt +++ b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformTimePicker.desktop.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util +package com.rafaelfelipeac.replyradar.core.util.datetime import androidx.compose.runtime.Composable import kotlinx.datetime.LocalDate diff --git a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/MainViewController.kt b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/MainViewController.kt index cf06585..cf8c34f 100644 --- a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/MainViewController.kt +++ b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/MainViewController.kt @@ -16,6 +16,10 @@ fun MainViewController() = ComposeUIViewController( override suspend fun ensureNotificationPermission(): Boolean { TODO("Not yet implemented for this platform.") } + + override suspend fun goToAppSettings() { + TODO("Not yet implemented for this platform.") + } } ) } diff --git a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Clock.ios.kt b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Clock.ios.kt similarity index 83% rename from composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Clock.ios.kt rename to composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Clock.ios.kt index 37003e1..545479d 100644 --- a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Clock.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Clock.ios.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util +package com.rafaelfelipeac.replyradar.core.util.datetime import platform.Foundation.NSDate import platform.Foundation.timeIntervalSince1970 diff --git a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.ios.kt b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformDatePicker.ios.kt similarity index 88% rename from composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.ios.kt rename to composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformDatePicker.ios.kt index 38b69b3..a73796e 100644 --- a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformDatePicker.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformDatePicker.ios.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util +package com.rafaelfelipeac.replyradar.core.util.datetime import androidx.compose.runtime.Composable import kotlinx.datetime.LocalDate diff --git a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.ios.kt b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformTimePicker.ios.kt similarity index 88% rename from composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.ios.kt rename to composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformTimePicker.ios.kt index c9c65ea..4d901ed 100644 --- a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/PlatformTimePicker.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformTimePicker.ios.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util +package com.rafaelfelipeac.replyradar.core.util.datetime import androidx.compose.runtime.Composable import kotlinx.datetime.LocalDate From 40893b82ff53943c404ab092f52e555b531e3238 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Fri, 27 Jun 2025 11:36:33 -0300 Subject: [PATCH 34/59] Package fixes. --- .../replyradar/core/ConfigureSystemBars.kt | 3 --- .../replyradar/core/{util => }/StatusBar.kt | 2 +- .../core/database/DatabaseFactory.kt | 2 +- .../core/{util => }/datetime/Clock.kt | 2 +- .../core/{util => }/datetime/DatetimeExt.kt | 2 +- .../datetime/PlatformDatePicker.android.kt | 4 +-- .../datetime/PlatformTimePicker.android.kt | 4 +-- .../{util => external}/External.android.kt | 2 +- .../rememberNotificationPermissionManager.kt | 6 ++--- .../{util => }/reminder/NotificationUtils.kt | 4 +-- .../reminder/ReminderSchedulerImpl.kt | 4 +-- .../{util => }/reminder/ReminderWorker.kt | 4 +-- .../core/{util => version}/Version.android.kt | 2 +- .../replyradar/di/Modules.android.kt | 4 +-- .../replyradar/app/ReplyRadarApp.kt | 18 ++++++------- .../core/{common => }/clock/LocalClock.kt | 4 +-- .../ReplyNotificationPermissionDialog.kt | 2 +- .../ui/components/ReplyOutlinedButton.kt | 2 +- .../common/ui/components/ReplyRadarError.kt | 2 +- .../common/ui/components/ReplyReminder.kt | 10 +++---- .../common/ui/components/ReplySnackbar.kt | 2 +- .../core/common/ui/components/ReplyTab.kt | 2 +- .../common/ui/components/ReplyTextField.kt | 2 +- .../core/common/ui/components/ReplyToggle.kt | 2 +- .../ui/components/util/FormatReminderText.kt | 8 +++--- .../core/{util => }/datetime/Clock.kt | 2 +- .../core/{util => }/datetime/Datetime.kt | 6 ++--- .../{util => }/datetime/PlatformDatePicker.kt | 2 +- .../{util => }/datetime/PlatformTimePicker.kt | 2 +- .../core/{util => }/datetime/Timestamp.kt | 10 +++---- .../core/{util => external}/External.kt | 2 +- .../core/{common => }/language/AppLanguage.kt | 2 +- .../{util => }/reminder/ReminderScheduler.kt | 2 +- .../strings/LocalReplyRadarStrings.kt | 2 +- .../core/{common => }/strings/Strings.kt | 2 +- .../core/{common => }/strings/StringsEn.kt | 4 +-- .../{common => }/strings/StringsProvider.kt | 10 +++---- .../core/{common => }/strings/StringsPt.kt | 4 +-- .../core/{common/ui => }/theme/ColorScheme.kt | 2 +- .../ui => }/theme/ReplyRadarColorScheme.kt | 4 +-- .../{common/ui => }/theme/ReplyRadarColors.kt | 2 +- .../{common/ui => }/theme/ReplyRadarTheme.kt | 2 +- .../ui => }/theme/ReplyRadarThemeAccessors.kt | 2 +- .../{common/ui => }/theme/model/AppTheme.kt | 2 +- .../core/{ => util}/AppConstants.kt | 2 +- .../replyradar/core/util/Version.kt | 3 --- .../replyradar/core/version/Version.kt | 3 +++ .../rafaelfelipeac/replyradar/di/Modules.kt | 4 +-- .../presentation/ActivityLogScreen.kt | 6 ++--- .../app/settings/AppSettingsViewModel.kt | 4 +-- .../reply/data/database/entity/ReplyEntity.kt | 2 +- .../data/repository/ReplyRepositoryImpl.kt | 4 +-- .../features/reply/domain/model/Reply.kt | 2 +- .../presentation/replylist/ReplyListScreen.kt | 8 +++--- .../replylist/ReplyListViewModel.kt | 4 +-- .../components/FloatingActionButton.kt | 2 +- .../components/RepliesArchivedScreen.kt | 2 +- .../components/RepliesOnTheRadarScreen.kt | 2 +- .../components/RepliesResolvedScreen.kt | 2 +- .../replylist/components/ReplyList.kt | 2 +- .../components/ReplyTimestampInfo.kt | 6 ++--- .../replylist/components/TopBar.kt | 4 +-- .../ReplyBottomSheetActions.kt | 8 +++--- .../ReplyBottomSheetContent.kt | 8 +++--- .../data/repository/SettingsRepositoryImpl.kt | 4 +-- .../domain/repository/SettingsRepository.kt | 4 +-- .../domain/usecase/GetLanguageUseCase.kt | 2 +- .../domain/usecase/GetThemeUseCase.kt | 2 +- .../domain/usecase/SetLanguageUseCase.kt | 2 +- .../domain/usecase/SetThemeUseCase.kt | 2 +- .../settings/presentation/SettingsIntent.kt | 4 +-- .../settings/presentation/SettingsScreen.kt | 26 +++++++++---------- .../settings/presentation/SettingsState.kt | 4 +-- .../presentation/SettingsViewModel.kt | 4 +-- .../repository/UserActionRepositoryImpl.kt | 2 +- .../replyradar/fakes/core/util/FakeClock.kt | 2 +- .../settings/data/FakeSettingsRepository.kt | 4 +-- .../settings/domain/FakeGetLanguageUseCase.kt | 4 +-- .../settings/domain/FakeGetThemeUseCase.kt | 4 +-- .../app/settings/AppSettingsViewModelTest.kt | 4 +-- .../reply/data/ReplyRepositoryTest.kt | 2 +- .../presentation/ReplyListViewModelTest.kt | 2 +- .../domain/GetLanguageUseCaseImplTest.kt | 2 +- .../domain/GetThemeUseCaseImplTest.kt | 2 +- .../domain/SetLanguageUseCaseImplTest.kt | 2 +- .../settings/domain/SetThemeUseCaseTest.kt | 2 +- .../presentation/SettingsViewModelTest.kt | 4 +-- .../core/database/DatabaseFactory.kt | 2 +- .../core/{util => }/datetime/Clock.desktop.kt | 2 +- .../datetime/PlatformDatePicker.desktop.kt | 2 +- .../datetime/PlatformTimePicker.desktop.kt | 2 +- .../{util => external}/External.desktop.kt | 2 +- .../core/{util => version}/Version.desktop.kt | 2 +- .../core/database/DatabaseFactory.kt | 2 +- .../core/{util => }/datetime/Clock.ios.kt | 2 +- .../datetime/PlatformDatePicker.ios.kt | 2 +- .../datetime/PlatformTimePicker.ios.kt | 2 +- .../core/{util => external}/External.ios.kt | 2 +- .../core/{util => version}/Version.native.kt | 2 +- 99 files changed, 175 insertions(+), 180 deletions(-) rename composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => }/StatusBar.kt (92%) rename composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => }/datetime/Clock.kt (65%) rename composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => }/datetime/DatetimeExt.kt (89%) rename composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => }/datetime/PlatformDatePicker.android.kt (96%) rename composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => }/datetime/PlatformTimePicker.android.kt (95%) rename composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => external}/External.android.kt (97%) rename composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => }/reminder/NotificationUtils.kt (93%) rename composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => }/reminder/ReminderSchedulerImpl.kt (95%) rename composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => }/reminder/ReminderWorker.kt (88%) rename composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => version}/Version.android.kt (89%) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/{common => }/clock/LocalClock.kt (54%) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => }/datetime/Clock.kt (55%) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => }/datetime/Datetime.kt (91%) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => }/datetime/PlatformDatePicker.kt (87%) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => }/datetime/PlatformTimePicker.kt (86%) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => }/datetime/Timestamp.kt (80%) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => external}/External.kt (69%) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/{common => }/language/AppLanguage.kt (79%) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => }/reminder/ReminderScheduler.kt (78%) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/{common => }/strings/LocalReplyRadarStrings.kt (73%) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/{common => }/strings/Strings.kt (98%) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/{common => }/strings/StringsEn.kt (98%) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/{common => }/strings/StringsProvider.kt (53%) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/{common => }/strings/StringsPt.kt (98%) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/{common/ui => }/theme/ColorScheme.kt (96%) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/{common/ui => }/theme/ReplyRadarColorScheme.kt (84%) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/{common/ui => }/theme/ReplyRadarColors.kt (90%) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/{common/ui => }/theme/ReplyRadarTheme.kt (90%) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/{common/ui => }/theme/ReplyRadarThemeAccessors.kt (75%) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/{common/ui => }/theme/model/AppTheme.kt (77%) rename composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/{ => util}/AppConstants.kt (88%) delete mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Version.kt create mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/version/Version.kt rename composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => }/datetime/Clock.desktop.kt (65%) rename composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => }/datetime/PlatformDatePicker.desktop.kt (88%) rename composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => }/datetime/PlatformTimePicker.desktop.kt (88%) rename composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => external}/External.desktop.kt (81%) rename composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => version}/Version.desktop.kt (64%) rename composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => }/datetime/Clock.ios.kt (83%) rename composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => }/datetime/PlatformDatePicker.ios.kt (88%) rename composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => }/datetime/PlatformTimePicker.ios.kt (88%) rename composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => external}/External.ios.kt (81%) rename composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/{util => version}/Version.native.kt (80%) diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/ConfigureSystemBars.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/ConfigureSystemBars.kt index c0f1ca6..36526f8 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/ConfigureSystemBars.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/ConfigureSystemBars.kt @@ -6,7 +6,6 @@ import androidx.compose.runtime.SideEffect import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import com.google.accompanist.systemuicontroller.rememberSystemUiController -import com.rafaelfelipeac.replyradar.core.util.setStatusBarColorCompat @Composable fun ConfigureSystemBars(darkTheme: Boolean, backgroundColor: Color) { @@ -14,13 +13,11 @@ fun ConfigureSystemBars(darkTheme: Boolean, backgroundColor: Color) { val systemUiController = rememberSystemUiController() SideEffect { - // Navigation Bar systemUiController.setNavigationBarColor( color = backgroundColor, darkIcons = !darkTheme ) - // Status Bar activity?.setStatusBarColorCompat( color = backgroundColor, useDarkIcons = !darkTheme diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/StatusBar.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/StatusBar.kt similarity index 92% rename from composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/StatusBar.kt rename to composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/StatusBar.kt index b888090..3da6157 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/StatusBar.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/StatusBar.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util +package com.rafaelfelipeac.replyradar.core import android.app.Activity import androidx.compose.ui.graphics.Color diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/database/DatabaseFactory.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/database/DatabaseFactory.kt index 89d8654..4026a0f 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/database/DatabaseFactory.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/database/DatabaseFactory.kt @@ -3,7 +3,7 @@ package com.rafaelfelipeac.replyradar.core.database import android.content.Context import androidx.room.Room import androidx.room.RoomDatabase -import com.rafaelfelipeac.replyradar.core.AppConstants.DB_NAME +import com.rafaelfelipeac.replyradar.core.util.AppConstants.DB_NAME import com.rafaelfelipeac.replyradar.core.database.ReplyRadarMigrations.ALL_MIGRATIONS actual class DatabaseFactory( diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Clock.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.kt similarity index 65% rename from composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Clock.kt rename to composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.kt index a83de67..46c7e91 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Clock.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util.datetime +package com.rafaelfelipeac.replyradar.core.datetime actual fun getClock(): Clock = object : Clock { override fun now(): Long = System.currentTimeMillis() diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/DatetimeExt.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/DatetimeExt.kt similarity index 89% rename from composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/DatetimeExt.kt rename to composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/DatetimeExt.kt index 29fce45..0629dc5 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/DatetimeExt.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/DatetimeExt.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util.datetime +package com.rafaelfelipeac.replyradar.core.datetime import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformDatePicker.android.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformDatePicker.android.kt similarity index 96% rename from composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformDatePicker.android.kt rename to composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformDatePicker.android.kt index 9130b37..22f94ac 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformDatePicker.android.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformDatePicker.android.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util.datetime +package com.rafaelfelipeac.replyradar.core.datetime import androidx.compose.material3.DatePicker import androidx.compose.material3.DatePickerDialog @@ -12,7 +12,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import com.rafaelfelipeac.replyradar.core.common.clock.LocalClock +import com.rafaelfelipeac.replyradar.core.clock.LocalClock import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalTime import kotlinx.datetime.TimeZone.Companion.UTC diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformTimePicker.android.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformTimePicker.android.kt similarity index 95% rename from composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformTimePicker.android.kt rename to composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformTimePicker.android.kt index 1a6ddf6..518c5b4 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformTimePicker.android.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformTimePicker.android.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util.datetime +package com.rafaelfelipeac.replyradar.core.datetime import androidx.compose.material3.AlertDialog import androidx.compose.material3.ExperimentalMaterial3Api @@ -11,7 +11,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import com.rafaelfelipeac.replyradar.core.common.clock.LocalClock +import com.rafaelfelipeac.replyradar.core.clock.LocalClock import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalTime diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/External.android.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/external/External.android.kt similarity index 97% rename from composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/External.android.kt rename to composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/external/External.android.kt index 0fc7fea..67cfcde 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/External.android.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/external/External.android.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util +package com.rafaelfelipeac.replyradar.core.external import android.content.ActivityNotFoundException import android.content.Intent diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt index 1d3e442..8457ebd 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt @@ -40,14 +40,12 @@ fun rememberNotificationPermissionManager(): NotificationPermissionManager { return remember { object : NotificationPermissionManager { override suspend fun ensureNotificationPermission(): Boolean { + // double check everything if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { return true } - val granted = ContextCompat.checkSelfPermission( - context, - POST_NOTIFICATIONS - ) == PERMISSION_GRANTED + val granted = checkSelfPermission(context, POST_NOTIFICATIONS) == PERMISSION_GRANTED if (granted) { return true diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/NotificationUtils.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/NotificationUtils.kt similarity index 93% rename from composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/NotificationUtils.kt rename to composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/NotificationUtils.kt index a8b7048..28fc362 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/NotificationUtils.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/NotificationUtils.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util.reminder +package com.rafaelfelipeac.replyradar.core.reminder import android.Manifest.permission.POST_NOTIFICATIONS import android.content.Context @@ -11,7 +11,7 @@ import com.rafaelfelipeac.replyradar.R import com.rafaelfelipeac.replyradar.R.string.notification_channel_id import com.rafaelfelipeac.replyradar.R.string.notification_content_reminder_title -object NotificationUtils { +object NotificationUtils {// fun showReminderNotification(context: Context, name: String, subject: String) { val notification = NotificationCompat.Builder( context, diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderSchedulerImpl.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderSchedulerImpl.kt similarity index 95% rename from composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderSchedulerImpl.kt rename to composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderSchedulerImpl.kt index 29cee13..5d036e9 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderSchedulerImpl.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderSchedulerImpl.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util.reminder +package com.rafaelfelipeac.replyradar.core.reminder import android.content.Context import androidx.work.OneTimeWorkRequestBuilder @@ -12,7 +12,7 @@ import java.util.concurrent.TimeUnit private const val INVALID_DELAY = 0 -class ReminderSchedulerImpl( +class ReminderSchedulerImpl(// private val context: Context ) : ReminderScheduler { diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderWorker.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderWorker.kt similarity index 88% rename from composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderWorker.kt rename to composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderWorker.kt index bfa130d..9610abd 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderWorker.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderWorker.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util.reminder +package com.rafaelfelipeac.replyradar.core.reminder import android.content.Context import androidx.work.Worker @@ -11,7 +11,7 @@ class ReminderWorker( workerParams: WorkerParameters ) : Worker(appContext, workerParams) { - override fun doWork(): Result { + override fun doWork(): Result {// val name = inputData.getString(applicationContext.getString(reminder_name)) ?: return Result.failure() val subject = inputData.getString(applicationContext.getString(reminder_subject)) ?: return Result.failure() diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Version.android.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/version/Version.android.kt similarity index 89% rename from composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Version.android.kt rename to composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/version/Version.android.kt index fa492ae..5cd63c0 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Version.android.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/version/Version.android.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util +package com.rafaelfelipeac.replyradar.core.version import android.content.Context import com.rafaelfelipeac.replyradar.R diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/di/Modules.android.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/di/Modules.android.kt index d1f1fb3..2d48cc7 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/di/Modules.android.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/di/Modules.android.kt @@ -5,8 +5,8 @@ import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import com.rafaelfelipeac.replyradar.core.database.DatabaseFactory import com.rafaelfelipeac.replyradar.core.preferences.CreateDataStore -import com.rafaelfelipeac.replyradar.core.util.reminder.ReminderScheduler -import com.rafaelfelipeac.replyradar.core.util.reminder.ReminderSchedulerImpl +import com.rafaelfelipeac.replyradar.core.reminder.ReminderScheduler +import com.rafaelfelipeac.replyradar.core.reminder.ReminderSchedulerImpl import io.ktor.client.engine.HttpClientEngine import io.ktor.client.engine.okhttp.OkHttp import org.koin.android.ext.koin.androidApplication diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt index 2d62154..979bf70 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt @@ -8,18 +8,18 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.graphics.Color import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.compose.rememberNavController -import com.rafaelfelipeac.replyradar.core.common.clock.LocalClock -import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings -import com.rafaelfelipeac.replyradar.core.common.strings.StringsProvider -import com.rafaelfelipeac.replyradar.core.common.ui.theme.DarkColorScheme -import com.rafaelfelipeac.replyradar.core.common.ui.theme.LightColorScheme -import com.rafaelfelipeac.replyradar.core.common.ui.theme.ReplyRadarTheme -import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme.DARK -import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme.SYSTEM +import com.rafaelfelipeac.replyradar.core.clock.LocalClock +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.strings.StringsProvider +import com.rafaelfelipeac.replyradar.core.theme.DarkColorScheme +import com.rafaelfelipeac.replyradar.core.theme.LightColorScheme +import com.rafaelfelipeac.replyradar.core.theme.ReplyRadarTheme +import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme.DARK +import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme.SYSTEM import com.rafaelfelipeac.replyradar.core.navigation.AppNavHost import com.rafaelfelipeac.replyradar.core.notification.LocalNotificationPermissionManager import com.rafaelfelipeac.replyradar.core.notification.NotificationPermissionManager -import com.rafaelfelipeac.replyradar.core.util.datetime.getClock +import com.rafaelfelipeac.replyradar.core.datetime.getClock import com.rafaelfelipeac.replyradar.features.app.settings.AppSettingsViewModel import org.koin.compose.viewmodel.koinViewModel diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/clock/LocalClock.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/clock/LocalClock.kt similarity index 54% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/clock/LocalClock.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/clock/LocalClock.kt index a982749..6217be4 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/clock/LocalClock.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/clock/LocalClock.kt @@ -1,7 +1,7 @@ -package com.rafaelfelipeac.replyradar.core.common.clock +package com.rafaelfelipeac.replyradar.core.clock import androidx.compose.runtime.staticCompositionLocalOf -import com.rafaelfelipeac.replyradar.core.util.datetime.Clock +import com.rafaelfelipeac.replyradar.core.datetime.Clock val LocalClock = staticCompositionLocalOf { error("No Clock provided") diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyNotificationPermissionDialog.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyNotificationPermissionDialog.kt index ccb7332..f47a4a6 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyNotificationPermissionDialog.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyNotificationPermissionDialog.kt @@ -4,7 +4,7 @@ import androidx.compose.material3.AlertDialog import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable -import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings @Composable fun NotificationPermissionDialog( diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyOutlinedButton.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyOutlinedButton.kt index 347ff6c..7858bb0 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyOutlinedButton.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyOutlinedButton.kt @@ -19,7 +19,7 @@ import com.rafaelfelipeac.replyradar.core.common.ui.buttonHeight import com.rafaelfelipeac.replyradar.core.common.ui.iconSize import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall import com.rafaelfelipeac.replyradar.core.common.ui.paddingXSmall -import com.rafaelfelipeac.replyradar.core.common.ui.theme.buttonBorderColor +import com.rafaelfelipeac.replyradar.core.theme.buttonBorderColor import org.jetbrains.compose.resources.DrawableResource import org.jetbrains.compose.resources.painterResource diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyRadarError.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyRadarError.kt index 2a62850..a973dc8 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyRadarError.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyRadarError.kt @@ -4,7 +4,7 @@ import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.text.style.TextAlign -import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings @Composable fun ReplyRadarError(errorMessage: String?) { diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt index 0a4452f..5d30674 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt @@ -17,7 +17,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.components.util.formatReminderText import com.rafaelfelipeac.replyradar.core.common.ui.iconButtonSize import com.rafaelfelipeac.replyradar.core.common.ui.iconSize @@ -25,10 +25,10 @@ import com.rafaelfelipeac.replyradar.core.common.ui.listDividerThickness import com.rafaelfelipeac.replyradar.core.common.ui.paddingLarge import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall import com.rafaelfelipeac.replyradar.core.common.ui.paddingXSmall -import com.rafaelfelipeac.replyradar.core.common.ui.theme.horizontalDividerColor -import com.rafaelfelipeac.replyradar.core.common.ui.theme.toolbarIconsColor -import com.rafaelfelipeac.replyradar.core.util.datetime.PlatformDatePicker -import com.rafaelfelipeac.replyradar.core.util.datetime.PlatformTimePicker +import com.rafaelfelipeac.replyradar.core.theme.horizontalDividerColor +import com.rafaelfelipeac.replyradar.core.theme.toolbarIconsColor +import com.rafaelfelipeac.replyradar.core.datetime.PlatformDatePicker +import com.rafaelfelipeac.replyradar.core.datetime.PlatformTimePicker import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalTime import org.jetbrains.compose.resources.painterResource diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplySnackbar.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplySnackbar.kt index 8955e21..ed383b3 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplySnackbar.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplySnackbar.kt @@ -5,7 +5,7 @@ import androidx.compose.material3.Snackbar import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable -import com.rafaelfelipeac.replyradar.core.common.ui.theme.snackbarBackgroundColor +import com.rafaelfelipeac.replyradar.core.theme.snackbarBackgroundColor @Composable fun ReplySnackbar(snackbarHostState: SnackbarHostState) { diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyTab.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyTab.kt index f847cb4..ef878ec 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyTab.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyTab.kt @@ -7,7 +7,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.rafaelfelipeac.replyradar.core.common.ui.tabVerticalPadding -import com.rafaelfelipeac.replyradar.core.common.ui.theme.unselectedTabColor +import com.rafaelfelipeac.replyradar.core.theme.unselectedTabColor @Composable fun ReplyTab(modifier: Modifier, selected: Boolean, onClick: () -> Unit, text: String) { diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyTextField.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyTextField.kt index 93422de..673805a 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyTextField.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyTextField.kt @@ -24,7 +24,7 @@ import com.rafaelfelipeac.replyradar.core.common.ui.paddingXSmall import com.rafaelfelipeac.replyradar.core.common.ui.textSizeLarge import com.rafaelfelipeac.replyradar.core.common.ui.textSizeMedium import com.rafaelfelipeac.replyradar.core.common.ui.textSizeSmall -import com.rafaelfelipeac.replyradar.core.common.ui.theme.textFieldPlaceholderColor +import com.rafaelfelipeac.replyradar.core.theme.textFieldPlaceholderColor @Composable fun ReplyTextField( diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyToggle.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyToggle.kt index 6733279..7ffa9cb 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyToggle.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyToggle.kt @@ -23,7 +23,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color.Companion.Transparent import androidx.compose.ui.hapticfeedback.HapticFeedbackType.Companion.LongPress import androidx.compose.ui.platform.LocalHapticFeedback -import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.iconSize import com.rafaelfelipeac.replyradar.core.common.ui.listItemToggleBorderWidth import com.rafaelfelipeac.replyradar.core.common.ui.listItemToggleSize diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/util/FormatReminderText.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/util/FormatReminderText.kt index db17fb5..59ea350 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/util/FormatReminderText.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/util/FormatReminderText.kt @@ -1,10 +1,10 @@ package com.rafaelfelipeac.replyradar.core.common.ui.components.util import androidx.compose.runtime.Composable -import com.rafaelfelipeac.replyradar.core.common.clock.LocalClock -import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings -import com.rafaelfelipeac.replyradar.core.util.datetime.dateTime -import com.rafaelfelipeac.replyradar.core.util.datetime.getDefaultTime +import com.rafaelfelipeac.replyradar.core.clock.LocalClock +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.datetime.dateTime +import com.rafaelfelipeac.replyradar.core.datetime.getDefaultTime import com.rafaelfelipeac.replyradar.core.util.toTwoDigitString import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Clock.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.kt similarity index 55% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Clock.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.kt index f3df169..149b97a 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Clock.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util.datetime +package com.rafaelfelipeac.replyradar.core.datetime interface Clock { fun now(): Long diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Datetime.kt similarity index 91% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Datetime.kt index 1ba734f..fb3f211 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Datetime.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Datetime.kt @@ -1,7 +1,7 @@ -package com.rafaelfelipeac.replyradar.core.util.datetime +package com.rafaelfelipeac.replyradar.core.datetime -import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_DEFAULT_HOUR -import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_DEFAULT_MINUTE +import com.rafaelfelipeac.replyradar.core.util.AppConstants.REMINDER_DEFAULT_HOUR +import com.rafaelfelipeac.replyradar.core.util.AppConstants.REMINDER_DEFAULT_MINUTE import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformDatePicker.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformDatePicker.kt similarity index 87% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformDatePicker.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformDatePicker.kt index bb735e2..2ce6b0b 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformDatePicker.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformDatePicker.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util.datetime +package com.rafaelfelipeac.replyradar.core.datetime import androidx.compose.runtime.Composable import kotlinx.datetime.LocalDate diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformTimePicker.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformTimePicker.kt similarity index 86% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformTimePicker.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformTimePicker.kt index e88ec5e..f3c05de 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformTimePicker.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformTimePicker.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util.datetime +package com.rafaelfelipeac.replyradar.core.datetime import androidx.compose.runtime.Composable import kotlinx.datetime.LocalDate diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Timestamp.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Timestamp.kt similarity index 80% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Timestamp.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Timestamp.kt index 12e051e..07c413c 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Timestamp.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Timestamp.kt @@ -1,9 +1,9 @@ -package com.rafaelfelipeac.replyradar.core.util.datetime +package com.rafaelfelipeac.replyradar.core.datetime -import com.rafaelfelipeac.replyradar.core.AppConstants.INITIAL_DATE -import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_DEFAULT_HOUR -import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_DEFAULT_MINUTE -import com.rafaelfelipeac.replyradar.core.AppConstants.REMINDER_TOMORROW_OFFSET +import com.rafaelfelipeac.replyradar.core.util.AppConstants.INITIAL_DATE +import com.rafaelfelipeac.replyradar.core.util.AppConstants.REMINDER_DEFAULT_HOUR +import com.rafaelfelipeac.replyradar.core.util.AppConstants.REMINDER_DEFAULT_MINUTE +import com.rafaelfelipeac.replyradar.core.util.AppConstants.REMINDER_TOMORROW_OFFSET import com.rafaelfelipeac.replyradar.core.util.toTwoDigitString import kotlinx.datetime.DateTimeUnit.Companion.DAY import kotlinx.datetime.LocalDate diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/External.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/external/External.kt similarity index 69% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/External.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/external/External.kt index 4c9c91a..1b88284 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/External.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/external/External.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util +package com.rafaelfelipeac.replyradar.core.external expect fun openEmailApp(to: String, subject: String, body: String) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/language/AppLanguage.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/language/AppLanguage.kt similarity index 79% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/language/AppLanguage.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/language/AppLanguage.kt index de605f1..5c92c91 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/language/AppLanguage.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/language/AppLanguage.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.common.language +package com.rafaelfelipeac.replyradar.core.language enum class AppLanguage { ENGLISH, diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderScheduler.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderScheduler.kt similarity index 78% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderScheduler.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderScheduler.kt index 5ff4c25..2cd51f0 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/reminder/ReminderScheduler.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderScheduler.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util.reminder +package com.rafaelfelipeac.replyradar.core.reminder interface ReminderScheduler { diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/LocalReplyRadarStrings.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/LocalReplyRadarStrings.kt similarity index 73% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/LocalReplyRadarStrings.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/LocalReplyRadarStrings.kt index 69434dc..19c069c 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/LocalReplyRadarStrings.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/LocalReplyRadarStrings.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.common.strings +package com.rafaelfelipeac.replyradar.core.strings import androidx.compose.runtime.staticCompositionLocalOf diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/Strings.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/Strings.kt similarity index 98% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/Strings.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/Strings.kt index 2eb379a..23a56e5 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/Strings.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/Strings.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.common.strings +package com.rafaelfelipeac.replyradar.core.strings interface Strings { val appName: String diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsEn.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt similarity index 98% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsEn.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt index e6eaf4c..76ca532 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsEn.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt @@ -1,6 +1,6 @@ -package com.rafaelfelipeac.replyradar.core.common.strings +package com.rafaelfelipeac.replyradar.core.strings -import com.rafaelfelipeac.replyradar.core.util.getAppVersion +import com.rafaelfelipeac.replyradar.core.version.getAppVersion object StringsEn : Strings { override val appName = "Reply Radar" diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsProvider.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsProvider.kt similarity index 53% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsProvider.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsProvider.kt index f669ee8..f25b583 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsProvider.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsProvider.kt @@ -1,9 +1,9 @@ -package com.rafaelfelipeac.replyradar.core.common.strings +package com.rafaelfelipeac.replyradar.core.strings -import com.rafaelfelipeac.replyradar.core.common.language.AppLanguage -import com.rafaelfelipeac.replyradar.core.common.language.AppLanguage.ENGLISH -import com.rafaelfelipeac.replyradar.core.common.language.AppLanguage.PORTUGUESE -import com.rafaelfelipeac.replyradar.core.common.language.AppLanguage.SYSTEM +import com.rafaelfelipeac.replyradar.core.language.AppLanguage +import com.rafaelfelipeac.replyradar.core.language.AppLanguage.ENGLISH +import com.rafaelfelipeac.replyradar.core.language.AppLanguage.PORTUGUESE +import com.rafaelfelipeac.replyradar.core.language.AppLanguage.SYSTEM object StringsProvider { private val english = StringsEn diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsPt.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt similarity index 98% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsPt.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt index a4fe109..6b463f3 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/strings/StringsPt.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt @@ -1,6 +1,6 @@ -package com.rafaelfelipeac.replyradar.core.common.strings +package com.rafaelfelipeac.replyradar.core.strings -import com.rafaelfelipeac.replyradar.core.util.getAppVersion +import com.rafaelfelipeac.replyradar.core.version.getAppVersion object StringsPt : Strings { override val appName = "Reply Radar" diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ColorScheme.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/theme/ColorScheme.kt similarity index 96% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ColorScheme.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/theme/ColorScheme.kt index c5a17f8..dce94ee 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ColorScheme.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/theme/ColorScheme.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.common.ui.theme +package com.rafaelfelipeac.replyradar.core.theme import androidx.compose.material3.darkColorScheme import androidx.compose.material3.lightColorScheme diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ReplyRadarColorScheme.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/theme/ReplyRadarColorScheme.kt similarity index 84% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ReplyRadarColorScheme.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/theme/ReplyRadarColorScheme.kt index 9d8f692..83a9170 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ReplyRadarColorScheme.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/theme/ReplyRadarColorScheme.kt @@ -1,9 +1,9 @@ -package com.rafaelfelipeac.replyradar.core.common.ui.theme +package com.rafaelfelipeac.replyradar.core.theme import androidx.compose.material3.ColorScheme import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color -import com.rafaelfelipeac.replyradar.core.common.ui.theme.ReplyRadarThemeAccessors.colors +import com.rafaelfelipeac.replyradar.core.theme.ReplyRadarThemeAccessors.colors val ColorScheme.buttonBorderColor: Color @Composable get() = colors.buttonBorderColor diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ReplyRadarColors.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/theme/ReplyRadarColors.kt similarity index 90% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ReplyRadarColors.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/theme/ReplyRadarColors.kt index fa5a905..949a11e 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ReplyRadarColors.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/theme/ReplyRadarColors.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.common.ui.theme +package com.rafaelfelipeac.replyradar.core.theme import androidx.compose.runtime.Immutable import androidx.compose.runtime.staticCompositionLocalOf diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ReplyRadarTheme.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/theme/ReplyRadarTheme.kt similarity index 90% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ReplyRadarTheme.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/theme/ReplyRadarTheme.kt index caabd78..2825672 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ReplyRadarTheme.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/theme/ReplyRadarTheme.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.common.ui.theme +package com.rafaelfelipeac.replyradar.core.theme import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ReplyRadarThemeAccessors.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/theme/ReplyRadarThemeAccessors.kt similarity index 75% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ReplyRadarThemeAccessors.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/theme/ReplyRadarThemeAccessors.kt index 19cda12..7e15171 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/ReplyRadarThemeAccessors.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/theme/ReplyRadarThemeAccessors.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.common.ui.theme +package com.rafaelfelipeac.replyradar.core.theme import androidx.compose.runtime.Composable diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/model/AppTheme.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/theme/model/AppTheme.kt similarity index 77% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/model/AppTheme.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/theme/model/AppTheme.kt index d508865..ce2665b 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/theme/model/AppTheme.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/theme/model/AppTheme.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.common.ui.theme.model +package com.rafaelfelipeac.replyradar.core.theme.model enum class AppTheme { LIGHT, diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/AppConstants.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/AppConstants.kt similarity index 88% rename from composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/AppConstants.kt rename to composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/AppConstants.kt index 2785370..af33bae 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/AppConstants.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/AppConstants.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core +package com.rafaelfelipeac.replyradar.core.util object AppConstants { const val EMPTY = "" diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Version.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Version.kt deleted file mode 100644 index 296a2be..0000000 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Version.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.rafaelfelipeac.replyradar.core.util - -expect fun getAppVersion(): String diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/version/Version.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/version/Version.kt new file mode 100644 index 0000000..b83fb3b --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/version/Version.kt @@ -0,0 +1,3 @@ +package com.rafaelfelipeac.replyradar.core.version + +expect fun getAppVersion(): String diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/di/Modules.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/di/Modules.kt index 0e0386a..b5d3ce2 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/di/Modules.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/di/Modules.kt @@ -2,8 +2,8 @@ package com.rafaelfelipeac.replyradar.di import androidx.sqlite.driver.bundled.BundledSQLiteDriver import com.rafaelfelipeac.replyradar.core.database.DatabaseFactory -import com.rafaelfelipeac.replyradar.core.util.datetime.Clock -import com.rafaelfelipeac.replyradar.core.util.datetime.getClock +import com.rafaelfelipeac.replyradar.core.datetime.Clock +import com.rafaelfelipeac.replyradar.core.datetime.getClock import org.koin.core.module.Module import org.koin.dsl.module diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/activitylog/presentation/ActivityLogScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/activitylog/presentation/ActivityLogScreen.kt index b7c67fc..16ad645 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/activitylog/presentation/ActivityLogScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/activitylog/presentation/ActivityLogScreen.kt @@ -34,16 +34,16 @@ import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyProgress import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyRadarError import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyRadarPlaceholder import com.rafaelfelipeac.replyradar.core.common.ui.iconSize import com.rafaelfelipeac.replyradar.core.common.ui.listDividerThickness import com.rafaelfelipeac.replyradar.core.common.ui.paddingMedium -import com.rafaelfelipeac.replyradar.core.common.ui.theme.horizontalDividerColor +import com.rafaelfelipeac.replyradar.core.theme.horizontalDividerColor import com.rafaelfelipeac.replyradar.core.util.format -import com.rafaelfelipeac.replyradar.core.util.datetime.formatTimestamp +import com.rafaelfelipeac.replyradar.core.datetime.formatTimestamp import com.rafaelfelipeac.replyradar.features.activitylog.presentation.ActivityLogViewModel.Companion.ERROR_GET_ACTIVITY_LOG import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserAction import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionTargetType diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/app/settings/AppSettingsViewModel.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/app/settings/AppSettingsViewModel.kt index 8aa7a05..6ffe946 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/app/settings/AppSettingsViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/app/settings/AppSettingsViewModel.kt @@ -2,8 +2,8 @@ package com.rafaelfelipeac.replyradar.features.app.settings import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.rafaelfelipeac.replyradar.core.common.language.AppLanguage -import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme +import com.rafaelfelipeac.replyradar.core.language.AppLanguage +import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme import com.rafaelfelipeac.replyradar.features.settings.domain.usecase.GetLanguageUseCase import com.rafaelfelipeac.replyradar.features.settings.domain.usecase.GetThemeUseCase import kotlinx.coroutines.flow.SharingStarted.Companion.Eagerly diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/database/entity/ReplyEntity.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/database/entity/ReplyEntity.kt index 477b5cf..8833b09 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/database/entity/ReplyEntity.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/database/entity/ReplyEntity.kt @@ -2,7 +2,7 @@ package com.rafaelfelipeac.replyradar.features.reply.data.database.entity import androidx.room.Entity import androidx.room.PrimaryKey -import com.rafaelfelipeac.replyradar.core.AppConstants.INITIAL_DATE +import com.rafaelfelipeac.replyradar.core.util.AppConstants.INITIAL_DATE @Entity(tableName = "replies") data class ReplyEntity( diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/repository/ReplyRepositoryImpl.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/repository/ReplyRepositoryImpl.kt index bd03f7d..deaa05f 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/repository/ReplyRepositoryImpl.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/repository/ReplyRepositoryImpl.kt @@ -1,7 +1,7 @@ package com.rafaelfelipeac.replyradar.features.reply.data.repository -import com.rafaelfelipeac.replyradar.core.AppConstants.INITIAL_DATE -import com.rafaelfelipeac.replyradar.core.util.datetime.Clock +import com.rafaelfelipeac.replyradar.core.util.AppConstants.INITIAL_DATE +import com.rafaelfelipeac.replyradar.core.datetime.Clock import com.rafaelfelipeac.replyradar.features.reply.data.database.dao.ReplyDao import com.rafaelfelipeac.replyradar.features.reply.data.mapper.toReply import com.rafaelfelipeac.replyradar.features.reply.data.mapper.toReplyEntity diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/domain/model/Reply.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/domain/model/Reply.kt index b1335b2..8c580a2 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/domain/model/Reply.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/domain/model/Reply.kt @@ -1,6 +1,6 @@ package com.rafaelfelipeac.replyradar.features.reply.domain.model -import com.rafaelfelipeac.replyradar.core.AppConstants.INITIAL_DATE +import com.rafaelfelipeac.replyradar.core.util.AppConstants.INITIAL_DATE data class Reply( val id: Long = 0, diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt index 3402e51..c01fd87 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt @@ -27,9 +27,9 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.rafaelfelipeac.replyradar.core.AppConstants.INITIAL_DATE -import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings -import com.rafaelfelipeac.replyradar.core.common.strings.Strings +import com.rafaelfelipeac.replyradar.core.util.AppConstants.INITIAL_DATE +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.strings.Strings import com.rafaelfelipeac.replyradar.core.common.ui.components.NotificationPermissionDialog import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplySnackbar import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyTab @@ -119,7 +119,7 @@ fun ReplyListScreen( effect.collect { effect -> when (effect) { is SnackbarState -> snackbarHostState.showSnackbar( - getSnackbarMessage( + getSnackbarMessage( // esses valores estao aparecendo com o idioma trocado? checar. effect, strings ) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt index 178ad2b..804823a 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt @@ -2,8 +2,8 @@ package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.rafaelfelipeac.replyradar.core.AppConstants.INITIAL_DATE -import com.rafaelfelipeac.replyradar.core.util.reminder.ReminderScheduler +import com.rafaelfelipeac.replyradar.core.util.AppConstants.INITIAL_DATE +import com.rafaelfelipeac.replyradar.core.reminder.ReminderScheduler import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.domain.usecase.DeleteReplyUseCase import com.rafaelfelipeac.replyradar.features.reply.domain.usecase.GetRepliesUseCase diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/FloatingActionButton.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/FloatingActionButton.kt index 377353a..4fd99d3 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/FloatingActionButton.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/FloatingActionButton.kt @@ -6,7 +6,7 @@ import androidx.compose.material3.ColorScheme import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.runtime.Composable -import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnAddReplyClick diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesArchivedScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesArchivedScreen.kt index 9327cd9..51a27eb 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesArchivedScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesArchivedScreen.kt @@ -3,7 +3,7 @@ package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.comp import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyRadarPlaceholder import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnReplyClick diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesOnTheRadarScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesOnTheRadarScreen.kt index decc73e..55816e5 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesOnTheRadarScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesOnTheRadarScreen.kt @@ -3,7 +3,7 @@ package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.comp import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyProgress import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyRadarError import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyRadarPlaceholder diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesResolvedScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesResolvedScreen.kt index 3c54648..6a55539 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesResolvedScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesResolvedScreen.kt @@ -3,7 +3,7 @@ package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.comp import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyRadarPlaceholder import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnReplyClick diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/ReplyList.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/ReplyList.kt index 9077b3e..cd0a9e8 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/ReplyList.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/ReplyList.kt @@ -14,7 +14,7 @@ import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Modifier import com.rafaelfelipeac.replyradar.core.common.ui.listDividerThickness import com.rafaelfelipeac.replyradar.core.common.ui.paddingMedium -import com.rafaelfelipeac.replyradar.core.common.ui.theme.horizontalDividerColor +import com.rafaelfelipeac.replyradar.core.theme.horizontalDividerColor import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply @Composable diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/ReplyTimestampInfo.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/ReplyTimestampInfo.kt index 8962932..92a2553 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/ReplyTimestampInfo.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/ReplyTimestampInfo.kt @@ -7,11 +7,11 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment.Companion.Start import androidx.compose.ui.Modifier -import com.rafaelfelipeac.replyradar.core.AppConstants.INITIAL_DATE -import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.util.AppConstants.INITIAL_DATE +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall import com.rafaelfelipeac.replyradar.core.util.format -import com.rafaelfelipeac.replyradar.core.util.datetime.formatTimestamp +import com.rafaelfelipeac.replyradar.core.datetime.formatTimestamp import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetState diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/TopBar.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/TopBar.kt index 5bff6d5..1e35512 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/TopBar.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/TopBar.kt @@ -14,11 +14,11 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign -import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.fontSizeLarge import com.rafaelfelipeac.replyradar.core.common.ui.iconSize import com.rafaelfelipeac.replyradar.core.common.ui.paddingMedium -import com.rafaelfelipeac.replyradar.core.common.ui.theme.toolbarIconsColor +import com.rafaelfelipeac.replyradar.core.theme.toolbarIconsColor import org.jetbrains.compose.resources.painterResource import replyradar.composeapp.generated.resources.Res.drawable import replyradar.composeapp.generated.resources.ic_settings diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetActions.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetActions.kt index 66d3b20..111859f 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetActions.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetActions.kt @@ -14,16 +14,16 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import com.rafaelfelipeac.replyradar.core.common.clock.LocalClock -import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.clock.LocalClock +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyButton import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyConfirmationDialog import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyOutlinedButton import com.rafaelfelipeac.replyradar.core.common.ui.paddingMedium import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall -import com.rafaelfelipeac.replyradar.core.util.datetime.dateTime +import com.rafaelfelipeac.replyradar.core.datetime.dateTime import com.rafaelfelipeac.replyradar.core.util.format -import com.rafaelfelipeac.replyradar.core.util.datetime.getReminderTimestamp +import com.rafaelfelipeac.replyradar.core.datetime.getReminderTimestamp import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetMode.EDIT import kotlinx.datetime.LocalDate diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt index 1d2a52a..eaa80cf 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt @@ -16,15 +16,15 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import com.rafaelfelipeac.replyradar.core.AppConstants.EMPTY -import com.rafaelfelipeac.replyradar.core.AppConstants.INITIAL_DATE -import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.util.AppConstants.EMPTY +import com.rafaelfelipeac.replyradar.core.util.AppConstants.INITIAL_DATE +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyReminder import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyTextField import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyTextFieldSize.Large import com.rafaelfelipeac.replyradar.core.common.ui.paddingMedium import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall -import com.rafaelfelipeac.replyradar.core.util.datetime.dateTime +import com.rafaelfelipeac.replyradar.core.datetime.dateTime import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.ReplyTimestampInfo diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/data/repository/SettingsRepositoryImpl.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/data/repository/SettingsRepositoryImpl.kt index 0e15df6..e627022 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/data/repository/SettingsRepositoryImpl.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/data/repository/SettingsRepositoryImpl.kt @@ -4,8 +4,8 @@ import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey -import com.rafaelfelipeac.replyradar.core.common.language.AppLanguage -import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme +import com.rafaelfelipeac.replyradar.core.language.AppLanguage +import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme import com.rafaelfelipeac.replyradar.features.settings.data.repository.SettingsRepositoryImpl.Keys.LANGUAGE import com.rafaelfelipeac.replyradar.features.settings.data.repository.SettingsRepositoryImpl.Keys.THEME import com.rafaelfelipeac.replyradar.features.settings.domain.repository.SettingsRepository diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/repository/SettingsRepository.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/repository/SettingsRepository.kt index 511057c..f1016ce 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/repository/SettingsRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/repository/SettingsRepository.kt @@ -1,7 +1,7 @@ package com.rafaelfelipeac.replyradar.features.settings.domain.repository -import com.rafaelfelipeac.replyradar.core.common.language.AppLanguage -import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme +import com.rafaelfelipeac.replyradar.core.language.AppLanguage +import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme import kotlinx.coroutines.flow.Flow interface SettingsRepository { diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/usecase/GetLanguageUseCase.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/usecase/GetLanguageUseCase.kt index 1ee5f31..3201fb9 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/usecase/GetLanguageUseCase.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/usecase/GetLanguageUseCase.kt @@ -1,6 +1,6 @@ package com.rafaelfelipeac.replyradar.features.settings.domain.usecase -import com.rafaelfelipeac.replyradar.core.common.language.AppLanguage +import com.rafaelfelipeac.replyradar.core.language.AppLanguage import com.rafaelfelipeac.replyradar.features.settings.domain.repository.SettingsRepository import kotlinx.coroutines.flow.Flow diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/usecase/GetThemeUseCase.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/usecase/GetThemeUseCase.kt index dcd045b..9c2e664 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/usecase/GetThemeUseCase.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/usecase/GetThemeUseCase.kt @@ -1,6 +1,6 @@ package com.rafaelfelipeac.replyradar.features.settings.domain.usecase -import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme +import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme import com.rafaelfelipeac.replyradar.features.settings.domain.repository.SettingsRepository import kotlinx.coroutines.flow.Flow diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/usecase/SetLanguageUseCase.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/usecase/SetLanguageUseCase.kt index 5046980..8a60699 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/usecase/SetLanguageUseCase.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/usecase/SetLanguageUseCase.kt @@ -1,6 +1,6 @@ package com.rafaelfelipeac.replyradar.features.settings.domain.usecase -import com.rafaelfelipeac.replyradar.core.common.language.AppLanguage +import com.rafaelfelipeac.replyradar.core.language.AppLanguage import com.rafaelfelipeac.replyradar.features.settings.domain.repository.SettingsRepository interface SetLanguageUseCase { diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/usecase/SetThemeUseCase.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/usecase/SetThemeUseCase.kt index 84b76ff..f4b2f6a 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/usecase/SetThemeUseCase.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/usecase/SetThemeUseCase.kt @@ -1,6 +1,6 @@ package com.rafaelfelipeac.replyradar.features.settings.domain.usecase -import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme +import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme import com.rafaelfelipeac.replyradar.features.settings.domain.repository.SettingsRepository interface SetThemeUseCase { diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsIntent.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsIntent.kt index 90daf45..dcc0c16 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsIntent.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsIntent.kt @@ -1,7 +1,7 @@ package com.rafaelfelipeac.replyradar.features.settings.presentation -import com.rafaelfelipeac.replyradar.core.common.language.AppLanguage -import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme +import com.rafaelfelipeac.replyradar.core.language.AppLanguage +import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme sealed interface SettingsIntent { data class OnSelectTheme(val theme: AppTheme) : SettingsIntent diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsScreen.kt index 55d93f7..123b5d5 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsScreen.kt @@ -31,25 +31,25 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.rafaelfelipeac.replyradar.core.AppConstants.EMAIL -import com.rafaelfelipeac.replyradar.core.AppConstants.PACKAGE_NAME -import com.rafaelfelipeac.replyradar.core.common.language.AppLanguage -import com.rafaelfelipeac.replyradar.core.common.language.AppLanguage.ENGLISH -import com.rafaelfelipeac.replyradar.core.common.language.AppLanguage.PORTUGUESE -import com.rafaelfelipeac.replyradar.core.common.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.util.AppConstants.EMAIL +import com.rafaelfelipeac.replyradar.core.util.AppConstants.PACKAGE_NAME +import com.rafaelfelipeac.replyradar.core.language.AppLanguage +import com.rafaelfelipeac.replyradar.core.language.AppLanguage.ENGLISH +import com.rafaelfelipeac.replyradar.core.language.AppLanguage.PORTUGUESE +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.iconSizeLarge import com.rafaelfelipeac.replyradar.core.common.ui.paddingMedium import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall import com.rafaelfelipeac.replyradar.core.common.ui.paddingXSmall import com.rafaelfelipeac.replyradar.core.common.ui.radioButtonSize import com.rafaelfelipeac.replyradar.core.common.ui.settingsAppVersionOffset -import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme -import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme.DARK -import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme.LIGHT -import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme.SYSTEM -import com.rafaelfelipeac.replyradar.core.util.getAppVersion -import com.rafaelfelipeac.replyradar.core.util.openEmailApp -import com.rafaelfelipeac.replyradar.core.util.openPlayStoreApp +import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme +import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme.DARK +import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme.LIGHT +import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme.SYSTEM +import com.rafaelfelipeac.replyradar.core.version.getAppVersion +import com.rafaelfelipeac.replyradar.core.external.openEmailApp +import com.rafaelfelipeac.replyradar.core.external.openPlayStoreApp import com.rafaelfelipeac.replyradar.features.settings.presentation.SettingsIntent.OnSelectFeedback import com.rafaelfelipeac.replyradar.features.settings.presentation.SettingsIntent.OnSelectLanguage import com.rafaelfelipeac.replyradar.features.settings.presentation.SettingsIntent.OnSelectRate diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsState.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsState.kt index 848991e..524f62a 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsState.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsState.kt @@ -1,7 +1,7 @@ package com.rafaelfelipeac.replyradar.features.settings.presentation -import com.rafaelfelipeac.replyradar.core.common.language.AppLanguage -import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme +import com.rafaelfelipeac.replyradar.core.language.AppLanguage +import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme data class SettingsState( val theme: AppTheme = AppTheme.SYSTEM, diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsViewModel.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsViewModel.kt index 747d568..f3b3b27 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsViewModel.kt @@ -2,8 +2,8 @@ package com.rafaelfelipeac.replyradar.features.settings.presentation import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.rafaelfelipeac.replyradar.core.common.language.AppLanguage -import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme +import com.rafaelfelipeac.replyradar.core.language.AppLanguage +import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme import com.rafaelfelipeac.replyradar.features.settings.domain.usecase.GetLanguageUseCase import com.rafaelfelipeac.replyradar.features.settings.domain.usecase.GetThemeUseCase import com.rafaelfelipeac.replyradar.features.settings.domain.usecase.SetLanguageUseCase diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/useractions/data/repository/UserActionRepositoryImpl.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/useractions/data/repository/UserActionRepositoryImpl.kt index 80aabe4..6b43c3b 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/useractions/data/repository/UserActionRepositoryImpl.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/useractions/data/repository/UserActionRepositoryImpl.kt @@ -1,6 +1,6 @@ package com.rafaelfelipeac.replyradar.features.useractions.data.repository -import com.rafaelfelipeac.replyradar.core.util.datetime.Clock +import com.rafaelfelipeac.replyradar.core.datetime.Clock import com.rafaelfelipeac.replyradar.features.reply.data.database.dao.ReplyDao import com.rafaelfelipeac.replyradar.features.useractions.data.database.dao.UserActionDao import com.rafaelfelipeac.replyradar.features.useractions.data.database.entity.UserActionEntity diff --git a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/core/util/FakeClock.kt b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/core/util/FakeClock.kt index 1f64b74..3de5788 100644 --- a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/core/util/FakeClock.kt +++ b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/core/util/FakeClock.kt @@ -1,6 +1,6 @@ package com.rafaelfelipeac.replyradar.fakes.core.util -import com.rafaelfelipeac.replyradar.core.util.datetime.Clock +import com.rafaelfelipeac.replyradar.core.datetime.Clock class FakeClock(private val fixedNow: Long) : Clock { override fun now(): Long = fixedNow diff --git a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/settings/data/FakeSettingsRepository.kt b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/settings/data/FakeSettingsRepository.kt index c01bd50..3af4d40 100644 --- a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/settings/data/FakeSettingsRepository.kt +++ b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/settings/data/FakeSettingsRepository.kt @@ -1,7 +1,7 @@ package com.rafaelfelipeac.replyradar.fakes.settings.data -import com.rafaelfelipeac.replyradar.core.common.language.AppLanguage -import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme +import com.rafaelfelipeac.replyradar.core.language.AppLanguage +import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme import com.rafaelfelipeac.replyradar.features.settings.domain.repository.SettingsRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf diff --git a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/settings/domain/FakeGetLanguageUseCase.kt b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/settings/domain/FakeGetLanguageUseCase.kt index 70678b7..447bc54 100644 --- a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/settings/domain/FakeGetLanguageUseCase.kt +++ b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/settings/domain/FakeGetLanguageUseCase.kt @@ -1,7 +1,7 @@ package com.rafaelfelipeac.replyradar.fakes.settings.domain -import com.rafaelfelipeac.replyradar.core.common.language.AppLanguage -import com.rafaelfelipeac.replyradar.core.common.language.AppLanguage.SYSTEM +import com.rafaelfelipeac.replyradar.core.language.AppLanguage +import com.rafaelfelipeac.replyradar.core.language.AppLanguage.SYSTEM import com.rafaelfelipeac.replyradar.features.settings.domain.usecase.GetLanguageUseCase import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf diff --git a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/settings/domain/FakeGetThemeUseCase.kt b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/settings/domain/FakeGetThemeUseCase.kt index ff55b57..3363c93 100644 --- a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/settings/domain/FakeGetThemeUseCase.kt +++ b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/settings/domain/FakeGetThemeUseCase.kt @@ -1,7 +1,7 @@ package com.rafaelfelipeac.replyradar.fakes.settings.domain -import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme -import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme.SYSTEM +import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme +import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme.SYSTEM import com.rafaelfelipeac.replyradar.features.settings.domain.usecase.GetThemeUseCase import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf diff --git a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/app/settings/AppSettingsViewModelTest.kt b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/app/settings/AppSettingsViewModelTest.kt index 710eb3f..5a62b92 100644 --- a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/app/settings/AppSettingsViewModelTest.kt +++ b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/app/settings/AppSettingsViewModelTest.kt @@ -1,7 +1,7 @@ package com.rafaelfelipeac.replyradar.features.app.settings -import com.rafaelfelipeac.replyradar.core.common.language.AppLanguage.PORTUGUESE -import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme.DARK +import com.rafaelfelipeac.replyradar.core.language.AppLanguage.PORTUGUESE +import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme.DARK import com.rafaelfelipeac.replyradar.fakes.settings.domain.FakeGetLanguageUseCase import com.rafaelfelipeac.replyradar.fakes.settings.domain.FakeGetThemeUseCase import kotlin.test.AfterTest diff --git a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/ReplyRepositoryTest.kt b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/ReplyRepositoryTest.kt index 797e138..5347da2 100644 --- a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/ReplyRepositoryTest.kt +++ b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/ReplyRepositoryTest.kt @@ -1,6 +1,6 @@ package com.rafaelfelipeac.replyradar.features.reply.data -import com.rafaelfelipeac.replyradar.core.AppConstants.EMPTY +import com.rafaelfelipeac.replyradar.core.util.AppConstants.EMPTY import com.rafaelfelipeac.replyradar.fakes.core.util.FakeClock import com.rafaelfelipeac.replyradar.fakes.reply.data.FakeReplyDao import com.rafaelfelipeac.replyradar.features.reply.data.database.entity.ReplyEntity diff --git a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/ReplyListViewModelTest.kt b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/ReplyListViewModelTest.kt index 0451615..fee0e50 100644 --- a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/ReplyListViewModelTest.kt +++ b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/ReplyListViewModelTest.kt @@ -14,7 +14,7 @@ import com.rafaelfelipeac.replyradar.fakes.reply.domain.FakeToggleResolveReplyUs import com.rafaelfelipeac.replyradar.fakes.reply.domain.FakeUpsertReplyUseCase import com.rafaelfelipeac.replyradar.fakes.useractions.domain.FakeLogUserActionUseCase import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnAddReply +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnAddOrEditReply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnDeleteReply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnDismissBottomSheet import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnEditReply diff --git a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/GetLanguageUseCaseImplTest.kt b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/GetLanguageUseCaseImplTest.kt index 2b7cf0a..ef353e3 100644 --- a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/GetLanguageUseCaseImplTest.kt +++ b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/GetLanguageUseCaseImplTest.kt @@ -1,7 +1,7 @@ package com.rafaelfelipeac.replyradar.features.settings.domain import app.cash.turbine.test -import com.rafaelfelipeac.replyradar.core.common.language.AppLanguage.PORTUGUESE +import com.rafaelfelipeac.replyradar.core.language.AppLanguage.PORTUGUESE import com.rafaelfelipeac.replyradar.fakes.settings.data.FakeSettingsRepository import com.rafaelfelipeac.replyradar.features.settings.domain.usecase.GetLanguageUseCaseImpl import kotlin.test.Test diff --git a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/GetThemeUseCaseImplTest.kt b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/GetThemeUseCaseImplTest.kt index 7e9e46c..64cb9a7 100644 --- a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/GetThemeUseCaseImplTest.kt +++ b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/GetThemeUseCaseImplTest.kt @@ -1,7 +1,7 @@ package com.rafaelfelipeac.replyradar.features.settings.domain import app.cash.turbine.test -import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme.DARK +import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme.DARK import com.rafaelfelipeac.replyradar.fakes.settings.data.FakeSettingsRepository import com.rafaelfelipeac.replyradar.features.settings.domain.usecase.GetThemeUseCaseImpl import kotlin.test.Test diff --git a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/SetLanguageUseCaseImplTest.kt b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/SetLanguageUseCaseImplTest.kt index 5ed7dc3..4ecf30d 100644 --- a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/SetLanguageUseCaseImplTest.kt +++ b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/SetLanguageUseCaseImplTest.kt @@ -1,6 +1,6 @@ package com.rafaelfelipeac.replyradar.features.settings.domain -import com.rafaelfelipeac.replyradar.core.common.language.AppLanguage.PORTUGUESE +import com.rafaelfelipeac.replyradar.core.language.AppLanguage.PORTUGUESE import com.rafaelfelipeac.replyradar.fakes.settings.data.FakeSettingsRepository import com.rafaelfelipeac.replyradar.features.settings.domain.usecase.SetLanguageUseCaseImpl import kotlin.test.Test diff --git a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/SetThemeUseCaseTest.kt b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/SetThemeUseCaseTest.kt index 4be3f6f..faa076a 100644 --- a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/SetThemeUseCaseTest.kt +++ b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/settings/domain/SetThemeUseCaseTest.kt @@ -1,6 +1,6 @@ package com.rafaelfelipeac.replyradar.features.settings.domain -import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme.DARK +import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme.DARK import com.rafaelfelipeac.replyradar.fakes.settings.data.FakeSettingsRepository import com.rafaelfelipeac.replyradar.features.settings.domain.usecase.SetThemeUseCaseImpl import kotlin.test.Test diff --git a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsViewModelTest.kt b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsViewModelTest.kt index 24b4fd2..3aae7ac 100644 --- a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsViewModelTest.kt +++ b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsViewModelTest.kt @@ -1,8 +1,8 @@ package com.rafaelfelipeac.replyradar.features.settings.presentation import app.cash.turbine.test -import com.rafaelfelipeac.replyradar.core.common.language.AppLanguage.ENGLISH -import com.rafaelfelipeac.replyradar.core.common.ui.theme.model.AppTheme.DARK +import com.rafaelfelipeac.replyradar.core.language.AppLanguage.ENGLISH +import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme.DARK import com.rafaelfelipeac.replyradar.fakes.settings.data.FakeSettingsRepository import com.rafaelfelipeac.replyradar.fakes.useractions.domain.FakeLogUserActionUseCase import com.rafaelfelipeac.replyradar.features.settings.domain.usecase.GetLanguageUseCaseImpl diff --git a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/database/DatabaseFactory.kt b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/database/DatabaseFactory.kt index 2fa94ea..60a4cc4 100644 --- a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/database/DatabaseFactory.kt +++ b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/database/DatabaseFactory.kt @@ -2,7 +2,7 @@ package com.rafaelfelipeac.replyradar.core.database import androidx.room.Room import androidx.room.RoomDatabase -import com.rafaelfelipeac.replyradar.core.AppConstants.DB_NAME +import com.rafaelfelipeac.replyradar.core.util.AppConstants.DB_NAME import java.io.File actual class DatabaseFactory { diff --git a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Clock.desktop.kt b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.desktop.kt similarity index 65% rename from composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Clock.desktop.kt rename to composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.desktop.kt index a83de67..46c7e91 100644 --- a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Clock.desktop.kt +++ b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.desktop.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util.datetime +package com.rafaelfelipeac.replyradar.core.datetime actual fun getClock(): Clock = object : Clock { override fun now(): Long = System.currentTimeMillis() diff --git a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformDatePicker.desktop.kt b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformDatePicker.desktop.kt similarity index 88% rename from composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformDatePicker.desktop.kt rename to composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformDatePicker.desktop.kt index a73796e..61b732c 100644 --- a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformDatePicker.desktop.kt +++ b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformDatePicker.desktop.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util.datetime +package com.rafaelfelipeac.replyradar.core.datetime import androidx.compose.runtime.Composable import kotlinx.datetime.LocalDate diff --git a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformTimePicker.desktop.kt b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformTimePicker.desktop.kt similarity index 88% rename from composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformTimePicker.desktop.kt rename to composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformTimePicker.desktop.kt index 4d901ed..bffa00f 100644 --- a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformTimePicker.desktop.kt +++ b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformTimePicker.desktop.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util.datetime +package com.rafaelfelipeac.replyradar.core.datetime import androidx.compose.runtime.Composable import kotlinx.datetime.LocalDate diff --git a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/External.desktop.kt b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/external/External.desktop.kt similarity index 81% rename from composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/External.desktop.kt rename to composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/external/External.desktop.kt index 73cd7a4..267aabd 100644 --- a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/External.desktop.kt +++ b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/external/External.desktop.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util +package com.rafaelfelipeac.replyradar.core.external actual fun openEmailApp(to: String, subject: String, body: String) { TODO("Not yet implemented for this platform.") diff --git a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Version.desktop.kt b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/version/Version.desktop.kt similarity index 64% rename from composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Version.desktop.kt rename to composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/version/Version.desktop.kt index 2d4b1d5..ce6c528 100644 --- a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Version.desktop.kt +++ b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/version/Version.desktop.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util +package com.rafaelfelipeac.replyradar.core.version actual fun getAppVersion(): String { TODO("Not yet implemented for this platform.") diff --git a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/database/DatabaseFactory.kt b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/database/DatabaseFactory.kt index 55a20b0..7b0e98a 100644 --- a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/database/DatabaseFactory.kt +++ b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/database/DatabaseFactory.kt @@ -2,7 +2,7 @@ package com.rafaelfelipeac.replyradar.core.database import androidx.room.Room import androidx.room.RoomDatabase -import com.rafaelfelipeac.replyradar.core.AppConstants.DB_NAME +import com.rafaelfelipeac.replyradar.core.util.AppConstants.DB_NAME import kotlinx.cinterop.ExperimentalForeignApi import platform.Foundation.NSDocumentDirectory import platform.Foundation.NSFileManager diff --git a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Clock.ios.kt b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.ios.kt similarity index 83% rename from composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Clock.ios.kt rename to composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.ios.kt index 545479d..fba588e 100644 --- a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/Clock.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.ios.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util.datetime +package com.rafaelfelipeac.replyradar.core.datetime import platform.Foundation.NSDate import platform.Foundation.timeIntervalSince1970 diff --git a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformDatePicker.ios.kt b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformDatePicker.ios.kt similarity index 88% rename from composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformDatePicker.ios.kt rename to composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformDatePicker.ios.kt index a73796e..61b732c 100644 --- a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformDatePicker.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformDatePicker.ios.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util.datetime +package com.rafaelfelipeac.replyradar.core.datetime import androidx.compose.runtime.Composable import kotlinx.datetime.LocalDate diff --git a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformTimePicker.ios.kt b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformTimePicker.ios.kt similarity index 88% rename from composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformTimePicker.ios.kt rename to composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformTimePicker.ios.kt index 4d901ed..bffa00f 100644 --- a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/datetime/PlatformTimePicker.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformTimePicker.ios.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util.datetime +package com.rafaelfelipeac.replyradar.core.datetime import androidx.compose.runtime.Composable import kotlinx.datetime.LocalDate diff --git a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/External.ios.kt b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/external/External.ios.kt similarity index 81% rename from composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/External.ios.kt rename to composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/external/External.ios.kt index 73cd7a4..267aabd 100644 --- a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/External.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/external/External.ios.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util +package com.rafaelfelipeac.replyradar.core.external actual fun openEmailApp(to: String, subject: String, body: String) { TODO("Not yet implemented for this platform.") diff --git a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Version.native.kt b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/version/Version.native.kt similarity index 80% rename from composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Version.native.kt rename to composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/version/Version.native.kt index ce41887..9e76b62 100644 --- a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/util/Version.native.kt +++ b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/version/Version.native.kt @@ -1,4 +1,4 @@ -package com.rafaelfelipeac.replyradar.core.util +package com.rafaelfelipeac.replyradar.core.version import platform.Foundation.NSBundle From 23de72ac3df902846a69785068e6707fa7e5103d Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Sun, 29 Jun 2025 18:06:26 -0300 Subject: [PATCH 35/59] Minor fixes. --- .../replybottomsheet/ReplyBottomSheetContent.kt | 4 ++-- .../features/settings/presentation/SettingsScreen.kt | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt index eaa80cf..be74929 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt @@ -74,8 +74,6 @@ fun ReplyBottomSheetContent( onValueChange = { subject = it } ) - ReplyTimestampInfo(state) - ReplyReminder( selectedTime = selectedTime, selectedDate = selectedDate, @@ -84,6 +82,8 @@ fun ReplyBottomSheetContent( closeKeyboard = { keyboardController?.hide() } ) + ReplyTimestampInfo(state) + ReplyBottomSheetActions( state = replyBottomSheetState, onArchive = onArchive, diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsScreen.kt index 123b5d5..93fa67c 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsScreen.kt @@ -100,20 +100,26 @@ fun SettingsScreen( .padding(bottom = settingsAppVersionOffset) ) { ActivityLog(onActivityLogClick = onActivityLogClick) + HorizontalDivider( modifier = Modifier .padding(vertical = paddingMedium) ) + Theme(state = state, onIntent = { viewModel.onIntent(it) }) + HorizontalDivider( modifier = Modifier .padding(vertical = paddingMedium) ) + Language(state = state, onIntent = { viewModel.onIntent(it) }) + HorizontalDivider( modifier = Modifier .padding(vertical = paddingMedium) ) + App(onIntent = { viewModel.onIntent(it) }) } @@ -219,11 +225,13 @@ private fun ThemeOptions(state: SettingsState, onThemeSelected: (AppTheme) -> Un selectedTheme = state.theme, onThemeSelected = { onThemeSelected(LIGHT) } ) + ThemeOption( theme = DARK, selectedTheme = state.theme, onThemeSelected = { onThemeSelected(DARK) } ) + ThemeOption( theme = SYSTEM, selectedTheme = state.theme, @@ -270,7 +278,9 @@ private fun getThemeOptionLabel(theme: AppTheme) = when (theme) { @Composable private fun LanguageOptions(state: SettingsState, onLanguageSelected: (AppLanguage) -> Unit) { LanguageOption(ENGLISH, state.language, onLanguageSelected) + LanguageOption(PORTUGUESE, state.language, onLanguageSelected) + LanguageOption(AppLanguage.SYSTEM, state.language, onLanguageSelected) } From 17829e3e2b3be037c8c04be0a5b52d5ffbffe300 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Tue, 1 Jul 2025 22:32:01 -0300 Subject: [PATCH 36/59] Added PendingIntent to notifications. --- .../rafaelfelipeac/replyradar/AndroidApp.kt | 7 +- .../rafaelfelipeac/replyradar/MainActivity.kt | 7 +- ...er.kt => NotificationPermissionManager.kt} | 28 ++----- .../core/reminder/NotificationUtils.kt | 39 ++++++++-- .../core/reminder/ReminderSchedulerImpl.kt | 19 +++-- .../core/reminder/ReminderWorker.kt | 12 ++- .../replyradar/app/ReplyRadarApp.kt | 5 +- .../replyradar/core/navigation/AppNavHost.kt | 6 +- .../replyradar/core/util/AppConstants.kt | 8 ++ .../presentation/replylist/ReplyListScreen.kt | 14 +++- .../replylist/ReplyListScreenIntent.kt | 3 +- .../replylist/ReplyListViewModel.kt | 76 ++++++++++++++----- .../components/RepliesArchivedScreen.kt | 4 +- .../components/RepliesOnTheRadarScreen.kt | 4 +- .../components/RepliesResolvedScreen.kt | 4 +- .../replylist/components/RepliesScreen.kt | 6 +- .../com/rafaelfelipeac/replyradar/Main.kt | 3 +- .../replyradar/MainViewController.kt | 3 +- 18 files changed, 171 insertions(+), 77 deletions(-) rename composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/{rememberNotificationPermissionManager.kt => NotificationPermissionManager.kt} (72%) diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/AndroidApp.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/AndroidApp.kt index feb62bc..a8c9f74 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/AndroidApp.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/AndroidApp.kt @@ -6,14 +6,12 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.graphics.Color.Companion.Black -import androidx.compose.ui.tooling.preview.Preview import com.rafaelfelipeac.replyradar.app.ReplyRadarApp import com.rafaelfelipeac.replyradar.core.ConfigureSystemBars import com.rafaelfelipeac.replyradar.core.notification.rememberNotificationPermissionManager @Composable -@Preview -fun AndroidApp() { +fun AndroidApp(pendingReplyId: Long?) { var isDark by remember { mutableStateOf(false) } var backgroundColor by remember { mutableStateOf(Black) } @@ -24,6 +22,7 @@ fun AndroidApp() { isDark = dark backgroundColor = bgColor }, - notificationPermissionManager = rememberNotificationPermissionManager() + notificationPermissionManager = rememberNotificationPermissionManager(), + pendingReplyId = pendingReplyId ) } diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/MainActivity.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/MainActivity.kt index 1b74193..42080d4 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/MainActivity.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/MainActivity.kt @@ -3,13 +3,18 @@ package com.rafaelfelipeac.replyradar import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import com.rafaelfelipeac.replyradar.core.util.AppConstants.INVALID_ID +import com.rafaelfelipeac.replyradar.core.util.AppConstants.PENDING_REPLY_ID_KEY class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + val pendingReplyId = intent?.getLongExtra(PENDING_REPLY_ID_KEY, INVALID_ID) + setContent { - AndroidApp() + AndroidApp(pendingReplyId = pendingReplyId) } } } diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/NotificationPermissionManager.kt similarity index 72% rename from composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt rename to composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/NotificationPermissionManager.kt index 8457ebd..66b0e65 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/rememberNotificationPermissionManager.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/NotificationPermissionManager.kt @@ -5,7 +5,8 @@ import android.app.Activity import android.content.Intent import android.content.pm.PackageManager.PERMISSION_GRANTED import android.net.Uri -import android.os.Build +import android.os.Build.VERSION.SDK_INT +import android.os.Build.VERSION_CODES.TIRAMISU import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts @@ -14,7 +15,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.platform.LocalContext -import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat.checkSelfPermission import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -39,9 +39,9 @@ fun rememberNotificationPermissionManager(): NotificationPermissionManager { return remember { object : NotificationPermissionManager { + override suspend fun ensureNotificationPermission(): Boolean { - // double check everything - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + if (SDK_INT < TIRAMISU) { return true } @@ -51,32 +51,20 @@ fun rememberNotificationPermissionManager(): NotificationPermissionManager { return true } - return suspendCancellableCoroutine { cont -> + return suspendCancellableCoroutine { permissionContinuation -> permissionResultState.value = null permissionLauncher.launch(POST_NOTIFICATIONS) - val scope = CoroutineScope(Dispatchers.Main.immediate) - - val job = scope.launch { + val job = CoroutineScope(Dispatchers.Main.immediate).launch { snapshotFlow { permissionResultState.value } .filterNotNull() .first() .let { result -> - if (!result) { - val activity = context as? Activity - activity?.let { - ActivityCompat.shouldShowRequestPermissionRationale( - it, - POST_NOTIFICATIONS - ) - } ?: true - } - - cont.resume(result) + permissionContinuation.resume(result) } } - cont.invokeOnCancellation { job.cancel() } + permissionContinuation.invokeOnCancellation { job.cancel() } } } diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/NotificationUtils.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/NotificationUtils.kt index 28fc362..edd3e35 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/NotificationUtils.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/NotificationUtils.kt @@ -1,18 +1,27 @@ package com.rafaelfelipeac.replyradar.core.reminder import android.Manifest.permission.POST_NOTIFICATIONS +import android.app.PendingIntent +import android.app.PendingIntent.FLAG_IMMUTABLE +import android.app.PendingIntent.FLAG_UPDATE_CURRENT import android.content.Context +import android.content.Intent +import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK +import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.pm.PackageManager.PERMISSION_GRANTED import androidx.core.app.ActivityCompat import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat.PRIORITY_HIGH import androidx.core.app.NotificationManagerCompat +import com.rafaelfelipeac.replyradar.MainActivity import com.rafaelfelipeac.replyradar.R import com.rafaelfelipeac.replyradar.R.string.notification_channel_id import com.rafaelfelipeac.replyradar.R.string.notification_content_reminder_title +import com.rafaelfelipeac.replyradar.core.util.AppConstants.PENDING_REPLY_ID_KEY -object NotificationUtils {// - fun showReminderNotification(context: Context, name: String, subject: String) { +object NotificationUtils { + + fun showReminderNotification(context: Context, replyId: Long, name: String, subject: String) { val notification = NotificationCompat.Builder( context, context.getString(notification_channel_id) @@ -21,16 +30,32 @@ object NotificationUtils {// .setContentTitle(context.getString(notification_content_reminder_title, name)) .setContentText(subject) .setPriority(PRIORITY_HIGH) + .setContentIntent(getPendingIntent(context = context, replyId = replyId, name = name)) + .setAutoCancel(true) .build() - if (ActivityCompat.checkSelfPermission( - context, - POST_NOTIFICATIONS - ) != PERMISSION_GRANTED - ) { + if (ActivityCompat.checkSelfPermission(context, POST_NOTIFICATIONS) != PERMISSION_GRANTED) { return } NotificationManagerCompat.from(context).notify(name.hashCode(), notification) } + + private fun getPendingIntent( + context: Context, + replyId: Long, + name: String, + ): PendingIntent? { + val intent = Intent(context, MainActivity::class.java).apply { + flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK + putExtra(PENDING_REPLY_ID_KEY, replyId) + } + + return PendingIntent.getActivity( + context, + name.hashCode(), + intent, + FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE + ) + } } diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderSchedulerImpl.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderSchedulerImpl.kt index 5d036e9..6b768bd 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderSchedulerImpl.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderSchedulerImpl.kt @@ -8,11 +8,11 @@ import com.rafaelfelipeac.replyradar.R.string.reminder_name import com.rafaelfelipeac.replyradar.R.string.reminder_reply_id import com.rafaelfelipeac.replyradar.R.string.reminder_subject import com.rafaelfelipeac.replyradar.R.string.reminder_tag -import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeUnit.MILLISECONDS private const val INVALID_DELAY = 0 -class ReminderSchedulerImpl(// +class ReminderSchedulerImpl( private val context: Context ) : ReminderScheduler { @@ -22,20 +22,25 @@ class ReminderSchedulerImpl(// subject: String, replyId: Long ) { - val delay = reminderAtMillis - System.currentTimeMillis() + val delay = getDelay(reminderAtMillis) + if (delay <= INVALID_DELAY) return enqueueReminder(delay, name, subject, replyId) } + override fun cancelReminder(replyId: Long) { + WorkManager.getInstance(context).cancelAllWorkByTag(getTag(replyId)) + } + private fun enqueueReminder(delay: Long, name: String, subject: String, replyId: Long) { val workRequest = OneTimeWorkRequestBuilder() - .setInitialDelay(delay, TimeUnit.MILLISECONDS) + .setInitialDelay(delay, MILLISECONDS) .setInputData( workDataOf( + context.getString(reminder_reply_id) to replyId, context.getString(reminder_name) to name, context.getString(reminder_subject) to subject, - context.getString(reminder_reply_id) to replyId.toString() ) ) .addTag(getTag(replyId)) @@ -44,9 +49,7 @@ class ReminderSchedulerImpl(// WorkManager.getInstance(context).enqueue(workRequest) } - override fun cancelReminder(replyId: Long) { - WorkManager.getInstance(context).cancelAllWorkByTag(getTag(replyId)) - } + private fun getDelay(reminderAtMillis: Long) = reminderAtMillis - System.currentTimeMillis() private fun getTag(replyId: Long) = context.getString(reminder_tag, replyId) } diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderWorker.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderWorker.kt index 9610abd..a73d744 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderWorker.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderWorker.kt @@ -4,18 +4,26 @@ import android.content.Context import androidx.work.Worker import androidx.work.WorkerParameters import com.rafaelfelipeac.replyradar.R.string.reminder_name +import com.rafaelfelipeac.replyradar.R.string.reminder_reply_id import com.rafaelfelipeac.replyradar.R.string.reminder_subject +import com.rafaelfelipeac.replyradar.core.util.AppConstants.INVALID_ID class ReminderWorker( appContext: Context, workerParams: WorkerParameters ) : Worker(appContext, workerParams) { - override fun doWork(): Result {// + override fun doWork(): Result { + val replyId = inputData.getLong(applicationContext.getString(reminder_reply_id), INVALID_ID) val name = inputData.getString(applicationContext.getString(reminder_name)) ?: return Result.failure() val subject = inputData.getString(applicationContext.getString(reminder_subject)) ?: return Result.failure() - NotificationUtils.showReminderNotification(applicationContext, name, subject) + NotificationUtils.showReminderNotification( + context = applicationContext, + replyId = replyId, + name = name, + subject = subject + ) return Result.success() } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt index 979bf70..299ef8d 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt @@ -26,7 +26,8 @@ import org.koin.compose.viewmodel.koinViewModel @Composable fun ReplyRadarApp( onSystemBarsConfigured: ((isDark: Boolean, backgroundColor: Color) -> Unit)? = null, - notificationPermissionManager: NotificationPermissionManager + notificationPermissionManager: NotificationPermissionManager, + pendingReplyId: Long? ) { val navController = rememberNavController() val appSettingsViewModel = koinViewModel() @@ -50,7 +51,7 @@ fun ReplyRadarApp( LocalNotificationPermissionManager provides notificationPermissionManager ) { ReplyRadarTheme(darkTheme = isDark) { - AppNavHost(navController = navController) + AppNavHost(navController = navController, pendingReplyId = pendingReplyId) } } } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/navigation/AppNavHost.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/navigation/AppNavHost.kt index 419c580..f770974 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/navigation/AppNavHost.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/navigation/AppNavHost.kt @@ -22,7 +22,10 @@ import com.rafaelfelipeac.replyradar.features.settings.presentation.SettingsView import org.koin.compose.viewmodel.koinViewModel @Composable -fun AppNavHost(navController: NavHostController) { +fun AppNavHost( + navController: NavHostController, + pendingReplyId: Long? +) { NavHost( navController = navController, startDestination = ReplyGraph @@ -38,6 +41,7 @@ fun AppNavHost(navController: NavHostController) { ReplyListScreenRoot( viewModel = viewModel, + pendingReplyId = pendingReplyId, onSettingsClick = { navController.navigate(Settings) }, onActivityLogClick = { navController.navigate(ActivityLog) } ) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/AppConstants.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/AppConstants.kt index af33bae..f000186 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/AppConstants.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/AppConstants.kt @@ -5,8 +5,16 @@ object AppConstants { const val DB_NAME = "replyradar.db" const val PACKAGE_NAME = "com.rafaelfelipeac.replyradar" const val EMAIL = "rafaelfelipeac@gmail.com" + + const val PENDING_REPLY_ID_KEY = "PENDING_REPLY_ID" + const val INITIAL_DATE = 0L + const val INVALID_ID = -1L const val REMINDER_DEFAULT_HOUR = 8 const val REMINDER_DEFAULT_MINUTE = 0 const val REMINDER_TOMORROW_OFFSET = 1 + + const val ON_THE_RADAR_INDEX = 0 + const val RESOLVED_INDEX = 1 + const val ARCHIVED_INDEX = 2 } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt index c01fd87..2e749f6 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt @@ -37,6 +37,9 @@ import com.rafaelfelipeac.replyradar.core.common.ui.paddingMedium import com.rafaelfelipeac.replyradar.core.common.ui.spacerXSmall import com.rafaelfelipeac.replyradar.core.common.ui.tabRowTopPadding import com.rafaelfelipeac.replyradar.core.notification.LocalNotificationPermissionManager +import com.rafaelfelipeac.replyradar.core.util.AppConstants.ARCHIVED_INDEX +import com.rafaelfelipeac.replyradar.core.util.AppConstants.ON_THE_RADAR_INDEX +import com.rafaelfelipeac.replyradar.core.util.AppConstants.RESOLVED_INDEX import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.CheckNotificationPermission import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.GoToSettings import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.RequestNotificationPermission @@ -54,6 +57,7 @@ import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnDismissBottomSheet import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnToggleArchive import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnToggleResolve +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnPendingReplyId import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnTabSelected import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.FloatingActionButton import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.RepliesScreen @@ -64,19 +68,23 @@ import org.koin.compose.viewmodel.koinViewModel private const val WEIGHT = 1f private const val PAGER_PAGE_COUNT = 3 -const val ON_THE_RADAR_INDEX = 0 -const val RESOLVED_INDEX = 1 -const val ARCHIVED_INDEX = 2 @Composable fun ReplyListScreenRoot( viewModel: ReplyListViewModel = koinViewModel(), + pendingReplyId: Long?, onSettingsClick: () -> Unit, onActivityLogClick: () -> Unit ) { val state by viewModel.state.collectAsStateWithLifecycle() val effect = viewModel.effect + LaunchedEffect(pendingReplyId) { + pendingReplyId?.let { + viewModel.onIntent(OnPendingReplyId(pendingReplyId)) + } + } + ReplyListScreen( state = state, effect = effect, diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt index 9335d5c..fd04bd7 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt @@ -5,9 +5,10 @@ import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply sealed interface ReplyListScreenIntent { sealed interface ReplyListIntent : ReplyListScreenIntent { + data class OnPendingReplyId(val pendingReplyId: Long?) : ReplyListIntent data object OnAddReplyClick : ReplyListIntent data class OnTabSelected(val index: Int) : ReplyListIntent - data class OnReplyClick(val reply: Reply) : ReplyListIntent + data class OnOpenReply(val reply: Reply) : ReplyListIntent data class OnReplyToggle(val reply: Reply) : ReplyListIntent } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt index 804823a..f2573aa 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt @@ -2,8 +2,12 @@ package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.rafaelfelipeac.replyradar.core.util.AppConstants.INITIAL_DATE import com.rafaelfelipeac.replyradar.core.reminder.ReminderScheduler +import com.rafaelfelipeac.replyradar.core.util.AppConstants.ARCHIVED_INDEX +import com.rafaelfelipeac.replyradar.core.util.AppConstants.INITIAL_DATE +import com.rafaelfelipeac.replyradar.core.util.AppConstants.INVALID_ID +import com.rafaelfelipeac.replyradar.core.util.AppConstants.ON_THE_RADAR_INDEX +import com.rafaelfelipeac.replyradar.core.util.AppConstants.RESOLVED_INDEX import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.domain.usecase.DeleteReplyUseCase import com.rafaelfelipeac.replyradar.features.reply.domain.usecase.GetRepliesUseCase @@ -29,7 +33,8 @@ import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnToggleResolve import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnAddReplyClick -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnReplyClick +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnOpenReply +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnPendingReplyId import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnReplyToggle import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnTabSelected import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetMode.CREATE @@ -85,6 +90,8 @@ class ReplyListViewModel( private val _effect = MutableSharedFlow() val effect = _effect + private var pendingReplyId: Long? = INVALID_ID + fun onIntent(intent: ReplyListScreenIntent) { when (intent) { is ReplyListIntent -> handleReplyListIntent(intent) @@ -95,6 +102,10 @@ class ReplyListViewModel( private fun handleReplyListIntent(intent: ReplyListIntent) { when (intent) { + is OnPendingReplyId -> { + pendingReplyId = intent.pendingReplyId + } + OnAddReplyClick -> { updateState { copy( @@ -105,26 +116,13 @@ class ReplyListViewModel( } } - is OnReplyClick -> { - updateState { - copy( - replyBottomSheetState = ReplyBottomSheetState( - replyBottomSheetMode = EDIT, - reply = intent.reply - ) - ) - } - } + is OnOpenReply -> onOpenReply(intent.reply) is OnReplyToggle -> { onToggleResolveReply(reply = intent.reply) } - is OnTabSelected -> { - updateState { - copy(selectedTabIndex = intent.index) - } - } + is OnTabSelected -> onTabSelected(intent.index) } } @@ -148,6 +146,17 @@ class ReplyListViewModel( } } + private fun onOpenReply(reply: Reply) { + updateState { + copy( + replyBottomSheetState = ReplyBottomSheetState( + replyBottomSheetMode = EDIT, + reply = reply + ) + ) + } + } + private fun getReplies() = viewModelScope.launch { updateState { copy(isLoading = true) } @@ -161,6 +170,11 @@ class ReplyListViewModel( replies = replies ) } + + checkPendingReplyId( + replies = replies, + onTabSelection = { onTabSelected(ON_THE_RADAR_INDEX) } + ) } } catch (e: Exception) { updateState { @@ -178,6 +192,11 @@ class ReplyListViewModel( .getReplies(isResolved = true) .collect { resolvedReplies -> updateState { copy(resolvedReplies = resolvedReplies) } + + checkPendingReplyId( + replies = resolvedReplies, + onTabSelection = { onTabSelected(RESOLVED_INDEX) } + ) } } catch (_: Exception) { } @@ -189,6 +208,11 @@ class ReplyListViewModel( .getReplies(isArchived = true) .collect { archivedReplies -> updateState { copy(archivedReplies = archivedReplies) } + + checkPendingReplyId( + replies = archivedReplies, + onTabSelection = { onTabSelected(ARCHIVED_INDEX) } + ) } } catch (_: Exception) { } @@ -262,6 +286,24 @@ class ReplyListViewModel( ) } + private fun checkPendingReplyId(replies: List, onTabSelection: () -> Unit) { + if (pendingReplyId != INVALID_ID) { + val reply = replies.find { it.id == pendingReplyId } + + if (reply != null) { + onTabSelection() + onOpenReply(reply) + pendingReplyId = null + } + } + } + + private fun onTabSelected(index: Int) { + updateState { + copy(selectedTabIndex = index) + } + } + companion object { private const val STOP_TIMEOUT = 5_000L const val ERROR_GET_REPLIES = "error_get_replies" diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesArchivedScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesArchivedScreen.kt index 51a27eb..672685e 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesArchivedScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesArchivedScreen.kt @@ -6,7 +6,7 @@ import androidx.compose.ui.Modifier import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyRadarPlaceholder import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnReplyClick +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnOpenReply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnReplyToggle import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListState @@ -19,7 +19,7 @@ fun RepliesArchivedScreen(state: ReplyListState, onIntent: (ReplyListScreenInten modifier = Modifier .fillMaxSize(), replies = state.archivedReplies, - onReplyClick = { onIntent(OnReplyClick(it)) }, + onReplyClick = { onIntent(OnOpenReply(it)) }, onReplyToggle = { onIntent(OnReplyToggle(it)) } ) } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesOnTheRadarScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesOnTheRadarScreen.kt index 55816e5..8cf26cc 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesOnTheRadarScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesOnTheRadarScreen.kt @@ -8,7 +8,7 @@ import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyProgress import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyRadarError import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyRadarPlaceholder import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnReplyClick +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnOpenReply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnReplyToggle import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListState import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListViewModel.Companion.ERROR_GET_REPLIES @@ -34,7 +34,7 @@ fun RepliesOnTheRadarScreen(state: ReplyListState, onIntent: (ReplyListScreenInt modifier = Modifier .fillMaxSize(), replies = state.replies, - onReplyClick = { onIntent(OnReplyClick(it)) }, + onReplyClick = { onIntent(OnOpenReply(it)) }, onReplyToggle = { onIntent(OnReplyToggle(it)) } ) } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesResolvedScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesResolvedScreen.kt index 6a55539..babf509 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesResolvedScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesResolvedScreen.kt @@ -6,7 +6,7 @@ import androidx.compose.ui.Modifier import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyRadarPlaceholder import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnReplyClick +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnOpenReply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListState @Composable @@ -18,7 +18,7 @@ fun RepliesResolvedScreen(state: ReplyListState, onIntent: (ReplyListScreenInten modifier = Modifier .fillMaxSize(), replies = state.resolvedReplies, - onReplyClick = { onIntent(OnReplyClick(it)) } + onReplyClick = { onIntent(OnOpenReply(it)) } ) } } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesScreen.kt index d048080..1902256 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesScreen.kt @@ -9,9 +9,9 @@ import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ARCHIVED_INDEX -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ON_THE_RADAR_INDEX -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.RESOLVED_INDEX +import com.rafaelfelipeac.replyradar.core.util.AppConstants.ARCHIVED_INDEX +import com.rafaelfelipeac.replyradar.core.util.AppConstants.ON_THE_RADAR_INDEX +import com.rafaelfelipeac.replyradar.core.util.AppConstants.RESOLVED_INDEX import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListState diff --git a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/Main.kt b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/Main.kt index 2c9911f..0c809eb 100644 --- a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/Main.kt +++ b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/Main.kt @@ -22,7 +22,8 @@ fun main() { override suspend fun goToAppSettings() { TODO("Not yet implemented for this platform.") } - } + }, + pendingReplyId = -1 ) } } diff --git a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/MainViewController.kt b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/MainViewController.kt index cf8c34f..206a28f 100644 --- a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/MainViewController.kt +++ b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/MainViewController.kt @@ -20,6 +20,7 @@ fun MainViewController() = ComposeUIViewController( override suspend fun goToAppSettings() { TODO("Not yet implemented for this platform.") } - } + }, + pendingReplyId = -1 ) } From ea8b5ec4e967d0179a6c55d9b9a9e4e9b454ff45 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Tue, 1 Jul 2025 22:41:57 -0300 Subject: [PATCH 37/59] Minor improvements. --- .../replyradar/core/util/AppConstants.kt | 1 + .../presentation/replylist/ReplyListScreen.kt | 22 ++++++++++++++----- .../replylist/ReplyListViewModel.kt | 3 ++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/AppConstants.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/AppConstants.kt index f000186..3d3a0ee 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/AppConstants.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/util/AppConstants.kt @@ -9,6 +9,7 @@ object AppConstants { const val PENDING_REPLY_ID_KEY = "PENDING_REPLY_ID" const val INITIAL_DATE = 0L + const val INITIAL_ID = 0L const val INVALID_ID = -1L const val REMINDER_DEFAULT_HOUR = 8 const val REMINDER_DEFAULT_MINUTE = 0 diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt index 2e749f6..29ffa23 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt @@ -40,6 +40,7 @@ import com.rafaelfelipeac.replyradar.core.notification.LocalNotificationPermissi import com.rafaelfelipeac.replyradar.core.util.AppConstants.ARCHIVED_INDEX import com.rafaelfelipeac.replyradar.core.util.AppConstants.ON_THE_RADAR_INDEX import com.rafaelfelipeac.replyradar.core.util.AppConstants.RESOLVED_INDEX +import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.CheckNotificationPermission import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.GoToSettings import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.RequestNotificationPermission @@ -248,13 +249,13 @@ fun ReplyListScreen( onArchive = { onIntent(OnToggleArchive(it)) }, onDelete = { onIntent(OnDeleteReply(it)) }, onSave = { reply -> - when { - reply.reminderAt != INITIAL_DATE -> { + onSaveReply( + reply = reply, + onCheckNotificationPermission = { onIntent(OnCheckNotificationPermission(reply)) - } - - else -> onIntent(OnAddOrEditReply(reply)) - } + }, + onAddOrEditReply = { onIntent(OnAddOrEditReply(reply)) } + ) }, onDismiss = { onIntent(OnDismissBottomSheet) }, replyBottomSheetState = state.replyBottomSheetState, @@ -263,6 +264,15 @@ fun ReplyListScreen( } } +private fun onSaveReply( + reply: Reply, + onCheckNotificationPermission: (Reply) -> Unit, + onAddOrEditReply: (Reply) -> Unit +) = when { + reply.reminderAt != INITIAL_DATE -> onCheckNotificationPermission(reply) + else -> onAddOrEditReply(reply) +} + private fun getSnackbarMessage( effect: SnackbarState, strings: Strings diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt index f2573aa..242c760 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope import com.rafaelfelipeac.replyradar.core.reminder.ReminderScheduler import com.rafaelfelipeac.replyradar.core.util.AppConstants.ARCHIVED_INDEX import com.rafaelfelipeac.replyradar.core.util.AppConstants.INITIAL_DATE +import com.rafaelfelipeac.replyradar.core.util.AppConstants.INITIAL_ID import com.rafaelfelipeac.replyradar.core.util.AppConstants.INVALID_ID import com.rafaelfelipeac.replyradar.core.util.AppConstants.ON_THE_RADAR_INDEX import com.rafaelfelipeac.replyradar.core.util.AppConstants.RESOLVED_INDEX @@ -219,7 +220,7 @@ class ReplyListViewModel( } private fun onUpsertReply(reply: Reply) = viewModelScope.launch { - val actionType = if (reply.id == 0L) Create else Edit + val actionType = if (reply.id == INITIAL_ID) Create else Edit val replyId = upsertReplyUseCase.upsertReply(reply) logUserAction(actionType = actionType, targetId = replyId) From 9a40b47f4830106e7a4bf290d525573a0b177271 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Thu, 3 Jul 2025 18:18:26 -0300 Subject: [PATCH 38/59] Notification content improvements. --- .../core/reminder/NotificationUtils.kt | 21 +++++---- .../core/reminder/ReminderSchedulerImpl.kt | 46 ++++++++++--------- .../core/reminder/ReminderWorker.kt | 26 +++++++---- .../core/reminder/ReminderScheduler.kt | 6 +-- .../model/NotificationReminderParams.kt | 7 +++ .../replyradar/core/strings/Strings.kt | 3 ++ .../replyradar/core/strings/StringsEn.kt | 3 ++ .../replyradar/core/strings/StringsPt.kt | 3 ++ .../presentation/replylist/ReplyListEffect.kt | 2 + .../presentation/replylist/ReplyListScreen.kt | 37 +++++++++++++++ .../replylist/ReplyListScreenIntent.kt | 5 ++ .../replylist/ReplyListViewModel.kt | 24 +++++++--- composeApp/src/main/res/values/strings.xml | 8 ++-- 13 files changed, 139 insertions(+), 52 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/model/NotificationReminderParams.kt diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/NotificationUtils.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/NotificationUtils.kt index edd3e35..3f318b9 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/NotificationUtils.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/NotificationUtils.kt @@ -16,21 +16,25 @@ import androidx.core.app.NotificationManagerCompat import com.rafaelfelipeac.replyradar.MainActivity import com.rafaelfelipeac.replyradar.R import com.rafaelfelipeac.replyradar.R.string.notification_channel_id -import com.rafaelfelipeac.replyradar.R.string.notification_content_reminder_title import com.rafaelfelipeac.replyradar.core.util.AppConstants.PENDING_REPLY_ID_KEY object NotificationUtils { - fun showReminderNotification(context: Context, replyId: Long, name: String, subject: String) { + fun showReminderNotification( + context: Context, + replyId: Long, + notificationTitle: String, + notificationContent: String + ) { val notification = NotificationCompat.Builder( context, context.getString(notification_channel_id) ) .setSmallIcon(R.drawable.ic_launcher_background) - .setContentTitle(context.getString(notification_content_reminder_title, name)) - .setContentText(subject) + .setContentTitle(notificationTitle) + .setContentText(notificationContent) .setPriority(PRIORITY_HIGH) - .setContentIntent(getPendingIntent(context = context, replyId = replyId, name = name)) + .setContentIntent(getPendingIntent(context = context, replyId = replyId)) .setAutoCancel(true) .build() @@ -38,13 +42,12 @@ object NotificationUtils { return } - NotificationManagerCompat.from(context).notify(name.hashCode(), notification) + NotificationManagerCompat.from(context).notify(replyId.hashCode(), notification) } private fun getPendingIntent( context: Context, - replyId: Long, - name: String, + replyId: Long ): PendingIntent? { val intent = Intent(context, MainActivity::class.java).apply { flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK @@ -53,7 +56,7 @@ object NotificationUtils { return PendingIntent.getActivity( context, - name.hashCode(), + replyId.hashCode(), intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE ) diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderSchedulerImpl.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderSchedulerImpl.kt index 6b768bd..c0336d0 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderSchedulerImpl.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderSchedulerImpl.kt @@ -4,10 +4,11 @@ import android.content.Context import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkManager import androidx.work.workDataOf -import com.rafaelfelipeac.replyradar.R.string.reminder_name -import com.rafaelfelipeac.replyradar.R.string.reminder_reply_id -import com.rafaelfelipeac.replyradar.R.string.reminder_subject -import com.rafaelfelipeac.replyradar.R.string.reminder_tag +import com.rafaelfelipeac.replyradar.R.string.notification_reminder_content +import com.rafaelfelipeac.replyradar.R.string.notification_reminder_reply_id +import com.rafaelfelipeac.replyradar.R.string.notification_reminder_tag +import com.rafaelfelipeac.replyradar.R.string.notification_reminder_title +import com.rafaelfelipeac.replyradar.core.reminder.model.NotificationReminderParams import java.util.concurrent.TimeUnit.MILLISECONDS private const val INVALID_DELAY = 0 @@ -18,38 +19,41 @@ class ReminderSchedulerImpl( override fun scheduleReminder( reminderAtMillis: Long, - name: String, - subject: String, - replyId: Long + notificationReminderParams: NotificationReminderParams ) { val delay = getDelay(reminderAtMillis) if (delay <= INVALID_DELAY) return - enqueueReminder(delay, name, subject, replyId) + enqueueReminder(delay, notificationReminderParams) } override fun cancelReminder(replyId: Long) { WorkManager.getInstance(context).cancelAllWorkByTag(getTag(replyId)) } - private fun enqueueReminder(delay: Long, name: String, subject: String, replyId: Long) { - val workRequest = OneTimeWorkRequestBuilder() - .setInitialDelay(delay, MILLISECONDS) - .setInputData( - workDataOf( - context.getString(reminder_reply_id) to replyId, - context.getString(reminder_name) to name, - context.getString(reminder_subject) to subject, + private fun enqueueReminder( + delay: Long, + notificationReminderParams: NotificationReminderParams + ) { + with(notificationReminderParams) { + val workRequest = OneTimeWorkRequestBuilder() + .setInitialDelay(delay, MILLISECONDS) + .setInputData( + workDataOf( + context.getString(notification_reminder_reply_id) to replyId, + context.getString(notification_reminder_title) to notificationTitle, + context.getString(notification_reminder_content) to notificationContent + ) ) - ) - .addTag(getTag(replyId)) - .build() + .addTag(getTag(replyId)) + .build() - WorkManager.getInstance(context).enqueue(workRequest) + WorkManager.getInstance(context).enqueue(workRequest) + } } private fun getDelay(reminderAtMillis: Long) = reminderAtMillis - System.currentTimeMillis() - private fun getTag(replyId: Long) = context.getString(reminder_tag, replyId) + private fun getTag(replyId: Long) = context.getString(notification_reminder_tag, replyId) } diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderWorker.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderWorker.kt index a73d744..29d0ca4 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderWorker.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderWorker.kt @@ -3,9 +3,10 @@ package com.rafaelfelipeac.replyradar.core.reminder import android.content.Context import androidx.work.Worker import androidx.work.WorkerParameters -import com.rafaelfelipeac.replyradar.R.string.reminder_name -import com.rafaelfelipeac.replyradar.R.string.reminder_reply_id -import com.rafaelfelipeac.replyradar.R.string.reminder_subject +import com.rafaelfelipeac.replyradar.R.string.notification_reminder_content +import com.rafaelfelipeac.replyradar.R.string.notification_reminder_reply_id +import com.rafaelfelipeac.replyradar.R.string.notification_reminder_title +import com.rafaelfelipeac.replyradar.core.reminder.NotificationUtils.showReminderNotification import com.rafaelfelipeac.replyradar.core.util.AppConstants.INVALID_ID class ReminderWorker( @@ -14,15 +15,22 @@ class ReminderWorker( ) : Worker(appContext, workerParams) { override fun doWork(): Result { - val replyId = inputData.getLong(applicationContext.getString(reminder_reply_id), INVALID_ID) - val name = inputData.getString(applicationContext.getString(reminder_name)) ?: return Result.failure() - val subject = inputData.getString(applicationContext.getString(reminder_subject)) ?: return Result.failure() + val replyId = inputData.getLong( + applicationContext.getString(notification_reminder_reply_id), + INVALID_ID + ) + val notificationTitle = + inputData.getString(applicationContext.getString(notification_reminder_title)) + ?: return Result.failure() + val notificationContent = + inputData.getString(applicationContext.getString(notification_reminder_content)) + ?: return Result.failure() - NotificationUtils.showReminderNotification( + showReminderNotification( context = applicationContext, replyId = replyId, - name = name, - subject = subject + notificationTitle = notificationTitle, + notificationContent = notificationContent ) return Result.success() diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderScheduler.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderScheduler.kt index 2cd51f0..b45db60 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderScheduler.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderScheduler.kt @@ -1,12 +1,12 @@ package com.rafaelfelipeac.replyradar.core.reminder +import com.rafaelfelipeac.replyradar.core.reminder.model.NotificationReminderParams + interface ReminderScheduler { fun scheduleReminder( reminderAtMillis: Long, - name: String, - subject: String, - replyId: Long + notificationReminderParams: NotificationReminderParams ) fun cancelReminder(replyId: Long) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/model/NotificationReminderParams.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/model/NotificationReminderParams.kt new file mode 100644 index 0000000..320e505 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/model/NotificationReminderParams.kt @@ -0,0 +1,7 @@ +package com.rafaelfelipeac.replyradar.core.reminder.model + +data class NotificationReminderParams( + val replyId: Long, + val notificationTitle: String, + val notificationContent: String +) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/Strings.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/Strings.kt index 23a56e5..e498a41 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/Strings.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/Strings.kt @@ -93,4 +93,7 @@ interface Strings { val notificationPermissionDialogDescription: String val notificationPermissionDialogConfirmButton: String val notificationPermissionDialogDismissButton: String + val notificationTitle: String + val notificationContent: String + val notificationContentWithoutSubject: String } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt index 76ca532..da0f8a6 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt @@ -112,4 +112,7 @@ App version: ${getAppVersion()} override val notificationPermissionDialogDescription = "To remind you to reply to your messages, we need permission to send notifications. \n\nYou can enable it in the app settings." override val notificationPermissionDialogConfirmButton = "Open Settings" override val notificationPermissionDialogDismissButton = "Got it" + override val notificationTitle = "Hey, how about replying to %1?" + override val notificationContent = "%1 is waiting for your reply about \"%2\"." + override val notificationContentWithoutSubject = "%1 is waiting for your reply." } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt index 6b463f3..fd62938 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt @@ -112,4 +112,7 @@ Versão do app: ${getAppVersion()} override val notificationPermissionDialogDescription = "Para que possamos te lembrar de responder às suas mensagens, precisamos que você permita o envio de notificações. \n\nVocê pode ativar isso nas configurações do app." override val notificationPermissionDialogConfirmButton = "Abrir Configurações" override val notificationPermissionDialogDismissButton = "Entendido" + override val notificationTitle = "Hey, que tal responder %1?" + override val notificationContent = "%1 está esperando sua resposta sobre \"%2\"." + override val notificationContentWithoutSubject = "%1 está esperando sua resposta." } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListEffect.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListEffect.kt index 795ed69..5c49c08 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListEffect.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListEffect.kt @@ -17,4 +17,6 @@ sealed interface ReplyListEffect { data object Archived : SnackbarState data object Unarchived : SnackbarState } + + data class ScheduleReminder(val reply: Reply) : ReplyListEffect } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt index 29ffa23..9524f67 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt @@ -40,10 +40,12 @@ import com.rafaelfelipeac.replyradar.core.notification.LocalNotificationPermissi import com.rafaelfelipeac.replyradar.core.util.AppConstants.ARCHIVED_INDEX import com.rafaelfelipeac.replyradar.core.util.AppConstants.ON_THE_RADAR_INDEX import com.rafaelfelipeac.replyradar.core.util.AppConstants.RESOLVED_INDEX +import com.rafaelfelipeac.replyradar.core.util.format import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.CheckNotificationPermission import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.GoToSettings import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.RequestNotificationPermission +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.ScheduleReminder import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Archived import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Removed @@ -53,6 +55,7 @@ import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.NotificationPermissionIntent.OnCheckNotificationPermission import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.NotificationPermissionIntent.OnGoToSettings import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.NotificationPermissionIntent.OnRequestNotificationPermission +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.NotificationPermissionIntent.OnScheduleReminder import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnAddOrEditReply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnDeleteReply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnDismissBottomSheet @@ -148,6 +151,16 @@ fun ReplyListScreen( else -> onIntent(OnRequestNotificationPermission) } } + + is ScheduleReminder -> { + onIntent( + OnScheduleReminder( + reply = effect.reply, + notificationTitle = getNotificationTitle(strings, effect), + notificationContent = getNotificationContent(strings, effect) + ) + ) + } } } } @@ -283,3 +296,27 @@ private fun getSnackbarMessage( Resolved -> strings.replyListSnackbarResolved Unarchived -> strings.replyListSnackbarUnarchived } + +private fun getNotificationTitle( + strings: Strings, + effect: ScheduleReminder +) = format( + strings.notificationTitle, + effect.reply.name +) + +private fun getNotificationContent( + strings: Strings, + effect: ScheduleReminder +) = if (effect.reply.subject.isNotBlank()) { + format( + strings.notificationContent, + effect.reply.name, + effect.reply.subject + ) +} else { + format( + strings.notificationContentWithoutSubject, + effect.reply.name + ) +} diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt index fd04bd7..1105508 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt @@ -24,5 +24,10 @@ sealed interface ReplyListScreenIntent { data object OnRequestNotificationPermission : NotificationPermissionIntent data class OnCheckNotificationPermission(val reply: Reply) : NotificationPermissionIntent data object OnGoToSettings : NotificationPermissionIntent + data class OnScheduleReminder( + val reply: Reply, + val notificationTitle: String, + val notificationContent: String + ) : NotificationPermissionIntent } } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt index 242c760..4bd8030 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt @@ -3,6 +3,7 @@ package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.rafaelfelipeac.replyradar.core.reminder.ReminderScheduler +import com.rafaelfelipeac.replyradar.core.reminder.model.NotificationReminderParams import com.rafaelfelipeac.replyradar.core.util.AppConstants.ARCHIVED_INDEX import com.rafaelfelipeac.replyradar.core.util.AppConstants.INITIAL_DATE import com.rafaelfelipeac.replyradar.core.util.AppConstants.INITIAL_ID @@ -17,6 +18,7 @@ import com.rafaelfelipeac.replyradar.features.reply.domain.usecase.ToggleResolve import com.rafaelfelipeac.replyradar.features.reply.domain.usecase.UpsertReplyUseCase import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.GoToSettings import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.RequestNotificationPermission +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.ScheduleReminder import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Archived import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Removed import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.SnackbarState.Reopened @@ -26,6 +28,7 @@ import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.NotificationPermissionIntent.OnCheckNotificationPermission import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.NotificationPermissionIntent.OnGoToSettings import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.NotificationPermissionIntent.OnRequestNotificationPermission +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.NotificationPermissionIntent.OnScheduleReminder import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnAddOrEditReply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnDeleteReply @@ -144,6 +147,7 @@ class ReplyListViewModel( is OnCheckNotificationPermission -> checkNotificationPermission(intent.reply) OnGoToSettings -> goToSettings() OnRequestNotificationPermission -> requestNotificationPermission() + is OnScheduleReminder -> onScheduleReminder(intent) } } @@ -226,12 +230,7 @@ class ReplyListViewModel( logUserAction(actionType = actionType, targetId = replyId) if (reply.reminderAt != INITIAL_DATE) { - reminderScheduler.scheduleReminder( - reminderAtMillis = reply.reminderAt, - name = reply.name, - subject = reply.subject, - replyId = replyId - ) + _effect.emit(ScheduleReminder(reply)) } } @@ -287,6 +286,19 @@ class ReplyListViewModel( ) } + private fun onScheduleReminder(intent: OnScheduleReminder) { + with(intent.reply) { + reminderScheduler.scheduleReminder( + reminderAtMillis = reminderAt, + notificationReminderParams = NotificationReminderParams( + replyId = id, + notificationTitle = intent.notificationTitle, + notificationContent = intent.notificationContent + ) + ) + } + } + private fun checkPendingReplyId(replies: List, onTabSelection: () -> Unit) { if (pendingReplyId != INVALID_ID) { val reply = replies.find { it.id == pendingReplyId } diff --git a/composeApp/src/main/res/values/strings.xml b/composeApp/src/main/res/values/strings.xml index 4c03390..423792b 100644 --- a/composeApp/src/main/res/values/strings.xml +++ b/composeApp/src/main/res/values/strings.xml @@ -7,8 +7,8 @@ Notifications for scheduled reminders Reminder: %1$s - name - subject - replyId - reminder-%1$d + notification-reminder-reply-id + notification-reminder-%1$d + notification-reminder-title + notification-reminder-content \ No newline at end of file From 3a23a4667aed1bccffb1ef13223cb60efdf67029 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Thu, 3 Jul 2025 18:50:00 -0300 Subject: [PATCH 39/59] Improvements in notifications. --- .../replyradar/core/reminder/NotificationUtils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/NotificationUtils.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/NotificationUtils.kt index 3f318b9..dccb1e6 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/NotificationUtils.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/NotificationUtils.kt @@ -30,7 +30,7 @@ object NotificationUtils { context, context.getString(notification_channel_id) ) - .setSmallIcon(R.drawable.ic_launcher_background) + .setSmallIcon(R.mipmap.ic_launcher_round) .setContentTitle(notificationTitle) .setContentText(notificationContent) .setPriority(PRIORITY_HIGH) From a1a93fbe3a6cc1956a891311b825ce138baf162c Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Thu, 3 Jul 2025 19:03:40 -0300 Subject: [PATCH 40/59] Removing comment. --- .../features/reply/presentation/replylist/ReplyListScreen.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt index 9524f67..41f41cb 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt @@ -131,10 +131,7 @@ fun ReplyListScreen( effect.collect { effect -> when (effect) { is SnackbarState -> snackbarHostState.showSnackbar( - getSnackbarMessage( // esses valores estao aparecendo com o idioma trocado? checar. - effect, - strings - ) + getSnackbarMessage(effect, strings) ) RequestNotificationPermission -> showPermissionDialog = true From 61eb43701c143a39f3369359bb17106ca755a8b5 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Fri, 4 Jul 2025 17:18:52 -0300 Subject: [PATCH 41/59] Fix unit tests. --- .../fakes/core/util/FakeReminderScheduler.kt | 21 +++++ .../presentation/ReplyListViewModelTest.kt | 87 +++++++++++-------- 2 files changed, 74 insertions(+), 34 deletions(-) create mode 100644 composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/core/util/FakeReminderScheduler.kt diff --git a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/core/util/FakeReminderScheduler.kt b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/core/util/FakeReminderScheduler.kt new file mode 100644 index 0000000..69a087c --- /dev/null +++ b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/core/util/FakeReminderScheduler.kt @@ -0,0 +1,21 @@ +package com.rafaelfelipeac.replyradar.fakes.core.util + +import com.rafaelfelipeac.replyradar.core.reminder.ReminderScheduler +import com.rafaelfelipeac.replyradar.core.reminder.model.NotificationReminderParams + +class FakeReminderScheduler : ReminderScheduler { + + private val scheduledReminders = mutableListOf>() + private val cancelledReminders = mutableListOf() + + override fun scheduleReminder( + reminderAtMillis: Long, + notificationReminderParams: NotificationReminderParams + ) { + scheduledReminders.add(reminderAtMillis to notificationReminderParams) + } + + override fun cancelReminder(replyId: Long) { + cancelledReminders.add(replyId) + } +} diff --git a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/ReplyListViewModelTest.kt b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/ReplyListViewModelTest.kt index fee0e50..0259e2c 100644 --- a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/ReplyListViewModelTest.kt +++ b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/ReplyListViewModelTest.kt @@ -2,11 +2,12 @@ package com.rafaelfelipeac.replyradar.features.reply.presentation import app.cash.turbine.test import com.rafaelfelipeac.replyradar.ARCHIVE -import com.rafaelfelipeac.replyradar.CREATE import com.rafaelfelipeac.replyradar.DELETE import com.rafaelfelipeac.replyradar.EDIT import com.rafaelfelipeac.replyradar.RESOLVE +import com.rafaelfelipeac.replyradar.core.util.AppConstants.INITIAL_ID import com.rafaelfelipeac.replyradar.dropFirst +import com.rafaelfelipeac.replyradar.fakes.core.util.FakeReminderScheduler import com.rafaelfelipeac.replyradar.fakes.reply.domain.FakeDeleteReplyUseCase import com.rafaelfelipeac.replyradar.fakes.reply.domain.FakeGetRepliesUseCase import com.rafaelfelipeac.replyradar.fakes.reply.domain.FakeToggleArchiveReplyUseCase @@ -14,23 +15,19 @@ import com.rafaelfelipeac.replyradar.fakes.reply.domain.FakeToggleResolveReplyUs import com.rafaelfelipeac.replyradar.fakes.reply.domain.FakeUpsertReplyUseCase import com.rafaelfelipeac.replyradar.fakes.useractions.domain.FakeLogUserActionUseCase import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.ScheduleReminder import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnAddOrEditReply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnDeleteReply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnDismissBottomSheet -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnEditReply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnToggleArchive import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyBottomSheetIntent.OnToggleResolve import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnAddReplyClick -import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnReplyClick +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnOpenReply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnTabSelected import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListViewModel import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListViewModel.Companion.ERROR_GET_REPLIES import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetMode import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetState -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertEquals import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.drop @@ -38,6 +35,10 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals @OptIn(ExperimentalCoroutinesApi::class) class ReplyListViewModelTest { @@ -60,6 +61,7 @@ class ReplyListViewModelTest { private val deleteReplyUseCase = FakeDeleteReplyUseCase() private val getRepliesUseCase = FakeGetRepliesUseCase() private val logUserActionUseCase = FakeLogUserActionUseCase() + private val reminderScheduler = FakeReminderScheduler() private val viewModel = ReplyListViewModel( upsertReplyUseCase = upsertReplyUseCase, @@ -68,6 +70,7 @@ class ReplyListViewModelTest { deleteReplyUseCase = deleteReplyUseCase, getRepliesUseCase = getRepliesUseCase, logUserActionUseCase = logUserActionUseCase, + reminderScheduler = reminderScheduler, dispatcher = testDispatcher ) @@ -95,7 +98,7 @@ class ReplyListViewModelTest { @Test fun `OnReplyClick should open bottom sheet in EDIT mode with reply`() = runTest { viewModel.state.drop(dropFirst).test { - viewModel.onIntent(OnReplyClick(sampleReply)) + viewModel.onIntent(OnOpenReply(sampleReply)) val updatedState = awaitItem() val expectedState = ReplyBottomSheetState( @@ -120,14 +123,30 @@ class ReplyListViewModelTest { } @Test - fun `OnAddReply should upsert reply log action and dismiss bottom sheet`() = runTest { + fun `OnAddReply should upsert reply log action and dismiss bottom sheet with no reminder scheduled if reminderAt is INITIAL_DATE`() = runTest { viewModel.state.test { - viewModel.onIntent(OnAddReply(sampleReply)) + viewModel.onIntent(OnAddOrEditReply(sampleReply)) val updatedState = awaitItem() assertEquals(null, updatedState.replyBottomSheetState) assertEquals(sampleReply, upsertReplyUseCase.insertedReplies.first()) - assertEquals(CREATE, logUserActionUseCase.loggedActions.first().first.value) + assertEquals(EDIT, logUserActionUseCase.loggedActions.first().first.value) + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `OnAddReply should emit ScheduleReminder effect when reminderAt is valid`() = runTest { + val fixedReminderAt = 1735689600000L + val replyWithReminder = sampleReply.copy(id = INITIAL_ID, reminderAt = fixedReminderAt) + + viewModel.effect.test { + viewModel.onIntent(OnAddOrEditReply(replyWithReminder)) + + val effect = awaitItem() + assertEquals(effect is ScheduleReminder, true) + assertEquals(replyWithReminder, (effect as ScheduleReminder).reply) + cancelAndIgnoreRemainingEvents() } } @@ -135,7 +154,7 @@ class ReplyListViewModelTest { @Test fun `OnEditReply should upsert reply log action and dismiss bottom sheet`() = runTest { viewModel.state.test { - viewModel.onIntent(OnEditReply(sampleReply)) + viewModel.onIntent(OnAddOrEditReply(sampleReply)) val updatedState = awaitItem() assertEquals(null, updatedState.replyBottomSheetState) @@ -159,32 +178,30 @@ class ReplyListViewModelTest { } @Test - fun `OnToggleArchive should toggle archive log correct action and dismiss bottom sheet`() = - runTest { - viewModel.state.test { - viewModel.onIntent(OnToggleArchive(sampleReply)) - - val updatedState = awaitItem() - assertEquals(null, updatedState.replyBottomSheetState) - assertEquals(sampleReply, toggleArchiveReplyUseCase.toggledReplies.first()) - assertEquals(ARCHIVE, logUserActionUseCase.loggedActions.first().first.value) - cancelAndIgnoreRemainingEvents() - } + fun `OnToggleArchive should toggle archive log correct action and dismiss bottom sheet`() = runTest { + viewModel.state.test { + viewModel.onIntent(OnToggleArchive(sampleReply)) + + val updatedState = awaitItem() + assertEquals(null, updatedState.replyBottomSheetState) + assertEquals(sampleReply, toggleArchiveReplyUseCase.toggledReplies.first()) + assertEquals(ARCHIVE, logUserActionUseCase.loggedActions.first().first.value) + cancelAndIgnoreRemainingEvents() } + } @Test - fun `OnToggleResolve should toggle resolve log correct action and dismiss bottom sheet`() = - runTest { - viewModel.state.test { - viewModel.onIntent(OnToggleResolve(sampleReply)) - - val updatedState = awaitItem() - assertEquals(null, updatedState.replyBottomSheetState) - assertEquals(sampleReply, toggleResolveReplyUseCase.toggledReplies.first()) - assertEquals(RESOLVE, logUserActionUseCase.loggedActions.first().first.value) - cancelAndIgnoreRemainingEvents() - } + fun `OnToggleResolve should toggle resolve log correct action and dismiss bottom sheet`() = runTest { + viewModel.state.test { + viewModel.onIntent(OnToggleResolve(sampleReply)) + + val updatedState = awaitItem() + assertEquals(null, updatedState.replyBottomSheetState) + assertEquals(sampleReply, toggleResolveReplyUseCase.toggledReplies.first()) + assertEquals(RESOLVE, logUserActionUseCase.loggedActions.first().first.value) + cancelAndIgnoreRemainingEvents() } + } @Test fun `OnDismissBottomSheet should clear bottom sheet state`() = runTest { @@ -214,6 +231,7 @@ class ReplyListViewModelTest { deleteReplyUseCase = deleteReplyUseCase, getRepliesUseCase = getRepliesUseCase, logUserActionUseCase = logUserActionUseCase, + reminderScheduler = reminderScheduler, dispatcher = testDispatcher ) @@ -239,6 +257,7 @@ class ReplyListViewModelTest { deleteReplyUseCase = deleteReplyUseCase, getRepliesUseCase = getRepliesUseCase, logUserActionUseCase = logUserActionUseCase, + reminderScheduler = reminderScheduler, dispatcher = testDispatcher ) From 82d97465aa71dd62ff0f2f71bdb2592b961d9440 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Fri, 4 Jul 2025 18:31:22 -0300 Subject: [PATCH 42/59] Added support to reminders and notifications on ActivityLogScreen. --- .../drawable/ic_notification.png | Bin 0 -> 1129 bytes .../replyradar/core/strings/Strings.kt | 2 + .../replyradar/core/strings/StringsEn.kt | 2 + .../replyradar/core/strings/StringsPt.kt | 2 + .../presentation/ActivityLogScreen.kt | 8 ++++ .../presentation/replylist/ReplyListEffect.kt | 2 +- .../presentation/replylist/ReplyListScreen.kt | 2 +- .../replylist/ReplyListScreenIntent.kt | 1 + .../replylist/ReplyListViewModel.kt | 37 ++++++++++++------ .../domain/model/UserActionType.kt | 6 +++ 10 files changed, 49 insertions(+), 13 deletions(-) create mode 100644 composeApp/src/commonMain/composeResources/drawable/ic_notification.png diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_notification.png b/composeApp/src/commonMain/composeResources/drawable/ic_notification.png new file mode 100644 index 0000000000000000000000000000000000000000..13058a1c1c111832c80828eefbc37db59a18b2a0 GIT binary patch literal 1129 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGojKx9jP7LeL$-HD>U{UsTaSW-5 zdpq}j;2i^jW2(=%8r;)#4wpJuamYHbFKiBQbYk^nzu@>np+vySVuQ`$WQ_-Gtf>jl zoX$<#v-{foho6;~=jYb$zdQBqXUVl2QcE4u?^>6wV^Am-0$P<)rttY)AS1(sn~i67 zyR$MJC}Cf-HGXPPyxG)qJ2!93UBArz^y(GsnAU8~J7bjW6I#6LRf6-g{Y@)5olF^a zt^2)X>%N{$iKy3~mu2od-gDM=nH0muoAJ@23=Ypg#&;Am=xUr({}@>J@$2(HQLhho z9?YLxES`Hi&i{(4Y+MTWrfYQ}UVj6;-g@oa7@;%m_wV23K_8bq5@B8N{yXLex-emAO^T&PZm6e9i)Q&Z;d+oR7TAg73t)eg| zF^1Jwv&uI{{OK!reoUfR#xSqIBZpmpTg=dSrE={m*t$o_k)z{u#GMcG=;x7yD>FJWL>`}~^3XT@g=b|$ac z`Y-8yhKAp%uqiwa54fkgC!dkKenYb=d#ydE#ICD_i9H|hTc6*j%4{(0ucmR{<%>dn znw$+A@6G?5*>>Qo)1<8mK;Gv5n4ABjIF&;uEYVeCG%z};5@~krM0{TD|2t-Cu0?0~ z65gybKgyCEc5xxg#61#c%49dZ)ikR&D2^*QI%A*Zve&z=Zkt`{@9+Pdot=HN-94jf zR-U=V&tq!tGws^H|NoxNn?JvOd;hQO^=-N7fA1-1O}E;-^6l&LwlZ++efw>HOKlzVwb08Z=_`$9?wYT~@Z)6b zz2AE>9X!`Nb2qMJJHXvCa#$!)1qcG7LNTjK0=0D72*1zo}U96PV)|JYD@< J);T3K0RZla@`C^X literal 0 HcmV?d00001 diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/Strings.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/Strings.kt index e498a41..e19eda4 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/Strings.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/Strings.kt @@ -84,6 +84,8 @@ interface Strings { val activityLogUserActionResolveVerb: String val activityLogUserActionUnarchiveVerb: String val activityLogUserActionOpenVerb: String + val activityLogUserActionScheduledVerb: String + val activityLogUserActionOpenedNotificationVerb: String val activityLogUserActionTheme: String val activityLogUserActionLanguage: String val activityLogUserActionFeedback: String diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt index da0f8a6..bb08da3 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt @@ -103,6 +103,8 @@ App version: ${getAppVersion()} override val activityLogUserActionResolveVerb = "resolved" override val activityLogUserActionUnarchiveVerb = "unarchived" override val activityLogUserActionOpenVerb = "opened" + override val activityLogUserActionScheduledVerb = "scheduled a reminder for" + override val activityLogUserActionOpenedNotificationVerb = "opened a notification for" override val activityLogUserActionTheme = "You switched the app theme." override val activityLogUserActionLanguage = "You changed the app language." override val activityLogUserActionFeedback = "You gave feedback about the app." diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt index fd62938..df5ebf1 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt @@ -103,6 +103,8 @@ Versão do app: ${getAppVersion()} override val activityLogUserActionResolveVerb = "resolveu" override val activityLogUserActionUnarchiveVerb = "desarquivou" override val activityLogUserActionOpenVerb = "abriu" + override val activityLogUserActionScheduledVerb = "agendou um lembrete para" + override val activityLogUserActionOpenedNotificationVerb = "abriu uma notificação para" override val activityLogUserActionTheme = "Você mudou o tema do app." override val activityLogUserActionLanguage = "Você mudou o idioma do app." override val activityLogUserActionFeedback = "Você enviou um feedback sobre o app." diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/activitylog/presentation/ActivityLogScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/activitylog/presentation/ActivityLogScreen.kt index 16ad645..773a300 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/activitylog/presentation/ActivityLogScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/activitylog/presentation/ActivityLogScreen.kt @@ -58,8 +58,10 @@ import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActio import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Delete import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Edit import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Open +import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.OpenedNotification import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Reopen import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Resolve +import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Scheduled import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Unarchive import org.jetbrains.compose.resources.painterResource import org.koin.compose.viewmodel.koinViewModel @@ -76,6 +78,8 @@ import replyradar.composeapp.generated.resources.ic_rate import replyradar.composeapp.generated.resources.ic_reopen import replyradar.composeapp.generated.resources.ic_theme import replyradar.composeapp.generated.resources.ic_unarchive +import replyradar.composeapp.generated.resources.ic_time +import replyradar.composeapp.generated.resources.ic_notification @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -256,6 +260,8 @@ private fun getIconByActionType(actionType: UserActionType, targetType: UserActi Resolve -> drawable.ic_check Unarchive -> drawable.ic_unarchive Open -> drawable.ic_open + Scheduled -> drawable.ic_time + OpenedNotification -> drawable.ic_notification } Theme -> drawable.ic_theme @@ -293,6 +299,8 @@ private fun getActionVerb(actionType: UserActionType) = when (actionType) { Resolve -> LocalReplyRadarStrings.current.activityLogUserActionResolveVerb Unarchive -> LocalReplyRadarStrings.current.activityLogUserActionUnarchiveVerb Open -> LocalReplyRadarStrings.current.activityLogUserActionOpenVerb + Scheduled -> LocalReplyRadarStrings.current.activityLogUserActionScheduledVerb + OpenedNotification -> LocalReplyRadarStrings.current.activityLogUserActionOpenedNotificationVerb } @Composable diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListEffect.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListEffect.kt index 5c49c08..5b18230 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListEffect.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListEffect.kt @@ -18,5 +18,5 @@ sealed interface ReplyListEffect { data object Unarchived : SnackbarState } - data class ScheduleReminder(val reply: Reply) : ReplyListEffect + data class ScheduleReminder(val reply: Reply, val replyId: Long) : ReplyListEffect } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt index 41f41cb..4535364 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt @@ -136,7 +136,6 @@ fun ReplyListScreen( RequestNotificationPermission -> showPermissionDialog = true - GoToSettings -> notificationPermissionManager.goToAppSettings() is CheckNotificationPermission -> { @@ -153,6 +152,7 @@ fun ReplyListScreen( onIntent( OnScheduleReminder( reply = effect.reply, + replyId = effect.replyId, notificationTitle = getNotificationTitle(strings, effect), notificationContent = getNotificationContent(strings, effect) ) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt index 1105508..88e10d6 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreenIntent.kt @@ -26,6 +26,7 @@ sealed interface ReplyListScreenIntent { data object OnGoToSettings : NotificationPermissionIntent data class OnScheduleReminder( val reply: Reply, + val replyId: Long, val notificationTitle: String, val notificationContent: String ) : NotificationPermissionIntent diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt index 4bd8030..20c9df8 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt @@ -50,8 +50,10 @@ import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActio import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Create import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Delete import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Edit +import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.OpenedNotification import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Reopen import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Resolve +import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Scheduled import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Unarchive import com.rafaelfelipeac.replyradar.features.useractions.domain.usecase.LogUserActionUseCase import kotlinx.coroutines.CoroutineDispatcher @@ -230,7 +232,7 @@ class ReplyListViewModel( logUserAction(actionType = actionType, targetId = replyId) if (reply.reminderAt != INITIAL_DATE) { - _effect.emit(ScheduleReminder(reply)) + _effect.emit(ScheduleReminder(reply, replyId)) } } @@ -278,15 +280,7 @@ class ReplyListViewModel( _state.update { it.update() } } - private suspend fun logUserAction(actionType: UserActionType, targetId: Long) { - logUserActionUseCase.logUserAction( - actionType = actionType, - targetType = Message, - targetId = targetId - ) - } - - private fun onScheduleReminder(intent: OnScheduleReminder) { + private fun onScheduleReminder(intent: OnScheduleReminder) = viewModelScope.launch { with(intent.reply) { reminderScheduler.scheduleReminder( reminderAtMillis = reminderAt, @@ -297,15 +291,36 @@ class ReplyListViewModel( ) ) } + + logUserAction( + actionType = Scheduled, + targetId = intent.replyId + ) + } + + private suspend fun logUserAction(actionType: UserActionType, targetId: Long) { + logUserActionUseCase.logUserAction( + actionType = actionType, + targetType = Message, + targetId = targetId + ) } - private fun checkPendingReplyId(replies: List, onTabSelection: () -> Unit) { + private fun checkPendingReplyId(replies: List, onTabSelection: () -> Unit) = viewModelScope.launch { if (pendingReplyId != INVALID_ID) { val reply = replies.find { it.id == pendingReplyId } if (reply != null) { onTabSelection() onOpenReply(reply) + + pendingReplyId?.let { replyId -> + logUserAction( + actionType = OpenedNotification, + targetId = replyId + ) + } + pendingReplyId = null } } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/useractions/domain/model/UserActionType.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/useractions/domain/model/UserActionType.kt index c507e0a..e0122a8 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/useractions/domain/model/UserActionType.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/useractions/domain/model/UserActionType.kt @@ -8,6 +8,8 @@ sealed class UserActionType(val value: String) { data object Archive : UserActionType(ARCHIVE) data object Unarchive : UserActionType(UNARCHIVE) data object Delete : UserActionType(DELETE) + data object Scheduled : UserActionType(SCHEDULED) + data object OpenedNotification : UserActionType(OPENED_NOTIFICATION) data object Open : UserActionType(OPEN) companion object { @@ -20,6 +22,8 @@ sealed class UserActionType(val value: String) { ARCHIVE -> Archive DELETE -> Delete OPEN -> Open + SCHEDULED -> Scheduled + OPENED_NOTIFICATION -> OpenedNotification else -> Unarchive } } @@ -34,3 +38,5 @@ private const val ARCHIVE = "ARCHIVE" private const val UNARCHIVE = "UNARCHIVE" private const val DELETE = "DELETE" private const val OPEN = "OPEN" +private const val SCHEDULED = "SCHEDULED" +private const val OPENED_NOTIFICATION = "OPENED_NOTIFICATION" From 75cb6ee67bf63917c81a4bef74c7f064b0877415 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Tue, 22 Jul 2025 18:17:21 -0300 Subject: [PATCH 43/59] Removing Clock object and adding invalid datetime snackbar. --- .../replyradar/core/datetime/Clock.kt | 5 --- .../datetime/PlatformDatePicker.android.kt | 3 +- .../datetime/PlatformTimePicker.android.kt | 3 +- .../replyradar/app/ReplyRadarApp.kt | 3 -- .../replyradar/core/clock/LocalClock.kt | 8 ----- .../common/ui/components/ReplyReminder.kt | 3 +- .../ui/components/util/FormatReminderText.kt | 12 +++---- .../replyradar/core/datetime/Clock.kt | 7 ----- .../replyradar/core/datetime/Datetime.kt | 11 +++++++ .../rafaelfelipeac/replyradar/di/Modules.kt | 4 --- .../data/repository/ReplyRepositoryImpl.kt | 9 +++--- .../presentation/replylist/ReplyListScreen.kt | 29 +++++++++++++---- .../replybottomsheet/ReplyBottomSheet.kt | 31 ++++++++++++++++--- .../ReplyBottomSheetActions.kt | 28 +++++++++++------ .../ReplyBottomSheetContent.kt | 4 ++- .../repository/UserActionRepositoryImpl.kt | 7 ++--- .../replyradar/core/datetime/Clock.desktop.kt | 5 --- .../replyradar/core/datetime/Clock.ios.kt | 12 ------- 18 files changed, 97 insertions(+), 87 deletions(-) delete mode 100644 composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.kt delete mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/clock/LocalClock.kt delete mode 100644 composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.kt delete mode 100644 composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.desktop.kt delete mode 100644 composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.ios.kt diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.kt deleted file mode 100644 index 46c7e91..0000000 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.rafaelfelipeac.replyradar.core.datetime - -actual fun getClock(): Clock = object : Clock { - override fun now(): Long = System.currentTimeMillis() -} diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformDatePicker.android.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformDatePicker.android.kt index 22f94ac..18d94f5 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformDatePicker.android.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformDatePicker.android.kt @@ -12,7 +12,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import com.rafaelfelipeac.replyradar.core.clock.LocalClock import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalTime import kotlinx.datetime.TimeZone.Companion.UTC @@ -28,7 +27,7 @@ actual fun PlatformDatePicker( onTimeInvalidated: () -> Unit, onDismiss: () -> Unit ) { - val dateTime = LocalClock.current.now().dateTime() + val dateTime = getCurrentDateTime() val datePickerState = rememberDatePickerState( initialSelectedDateMillis = selectedDate?.toEpochMillis(), diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformTimePicker.android.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformTimePicker.android.kt index 518c5b4..92408ff 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformTimePicker.android.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformTimePicker.android.kt @@ -11,7 +11,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import com.rafaelfelipeac.replyradar.core.clock.LocalClock import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalTime @@ -39,7 +38,7 @@ actual fun PlatformTimePicker( if (showDialog) { val pickedTime = LocalTime(timePickerState.hour, timePickerState.minute) val isValid = isTimeValid( - dateTime = LocalClock.current.now().dateTime(), + dateTime = getCurrentDateTime(), date = selectedDate, time = pickedTime ) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt index 299ef8d..e9c1987 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt @@ -8,7 +8,6 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.graphics.Color import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.compose.rememberNavController -import com.rafaelfelipeac.replyradar.core.clock.LocalClock import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.strings.StringsProvider import com.rafaelfelipeac.replyradar.core.theme.DarkColorScheme @@ -19,7 +18,6 @@ import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme.SYSTEM import com.rafaelfelipeac.replyradar.core.navigation.AppNavHost import com.rafaelfelipeac.replyradar.core.notification.LocalNotificationPermissionManager import com.rafaelfelipeac.replyradar.core.notification.NotificationPermissionManager -import com.rafaelfelipeac.replyradar.core.datetime.getClock import com.rafaelfelipeac.replyradar.features.app.settings.AppSettingsViewModel import org.koin.compose.viewmodel.koinViewModel @@ -47,7 +45,6 @@ fun ReplyRadarApp( CompositionLocalProvider( LocalReplyRadarStrings provides strings, - LocalClock provides getClock(), LocalNotificationPermissionManager provides notificationPermissionManager ) { ReplyRadarTheme(darkTheme = isDark) { diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/clock/LocalClock.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/clock/LocalClock.kt deleted file mode 100644 index 6217be4..0000000 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/clock/LocalClock.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.rafaelfelipeac.replyradar.core.clock - -import androidx.compose.runtime.staticCompositionLocalOf -import com.rafaelfelipeac.replyradar.core.datetime.Clock - -val LocalClock = staticCompositionLocalOf { - error("No Clock provided") -} diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt index 5d30674..ff64a2f 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt @@ -49,8 +49,7 @@ fun ReplyReminder( var showDatePicker by remember { mutableStateOf(false) } val reminderText = formatReminderText( selectedDate = selectedDate, - selectedTime = selectedTime, - onTimeSelected = { onSelectedTimeChange(it) } + selectedTime = selectedTime ) ReminderText( diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/util/FormatReminderText.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/util/FormatReminderText.kt index 59ea350..11557ff 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/util/FormatReminderText.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/util/FormatReminderText.kt @@ -1,10 +1,9 @@ package com.rafaelfelipeac.replyradar.core.common.ui.components.util import androidx.compose.runtime.Composable -import com.rafaelfelipeac.replyradar.core.clock.LocalClock -import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings -import com.rafaelfelipeac.replyradar.core.datetime.dateTime +import com.rafaelfelipeac.replyradar.core.datetime.getCurrentDateTime import com.rafaelfelipeac.replyradar.core.datetime.getDefaultTime +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.util.toTwoDigitString import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime @@ -13,14 +12,13 @@ import kotlinx.datetime.LocalTime @Composable fun formatReminderText( selectedDate: LocalDate?, - selectedTime: LocalTime?, - onTimeSelected: (LocalTime) -> Unit, + selectedTime: LocalTime? ): String? { if (selectedDate == null && selectedTime == null) return null - val datetime = LocalClock.current.now().dateTime() + val datetime = getCurrentDateTime() val defaultTime = - getDefaultTime(datetime, selectedDate, selectedTime).also { onTimeSelected(it) } + getDefaultTime(datetime, selectedDate, selectedTime) val datePart = getDatePart(selectedDate, selectedTime, datetime) val timePart = getTimePart(selectedTime, selectedDate, defaultTime) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.kt deleted file mode 100644 index 149b97a..0000000 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.rafaelfelipeac.replyradar.core.datetime - -interface Clock { - fun now(): Long -} - -expect fun getClock(): Clock diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Datetime.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Datetime.kt index fb3f211..77b3b68 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Datetime.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Datetime.kt @@ -8,6 +8,7 @@ import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalTime import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime +import kotlinx.datetime.Clock const val LOCAL_TIME_HOUR_DEFAULT = 24 const val LOCAL_TIME_MINUTE_DEFAULT = 0 @@ -15,6 +16,16 @@ const val HOUR_OFFSET = 1 const val HOUR_OFFSET_DEFAULT = 0 const val MINUTE_EMPTY = 0 +fun now() = Clock.System.now() + +fun getCurrentDateTime(): LocalDateTime { + return now().toLocalDateTime(TimeZone.currentSystemDefault()) +} + +fun getCurrentTimeMillis(): Long { + return now().toEpochMilliseconds() +} + fun Long.dateTime(timeZone: TimeZone = TimeZone.currentSystemDefault()): LocalDateTime { return Instant.fromEpochMilliseconds(this) .toLocalDateTime(timeZone) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/di/Modules.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/di/Modules.kt index b5d3ce2..dd575ac 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/di/Modules.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/di/Modules.kt @@ -2,8 +2,6 @@ package com.rafaelfelipeac.replyradar.di import androidx.sqlite.driver.bundled.BundledSQLiteDriver import com.rafaelfelipeac.replyradar.core.database.DatabaseFactory -import com.rafaelfelipeac.replyradar.core.datetime.Clock -import com.rafaelfelipeac.replyradar.core.datetime.getClock import org.koin.core.module.Module import org.koin.dsl.module @@ -15,6 +13,4 @@ val sharedModule = module { .setDriver(BundledSQLiteDriver()) .build() } - - single { getClock() } } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/repository/ReplyRepositoryImpl.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/repository/ReplyRepositoryImpl.kt index deaa05f..ec35428 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/repository/ReplyRepositoryImpl.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/repository/ReplyRepositoryImpl.kt @@ -1,7 +1,7 @@ package com.rafaelfelipeac.replyradar.features.reply.data.repository +import com.rafaelfelipeac.replyradar.core.datetime.getCurrentTimeMillis import com.rafaelfelipeac.replyradar.core.util.AppConstants.INITIAL_DATE -import com.rafaelfelipeac.replyradar.core.datetime.Clock import com.rafaelfelipeac.replyradar.features.reply.data.database.dao.ReplyDao import com.rafaelfelipeac.replyradar.features.reply.data.mapper.toReply import com.rafaelfelipeac.replyradar.features.reply.data.mapper.toReplyEntity @@ -12,11 +12,10 @@ import kotlinx.coroutines.flow.map class ReplyRepositoryImpl( private val replyDao: ReplyDao, - private val clock: Clock ) : ReplyRepository { override suspend fun upsertReply(reply: Reply): Long { - val now = clock.now() + val now = getCurrentTimeMillis() val replyEntity = reply.toReplyEntity() val entityToSave = if (reply.id == INITIAL_DATE) { @@ -31,7 +30,7 @@ class ReplyRepositoryImpl( override suspend fun toggleReplyResolve(reply: Reply) { replyDao.update( reply.toReplyEntity().copy( - resolvedAt = if (!reply.isResolved) clock.now() else INITIAL_DATE, + resolvedAt = if (!reply.isResolved) getCurrentTimeMillis() else INITIAL_DATE, isResolved = !reply.isResolved ) ) @@ -40,7 +39,7 @@ class ReplyRepositoryImpl( override suspend fun toggleReplyArchive(reply: Reply) { replyDao.update( reply.toReplyEntity().copy( - archivedAt = if (!reply.isArchived) clock.now() else INITIAL_DATE, + archivedAt = if (!reply.isArchived) getCurrentTimeMillis() else INITIAL_DATE, isArchived = !reply.isArchived ) ) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt index 4535364..b19500e 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt @@ -27,17 +27,20 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.rafaelfelipeac.replyradar.core.util.AppConstants.INITIAL_DATE -import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings -import com.rafaelfelipeac.replyradar.core.strings.Strings import com.rafaelfelipeac.replyradar.core.common.ui.components.NotificationPermissionDialog import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplySnackbar import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyTab import com.rafaelfelipeac.replyradar.core.common.ui.paddingMedium import com.rafaelfelipeac.replyradar.core.common.ui.spacerXSmall import com.rafaelfelipeac.replyradar.core.common.ui.tabRowTopPadding +import com.rafaelfelipeac.replyradar.core.datetime.dateTime +import com.rafaelfelipeac.replyradar.core.datetime.getCurrentDateTime +import com.rafaelfelipeac.replyradar.core.datetime.isDateTimeValid import com.rafaelfelipeac.replyradar.core.notification.LocalNotificationPermissionManager +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.strings.Strings import com.rafaelfelipeac.replyradar.core.util.AppConstants.ARCHIVED_INDEX +import com.rafaelfelipeac.replyradar.core.util.AppConstants.INITIAL_DATE import com.rafaelfelipeac.replyradar.core.util.AppConstants.ON_THE_RADAR_INDEX import com.rafaelfelipeac.replyradar.core.util.AppConstants.RESOLVED_INDEX import com.rafaelfelipeac.replyradar.core.util.format @@ -278,9 +281,23 @@ private fun onSaveReply( reply: Reply, onCheckNotificationPermission: (Reply) -> Unit, onAddOrEditReply: (Reply) -> Unit -) = when { - reply.reminderAt != INITIAL_DATE -> onCheckNotificationPermission(reply) - else -> onAddOrEditReply(reply) +) { + val dateTime = getCurrentDateTime() + val reminderAt = reply.reminderAt.takeIf { it != INITIAL_DATE }?.dateTime() + val selectedTime = reminderAt?.time + val selectedDate = reminderAt?.date + + return when { + selectedTime?.let { time -> + isDateTimeValid( + date = selectedDate, + time = time, + dateTime = dateTime + ) + } == true -> onCheckNotificationPermission(reply) + + else -> onAddOrEditReply(reply) + } } private fun getSnackbarMessage( diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt index 3d34236..f63be8c 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt @@ -4,8 +4,15 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.SheetState +import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyRoundedCorner +import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplySnackbar import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetMode.CREATE import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetMode.EDIT @@ -21,6 +28,16 @@ fun ReplyBottomSheet( onDismiss: () -> Unit, replyBottomSheetState: ReplyBottomSheetState ) { + val snackbarHostState = remember { SnackbarHostState() } + var invalidReminderValue by remember { mutableStateOf(false) } + + LaunchedEffect(invalidReminderValue) { + if (invalidReminderValue) { + snackbarHostState.showSnackbar("A data e hora selecionadas já passaram.") + invalidReminderValue = false + } + } + ModalBottomSheet( sheetState = sheetState, onDismissRequest = onDismiss, @@ -35,7 +52,8 @@ fun ReplyBottomSheet( onSave = onSave, onResolve = onResolve, onArchive = onArchive, - onDelete = onDelete + onDelete = onDelete, + onInvalidReminderValue = { invalidReminderValue = true } ) } @@ -49,11 +67,14 @@ fun ReplyBottomSheet( onSave = onSave, onResolve = onResolve, onArchive = onArchive, - onDelete = onDelete + onDelete = onDelete, + onInvalidReminderValue = { invalidReminderValue = true } ) } } } + + ReplySnackbar(snackbarHostState) } } @@ -63,13 +84,15 @@ private fun BottomSheetContent( onSave: (Reply) -> Unit, onResolve: (Reply) -> Unit, onArchive: (Reply) -> Unit, - onDelete: (Reply) -> Unit + onDelete: (Reply) -> Unit, + onInvalidReminderValue: () -> Unit ) { ReplyBottomSheetContent( replyBottomSheetState = state, onResolve = onResolve, onArchive = onArchive, onDelete = onDelete, - onSave = onSave + onSave = onSave, + onInvalidReminderValue = onInvalidReminderValue ) } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetActions.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetActions.kt index 111859f..16a28c3 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetActions.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetActions.kt @@ -14,16 +14,16 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import com.rafaelfelipeac.replyradar.core.clock.LocalClock -import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyButton import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyConfirmationDialog import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyOutlinedButton import com.rafaelfelipeac.replyradar.core.common.ui.paddingMedium import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall -import com.rafaelfelipeac.replyradar.core.datetime.dateTime -import com.rafaelfelipeac.replyradar.core.util.format +import com.rafaelfelipeac.replyradar.core.datetime.getCurrentDateTime import com.rafaelfelipeac.replyradar.core.datetime.getReminderTimestamp +import com.rafaelfelipeac.replyradar.core.datetime.isDateTimeValid +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.util.format import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetMode.EDIT import kotlinx.datetime.LocalDate @@ -44,6 +44,7 @@ fun ReplyBottomSheetActions( onResolve: (Reply) -> Unit, onDelete: (Reply) -> Unit, onSave: (Reply) -> Unit, + onInvalidReminderValue: () -> Unit, selectedDate: LocalDate?, selectedTime: LocalTime?, reply: Reply?, @@ -64,12 +65,6 @@ fun ReplyBottomSheetActions( onDelete = onDelete ) - val reminderAtTimestamp = getReminderTimestamp( - dateTime = LocalClock.current.now().dateTime(), - selectedDate = selectedDate, - selectedTime = selectedTime - ) - ReplyButton( modifier = Modifier .wrapContentWidth() @@ -80,6 +75,19 @@ fun ReplyBottomSheetActions( LocalReplyRadarStrings.current.replyListBottomSheetSave }, onClick = { + val reminderIsValid = selectedTime != null && isDateTimeValid(selectedDate, selectedTime, getCurrentDateTime()) + + if ((selectedDate != null || selectedTime != null) && !reminderIsValid) { + onInvalidReminderValue() + return@ReplyButton + } + + val reminderAtTimestamp = getReminderTimestamp( + dateTime = getCurrentDateTime(), + selectedDate = selectedDate, + selectedTime = selectedTime + ) + onSave( getReplyToSave( stateReply = state.reply, diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt index be74929..ab30173 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt @@ -34,7 +34,8 @@ fun ReplyBottomSheetContent( onSave: (Reply) -> Unit, onResolve: (Reply) -> Unit, onArchive: (Reply) -> Unit, - onDelete: (Reply) -> Unit + onDelete: (Reply) -> Unit, + onInvalidReminderValue: () -> Unit, ) { replyBottomSheetState?.let { state -> var name by remember { mutableStateOf(state.reply?.name ?: EMPTY) } @@ -90,6 +91,7 @@ fun ReplyBottomSheetContent( onResolve = onResolve, onDelete = onDelete, onSave = onSave, + onInvalidReminderValue = { onInvalidReminderValue() }, selectedDate = selectedDate, selectedTime = selectedTime, reply = state.reply, diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/useractions/data/repository/UserActionRepositoryImpl.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/useractions/data/repository/UserActionRepositoryImpl.kt index 6b43c3b..0448cef 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/useractions/data/repository/UserActionRepositoryImpl.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/useractions/data/repository/UserActionRepositoryImpl.kt @@ -1,6 +1,6 @@ package com.rafaelfelipeac.replyradar.features.useractions.data.repository -import com.rafaelfelipeac.replyradar.core.datetime.Clock +import com.rafaelfelipeac.replyradar.core.datetime.getCurrentTimeMillis import com.rafaelfelipeac.replyradar.features.reply.data.database.dao.ReplyDao import com.rafaelfelipeac.replyradar.features.useractions.data.database.dao.UserActionDao import com.rafaelfelipeac.replyradar.features.useractions.data.database.entity.UserActionEntity @@ -17,8 +17,7 @@ import kotlinx.coroutines.withContext class UserActionRepositoryImpl( private val userActionDao: UserActionDao, - private val replyDao: ReplyDao, - private val clock: Clock + private val replyDao: ReplyDao ) : UserActionRepository { override suspend fun logUserAction( @@ -31,7 +30,7 @@ class UserActionRepositoryImpl( actionType = actionType.value, targetType = targetType.value, targetId = targetId, - createdAt = clock.now() + createdAt = getCurrentTimeMillis() ) ) } diff --git a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.desktop.kt b/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.desktop.kt deleted file mode 100644 index 46c7e91..0000000 --- a/composeApp/src/desktopMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.desktop.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.rafaelfelipeac.replyradar.core.datetime - -actual fun getClock(): Clock = object : Clock { - override fun now(): Long = System.currentTimeMillis() -} diff --git a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.ios.kt b/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.ios.kt deleted file mode 100644 index fba588e..0000000 --- a/composeApp/src/iosMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Clock.ios.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.rafaelfelipeac.replyradar.core.datetime - -import platform.Foundation.NSDate -import platform.Foundation.timeIntervalSince1970 - -actual fun getClock(): Clock = object : Clock { - override fun now(): Long { - return (NSDate().timeIntervalSince1970 * CLOCK_MULTIPLIER).toLong() - } -} - -private const val CLOCK_MULTIPLIER = 1000 From 93de3ac96b88397507481f3c31cca85dcf3f48f1 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Tue, 22 Jul 2025 18:36:20 -0300 Subject: [PATCH 44/59] Added replyListReminderInvalidDateTime message. --- .../com/rafaelfelipeac/replyradar/core/strings/Strings.kt | 1 + .../com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt | 1 + .../com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt | 1 + .../components/replybottomsheet/ReplyBottomSheet.kt | 5 ++++- 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/Strings.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/Strings.kt index e19eda4..427e2f1 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/Strings.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/Strings.kt @@ -44,6 +44,7 @@ interface Strings { val replyListReminderTimeIconContentDescription: String val replyListReminderDateIconContentDescription: String val replyListReminderCloseIconContentDescription: String + val replyListReminderInvalidDateTime: String val replyListReminderTimePickerTitle: String val replyListReminderTimePickerConfirmButton: String val replyListReminderTimePickerDismissButton: String diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt index bb08da3..79c49d4 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt @@ -49,6 +49,7 @@ object StringsEn : Strings { override val replyListReminderTimeIconContentDescription = "Time" override val replyListReminderDateIconContentDescription = "Date" override val replyListReminderCloseIconContentDescription = "Close" + override val replyListReminderInvalidDateTime = "The selected date and time has already passed." override val replyListReminderTimePickerTitle = "Select time" override val replyListReminderTimePickerConfirmButton = "OK" override val replyListReminderTimePickerDismissButton = "Cancel" diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt index df5ebf1..9b23578 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt @@ -49,6 +49,7 @@ object StringsPt : Strings { override val replyListReminderTimeIconContentDescription = "Horário" override val replyListReminderDateIconContentDescription = "Data" override val replyListReminderCloseIconContentDescription = "Remover" + override val replyListReminderInvalidDateTime = "A data e hora selecionadas já passaram." override val replyListReminderTimePickerTitle = "Selecionar horário" override val replyListReminderTimePickerConfirmButton = "OK" override val replyListReminderTimePickerDismissButton = "Cancelar" diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt index f63be8c..70c1be2 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheet.kt @@ -13,6 +13,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyRoundedCorner import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplySnackbar +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetMode.CREATE import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetMode.EDIT @@ -31,9 +32,11 @@ fun ReplyBottomSheet( val snackbarHostState = remember { SnackbarHostState() } var invalidReminderValue by remember { mutableStateOf(false) } + val localStrings = LocalReplyRadarStrings.current + LaunchedEffect(invalidReminderValue) { if (invalidReminderValue) { - snackbarHostState.showSnackbar("A data e hora selecionadas já passaram.") + snackbarHostState.showSnackbar(localStrings.replyListReminderInvalidDateTime) invalidReminderValue = false } } From ca9c8ba68eed44e848c5922c910c5c06c9ab416d Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Tue, 22 Jul 2025 18:52:53 -0300 Subject: [PATCH 45/59] Fixed unit tests. --- .../datetime/PlatformDatePicker.android.kt | 6 ++-- .../com/rafaelfelipeac/replyradar/Shared.kt | 2 +- .../replyradar/fakes/core/util/FakeClock.kt | 7 ---- .../reply/data/ReplyRepositoryTest.kt | 36 ++++++------------- .../data/UserActionRepositoryTest.kt | 7 +--- 5 files changed, 15 insertions(+), 43 deletions(-) delete mode 100644 composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/core/util/FakeClock.kt diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformDatePicker.android.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformDatePicker.android.kt index 18d94f5..153f918 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformDatePicker.android.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformDatePicker.android.kt @@ -54,13 +54,13 @@ actual fun PlatformDatePicker( val dateInMillis = datePickerState.selectedDateMillis if (dateInMillis != null) { - val pickedDate = dateInMillis.toLocalDate(UTC) + val selectedDate = dateInMillis.toLocalDate(UTC) - onDateSelected(pickedDate) + onDateSelected(selectedDate) selectedTime?.let { selectedTime -> val isStillValid = isDateTimeValid( - date = pickedDate, + date = selectedDate, time = selectedTime, dateTime = dateTime ) diff --git a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/Shared.kt b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/Shared.kt index eac83ed..54f9fd2 100644 --- a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/Shared.kt +++ b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/Shared.kt @@ -22,5 +22,5 @@ val sampleUserAction = UserAction( actionType = UserActionType.Create, targetType = UserActionTargetType.Message, targetName = "TargetName", - createdAt = 123456789L + createdAt = now ) diff --git a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/core/util/FakeClock.kt b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/core/util/FakeClock.kt deleted file mode 100644 index 3de5788..0000000 --- a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/fakes/core/util/FakeClock.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.rafaelfelipeac.replyradar.fakes.core.util - -import com.rafaelfelipeac.replyradar.core.datetime.Clock - -class FakeClock(private val fixedNow: Long) : Clock { - override fun now(): Long = fixedNow -} diff --git a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/ReplyRepositoryTest.kt b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/ReplyRepositoryTest.kt index 5347da2..df31aac 100644 --- a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/ReplyRepositoryTest.kt +++ b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/ReplyRepositoryTest.kt @@ -1,24 +1,21 @@ package com.rafaelfelipeac.replyradar.features.reply.data import com.rafaelfelipeac.replyradar.core.util.AppConstants.EMPTY -import com.rafaelfelipeac.replyradar.fakes.core.util.FakeClock import com.rafaelfelipeac.replyradar.fakes.reply.data.FakeReplyDao import com.rafaelfelipeac.replyradar.features.reply.data.database.entity.ReplyEntity import com.rafaelfelipeac.replyradar.features.reply.data.repository.ReplyRepositoryImpl import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply -import com.rafaelfelipeac.replyradar.now import com.rafaelfelipeac.replyradar.util.valueOrEmpty +import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals -import kotlinx.coroutines.test.runTest class ReplyRepositoryTest { @Test fun `upsertReply should insert reply with correct timestamps when id is 0`() = runTest { val dao = FakeReplyDao() - val clock = FakeClock(now) - val repository = ReplyRepositoryImpl(dao, clock) + val repository = ReplyRepositoryImpl(dao) val reply = Reply( id = 0L, @@ -29,18 +26,14 @@ class ReplyRepositoryTest { val returnedId = repository.upsertReply(reply) assertEquals(1, dao.insertedReplies.size) - val inserted = dao.insertedReplies.first() - assertEquals(now, inserted.createdAt) - assertEquals(now, inserted.updatedAt) assertEquals(1L, returnedId) } @Test fun `upsertReply should update reply with new updatedAt when id is not 0`() = runTest { val dao = FakeReplyDao() - val clock = FakeClock(now) - val repository = ReplyRepositoryImpl(dao, clock) + val repository = ReplyRepositoryImpl(dao) val reply = Reply( id = 42L, @@ -54,15 +47,13 @@ class ReplyRepositoryTest { val inserted = dao.insertedReplies.first() assertEquals(42L, inserted.id) - assertEquals(now, inserted.updatedAt) assertEquals(42L, returnedId) } @Test fun `toggleReplyResolve should toggle isResolved and set resolvedAt`() = runTest { val dao = FakeReplyDao() - val clock = FakeClock(now) - val repository = ReplyRepositoryImpl(dao, clock) + val repository = ReplyRepositoryImpl(dao) val reply = Reply( id = 1L, @@ -76,14 +67,12 @@ class ReplyRepositoryTest { val updated = dao.updatedReplies.first() assertEquals(true, updated.isResolved) assertEquals(true, updated.isArchived) - assertEquals(now, updated.resolvedAt) - } + } @Test fun `toggleReplyArchive should toggle isArchived and set archivedAt`() = runTest { val dao = FakeReplyDao() - val clock = FakeClock(now) - val repository = ReplyRepositoryImpl(dao, clock) + val repository = ReplyRepositoryImpl(dao) val reply = Reply( id = 2L, @@ -97,14 +86,12 @@ class ReplyRepositoryTest { val updated = dao.updatedReplies.first() assertEquals(true, updated.isArchived) assertEquals(true, updated.isResolved) - assertEquals(now, updated.archivedAt) } @Test fun `deleteReply should call deleteReply on dao`() = runTest { val dao = FakeReplyDao() - val clock = FakeClock(0L) - val repository = ReplyRepositoryImpl(dao, clock) + val repository = ReplyRepositoryImpl(dao) val reply = Reply( id = 10L, @@ -122,8 +109,7 @@ class ReplyRepositoryTest { val replyEntities = listOf(ReplyEntity(id = 1L, name = "Resolved", subject = "R", isResolved = true)) val dao = FakeReplyDao(resolved = replyEntities) - val clock = FakeClock(0L) - val repository = ReplyRepositoryImpl(dao, clock) + val repository = ReplyRepositoryImpl(dao) val replies = repository.getReplies(isResolved = true, isArchived = false).valueOrEmpty() @@ -136,8 +122,7 @@ class ReplyRepositoryTest { val replyEntities = listOf(ReplyEntity(id = 2L, name = "Archived", subject = "A", isArchived = true)) val dao = FakeReplyDao(archived = replyEntities) - val clock = FakeClock(0L) - val repository = ReplyRepositoryImpl(dao, clock) + val repository = ReplyRepositoryImpl(dao) val replies = repository.getReplies(isResolved = false, isArchived = true).valueOrEmpty() @@ -149,8 +134,7 @@ class ReplyRepositoryTest { fun `getReplies should return active replies when none is true`() = runTest { val replyEntities = listOf(ReplyEntity(id = 3L, name = "Active", subject = "A")) val dao = FakeReplyDao(active = replyEntities) - val clock = FakeClock(0L) - val repository = ReplyRepositoryImpl(dao, clock) + val repository = ReplyRepositoryImpl(dao) val replies = repository.getReplies(isResolved = false, isArchived = false).valueOrEmpty() diff --git a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/useractions/data/UserActionRepositoryTest.kt b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/useractions/data/UserActionRepositoryTest.kt index f7d5c28..977d075 100644 --- a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/useractions/data/UserActionRepositoryTest.kt +++ b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/useractions/data/UserActionRepositoryTest.kt @@ -1,7 +1,6 @@ package com.rafaelfelipeac.replyradar.features.useractions.data import app.cash.turbine.test -import com.rafaelfelipeac.replyradar.fakes.core.util.FakeClock import com.rafaelfelipeac.replyradar.fakes.reply.data.FakeReplyDao import com.rafaelfelipeac.replyradar.fakes.useractions.data.FakeUserActionDao import com.rafaelfelipeac.replyradar.features.useractions.data.database.entity.UserActionEntity @@ -18,12 +17,10 @@ class UserActionRepositoryTest { private val userActionDao = FakeUserActionDao() private val replyDao = FakeReplyDao() - private val clock = FakeClock(now) private val repository = UserActionRepositoryImpl( userActionDao = userActionDao, - replyDao = replyDao, - clock = clock + replyDao = replyDao ) @Test @@ -39,7 +36,6 @@ class UserActionRepositoryTest { assertEquals(actionType.value, entity.actionType) assertEquals(targetType.value, entity.targetType) assertEquals(targetId, entity.targetId) - assertEquals(now, entity.createdAt) } @Test @@ -66,7 +62,6 @@ class UserActionRepositoryTest { assertEquals(Create, action.actionType) assertEquals(Message, action.targetType) assertEquals(replyName, action.targetName) - assertEquals(now, action.createdAt) cancelAndIgnoreRemainingEvents() } From 41f387ee7cac160b0ee6f25f1cb769c46a2f7394 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Tue, 22 Jul 2025 18:59:51 -0300 Subject: [PATCH 46/59] Added replyListReminderSetSeparator message. --- .../core/common/ui/components/util/FormatReminderText.kt | 3 ++- .../com/rafaelfelipeac/replyradar/core/strings/Strings.kt | 1 + .../com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt | 1 + .../com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/util/FormatReminderText.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/util/FormatReminderText.kt index 11557ff..bee2444 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/util/FormatReminderText.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/util/FormatReminderText.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable import com.rafaelfelipeac.replyradar.core.datetime.getCurrentDateTime import com.rafaelfelipeac.replyradar.core.datetime.getDefaultTime import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.util.format import com.rafaelfelipeac.replyradar.core.util.toTwoDigitString import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime @@ -25,7 +26,7 @@ fun formatReminderText( return "${LocalReplyRadarStrings.current.replyListReminderSet} ${ when { - datePart != null && timePart != null -> "$datePart $timePart" + datePart != null && timePart != null -> format(LocalReplyRadarStrings.current.replyListReminderSetSeparator, datePart, timePart) datePart != null -> datePart else -> timePart } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/Strings.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/Strings.kt index 427e2f1..6026bfb 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/Strings.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/Strings.kt @@ -39,6 +39,7 @@ interface Strings { val replyListSnackbarUnarchived: String val replyListReminder: String val replyListReminderSet: String + val replyListReminderSetSeparator: String val replyListReminderToday: String val replyListReminderTomorrow: String val replyListReminderTimeIconContentDescription: String diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt index 79c49d4..e50f281 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt @@ -44,6 +44,7 @@ object StringsEn : Strings { override val replyListSnackbarUnarchived = "Item successfully unarchived." override val replyListReminder = "Reminder" override val replyListReminderSet = "Reminder set for:" + override val replyListReminderSetSeparator = "%1 at %2" override val replyListReminderToday = "today" override val replyListReminderTomorrow = "tomorrow" override val replyListReminderTimeIconContentDescription = "Time" diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt index 9b23578..8468088 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt @@ -44,6 +44,7 @@ object StringsPt : Strings { override val replyListSnackbarUnarchived = "Item desarquivado com sucesso." override val replyListReminder = "Lembrete" override val replyListReminderSet = "Lembrete definido para:" + override val replyListReminderSetSeparator = "%1 às %2" override val replyListReminderToday = "hoje" override val replyListReminderTomorrow = "amanhã" override val replyListReminderTimeIconContentDescription = "Horário" From 392268077ee178bda94200de80d24097235639ce Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Tue, 22 Jul 2025 19:11:16 -0300 Subject: [PATCH 47/59] Improvements. --- .../features/reply/presentation/replylist/ReplyListScreen.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt index b19500e..0195a2b 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt @@ -133,9 +133,7 @@ fun ReplyListScreen( LaunchedEffect(Unit) { effect.collect { effect -> when (effect) { - is SnackbarState -> snackbarHostState.showSnackbar( - getSnackbarMessage(effect, strings) - ) + is SnackbarState -> snackbarHostState.showSnackbar(getSnackbarMessage(effect, strings)) RequestNotificationPermission -> showPermissionDialog = true From 9f5fe981a2d965aa83882d57c39c1faef70b70ff Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Wed, 23 Jul 2025 15:51:06 -0300 Subject: [PATCH 48/59] Adding padding top for ReminderText. --- .../replyradar/core/common/ui/components/ReplyReminder.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt index ff64a2f..6f4955a 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt @@ -16,6 +16,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.components.util.formatReminderText @@ -163,7 +164,8 @@ private fun ReminderText( Row( modifier = Modifier .fillMaxWidth(), - horizontalArrangement = Arrangement.Start + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically ) { Text( modifier = Modifier @@ -174,7 +176,7 @@ private fun ReminderText( IconButton( modifier = Modifier - .padding(start = paddingLarge) + .padding(start = paddingLarge, top = paddingSmall) .size(iconButtonSize), onClick = { onDeleteClick() }, ) { From eea964f01a1a209a7c56e1a975bda868ca85fddf Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Wed, 23 Jul 2025 16:43:56 -0300 Subject: [PATCH 49/59] Fixed import. --- .../reply/presentation/replylist/ReplyListViewModel.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt index 20c9df8..849c951 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt @@ -16,6 +16,7 @@ import com.rafaelfelipeac.replyradar.features.reply.domain.usecase.GetRepliesUse import com.rafaelfelipeac.replyradar.features.reply.domain.usecase.ToggleArchiveReplyUseCase import com.rafaelfelipeac.replyradar.features.reply.domain.usecase.ToggleResolveReplyUseCase import com.rafaelfelipeac.replyradar.features.reply.domain.usecase.UpsertReplyUseCase +import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.CheckNotificationPermission import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.GoToSettings import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.RequestNotificationPermission import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListEffect.ScheduleReminder @@ -269,7 +270,7 @@ class ReplyListViewModel( } private fun checkNotificationPermission(reply: Reply) = viewModelScope.launch { - _effect.emit(ReplyListEffect.CheckNotificationPermission(reply)) + _effect.emit(CheckNotificationPermission(reply)) } private fun goToSettings() = viewModelScope.launch { From 842bfeb8440c6b60eb70c062e19595864dd09df3 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Wed, 23 Jul 2025 17:03:15 -0300 Subject: [PATCH 50/59] Ktlint fixes. --- .../replyradar/ReplyRadarApplication.kt | 2 +- .../core/database/DatabaseFactory.kt | 2 +- .../core/database/ReplyRadarMigrations.kt | 3 +- .../NotificationPermissionManager.kt | 2 +- .../core/reminder/NotificationUtils.kt | 5 +- .../replyradar/app/ReplyRadarApp.kt | 6 +-- .../ReplyNotificationPermissionDialog.kt | 5 +- .../common/ui/components/ReplyReminder.kt | 37 +++++++------ .../core/common/ui/components/ReplyToggle.kt | 2 +- .../ui/components/util/FormatReminderText.kt | 19 +++---- .../replyradar/core/datetime/Datetime.kt | 2 +- .../replyradar/core/datetime/Timestamp.kt | 2 +- .../replyradar/core/navigation/AppNavHost.kt | 5 +- .../replyradar/core/strings/StringsEn.kt | 2 + .../replyradar/core/strings/StringsPt.kt | 2 + .../presentation/ActivityLogScreen.kt | 16 +++--- .../data/repository/ReplyRepositoryImpl.kt | 2 +- .../presentation/replylist/ReplyListEffect.kt | 4 +- .../presentation/replylist/ReplyListScreen.kt | 44 +++++++--------- .../replylist/ReplyListViewModel.kt | 33 ++++++------ .../components/FloatingActionButton.kt | 4 +- .../components/RepliesArchivedScreen.kt | 2 +- .../components/RepliesOnTheRadarScreen.kt | 2 +- .../components/RepliesResolvedScreen.kt | 2 +- .../components/ReplyTimestampInfo.kt | 11 ++-- .../replylist/components/TopBar.kt | 2 +- .../ReplyBottomSheetActions.kt | 8 ++- .../ReplyBottomSheetContent.kt | 10 ++-- .../settings/presentation/SettingsScreen.kt | 16 +++--- .../reply/data/ReplyRepositoryTest.kt | 4 +- .../presentation/ReplyListViewModelTest.kt | 52 ++++++++++--------- 31 files changed, 154 insertions(+), 154 deletions(-) diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/ReplyRadarApplication.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/ReplyRadarApplication.kt index c40ba54..0799259 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/ReplyRadarApplication.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/ReplyRadarApplication.kt @@ -5,9 +5,9 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.app.NotificationManager.IMPORTANCE_HIGH import android.os.Build +import com.rafaelfelipeac.replyradar.R.string.notification_channel_description import com.rafaelfelipeac.replyradar.R.string.notification_channel_id import com.rafaelfelipeac.replyradar.R.string.notification_channel_name -import com.rafaelfelipeac.replyradar.R.string.notification_channel_description import com.rafaelfelipeac.replyradar.di.initKoin import org.koin.android.ext.koin.androidContext diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/database/DatabaseFactory.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/database/DatabaseFactory.kt index 4026a0f..db8977a 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/database/DatabaseFactory.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/database/DatabaseFactory.kt @@ -3,8 +3,8 @@ package com.rafaelfelipeac.replyradar.core.database import android.content.Context import androidx.room.Room import androidx.room.RoomDatabase -import com.rafaelfelipeac.replyradar.core.util.AppConstants.DB_NAME import com.rafaelfelipeac.replyradar.core.database.ReplyRadarMigrations.ALL_MIGRATIONS +import com.rafaelfelipeac.replyradar.core.util.AppConstants.DB_NAME actual class DatabaseFactory( private val context: Context diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/database/ReplyRadarMigrations.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/database/ReplyRadarMigrations.kt index 5f61984..9cd857e 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/database/ReplyRadarMigrations.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/database/ReplyRadarMigrations.kt @@ -1,8 +1,8 @@ package com.rafaelfelipeac.replyradar.core.database import androidx.room.migration.Migration -import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.SQLiteConnection +import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.execSQL object ReplyRadarMigrations { @@ -18,4 +18,3 @@ val MIGRATION_1_2 = object : Migration(1, 2) { connection.execSQL("ALTER TABLE replies ADD COLUMN reminderAt INTEGER NOT NULL DEFAULT 0") } } - diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/NotificationPermissionManager.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/NotificationPermissionManager.kt index 66b0e65..10fe2dd 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/NotificationPermissionManager.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/notification/NotificationPermissionManager.kt @@ -16,13 +16,13 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.platform.LocalContext import androidx.core.content.ContextCompat.checkSelfPermission +import kotlin.coroutines.resume import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine -import kotlin.coroutines.resume private const val PACKAGE = "package" diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/NotificationUtils.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/NotificationUtils.kt index dccb1e6..343c5c2 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/NotificationUtils.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/NotificationUtils.kt @@ -45,10 +45,7 @@ object NotificationUtils { NotificationManagerCompat.from(context).notify(replyId.hashCode(), notification) } - private fun getPendingIntent( - context: Context, - replyId: Long - ): PendingIntent? { + private fun getPendingIntent(context: Context, replyId: Long): PendingIntent? { val intent = Intent(context, MainActivity::class.java).apply { flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK putExtra(PENDING_REPLY_ID_KEY, replyId) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt index e9c1987..75322ab 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/app/ReplyRadarApp.kt @@ -8,6 +8,9 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.graphics.Color import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.compose.rememberNavController +import com.rafaelfelipeac.replyradar.core.navigation.AppNavHost +import com.rafaelfelipeac.replyradar.core.notification.LocalNotificationPermissionManager +import com.rafaelfelipeac.replyradar.core.notification.NotificationPermissionManager import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.strings.StringsProvider import com.rafaelfelipeac.replyradar.core.theme.DarkColorScheme @@ -15,9 +18,6 @@ import com.rafaelfelipeac.replyradar.core.theme.LightColorScheme import com.rafaelfelipeac.replyradar.core.theme.ReplyRadarTheme import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme.DARK import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme.SYSTEM -import com.rafaelfelipeac.replyradar.core.navigation.AppNavHost -import com.rafaelfelipeac.replyradar.core.notification.LocalNotificationPermissionManager -import com.rafaelfelipeac.replyradar.core.notification.NotificationPermissionManager import com.rafaelfelipeac.replyradar.features.app.settings.AppSettingsViewModel import org.koin.compose.viewmodel.koinViewModel diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyNotificationPermissionDialog.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyNotificationPermissionDialog.kt index f47a4a6..46b40b0 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyNotificationPermissionDialog.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyNotificationPermissionDialog.kt @@ -7,10 +7,7 @@ import androidx.compose.runtime.Composable import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings @Composable -fun NotificationPermissionDialog( - onDismiss: () -> Unit, - onGoToSettings: () -> Unit -) { +fun NotificationPermissionDialog(onDismiss: () -> Unit, onGoToSettings: () -> Unit) { AlertDialog( onDismissRequest = onDismiss, title = { diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt index 6f4955a..3046273 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyReminder.kt @@ -18,7 +18,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.components.util.formatReminderText import com.rafaelfelipeac.replyradar.core.common.ui.iconButtonSize import com.rafaelfelipeac.replyradar.core.common.ui.iconSize @@ -26,10 +25,11 @@ import com.rafaelfelipeac.replyradar.core.common.ui.listDividerThickness import com.rafaelfelipeac.replyradar.core.common.ui.paddingLarge import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall import com.rafaelfelipeac.replyradar.core.common.ui.paddingXSmall -import com.rafaelfelipeac.replyradar.core.theme.horizontalDividerColor -import com.rafaelfelipeac.replyradar.core.theme.toolbarIconsColor import com.rafaelfelipeac.replyradar.core.datetime.PlatformDatePicker import com.rafaelfelipeac.replyradar.core.datetime.PlatformTimePicker +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.theme.horizontalDividerColor +import com.rafaelfelipeac.replyradar.core.theme.toolbarIconsColor import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalTime import org.jetbrains.compose.resources.painterResource @@ -74,9 +74,12 @@ fun ReplyReminder( PlatformTimePicker( selectedTime = selectedTime, selectedDate = selectedDate, - confirmButtonText = LocalReplyRadarStrings.current.replyListReminderTimePickerConfirmButton, - dismissButtonText = LocalReplyRadarStrings.current.replyListReminderTimePickerDismissButton, - pickerTimeTitle = LocalReplyRadarStrings.current.replyListReminderTimePickerTitle, + confirmButtonText = LocalReplyRadarStrings.current + .replyListReminderTimePickerConfirmButton, + dismissButtonText = LocalReplyRadarStrings.current + .replyListReminderTimePickerDismissButton, + pickerTimeTitle = LocalReplyRadarStrings.current + .replyListReminderTimePickerTitle, onTimeSelected = { onSelectedTimeChange(it) showTimePicker = false @@ -91,8 +94,10 @@ fun ReplyReminder( PlatformDatePicker( selectedDate = selectedDate, selectedTime = selectedTime, - confirmButtonText = LocalReplyRadarStrings.current.replyListReminderDatePickerConfirmButton, - dismissButtonText = LocalReplyRadarStrings.current.replyListReminderDatePickerDismissButton, + confirmButtonText = LocalReplyRadarStrings.current + .replyListReminderDatePickerConfirmButton, + dismissButtonText = LocalReplyRadarStrings.current + .replyListReminderDatePickerDismissButton, onDateSelected = { onSelectedDateChange(it) showDatePicker = false @@ -124,7 +129,8 @@ fun ReplyReminder( modifier = Modifier .size(iconSize), painter = painterResource(drawable.ic_time), - contentDescription = LocalReplyRadarStrings.current.replyListReminderTimeIconContentDescription, + contentDescription = LocalReplyRadarStrings.current + .replyListReminderTimeIconContentDescription, tint = colorScheme.primary ) } @@ -141,7 +147,8 @@ fun ReplyReminder( modifier = Modifier .size(iconSize), painter = painterResource(drawable.ic_date), - contentDescription = LocalReplyRadarStrings.current.replyListReminderDateIconContentDescription, + contentDescription = LocalReplyRadarStrings.current + .replyListReminderDateIconContentDescription, tint = colorScheme.primary ) } @@ -156,10 +163,7 @@ fun ReplyReminder( } @Composable -private fun ReminderText( - reminderText: String?, - onDeleteClick: () -> Unit -) { +private fun ReminderText(reminderText: String?, onDeleteClick: () -> Unit) { if (reminderText != null) { Row( modifier = Modifier @@ -178,13 +182,14 @@ private fun ReminderText( modifier = Modifier .padding(start = paddingLarge, top = paddingSmall) .size(iconButtonSize), - onClick = { onDeleteClick() }, + onClick = { onDeleteClick() } ) { Icon( modifier = Modifier .size(iconSize), painter = painterResource(drawable.ic_close), - contentDescription = LocalReplyRadarStrings.current.replyListReminderCloseIconContentDescription, + contentDescription = LocalReplyRadarStrings.current + .replyListReminderCloseIconContentDescription, tint = colorScheme.toolbarIconsColor ) } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyToggle.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyToggle.kt index 7ffa9cb..1fa0c00 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyToggle.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/ReplyToggle.kt @@ -23,10 +23,10 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color.Companion.Transparent import androidx.compose.ui.hapticfeedback.HapticFeedbackType.Companion.LongPress import androidx.compose.ui.platform.LocalHapticFeedback -import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.iconSize import com.rafaelfelipeac.replyradar.core.common.ui.listItemToggleBorderWidth import com.rafaelfelipeac.replyradar.core.common.ui.listItemToggleSize +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import kotlinx.coroutines.delay import org.jetbrains.compose.resources.painterResource import replyradar.composeapp.generated.resources.Res.drawable diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/util/FormatReminderText.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/util/FormatReminderText.kt index bee2444..362a373 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/util/FormatReminderText.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/util/FormatReminderText.kt @@ -11,10 +11,7 @@ import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalTime @Composable -fun formatReminderText( - selectedDate: LocalDate?, - selectedTime: LocalTime? -): String? { +fun formatReminderText(selectedDate: LocalDate?, selectedTime: LocalTime?): String? { if (selectedDate == null && selectedTime == null) return null val datetime = getCurrentDateTime() @@ -25,11 +22,15 @@ fun formatReminderText( val timePart = getTimePart(selectedTime, selectedDate, defaultTime) return "${LocalReplyRadarStrings.current.replyListReminderSet} ${ - when { - datePart != null && timePart != null -> format(LocalReplyRadarStrings.current.replyListReminderSetSeparator, datePart, timePart) - datePart != null -> datePart - else -> timePart - } + when { + datePart != null && timePart != null -> format( + LocalReplyRadarStrings.current.replyListReminderSetSeparator, + datePart, + timePart + ) + datePart != null -> datePart + else -> timePart + } }" } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Datetime.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Datetime.kt index 77b3b68..657a247 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Datetime.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Datetime.kt @@ -2,13 +2,13 @@ package com.rafaelfelipeac.replyradar.core.datetime import com.rafaelfelipeac.replyradar.core.util.AppConstants.REMINDER_DEFAULT_HOUR import com.rafaelfelipeac.replyradar.core.util.AppConstants.REMINDER_DEFAULT_MINUTE +import kotlinx.datetime.Clock import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalTime import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime -import kotlinx.datetime.Clock const val LOCAL_TIME_HOUR_DEFAULT = 24 const val LOCAL_TIME_MINUTE_DEFAULT = 0 diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Timestamp.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Timestamp.kt index 07c413c..5a048df 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Timestamp.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Timestamp.kt @@ -17,7 +17,7 @@ fun formatTimestamp(timestampMillis: Long): String { val localDateTime = timestampMillis.dateTime() return "${localDateTime.dayOfMonth}/${localDateTime.monthNumber}/${localDateTime.year} " + - "${localDateTime.hour}:${localDateTime.minute.toTwoDigitString()}" + "${localDateTime.hour}:${localDateTime.minute.toTwoDigitString()}" } fun getReminderTimestamp( diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/navigation/AppNavHost.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/navigation/AppNavHost.kt index f770974..82ea131 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/navigation/AppNavHost.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/navigation/AppNavHost.kt @@ -22,10 +22,7 @@ import com.rafaelfelipeac.replyradar.features.settings.presentation.SettingsView import org.koin.compose.viewmodel.koinViewModel @Composable -fun AppNavHost( - navController: NavHostController, - pendingReplyId: Long? -) { +fun AppNavHost(navController: NavHostController, pendingReplyId: Long?) { NavHost( navController = navController, startDestination = ReplyGraph diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt index e50f281..020ff26 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt @@ -2,6 +2,7 @@ package com.rafaelfelipeac.replyradar.core.strings import com.rafaelfelipeac.replyradar.core.version.getAppVersion +// ktlint-disable max-line-length object StringsEn : Strings { override val appName = "Reply Radar" @@ -120,3 +121,4 @@ App version: ${getAppVersion()} override val notificationContent = "%1 is waiting for your reply about \"%2\"." override val notificationContentWithoutSubject = "%1 is waiting for your reply." } +// ktlint-enable max-line-length diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt index 8468088..653a5a2 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt @@ -2,6 +2,7 @@ package com.rafaelfelipeac.replyradar.core.strings import com.rafaelfelipeac.replyradar.core.version.getAppVersion +// ktlint-disable max-line-length object StringsPt : Strings { override val appName = "Reply Radar" @@ -120,3 +121,4 @@ Versão do app: ${getAppVersion()} override val notificationContent = "%1 está esperando sua resposta sobre \"%2\"." override val notificationContentWithoutSubject = "%1 está esperando sua resposta." } +// ktlint-enable max-line-length diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/activitylog/presentation/ActivityLogScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/activitylog/presentation/ActivityLogScreen.kt index 773a300..436eff5 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/activitylog/presentation/ActivityLogScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/activitylog/presentation/ActivityLogScreen.kt @@ -34,16 +34,16 @@ import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyProgress import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyRadarError import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyRadarPlaceholder import com.rafaelfelipeac.replyradar.core.common.ui.iconSize import com.rafaelfelipeac.replyradar.core.common.ui.listDividerThickness import com.rafaelfelipeac.replyradar.core.common.ui.paddingMedium +import com.rafaelfelipeac.replyradar.core.datetime.formatTimestamp +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.theme.horizontalDividerColor import com.rafaelfelipeac.replyradar.core.util.format -import com.rafaelfelipeac.replyradar.core.datetime.formatTimestamp import com.rafaelfelipeac.replyradar.features.activitylog.presentation.ActivityLogViewModel.Companion.ERROR_GET_ACTIVITY_LOG import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserAction import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionTargetType @@ -73,13 +73,13 @@ import replyradar.composeapp.generated.resources.ic_delete import replyradar.composeapp.generated.resources.ic_edit import replyradar.composeapp.generated.resources.ic_email import replyradar.composeapp.generated.resources.ic_language +import replyradar.composeapp.generated.resources.ic_notification import replyradar.composeapp.generated.resources.ic_open import replyradar.composeapp.generated.resources.ic_rate import replyradar.composeapp.generated.resources.ic_reopen import replyradar.composeapp.generated.resources.ic_theme -import replyradar.composeapp.generated.resources.ic_unarchive import replyradar.composeapp.generated.resources.ic_time -import replyradar.composeapp.generated.resources.ic_notification +import replyradar.composeapp.generated.resources.ic_unarchive @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -94,8 +94,8 @@ fun ActivityLogScreen(viewModel: ActivityLogViewModel = koinViewModel(), onBackC IconButton(onClick = onBackClick) { Icon( imageVector = Icons.Default.ArrowBack, - contentDescription = - LocalReplyRadarStrings.current.activityLogBackButton + contentDescription = LocalReplyRadarStrings.current + .activityLogBackButton ) } } @@ -218,8 +218,8 @@ fun ActivityLogListItem(userAction: UserAction) { ) ), tint = colorScheme.primary, - contentDescription = - LocalReplyRadarStrings.current.activityLogItemContentDescription + contentDescription = LocalReplyRadarStrings.current + .activityLogItemContentDescription ) } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/repository/ReplyRepositoryImpl.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/repository/ReplyRepositoryImpl.kt index ec35428..95b4538 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/repository/ReplyRepositoryImpl.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/repository/ReplyRepositoryImpl.kt @@ -11,7 +11,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map class ReplyRepositoryImpl( - private val replyDao: ReplyDao, + private val replyDao: ReplyDao ) : ReplyRepository { override suspend fun upsertReply(reply: Reply): Long { diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListEffect.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListEffect.kt index 5b18230..4407eae 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListEffect.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListEffect.kt @@ -4,9 +4,9 @@ import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply sealed interface ReplyListEffect { - data class CheckNotificationPermission(val reply: Reply): ReplyListEffect + data class CheckNotificationPermission(val reply: Reply) : ReplyListEffect - data object RequestNotificationPermission: ReplyListEffect + data object RequestNotificationPermission : ReplyListEffect data object GoToSettings : ReplyListEffect diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt index 0195a2b..2f9910a 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListScreen.kt @@ -133,7 +133,9 @@ fun ReplyListScreen( LaunchedEffect(Unit) { effect.collect { effect -> when (effect) { - is SnackbarState -> snackbarHostState.showSnackbar(getSnackbarMessage(effect, strings)) + is SnackbarState -> snackbarHostState.showSnackbar( + getSnackbarMessage(effect, strings) + ) RequestNotificationPermission -> showPermissionDialog = true @@ -269,7 +271,7 @@ fun ReplyListScreen( ) }, onDismiss = { onIntent(OnDismissBottomSheet) }, - replyBottomSheetState = state.replyBottomSheetState, + replyBottomSheetState = state.replyBottomSheetState ) } } @@ -298,10 +300,7 @@ private fun onSaveReply( } } -private fun getSnackbarMessage( - effect: SnackbarState, - strings: Strings -) = when (effect) { +private fun getSnackbarMessage(effect: SnackbarState, strings: Strings) = when (effect) { Archived -> strings.replyListSnackbarArchived Removed -> strings.replyListSnackbarRemoved Reopened -> strings.replyListSnackbarReopened @@ -309,26 +308,21 @@ private fun getSnackbarMessage( Unarchived -> strings.replyListSnackbarUnarchived } -private fun getNotificationTitle( - strings: Strings, - effect: ScheduleReminder -) = format( +private fun getNotificationTitle(strings: Strings, effect: ScheduleReminder) = format( strings.notificationTitle, effect.reply.name ) -private fun getNotificationContent( - strings: Strings, - effect: ScheduleReminder -) = if (effect.reply.subject.isNotBlank()) { - format( - strings.notificationContent, - effect.reply.name, - effect.reply.subject - ) -} else { - format( - strings.notificationContentWithoutSubject, - effect.reply.name - ) -} +private fun getNotificationContent(strings: Strings, effect: ScheduleReminder) = + if (effect.reply.subject.isNotBlank()) { + format( + strings.notificationContent, + effect.reply.name, + effect.reply.subject + ) + } else { + format( + strings.notificationContentWithoutSubject, + effect.reply.name + ) + } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt index 849c951..473e4ec 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt @@ -307,25 +307,26 @@ class ReplyListViewModel( ) } - private fun checkPendingReplyId(replies: List, onTabSelection: () -> Unit) = viewModelScope.launch { - if (pendingReplyId != INVALID_ID) { - val reply = replies.find { it.id == pendingReplyId } - - if (reply != null) { - onTabSelection() - onOpenReply(reply) - - pendingReplyId?.let { replyId -> - logUserAction( - actionType = OpenedNotification, - targetId = replyId - ) - } + private fun checkPendingReplyId(replies: List, onTabSelection: () -> Unit) = + viewModelScope.launch { + if (pendingReplyId != INVALID_ID) { + val reply = replies.find { it.id == pendingReplyId } + + if (reply != null) { + onTabSelection() + onOpenReply(reply) + + pendingReplyId?.let { replyId -> + logUserAction( + actionType = OpenedNotification, + targetId = replyId + ) + } - pendingReplyId = null + pendingReplyId = null + } } } - } private fun onTabSelected(index: Int) { updateState { diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/FloatingActionButton.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/FloatingActionButton.kt index 4fd99d3..18f7e80 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/FloatingActionButton.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/FloatingActionButton.kt @@ -18,8 +18,8 @@ fun FloatingActionButton(onIntent: (ReplyListScreenIntent) -> Unit, colorScheme: ) { Icon( imageVector = Icons.Filled.Add, - contentDescription = - LocalReplyRadarStrings.current.replyListFabContentDescription, + contentDescription = LocalReplyRadarStrings.current + .replyListFabContentDescription, tint = colorScheme.background ) } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesArchivedScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesArchivedScreen.kt index 672685e..eb165a4 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesArchivedScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesArchivedScreen.kt @@ -3,8 +3,8 @@ package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.comp import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyRadarPlaceholder +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnOpenReply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnReplyToggle diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesOnTheRadarScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesOnTheRadarScreen.kt index 8cf26cc..e8fb01e 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesOnTheRadarScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesOnTheRadarScreen.kt @@ -3,10 +3,10 @@ package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.comp import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyProgress import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyRadarError import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyRadarPlaceholder +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnOpenReply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnReplyToggle diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesResolvedScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesResolvedScreen.kt index babf509..96bd726 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesResolvedScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/RepliesResolvedScreen.kt @@ -3,8 +3,8 @@ package com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.comp import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyRadarPlaceholder +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreenIntent.ReplyListIntent.OnOpenReply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListState diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/ReplyTimestampInfo.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/ReplyTimestampInfo.kt index 92a2553..8134f26 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/ReplyTimestampInfo.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/ReplyTimestampInfo.kt @@ -7,19 +7,16 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment.Companion.Start import androidx.compose.ui.Modifier -import com.rafaelfelipeac.replyradar.core.util.AppConstants.INITIAL_DATE -import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall -import com.rafaelfelipeac.replyradar.core.util.format import com.rafaelfelipeac.replyradar.core.datetime.formatTimestamp +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.util.AppConstants.INITIAL_DATE +import com.rafaelfelipeac.replyradar.core.util.format import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetState - @Composable -fun ColumnScope.ReplyTimestampInfo( - state: ReplyBottomSheetState -) { +fun ColumnScope.ReplyTimestampInfo(state: ReplyBottomSheetState) { state.reply?.let { reply -> Text( modifier = Modifier diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/TopBar.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/TopBar.kt index 1e35512..a5924c0 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/TopBar.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/TopBar.kt @@ -14,10 +14,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign -import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.fontSizeLarge import com.rafaelfelipeac.replyradar.core.common.ui.iconSize import com.rafaelfelipeac.replyradar.core.common.ui.paddingMedium +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.theme.toolbarIconsColor import org.jetbrains.compose.resources.painterResource import replyradar.composeapp.generated.resources.Res.drawable diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetActions.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetActions.kt index 16a28c3..616206a 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetActions.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetActions.kt @@ -49,7 +49,7 @@ fun ReplyBottomSheetActions( selectedTime: LocalTime?, reply: Reply?, name: String, - subject: String, + subject: String ) { Row( modifier = Modifier @@ -75,7 +75,11 @@ fun ReplyBottomSheetActions( LocalReplyRadarStrings.current.replyListBottomSheetSave }, onClick = { - val reminderIsValid = selectedTime != null && isDateTimeValid(selectedDate, selectedTime, getCurrentDateTime()) + val reminderIsValid = selectedTime != null && isDateTimeValid( + selectedDate, + selectedTime, + getCurrentDateTime() + ) if ((selectedDate != null || selectedTime != null) && !reminderIsValid) { onInvalidReminderValue() diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt index ab30173..865c8e9 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetContent.kt @@ -16,15 +16,15 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import com.rafaelfelipeac.replyradar.core.util.AppConstants.EMPTY -import com.rafaelfelipeac.replyradar.core.util.AppConstants.INITIAL_DATE -import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyReminder import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyTextField import com.rafaelfelipeac.replyradar.core.common.ui.components.ReplyTextFieldSize.Large import com.rafaelfelipeac.replyradar.core.common.ui.paddingMedium import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall import com.rafaelfelipeac.replyradar.core.datetime.dateTime +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings +import com.rafaelfelipeac.replyradar.core.util.AppConstants.EMPTY +import com.rafaelfelipeac.replyradar.core.util.AppConstants.INITIAL_DATE import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.ReplyTimestampInfo @@ -35,7 +35,7 @@ fun ReplyBottomSheetContent( onResolve: (Reply) -> Unit, onArchive: (Reply) -> Unit, onDelete: (Reply) -> Unit, - onInvalidReminderValue: () -> Unit, + onInvalidReminderValue: () -> Unit ) { replyBottomSheetState?.let { state -> var name by remember { mutableStateOf(state.reply?.name ?: EMPTY) } @@ -96,7 +96,7 @@ fun ReplyBottomSheetContent( selectedTime = selectedTime, reply = state.reply, name = name, - subject = subject, + subject = subject ) } } diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsScreen.kt index 93fa67c..9cc0173 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/settings/presentation/SettingsScreen.kt @@ -31,25 +31,25 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.rafaelfelipeac.replyradar.core.util.AppConstants.EMAIL -import com.rafaelfelipeac.replyradar.core.util.AppConstants.PACKAGE_NAME -import com.rafaelfelipeac.replyradar.core.language.AppLanguage -import com.rafaelfelipeac.replyradar.core.language.AppLanguage.ENGLISH -import com.rafaelfelipeac.replyradar.core.language.AppLanguage.PORTUGUESE -import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.common.ui.iconSizeLarge import com.rafaelfelipeac.replyradar.core.common.ui.paddingMedium import com.rafaelfelipeac.replyradar.core.common.ui.paddingSmall import com.rafaelfelipeac.replyradar.core.common.ui.paddingXSmall import com.rafaelfelipeac.replyradar.core.common.ui.radioButtonSize import com.rafaelfelipeac.replyradar.core.common.ui.settingsAppVersionOffset +import com.rafaelfelipeac.replyradar.core.external.openEmailApp +import com.rafaelfelipeac.replyradar.core.external.openPlayStoreApp +import com.rafaelfelipeac.replyradar.core.language.AppLanguage +import com.rafaelfelipeac.replyradar.core.language.AppLanguage.ENGLISH +import com.rafaelfelipeac.replyradar.core.language.AppLanguage.PORTUGUESE +import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme.DARK import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme.LIGHT import com.rafaelfelipeac.replyradar.core.theme.model.AppTheme.SYSTEM +import com.rafaelfelipeac.replyradar.core.util.AppConstants.EMAIL +import com.rafaelfelipeac.replyradar.core.util.AppConstants.PACKAGE_NAME import com.rafaelfelipeac.replyradar.core.version.getAppVersion -import com.rafaelfelipeac.replyradar.core.external.openEmailApp -import com.rafaelfelipeac.replyradar.core.external.openPlayStoreApp import com.rafaelfelipeac.replyradar.features.settings.presentation.SettingsIntent.OnSelectFeedback import com.rafaelfelipeac.replyradar.features.settings.presentation.SettingsIntent.OnSelectLanguage import com.rafaelfelipeac.replyradar.features.settings.presentation.SettingsIntent.OnSelectRate diff --git a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/ReplyRepositoryTest.kt b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/ReplyRepositoryTest.kt index df31aac..a0b5fd6 100644 --- a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/ReplyRepositoryTest.kt +++ b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/ReplyRepositoryTest.kt @@ -6,9 +6,9 @@ import com.rafaelfelipeac.replyradar.features.reply.data.database.entity.ReplyEn import com.rafaelfelipeac.replyradar.features.reply.data.repository.ReplyRepositoryImpl import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply import com.rafaelfelipeac.replyradar.util.valueOrEmpty -import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals +import kotlinx.coroutines.test.runTest class ReplyRepositoryTest { @@ -67,7 +67,7 @@ class ReplyRepositoryTest { val updated = dao.updatedReplies.first() assertEquals(true, updated.isResolved) assertEquals(true, updated.isArchived) - } + } @Test fun `toggleReplyArchive should toggle isArchived and set archivedAt`() = runTest { diff --git a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/ReplyListViewModelTest.kt b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/ReplyListViewModelTest.kt index 0259e2c..ddcf272 100644 --- a/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/ReplyListViewModelTest.kt +++ b/composeApp/src/commonTest/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/ReplyListViewModelTest.kt @@ -28,6 +28,10 @@ import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.Reply import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListViewModel.Companion.ERROR_GET_REPLIES import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetMode import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.components.replybottomsheet.ReplyBottomSheetState +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.drop @@ -35,10 +39,6 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertEquals @OptIn(ExperimentalCoroutinesApi::class) class ReplyListViewModelTest { @@ -122,6 +122,7 @@ class ReplyListViewModelTest { } } + // ktlint-disable max-line-length @Test fun `OnAddReply should upsert reply log action and dismiss bottom sheet with no reminder scheduled if reminderAt is INITIAL_DATE`() = runTest { viewModel.state.test { @@ -134,6 +135,7 @@ class ReplyListViewModelTest { cancelAndIgnoreRemainingEvents() } } + // ktlint-enable max-line-length @Test fun `OnAddReply should emit ScheduleReminder effect when reminderAt is valid`() = runTest { @@ -178,30 +180,32 @@ class ReplyListViewModelTest { } @Test - fun `OnToggleArchive should toggle archive log correct action and dismiss bottom sheet`() = runTest { - viewModel.state.test { - viewModel.onIntent(OnToggleArchive(sampleReply)) - - val updatedState = awaitItem() - assertEquals(null, updatedState.replyBottomSheetState) - assertEquals(sampleReply, toggleArchiveReplyUseCase.toggledReplies.first()) - assertEquals(ARCHIVE, logUserActionUseCase.loggedActions.first().first.value) - cancelAndIgnoreRemainingEvents() + fun `OnToggleArchive should toggle archive log correct action and dismiss bottom sheet`() = + runTest { + viewModel.state.test { + viewModel.onIntent(OnToggleArchive(sampleReply)) + + val updatedState = awaitItem() + assertEquals(null, updatedState.replyBottomSheetState) + assertEquals(sampleReply, toggleArchiveReplyUseCase.toggledReplies.first()) + assertEquals(ARCHIVE, logUserActionUseCase.loggedActions.first().first.value) + cancelAndIgnoreRemainingEvents() + } } - } @Test - fun `OnToggleResolve should toggle resolve log correct action and dismiss bottom sheet`() = runTest { - viewModel.state.test { - viewModel.onIntent(OnToggleResolve(sampleReply)) - - val updatedState = awaitItem() - assertEquals(null, updatedState.replyBottomSheetState) - assertEquals(sampleReply, toggleResolveReplyUseCase.toggledReplies.first()) - assertEquals(RESOLVE, logUserActionUseCase.loggedActions.first().first.value) - cancelAndIgnoreRemainingEvents() + fun `OnToggleResolve should toggle resolve log correct action and dismiss bottom sheet`() = + runTest { + viewModel.state.test { + viewModel.onIntent(OnToggleResolve(sampleReply)) + + val updatedState = awaitItem() + assertEquals(null, updatedState.replyBottomSheetState) + assertEquals(sampleReply, toggleResolveReplyUseCase.toggledReplies.first()) + assertEquals(RESOLVE, logUserActionUseCase.loggedActions.first().first.value) + cancelAndIgnoreRemainingEvents() + } } - } @Test fun `OnDismissBottomSheet should clear bottom sheet state`() = runTest { From a8e2e9dce006b984c0e1f0d11948fa74129a8dea Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Wed, 23 Jul 2025 17:19:17 -0300 Subject: [PATCH 51/59] Detekt fixes. --- .../replyradar/core/database/DatabaseFactory.kt | 1 + .../replyradar/core/strings/StringsEn.kt | 2 ++ .../replyradar/core/strings/StringsPt.kt | 2 ++ config/detekt/detekt.yml | 13 ++++++++----- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/database/DatabaseFactory.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/database/DatabaseFactory.kt index db8977a..435af02 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/database/DatabaseFactory.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/database/DatabaseFactory.kt @@ -6,6 +6,7 @@ import androidx.room.RoomDatabase import com.rafaelfelipeac.replyradar.core.database.ReplyRadarMigrations.ALL_MIGRATIONS import com.rafaelfelipeac.replyradar.core.util.AppConstants.DB_NAME +@Suppress("SpreadOperator") actual class DatabaseFactory( private val context: Context ) { diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt index 020ff26..ca64cd6 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsEn.kt @@ -1,3 +1,5 @@ +@file:Suppress("MaxLineLength") + package com.rafaelfelipeac.replyradar.core.strings import com.rafaelfelipeac.replyradar.core.version.getAppVersion diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt index 653a5a2..918cb18 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/strings/StringsPt.kt @@ -1,3 +1,5 @@ +@file:Suppress("MaxLineLength") + package com.rafaelfelipeac.replyradar.core.strings import com.rafaelfelipeac.replyradar.core.version.getAppVersion diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml index 2fc63dc..0989c9d 100644 --- a/config/detekt/detekt.yml +++ b/config/detekt/detekt.yml @@ -12,6 +12,9 @@ style: UnusedPrivateMember: ignoreAnnotated: - 'Preview' + ReturnCount: + active: true + max: 3 naming: FunctionNaming: @@ -31,7 +34,7 @@ naming: complexity: LongMethod: active: true - threshold: 120 + threshold: 150 LargeClass: active: true threshold: 600 @@ -40,12 +43,12 @@ complexity: threshold: 20 LongParameterList: active: true - constructorThreshold: 10 - functionThreshold: 10 + constructorThreshold: 15 + functionThreshold: 15 TooManyFunctions: active: true - thresholdInClasses: 15 - thresholdInFiles: 15 + thresholdInClasses: 30 + thresholdInFiles: 30 comments: UndocumentedPublicClass: From 9059fb64c46e39af2119c1315889e90c54947604 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Wed, 23 Jul 2025 17:42:29 -0300 Subject: [PATCH 52/59] Ktlint fixes. --- .../core/common/ui/components/util/FormatReminderText.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/util/FormatReminderText.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/util/FormatReminderText.kt index 362a373..e192a43 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/util/FormatReminderText.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/common/ui/components/util/FormatReminderText.kt @@ -64,7 +64,9 @@ private fun getTimePart( selectedDate: LocalDate?, defaultTime: LocalTime ) = when { - selectedTime != null -> "${selectedTime.hour.toTwoDigitString()}:${selectedTime.minute.toTwoDigitString()}" - selectedDate != null -> "${defaultTime.hour.toTwoDigitString()}:${defaultTime.minute.toTwoDigitString()}" + selectedTime != null -> + "${selectedTime.hour.toTwoDigitString()}:${selectedTime.minute.toTwoDigitString()}" + selectedDate != null -> + "${defaultTime.hour.toTwoDigitString()}:${defaultTime.minute.toTwoDigitString()}" else -> null } From 55ec8e9a6ed911b224cb3ae637e170ee621f0b7f Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Thu, 24 Jul 2025 16:05:43 -0300 Subject: [PATCH 53/59] README update. --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 475a8a2..ce1f52d 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,6 @@ This app exists for anyone who's ever said, “Oops, I meant to reply to that! With Reply Radar, your forgotten replies are finally on the radar. A gentle nudge. A little order. A friendlier inbox — on your terms. - Get it on Google Play ⚠️ **Reminders (and other cool features) aren't here yet** — but they're definitely on the radar. Stay tuned! +> ⚠️ Some features are still brewing — but they're definitely on the radar. Stay tuned! --- @@ -91,6 +91,12 @@ This is still a work-in-progress — a personal playground to explore architectu --- +## 🧠 A playground for Android, KMP, and AI-enhanced development + +Besides being a way to dive deeper into Android and Kotlin Multiplatform, Reply Radar is also a personal sandbox to explore how **AI-assisted workflows** can support development — from ideation and architecture to automation and iteration. The goal is to build better, faster and with curiosity at the center of the process. + +--- + ## 🚫 Contributing Currently a solo mission, but who knows? Contributions might be welcome in the future. From 18e008149f9835376a1b7ead9debe7918ac021ad Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Thu, 24 Jul 2025 16:13:41 -0300 Subject: [PATCH 54/59] Bump version to 1.5.0. --- CHANGELOG.md | 20 ++++++++++++++++++++ composeApp/build.gradle.kts | 4 ++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39ab2f5..1e1553f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,26 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) --- +## [v1.5.0] - 2025-07-24 + +### Added +- Reminders with custom date and time. +- Notifications for reminders, with permission handling. +- Reminder UI components (picker, chips, dialogs). +- Delete confirmation modal. +- Timestamps on replies. +- Snackbar for actions. + +### Changed +- Improved UI and navigation from notifications. + +### Fixed +- Timestamp defaults and date/time validation. + +### Refactored +- Simplified state and intent handling. +- Code cleanup and test updates. + ## [v1.4.0] - 2025-04-18 ### Added diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index f1c0160..9747b1e 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -102,8 +102,8 @@ android { applicationId = "com.rafaelfelipeac.replyradar" minSdk = libs.versions.android.minSdk.get().toInt() targetSdk = libs.versions.android.targetSdk.get().toInt() - versionCode = 14 - versionName = "1.4.0" + versionCode = 15 + versionName = "1.5.0" manifestPlaceholders["appName"] = "@string/app_name" } From aedb80cc0f13c2b009b9384ec504d7991687c343 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Thu, 24 Jul 2025 19:09:52 -0300 Subject: [PATCH 55/59] PR fixes. --- .../datetime/PlatformTimePicker.android.kt | 2 +- .../core/reminder/ReminderWorker.kt | 5 ++ .../replyradar/core/datetime/Datetime.kt | 30 +++------ .../replyradar/core/datetime/Timestamp.kt | 14 ++++ .../presentation/ActivityLogScreen.kt | 66 +++++++++++-------- .../data/repository/ReplyRepositoryImpl.kt | 3 +- .../replylist/ReplyListViewModel.kt | 19 ++++-- .../ReplyBottomSheetActions.kt | 2 +- .../domain/model/UserActionType.kt | 5 +- 9 files changed, 89 insertions(+), 57 deletions(-) diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformTimePicker.android.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformTimePicker.android.kt index 92408ff..8f6c544 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformTimePicker.android.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/PlatformTimePicker.android.kt @@ -37,7 +37,7 @@ actual fun PlatformTimePicker( if (showDialog) { val pickedTime = LocalTime(timePickerState.hour, timePickerState.minute) - val isValid = isTimeValid( + val isValid = isDateTimeValid( dateTime = getCurrentDateTime(), date = selectedDate, time = pickedTime diff --git a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderWorker.kt b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderWorker.kt index 29d0ca4..b3ec56c 100644 --- a/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderWorker.kt +++ b/composeApp/src/androidMain/kotlin/com/rafaelfelipeac/replyradar/core/reminder/ReminderWorker.kt @@ -19,6 +19,11 @@ class ReminderWorker( applicationContext.getString(notification_reminder_reply_id), INVALID_ID ) + + if (replyId == INVALID_ID) { + return Result.failure() + } + val notificationTitle = inputData.getString(applicationContext.getString(notification_reminder_title)) ?: return Result.failure() diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Datetime.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Datetime.kt index 657a247..4c1a3e3 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Datetime.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Datetime.kt @@ -31,21 +31,26 @@ fun Long.dateTime(timeZone: TimeZone = TimeZone.currentSystemDefault()): LocalDa .toLocalDateTime(timeZone) } -fun isDateTimeValid(date: LocalDate?, time: LocalTime, dateTime: LocalDateTime): Boolean { +fun isDateTimeValid(date: LocalDate?, time: LocalTime?, dateTime: LocalDateTime): Boolean { return when { - date == null -> { + date == null && time == null -> true + + date != null && time == null -> { + date >= dateTime.date + } + + date == null && time != null -> { val todayTime = LocalDateTime(dateTime.date, time) todayTime > dateTime } - date == dateTime.date -> { + date != null && time != null -> { val selectedDateTime = LocalDateTime(date, time) selectedDateTime > dateTime } - date > dateTime.date -> true else -> false } } @@ -75,20 +80,3 @@ fun getDefaultTime( return LocalTime(hour = nextHour % LOCAL_TIME_HOUR_DEFAULT, minute = LOCAL_TIME_MINUTE_DEFAULT) } - -fun isTimeValid(dateTime: LocalDateTime, date: LocalDate?, time: LocalTime): Boolean { - return when { - date == null -> { - val todayTime = LocalDateTime(dateTime.date, time) - todayTime > dateTime - } - - date == dateTime.date -> { - val selectedDateTime = LocalDateTime(date, time) - selectedDateTime > dateTime - } - - date > dateTime.date -> true - else -> false - } -} diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Timestamp.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Timestamp.kt index 5a048df..42eba41 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Timestamp.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/core/datetime/Timestamp.kt @@ -20,6 +20,20 @@ fun formatTimestamp(timestampMillis: Long): String { "${localDateTime.hour}:${localDateTime.minute.toTwoDigitString()}" } +/** + * Calculates a reminder timestamp based on the current date-time and optional selections. + * + * Logic: + * - If selectedDate is provided, use it as the final date + * - If only selectedTime is provided, use today if the time is in the future, otherwise tomorrow + * - If neither is provided, return INITIAL_DATE constant + * - Final time defaults to REMINDER_DEFAULT_HOUR:REMINDER_DEFAULT_MINUTE if not provided + * + * @param dateTime Current date-time for comparison + * @param selectedDate Optional specific date for the reminder + * @param selectedTime Optional specific time for the reminder + * @return Timestamp in milliseconds for the calculated reminder date-time + */ fun getReminderTimestamp( dateTime: LocalDateTime, selectedDate: LocalDate?, diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/activitylog/presentation/ActivityLogScreen.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/activitylog/presentation/ActivityLogScreen.kt index 436eff5..cedaa37 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/activitylog/presentation/ActivityLogScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/activitylog/presentation/ActivityLogScreen.kt @@ -43,6 +43,7 @@ import com.rafaelfelipeac.replyradar.core.common.ui.paddingMedium import com.rafaelfelipeac.replyradar.core.datetime.formatTimestamp import com.rafaelfelipeac.replyradar.core.strings.LocalReplyRadarStrings import com.rafaelfelipeac.replyradar.core.theme.horizontalDividerColor +import com.rafaelfelipeac.replyradar.core.util.AppConstants.EMPTY import com.rafaelfelipeac.replyradar.core.util.format import com.rafaelfelipeac.replyradar.features.activitylog.presentation.ActivityLogViewModel.Companion.ERROR_GET_ACTIVITY_LOG import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserAction @@ -63,6 +64,7 @@ import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActio import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Resolve import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Scheduled import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Unarchive +import com.rafaelfelipeac.replyradar.features.useractions.domain.model.UserActionType.Unknown import org.jetbrains.compose.resources.painterResource import org.koin.compose.viewmodel.koinViewModel import replyradar.composeapp.generated.resources.Res.drawable @@ -207,35 +209,37 @@ fun ActivityLogListItem(userAction: UserAction) { horizontalArrangement = spacedBy(paddingMedium) ) { with(userAction) { - Column { - Icon( - modifier = Modifier - .size(iconSize), - painter = painterResource( - getIconByActionType( - actionType = actionType, - targetType = targetType - ) - ), - tint = colorScheme.primary, - contentDescription = LocalReplyRadarStrings.current - .activityLogItemContentDescription - ) - } + if (actionType != Unknown) { + Column { + Icon( + modifier = Modifier + .size(iconSize), + painter = painterResource( + getIconByActionType( + actionType = actionType, + targetType = targetType + ) + ), + tint = colorScheme.primary, + contentDescription = LocalReplyRadarStrings.current + .activityLogItemContentDescription + ) + } - Column { - Text( - text = formatUserActionLabel( - actionType = actionType, - targetType = targetType, - targetName = targetName - ), - style = typography.bodyMedium - ) - Text( - text = formatTimestamp(createdAt), - style = typography.bodySmall - ) + Column { + Text( + text = formatUserActionLabel( + actionType = actionType, + targetType = targetType, + targetName = targetName + ), + style = typography.bodyMedium + ) + Text( + text = formatTimestamp(createdAt), + style = typography.bodySmall + ) + } } } } @@ -262,6 +266,11 @@ private fun getIconByActionType(actionType: UserActionType, targetType: UserActi Open -> drawable.ic_open Scheduled -> drawable.ic_time OpenedNotification -> drawable.ic_notification + Unknown -> { + // no-op, this is just a icon placeholder for unknown actions + + drawable.ic_add + } } Theme -> drawable.ic_theme @@ -301,6 +310,7 @@ private fun getActionVerb(actionType: UserActionType) = when (actionType) { Open -> LocalReplyRadarStrings.current.activityLogUserActionOpenVerb Scheduled -> LocalReplyRadarStrings.current.activityLogUserActionScheduledVerb OpenedNotification -> LocalReplyRadarStrings.current.activityLogUserActionOpenedNotificationVerb + Unknown -> EMPTY } @Composable diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/repository/ReplyRepositoryImpl.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/repository/ReplyRepositoryImpl.kt index 95b4538..52a7da6 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/repository/ReplyRepositoryImpl.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/data/repository/ReplyRepositoryImpl.kt @@ -2,6 +2,7 @@ package com.rafaelfelipeac.replyradar.features.reply.data.repository import com.rafaelfelipeac.replyradar.core.datetime.getCurrentTimeMillis import com.rafaelfelipeac.replyradar.core.util.AppConstants.INITIAL_DATE +import com.rafaelfelipeac.replyradar.core.util.AppConstants.INITIAL_ID import com.rafaelfelipeac.replyradar.features.reply.data.database.dao.ReplyDao import com.rafaelfelipeac.replyradar.features.reply.data.mapper.toReply import com.rafaelfelipeac.replyradar.features.reply.data.mapper.toReplyEntity @@ -18,7 +19,7 @@ class ReplyRepositoryImpl( val now = getCurrentTimeMillis() val replyEntity = reply.toReplyEntity() - val entityToSave = if (reply.id == INITIAL_DATE) { + val entityToSave = if (reply.id == INITIAL_ID) { replyEntity.copy(createdAt = now, updatedAt = now) } else { replyEntity.copy(updatedAt = now) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt index 473e4ec..0f6f5b3 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt @@ -97,7 +97,8 @@ class ReplyListViewModel( private val _effect = MutableSharedFlow() val effect = _effect - private var pendingReplyId: Long? = INVALID_ID + private var pendingReplyId: Long? = null + private var isPendingReplyHandled = false fun onIntent(intent: ReplyListScreenIntent) { when (intent) { @@ -109,9 +110,7 @@ class ReplyListViewModel( private fun handleReplyListIntent(intent: ReplyListIntent) { when (intent) { - is OnPendingReplyId -> { - pendingReplyId = intent.pendingReplyId - } + is OnPendingReplyId -> handlePendingReplyIdOnce(intent.pendingReplyId) OnAddReplyClick -> { updateState { @@ -154,6 +153,18 @@ class ReplyListViewModel( } } + private fun handlePendingReplyIdOnce(pendingReplyIdParam: Long?) { + if (!isPendingReplyHandled && pendingReplyIdParam != null) { + isPendingReplyHandled = true + pendingReplyId = pendingReplyIdParam + + checkPendingReplyId( + replies = _state.value.replies, + onTabSelection = { onTabSelected(ON_THE_RADAR_INDEX) } + ) + } + } + private fun onOpenReply(reply: Reply) { updateState { copy( diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetActions.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetActions.kt index 616206a..532eb37 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetActions.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/components/replybottomsheet/ReplyBottomSheetActions.kt @@ -75,7 +75,7 @@ fun ReplyBottomSheetActions( LocalReplyRadarStrings.current.replyListBottomSheetSave }, onClick = { - val reminderIsValid = selectedTime != null && isDateTimeValid( + val reminderIsValid = isDateTimeValid( selectedDate, selectedTime, getCurrentDateTime() diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/useractions/domain/model/UserActionType.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/useractions/domain/model/UserActionType.kt index e0122a8..4282910 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/useractions/domain/model/UserActionType.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/useractions/domain/model/UserActionType.kt @@ -11,6 +11,7 @@ sealed class UserActionType(val value: String) { data object Scheduled : UserActionType(SCHEDULED) data object OpenedNotification : UserActionType(OPENED_NOTIFICATION) data object Open : UserActionType(OPEN) + data object Unknown : UserActionType(UNKNOWN) companion object { fun fromValue(value: String): UserActionType { @@ -20,11 +21,12 @@ sealed class UserActionType(val value: String) { RESOLVE -> Resolve REOPEN -> Reopen ARCHIVE -> Archive + UNARCHIVE -> Unarchive DELETE -> Delete OPEN -> Open SCHEDULED -> Scheduled OPENED_NOTIFICATION -> OpenedNotification - else -> Unarchive + else -> Unknown } } } @@ -40,3 +42,4 @@ private const val DELETE = "DELETE" private const val OPEN = "OPEN" private const val SCHEDULED = "SCHEDULED" private const val OPENED_NOTIFICATION = "OPENED_NOTIFICATION" +private const val UNKNOWN = "UNKNOWN" From 1e8b7af5f066a377447c6be2c9856c717ceace4c Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Thu, 24 Jul 2025 19:10:32 -0300 Subject: [PATCH 56/59] Detekt fixes. --- config/detekt/detekt.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml index 0989c9d..2691937 100644 --- a/config/detekt/detekt.yml +++ b/config/detekt/detekt.yml @@ -14,7 +14,7 @@ style: - 'Preview' ReturnCount: active: true - max: 3 + max: 5 naming: FunctionNaming: From b1087e3398262cd93ccf59fea48efd3807c71a7a Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Mon, 28 Jul 2025 15:21:01 -0300 Subject: [PATCH 57/59] PR fixes. --- .../features/reply/presentation/replylist/ReplyListViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt index 0f6f5b3..afa8838 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt @@ -320,7 +320,7 @@ class ReplyListViewModel( private fun checkPendingReplyId(replies: List, onTabSelection: () -> Unit) = viewModelScope.launch { - if (pendingReplyId != INVALID_ID) { + pendingReplyId?.let { val reply = replies.find { it.id == pendingReplyId } if (reply != null) { From 916cee4bec8cfb5f1cfd0241e5195934a1caf2c1 Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Mon, 28 Jul 2025 15:24:16 -0300 Subject: [PATCH 58/59] PR fixes. --- .../features/reply/presentation/replylist/ReplyListViewModel.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt index afa8838..8898408 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt @@ -7,7 +7,6 @@ import com.rafaelfelipeac.replyradar.core.reminder.model.NotificationReminderPar import com.rafaelfelipeac.replyradar.core.util.AppConstants.ARCHIVED_INDEX import com.rafaelfelipeac.replyradar.core.util.AppConstants.INITIAL_DATE import com.rafaelfelipeac.replyradar.core.util.AppConstants.INITIAL_ID -import com.rafaelfelipeac.replyradar.core.util.AppConstants.INVALID_ID import com.rafaelfelipeac.replyradar.core.util.AppConstants.ON_THE_RADAR_INDEX import com.rafaelfelipeac.replyradar.core.util.AppConstants.RESOLVED_INDEX import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply From aa288040d4f7f19c6a0a1482e6ec81e4e490f3de Mon Sep 17 00:00:00 2001 From: Rafael Cordeiro Date: Mon, 28 Jul 2025 15:28:15 -0300 Subject: [PATCH 59/59] PR fixes. --- .../features/reply/presentation/replylist/ReplyListViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt index 8898408..0abdbc7 100644 --- a/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/rafaelfelipeac/replyradar/features/reply/presentation/replylist/ReplyListViewModel.kt @@ -320,7 +320,7 @@ class ReplyListViewModel( private fun checkPendingReplyId(replies: List, onTabSelection: () -> Unit) = viewModelScope.launch { pendingReplyId?.let { - val reply = replies.find { it.id == pendingReplyId } + val reply = replies.find { reply -> reply.id == it } if (reply != null) { onTabSelection()