From 57ab371844b95ad4d00b5de576819ecc2b0bceee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yuniel=20Acosta=20P=C3=A9rez?= Date: Thu, 23 Nov 2023 07:59:18 +0100 Subject: [PATCH 1/4] feat: created basic component for row calendar in the daily note --- .gitignore | 3 +- shared/build.gradle.kts | 6 + .../SimpleRowCalendarDataSource.kt | 61 ++++++++ .../{FavoritesTab.kt => CurrentDayTab.kt} | 20 ++- .../mindsync/app/ui/components/DailyNote.kt | 107 ++++++++++++++ .../app/ui/components/SimpleRowCalendar.kt | 138 ++++++++++++++++++ .../app/ui/screens/CurrentDayScreen.kt | 88 +++++++++++ .../mindsync/app/ui/screens/MainScreen.kt | 16 +- .../viewmodel/SimpleRowCalendarUiModel.kt | 20 +++ .../io/astrum/mindsync/app/ui/theme/Color.kt | 97 ++++++------ 10 files changed, 492 insertions(+), 64 deletions(-) create mode 100644 shared/src/commonMain/kotlin/io/astrum/mindsync/app/data/repositories/SimpleRowCalendarDataSource.kt rename shared/src/commonMain/kotlin/io/astrum/mindsync/app/navigation/{FavoritesTab.kt => CurrentDayTab.kt} (51%) create mode 100644 shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/DailyNote.kt create mode 100644 shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/SimpleRowCalendar.kt create mode 100755 shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/CurrentDayScreen.kt create mode 100644 shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/viewmodel/SimpleRowCalendarUiModel.kt diff --git a/.gitignore b/.gitignore index b2779be..7d2ee0d 100755 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,6 @@ captures/ output.json # IntelliJ -*.iml .idea/ misc.xml deploymentTargetDropDown.xml @@ -34,4 +33,4 @@ google-services.json # Android Profiling *.hprof -xcuserdata \ No newline at end of file +xcuserdata diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index 8d7c2a9..f34e18b 100755 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -73,11 +73,13 @@ kotlin { } } val androidMain by getting { + dependsOn(commonMain) dependencies { implementation(libs.ktor.client.android) } } val iosMain by getting { + dependsOn(commonMain) dependencies { implementation(libs.ktor.client.darwin) } @@ -112,3 +114,7 @@ android { targetCompatibility = JavaVersion.VERSION_17 } } +dependencies { + implementation("androidx.core:core-ktx:+") + implementation("org.jetbrains.kotlin:kotlin-stdlib:1.0.0") +} diff --git a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/data/repositories/SimpleRowCalendarDataSource.kt b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/data/repositories/SimpleRowCalendarDataSource.kt new file mode 100644 index 0000000..d4aec10 --- /dev/null +++ b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/data/repositories/SimpleRowCalendarDataSource.kt @@ -0,0 +1,61 @@ +package io.astrum.mindsync.app.data.repositories + +import io.astrum.mindsync.app.ui.screens.viewmodel.SimpleRowCalendarUiModel +import kotlinx.datetime.Clock +import kotlinx.datetime.DateTimeUnit.Companion.DAY +import kotlinx.datetime.DayOfWeek +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.minus +import kotlinx.datetime.plus +import kotlinx.datetime.todayIn + +class SimpleRowCalendarDataSource { + private val today: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault()) + + fun getData( + startDate: LocalDate = today, + lastSelectedDate: LocalDate + ): SimpleRowCalendarUiModel { + val visibleDates = getVisibleDates(startDate) + val selectedDate = getSelectedDate(visibleDates, lastSelectedDate) + return SimpleRowCalendarUiModel(selectedDate, visibleDates) + } + + private fun getSelectedDate( + visibleDates: List, + lastSelectedDate: LocalDate + ): SimpleRowCalendarUiModel.Date { + /* if the lastSelectedDate is not in the visibleDates, + then return the same position as the lastSelectedDate was before */ + val index = visibleDates.indexOfFirst { it.date == lastSelectedDate } + val selectedDate = if (index == -1) { + // the day that has the same day of the week as the lastSelectedDate + val dayOfWeek = lastSelectedDate.dayOfWeek + visibleDates.firstOrNull { it.date.dayOfWeek == dayOfWeek } ?: visibleDates.first() + } else { + visibleDates[index] + } + return selectedDate.copy(isSelected = true) + } + + private fun getVisibleDates(startDate: LocalDate): List { + val dates = mutableListOf() + // Get the current week day + val currentDayOfWeek: DayOfWeek = startDate.dayOfWeek + // Get the first day of the week + val firstDayOfWeek: LocalDate = startDate.minus(currentDayOfWeek.ordinal, DAY) + // fill the dates with the days of the week + for (i in 0.. Unit, + onNextClickListener: (LocalDate) -> Unit, + onDateClickListener: (SimpleRowCalendarUiModel.Date) -> Unit, +) { + Row { + IconButton(onClick = { onPrevClickListener(data.startDate.date) }) { + Icon( + imageVector = Icons.Filled.ChevronLeft, + contentDescription = "Previous" + ) + } + SimpleRowCalendarContent(data, onDateClickListener) + IconButton(onClick = { onNextClickListener(data.endDate.date) }) { + Icon( + imageVector = Icons.Filled.ChevronRight, + contentDescription = "Next" + ) + } + } + Divider( + color = MaterialTheme.colorScheme.primary, + modifier = Modifier + .height(0.8.dp) + .fillMaxHeight() + .fillMaxWidth() + ) +} + +@Composable +private fun SimpleRowCalendarContent( + data: SimpleRowCalendarUiModel, + onDateClickListener: (SimpleRowCalendarUiModel.Date) -> Unit, +) { + LazyRow { + items(items = data.visibleDates) { date -> + ContentSimpleRowCalendarItem(date, onDateClickListener) + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ContentSimpleRowCalendarItem( + date: SimpleRowCalendarUiModel.Date, + onDateClickListener: (SimpleRowCalendarUiModel.Date) -> Unit +) { + val isSelected = date.isSelected + val modifier: Modifier = Modifier + .width(40.dp) + .height(48.dp) + .padding(2.dp) + .clickable { + onDateClickListener(date) + } + Column( + modifier = if (isSelected) { + modifier.border( + BorderStroke(0.4.dp, MaterialTheme.colorScheme.primary), + ShapeDefaults.ExtraSmall + ).background(MaterialTheme.colorScheme.primary.copy(alpha = 0.2f)) + } else { + modifier + } + ) { + Text( + text = date.date.dayOfMonth.toString(), + modifier = Modifier.align(Alignment.CenterHorizontally), + fontSize = 18.sp, + fontWeight = if(date.isSelected) { + FontWeight.Bold + } else { + FontWeight.Normal + }, + color = if (date.isToday) { + Color.Red + } else if(date.isSelected) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.secondary + } + ) + Text( + text = date.day.substring(0, 3), + modifier = Modifier.align(Alignment.CenterHorizontally), + fontSize = 8.sp, + fontWeight = if(date.isSelected) { + FontWeight.Bold + } else { + FontWeight.Light + }, + color = if (date.isSelected) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.onSurface + } + ) + } +} diff --git a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/CurrentDayScreen.kt b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/CurrentDayScreen.kt new file mode 100755 index 0000000..fc55262 --- /dev/null +++ b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/CurrentDayScreen.kt @@ -0,0 +1,88 @@ +package io.astrum.mindsync.app.ui.screens + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +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.Alignment +import androidx.compose.ui.Modifier +import io.astrum.mindsync.app.data.repositories.SimpleRowCalendarDataSource +import io.astrum.mindsync.app.ui.components.DailyNote +import io.astrum.mindsync.app.ui.components.SimpleRowCalendar +import io.astrum.mindsync.app.ui.screens.viewmodel.SimpleRowCalendarUiModel +import kotlinx.datetime.Clock +import kotlinx.datetime.DateTimeUnit +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.minus +import kotlinx.datetime.plus +import kotlinx.datetime.todayIn +import org.jetbrains.compose.resources.ExperimentalResourceApi + +@OptIn(ExperimentalResourceApi::class) +@Composable +fun CurrentDay( + date: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault()), + modifier: Modifier = Modifier +) { +// val localization = getCurrentLocalization() + val dataSource = SimpleRowCalendarDataSource() + // we use `mutableStateOf` and `remember` inside composable function to schedules recomposition + var calendarUiModel by remember { mutableStateOf(dataSource.getData(lastSelectedDate = date)) } + + fun onDateClickListener(date: SimpleRowCalendarUiModel.Date) { + // refresh the CalendarUiModel with new data + // by changing only the `selectedDate` with the date selected by User + calendarUiModel = calendarUiModel.copy( + selectedDate = date, + visibleDates = calendarUiModel.visibleDates.map { + it.copy( + isSelected = it.date == date.date + ) + } + ) + } + onDateClickListener(calendarUiModel.selectedDate) + fun onPreviousClickListener( + startDate: LocalDate + ) { + calendarUiModel = dataSource.getData( + startDate.minus(1, DateTimeUnit.DAY), + calendarUiModel.selectedDate.date + ) + } + + fun onNextClickListener( + startDate: LocalDate + ) { + calendarUiModel = dataSource.getData( + startDate.plus(2, kotlinx.datetime.DateTimeUnit.DAY), + calendarUiModel.selectedDate.date + ) + } + + Column( + modifier = modifier.fillMaxSize(), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.CenterHorizontally + ) { + SimpleRowCalendar(calendarUiModel, + onPrevClickListener = { startDate -> + onPreviousClickListener(startDate) + }, + onNextClickListener = { startDate -> + onNextClickListener(startDate) + }, + onDateClickListener = { date -> + onDateClickListener(date) + } + ) + DailyNote(calendarUiModel) + Text(text = "This part is for the 'Created today' row") + } +} diff --git a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/MainScreen.kt b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/MainScreen.kt index ac19f4c..4394e39 100755 --- a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/MainScreen.kt +++ b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/MainScreen.kt @@ -17,7 +17,7 @@ import cafe.adriel.voyager.navigator.tab.Tab import cafe.adriel.voyager.navigator.tab.TabNavigator import cafe.adriel.voyager.transitions.SlideTransition import io.astrum.mindsync.app.feature.petupload.PetUploadScreen -import io.astrum.mindsync.app.navigation.FavoritesTab +import io.astrum.mindsync.app.navigation.CurrentDayTab import io.astrum.mindsync.app.navigation.HomeTab import io.astrum.mindsync.app.navigation.InboxTab import io.astrum.mindsync.app.navigation.PetUploadTab @@ -41,24 +41,24 @@ class MainScreen : Screen { @Composable fun MainScreenView() { - val snackbarHostState = remember { SnackbarHostState() } + val snackBarHostState = remember { SnackbarHostState() } val rootNavigator = LocalNavigator.currentOrThrow TabNavigator( - HomeTab, + CurrentDayTab, disposeNestedNavigators = false ) { _ -> val rootNavigatorRepository = setupRootNavigator(rootNavigator, LocalTabNavigator.current) - val rootSnackbarHostStateRepository = setupRootSnackbarHostState(snackbarHostState) + val rootSnackBarHostStateRepository = setupRootSnackBarHostState(snackBarHostState) Scaffold( containerColor = MaterialTheme.colorScheme.background, contentColor = MaterialTheme.colorScheme.onBackground, contentWindowInsets = WindowInsets(0, 0, 0, 0), - snackbarHost = { SnackbarHost(rootSnackbarHostStateRepository.snackbarHostState) }, + snackbarHost = { SnackbarHost(rootSnackBarHostStateRepository.snackbarHostState) }, bottomBar = { ApplicationNavigationBar { TabNavigationItem(HomeTab, rootNavigatorRepository) - TabNavigationItem(FavoritesTab, rootNavigatorRepository) + TabNavigationItem(CurrentDayTab, rootNavigatorRepository) TabNavigationItem(PetUploadTab, rootNavigatorRepository) TabNavigationItem(InboxTab, rootNavigatorRepository) TabNavigationItem(ProfileTab, rootNavigatorRepository) @@ -77,9 +77,9 @@ fun setupRootNavigator(rootNavigator: Navigator, tabNavigator: TabNavigator): Ro return koin.get(null, parameters = { ParametersHolder(listOf(rootNavigator, tabNavigator).toMutableList(), false) }) } -fun setupRootSnackbarHostState(snackbarHostState: SnackbarHostState): RootSnackbarHostStateRepository { +fun setupRootSnackBarHostState(snackBarHostState: SnackbarHostState): RootSnackbarHostStateRepository { val koin = KoinPlatform.getKoin() - return koin.get(null, parameters = { ParametersHolder(listOf(snackbarHostState).toMutableList(), false) }) + return koin.get(null, parameters = { ParametersHolder(listOf(snackBarHostState).toMutableList(), false) }) } @Composable diff --git a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/viewmodel/SimpleRowCalendarUiModel.kt b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/viewmodel/SimpleRowCalendarUiModel.kt new file mode 100644 index 0000000..cb456e8 --- /dev/null +++ b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/viewmodel/SimpleRowCalendarUiModel.kt @@ -0,0 +1,20 @@ +package io.astrum.mindsync.app.ui.screens.viewmodel + +import kotlinx.datetime.LocalDate + +data class SimpleRowCalendarUiModel( + val selectedDate: Date, // the date selected by the User. by default is Today. + val visibleDates: List // the dates shown on the screen +) { + + val startDate: Date = visibleDates.first() // the first of the visible dates + val endDate: Date = visibleDates.last() // the last of the visible dates + + data class Date( + val date: LocalDate, + val isSelected: Boolean, + val isToday: Boolean + ) { + val day: String = date.dayOfWeek.name // get the day by accessing the dayOfWeek property + } +} diff --git a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/theme/Color.kt b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/theme/Color.kt index b6011e4..087098e 100755 --- a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/theme/Color.kt +++ b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/theme/Color.kt @@ -2,59 +2,60 @@ package io.astrum.mindsync.app.ui.theme import androidx.compose.ui.graphics.Color -val lightPrimary = Color(0xFF825500) +val lightPrimary = Color(0xFF6750A4) val lightOnPrimary = Color(0xFFFFFFFF) -val lightPrimaryContainer = Color(0xFFFFDDAE) -val lightOnPrimaryContainer = Color(0xFF2A1800) -val lightSecondary = Color(0xFF6F5B40) +val lightPrimaryContainer = Color(0xFFEADDFF) +val lightOnPrimaryContainer = Color(0xFF21005D) +val lightSecondary = Color(0xFF625B71) val lightOnSecondary = Color(0xFFFFFFFF) -val lightSecondaryContainer = Color(0xFFFADEBC) -val lightOnSecondaryContainer = Color(0xFF271904) -val lightTertiary = Color(0xFF516440) +val lightSecondaryContainer = Color(0xFFE8DEF8) +val lightOnSecondaryContainer = Color(0xFF1D192B) +val lightTertiary = Color(0xFF7D5260) val lightOnTertiary = Color(0xFFFFFFFF) -val lightTertiaryContainer = Color(0xFFD3EABC) -val lightOnTertiaryContainer = Color(0xFF102004) -val lightError = Color(0xFFBA1B1B) -val lightErrorContainer = Color(0xFFFFDAD4) +val lightTertiaryContainer = Color(0xFFFFD8E4) +val lightOnTertiaryContainer = Color(0xFF31111D) +val lightError = Color(0xFFB3261E) +val lightErrorContainer = Color(0xFFF9DEDC) val lightOnError = Color(0xFFFFFFFF) -val lightOnErrorContainer = Color(0xFF410001) -val lightBackground = Color(0xFFFCFCFC) -val lightOnBackground = Color(0xFF1F1B16) -val lightSurface = Color(0xFFFCFCFC) -val lightOnSurface = Color(0xFF1F1B16) -val lightSurfaceVariant = Color(0xFFF0E0CF) -val lightOnSurfaceVariant = Color(0xFF4F4539) -val lightOutline = Color(0xFF817567) -val lightInverseOnSurface = Color(0xFFF9EFE6) -val lightInverseSurface = Color(0xFF34302A) -val lightPrimaryInverse = Color(0xFFFFB945) +val lightOnErrorContainer = Color(0xFF410E0B) +val lightBackground = Color(0xFFFFFBFE) +val lightOnBackground = Color(0xFF1C1B1F) +val lightSurface = Color(0xFFFFFBFE) +val lightOnSurface = Color(0xFF1C1B1F) +val lightSurfaceVariant = Color(0xFFE7E0EC) +val lightOnSurfaceVariant = Color(0xFF49454F) +val lightOutline = Color(0xFF79747E) +val lightInverseOnSurface = Color(0xFFF4EFF4) +val lightInverseSurface = Color(0xFF313033) +val lightPrimaryInverse = Color(0xFFD0BCFF) + +val darkPrimary = Color(0xFFD0BCFF) +val darkOnPrimary = Color(0xFF381E72) +val darkPrimaryContainer = Color(0xFF4F378B) +val darkOnPrimaryContainer = Color(0xFFEADDFF) +val darkSecondary = Color(0xFFCCC2DC) +val darkOnSecondary = Color(0xFF332D41) +val darkSecondaryContainer = Color(0xFF4A4458) +val darkOnSecondaryContainer = Color(0xFFE8DEF8) +val darkTertiary = Color(0xFFEFB8C8) +val darkOnTertiary = Color(0xFF492532) +val darkTertiaryContainer = Color(0xFF633B48) +val darkOnTertiaryContainer = Color(0xFFFFD8E4) +val darkError = Color(0xFFF2B8B5) +val darkErrorContainer = Color(0xFF8C1D18) +val darkOnError = Color(0xFF601410) +val darkOnErrorContainer = Color(0xFFF9DEDC) +val darkBackground = Color(0xFF1C1B1F) +val darkOnBackground = Color(0xFFE6E1E5) +val darkSurface = Color(0xFF1C1B1F) +val darkOnSurface = Color(0xFFE6E1E5) +val darkSurfaceVariant = Color(0xFF49454F) +val darkOnSurfaceVariant = Color(0xFFCAC4D0) +val darkOutline = Color(0xFF938F99) +val darkInverseOnSurface = Color(0xFF313033) +val darkInverseSurface = Color(0xFFE6E1E5) +val darkPrimaryInverse = Color(0xFF6750A4) -val darkPrimary = Color(0xFFFFB945) -val darkOnPrimary = Color(0xFF452B00) -val darkPrimaryContainer = Color(0xFF624000) -val darkOnPrimaryContainer = Color(0xFFFFDDAE) -val darkSecondary = Color(0xFFDDC3A2) -val darkOnSecondary = Color(0xFF3E2E16) -val darkSecondaryContainer = Color(0xFF56442B) -val darkOnSecondaryContainer = Color(0xFFFADEBC) -val darkTertiary = Color(0xFFB8CEA2) -val darkOnTertiary = Color(0xFF243516) -val darkTertiaryContainer = Color(0xFF3A4C2B) -val darkOnTertiaryContainer = Color(0xFFD3EABC) -val darkError = Color(0xFFFFB4A9) -val darkErrorContainer = Color(0xFF930006) -val darkOnError = Color(0xFF680003) -val darkOnErrorContainer = Color(0xFFFFDAD4) -val darkBackground = Color(0xFF1F1B16) -val darkOnBackground = Color(0xFFEAE1D9) -val darkSurface = Color(0xFF1F1B16) -val darkOnSurface = Color(0xFFEAE1D9) -val darkSurfaceVariant = Color(0xFF4F4539) -val darkOnSurfaceVariant = Color(0xFFD3C4B4) -val darkOutline = Color(0xFF9C8F80) -val darkInverseOnSurface = Color(0xFF32281A) -val darkInverseSurface = Color(0xFFEAE1D9) -val darkPrimaryInverse = Color(0xFF624000) val successContainer = Color(0xFF017943) val errorContainer = Color(0xFFB21229) From d0f071f50d5b0cd9db615015908c391fc6414caf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yuniel=20Acosta=20P=C3=A9rez?= Date: Fri, 24 Nov 2023 11:40:07 +0100 Subject: [PATCH 2/4] feat: created basic editor toolbar component --- apps/iosApp/iosApp.xcodeproj/project.pbxproj | 10 +- .../xcshareddata/xcschemes/iosApp.xcscheme | 6 +- gradle/libs.versions.toml | 4 +- .../mindsync/app/ui/components/DailyNote.kt | 8 +- .../app/ui/components/DailyNoteEditor.kt | 16 +++ .../app/ui/components/EditorToolbar.kt | 121 ++++++++++++++++++ .../app/ui/components/RichTextEditor.kt | 84 ++++++++++++ .../app/ui/components/SimpleRowCalendar.kt | 4 +- .../viewmodel/TextFormatOptionUiModel.kt | 29 +++++ .../kotlin/io/astrum/mindsync/app/main.ios.kt | 8 +- 10 files changed, 273 insertions(+), 17 deletions(-) create mode 100644 shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/DailyNoteEditor.kt create mode 100644 shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/EditorToolbar.kt create mode 100644 shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/RichTextEditor.kt create mode 100644 shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/viewmodel/TextFormatOptionUiModel.kt diff --git a/apps/iosApp/iosApp.xcodeproj/project.pbxproj b/apps/iosApp/iosApp.xcodeproj/project.pbxproj index bd79aec..e3d21a1 100755 --- a/apps/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/apps/iosApp/iosApp.xcodeproj/project.pbxproj @@ -323,6 +323,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; + DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -331,17 +332,19 @@ ); INFOPLIST_FILE = iosApp/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = MindSync; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); + MARKETING_VERSION = 0.0.1; OTHER_LDFLAGS = ( "$(inherited)", "-framework", shared, ); PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.iosApp; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = "MindSync (Debug)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -354,6 +357,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; + DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -362,17 +366,19 @@ ); INFOPLIST_FILE = iosApp/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = MindSync; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); + MARKETING_VERSION = 0.0.1; OTHER_LDFLAGS = ( "$(inherited)", "-framework", shared, ); PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.iosApp; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = "MindSync"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; diff --git a/apps/iosApp/iosApp.xcodeproj/xcshareddata/xcschemes/iosApp.xcscheme b/apps/iosApp/iosApp.xcodeproj/xcshareddata/xcschemes/iosApp.xcscheme index f2368c9..a229b5e 100755 --- a/apps/iosApp/iosApp.xcodeproj/xcshareddata/xcschemes/iosApp.xcscheme +++ b/apps/iosApp/iosApp.xcodeproj/xcshareddata/xcschemes/iosApp.xcscheme @@ -15,7 +15,7 @@ @@ -44,7 +44,7 @@ @@ -61,7 +61,7 @@ diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 17e12bc..f5322a2 100755 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,6 +3,7 @@ kotlin = "1.9.20" ktor-client = "2.3.6" ktor-server = "2.3.6" multiplatform-settings = "1.1.0" +uiToolingPreviewAndroid = "1.5.4" voyager = "1.0.0-rc10" koin = "3.5.0" koin-compose = "1.1.0" @@ -20,7 +21,7 @@ androidx-junit = "1.1.5" espressoCore = "3.5.1" accompanistSystemuicontroller = "0.32.0" activityCompose = "1.8.1" -firebase = "32.5.0" +firebase = "32.6.0" owasp = "8.4.3" asciidoctor = "4.0.0-alpha.1" dokka = "1.9.10" @@ -28,6 +29,7 @@ dokka = "1.9.10" [libraries] # Gradle Plugins +androidx-ui-tooling-preview-android = { module = "androidx.compose.ui:ui-tooling-preview-android", version.ref = "uiToolingPreviewAndroid" } gradle-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } gradle-ktlint = { module = "org.jlleitschuh.gradle.ktlint:org.jlleitschuh.gradle.ktlint.gradle.plugin", version.ref = "klint-plugin" } gradle-ktlint-idea = { module = "org.jlleitschuh.gradle.ktlint-idea:org.jlleitschuh.gradle.ktlint-idea.gradle.plugin", version.ref = "klint-plugin" } diff --git a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/DailyNote.kt b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/DailyNote.kt index 6c1a3ad..eb5da49 100644 --- a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/DailyNote.kt +++ b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/DailyNote.kt @@ -93,13 +93,7 @@ fun DailyNote(calendarUiModel: SimpleRowCalendarUiModel) { Text(text = "Page") } } - Text( - text = """ - day: ${calendarUiModel.selectedDate.day} - isToday: ${calendarUiModel.selectedDate.isToday} - isSelected: ${calendarUiModel.selectedDate.isSelected} - """.trimIndent() - ) + DailyNoteEditor(calendarUiModel) } } diff --git a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/DailyNoteEditor.kt b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/DailyNoteEditor.kt new file mode 100644 index 0000000..244c4f7 --- /dev/null +++ b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/DailyNoteEditor.kt @@ -0,0 +1,16 @@ +package io.astrum.mindsync.app.ui.components + +import androidx.compose.runtime.Composable +import io.astrum.mindsync.app.ui.screens.viewmodel.SimpleRowCalendarUiModel + +@Composable +fun DailyNoteEditor(calendarUiModel: SimpleRowCalendarUiModel) { +// Text( +// text = """ +// day: ${calendarUiModel.selectedDate.day} +// isToday: ${calendarUiModel.selectedDate.isToday} +// isSelected: ${calendarUiModel.selectedDate.isSelected} +// """.trimIndent() +// ) + RichTextEditor() +} diff --git a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/EditorToolbar.kt b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/EditorToolbar.kt new file mode 100644 index 0000000..9f8b821 --- /dev/null +++ b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/EditorToolbar.kt @@ -0,0 +1,121 @@ +package io.astrum.mindsync.app.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.FormatBold +import androidx.compose.material.icons.filled.FormatItalic +import androidx.compose.material.icons.filled.FormatUnderlined +import androidx.compose.material.icons.filled.KeyboardHide +import androidx.compose.material3.Divider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.SoftwareKeyboardController +import androidx.compose.ui.unit.dp +import io.astrum.mindsync.app.ui.screens.viewmodel.TextFormatOptionUiModel + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun EditorToolbar( + textFormatOptionUiModel: TextFormatOptionUiModel, + keyboardController: SoftwareKeyboardController?, + onBoldClick: (TextFormatOptionUiModel) -> Unit, + onItalicClick: (TextFormatOptionUiModel) -> Unit, + onUnderlinedClick: (TextFormatOptionUiModel) -> Unit +) { + Divider( + color = MaterialTheme.colorScheme.primary, + modifier = Modifier + .height(0.8.dp) + .fillMaxHeight() + .fillMaxWidth() + ) + Row( + modifier = Modifier + .background(MaterialTheme.colorScheme.surface) + .fillMaxWidth() + .height(38.dp) + .padding(1.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Row { + IconButton( + onClick = {} + ) { + Icon( + imageVector = Icons.Filled.Add, + contentDescription = "Insert Block", + ) + } + EditorToolbarButton( + Icons.Filled.FormatBold, + "Format Bold", + textFormatOptionUiModel.isBold + ){ + onBoldClick(textFormatOptionUiModel) + } + EditorToolbarButton( + Icons.Filled.FormatItalic, + "Format Italic", + textFormatOptionUiModel.isItalic + ) { + onItalicClick(textFormatOptionUiModel) + } + EditorToolbarButton( + Icons.Filled.FormatUnderlined, + "Format Underline", + textFormatOptionUiModel.isUnderlined + ) { + onUnderlinedClick(textFormatOptionUiModel) + } + // Add other formatting options as needed + } + Row { + Divider( + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f), + modifier = Modifier + .fillMaxHeight() + .width(0.5.dp).padding(vertical = 5.dp) + ) + IconButton( + onClick = { + keyboardController?.hide() + } + ) { + Icon( + imageVector = Icons.Filled.KeyboardHide, + contentDescription = "Hide Keyboard", + ) + } + } + } +} + +@Composable +fun EditorToolbarButton( + imageVector: ImageVector, + contentDescription: String, + isSelected: Boolean, + onClick: () -> Unit +) { + IconButton( + onClick = onClick + ) { + Icon( + imageVector = imageVector, + contentDescription = contentDescription + ) + } +} diff --git a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/RichTextEditor.kt b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/RichTextEditor.kt new file mode 100644 index 0000000..aa2bd8d --- /dev/null +++ b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/RichTextEditor.kt @@ -0,0 +1,84 @@ +package io.astrum.mindsync.app.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialTheme +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.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import io.astrum.mindsync.app.ui.screens.viewmodel.TextFormatOptionUiModel + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun RichTextEditor() { + var textState by remember { mutableStateOf("Escribe aquí...") } + var textFormatOptionUiModel by remember { mutableStateOf(TextFormatOptionUiModel.Default) } + + val keyboardController = LocalSoftwareKeyboardController.current + + Column { + // Text Editor + BasicTextField( + value = textState, + onValueChange = { textState = it }, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Text, + imeAction = ImeAction.Done + ), + keyboardActions = KeyboardActions( + onDone = { + keyboardController?.hide() + } + ), + textStyle = LocalTextStyle.current.copy( + fontSize = 18.sp, + color = Color.Black, + fontWeight = if (textFormatOptionUiModel.isBold) FontWeight.Bold else null, + fontStyle = if (textFormatOptionUiModel.isItalic) FontStyle.Italic else null, + textDecoration = if (textFormatOptionUiModel.isUnderlined) TextDecoration.LineThrough else null + ), + modifier = Modifier + .padding(16.dp) + .fillMaxWidth() + .background(Color.White) + .clickable { } + .clip(MaterialTheme.shapes.small) + .shadow(1.dp) + ) + + EditorToolbar(textFormatOptionUiModel, keyboardController, + onBoldClick = { + textFormatOptionUiModel = textFormatOptionUiModel.copy(isBold = !it.isBold) + }, + onItalicClick = { + textFormatOptionUiModel = textFormatOptionUiModel.copy(isItalic = !it.isItalic) + }, + onUnderlinedClick = { + textFormatOptionUiModel = + textFormatOptionUiModel.copy(isUnderlined = !it.isUnderlined) + } + ) + } +} diff --git a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/SimpleRowCalendar.kt b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/SimpleRowCalendar.kt index 05ac2af..a3eba0c 100644 --- a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/SimpleRowCalendar.kt +++ b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/SimpleRowCalendar.kt @@ -88,7 +88,7 @@ fun ContentSimpleRowCalendarItem( val modifier: Modifier = Modifier .width(40.dp) .height(48.dp) - .padding(2.dp) + .padding(1.dp) .clickable { onDateClickListener(date) } @@ -105,7 +105,7 @@ fun ContentSimpleRowCalendarItem( Text( text = date.date.dayOfMonth.toString(), modifier = Modifier.align(Alignment.CenterHorizontally), - fontSize = 18.sp, + fontSize = 14.sp, fontWeight = if(date.isSelected) { FontWeight.Bold } else { diff --git a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/viewmodel/TextFormatOptionUiModel.kt b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/viewmodel/TextFormatOptionUiModel.kt new file mode 100644 index 0000000..254f6b8 --- /dev/null +++ b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/viewmodel/TextFormatOptionUiModel.kt @@ -0,0 +1,29 @@ +package io.astrum.mindsync.app.ui.screens.viewmodel + +data class TextFormatOptionUiModel( + val isBold: Boolean, + val isItalic: Boolean, + val isUnderlined: Boolean, + val isStrikethrough: Boolean, + val isBullet: Boolean, + val isNumbered: Boolean, + val isQuote: Boolean, + val isLink: Boolean, + val isCode: Boolean, + val isClear: Boolean +) { + companion object { + val Default = TextFormatOptionUiModel( + isBold = false, + isItalic = false, + isUnderlined = false, + isStrikethrough = false, + isBullet = false, + isNumbered = false, + isQuote = false, + isLink = false, + isCode = false, + isClear = false + ) + } +} diff --git a/shared/src/iosMain/kotlin/io/astrum/mindsync/app/main.ios.kt b/shared/src/iosMain/kotlin/io/astrum/mindsync/app/main.ios.kt index 9fa6860..6181dcc 100755 --- a/shared/src/iosMain/kotlin/io/astrum/mindsync/app/main.ios.kt +++ b/shared/src/iosMain/kotlin/io/astrum/mindsync/app/main.ios.kt @@ -3,11 +3,15 @@ package io.astrum.mindsync.app +import androidx.compose.ui.uikit.OnFocusBehavior import androidx.compose.ui.window.ComposeUIViewController -import io.astrum.mindsync.app.MainApp import platform.UIKit.UIViewController -fun homeScreenViewController(): UIViewController = ComposeUIViewController { +fun homeScreenViewController(): UIViewController = ComposeUIViewController( + configure = { + onFocusBehavior = OnFocusBehavior.DoNothing + } +) { MainApp() } From bee30e72cec613355397b86e3a3747fdc5463931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yuniel=20Acosta=20P=C3=A9rez?= Date: Wed, 29 Nov 2023 08:00:29 +0100 Subject: [PATCH 3/4] feat: created basic editor toolbar component sticky over keyboard --- apps/androidApp/build.gradle.kts | 4 +- apps/androidApp/src/main/AndroidManifest.xml | 1 + .../mindsync/app/android/MainActivity.kt | 3 + gradle/libs.versions.toml | 11 +- shared/build.gradle.kts | 5 +- .../mindsync/app/navigation/CurrentDayTab.kt | 11 +- .../astrum/mindsync/app/platform/Resources.kt | 1 + .../app/ui/components/DailyNoteEditor.kt | 73 +++- .../app/ui/components/EditorToolbar.kt | 1 + .../app/ui/components/RichTextEditor.kt | 84 ----- .../app/ui/components/SimpleRowCalendar.kt | 7 +- .../app/ui/screens/CurrentDayScreen.kt | 325 +++++++++++++++--- .../app/ui/screens/PetDetailScreen.kt | 3 +- .../kotlin/io/astrum/mindsync/app/main.ios.kt | 7 +- 14 files changed, 373 insertions(+), 163 deletions(-) delete mode 100644 shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/RichTextEditor.kt diff --git a/apps/androidApp/build.gradle.kts b/apps/androidApp/build.gradle.kts index 5a78ce2..c826d0f 100755 --- a/apps/androidApp/build.gradle.kts +++ b/apps/androidApp/build.gradle.kts @@ -46,7 +46,9 @@ android { dependencies { implementation(project(":shared")) - implementation(platform(libs.compose.bom)) + val composeBom = platform(libs.androidx.compose.bom) + implementation(composeBom) + androidTestImplementation(composeBom) implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.splashscreen) diff --git a/apps/androidApp/src/main/AndroidManifest.xml b/apps/androidApp/src/main/AndroidManifest.xml index 3cf92e4..f5d1472 100755 --- a/apps/androidApp/src/main/AndroidManifest.xml +++ b/apps/androidApp/src/main/AndroidManifest.xml @@ -15,6 +15,7 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.MindSync" + android:windowSoftInputMode="adjustResize" tools:targetApi="31" android:usesCleartextTraffic="true"> diff --git a/apps/androidApp/src/main/kotlin/io/astrum/mindsync/app/android/MainActivity.kt b/apps/androidApp/src/main/kotlin/io/astrum/mindsync/app/android/MainActivity.kt index ee6a63b..6d8836a 100755 --- a/apps/androidApp/src/main/kotlin/io/astrum/mindsync/app/android/MainActivity.kt +++ b/apps/androidApp/src/main/kotlin/io/astrum/mindsync/app/android/MainActivity.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.core.view.WindowCompat import com.google.accompanist.systemuicontroller.rememberSystemUiController import io.astrum.mindsync.app.MainApp import io.astrum.mindsync.app.common.model.theme.DarkThemeConfig @@ -17,6 +18,8 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + WindowCompat.setDecorFitsSystemWindows(window, false) + val uiState: MainActivityUiState by mutableStateOf(MainActivityUiState.Loading) setContent { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f5322a2..513cf2c 100755 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,10 +9,10 @@ koin = "3.5.0" koin-compose = "1.1.0" junit = "4.13.2" androidGradlePlugin = "8.1.4" -composeMultiplatform = "1.5.10" +composeMultiplatform = "1.5.11" compose-compiler = "1.5.4" exposed = "0.37.3" -skiko = "0.7.85" +skiko = "0.7.85.4" klint-plugin = "11.6.1" klint = "1.0.1" detekt = "1.23.3" @@ -25,6 +25,7 @@ firebase = "32.6.0" owasp = "8.4.3" asciidoctor = "4.0.0-alpha.1" dokka = "1.9.10" +androidxComposeBom = "2023.10.01" [libraries] @@ -74,8 +75,12 @@ voyager-koin = { module = "cafe.adriel.voyager:voyager-koin", version.ref = "voy koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } koin-test = { module = "io.insert-koin:koin-test", version.ref = "koin" } +# External Libs +rich-editor = { module = "com.mohamedrejeb.richeditor:richeditor-compose", version = "1.0.0-beta03"} + # Android -compose-bom = "androidx.compose:compose-bom:2023.10.01" +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "androidxComposeBom" } +androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation" } koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" } androidx-core-ktx = "androidx.core:core-ktx:1.12.0" diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index f34e18b..0f2369b 100755 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -7,7 +7,7 @@ plugins { @OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class) kotlin { - targetHierarchy.default() + kotlin.applyDefaultHierarchyTemplate() androidTarget { compilations.all { @@ -70,12 +70,15 @@ kotlin { // Logging implementation(libs.kermit) + + implementation(libs.rich.editor) } } val androidMain by getting { dependsOn(commonMain) dependencies { implementation(libs.ktor.client.android) + implementation(libs.androidx.core.ktx) } } val iosMain by getting { diff --git a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/navigation/CurrentDayTab.kt b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/navigation/CurrentDayTab.kt index 9a39f0d..8b64455 100755 --- a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/navigation/CurrentDayTab.kt +++ b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/navigation/CurrentDayTab.kt @@ -3,10 +3,12 @@ package io.astrum.mindsync.app.navigation import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.graphics.vector.rememberVectorPainter +import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.tab.Tab import cafe.adriel.voyager.navigator.tab.TabOptions +import cafe.adriel.voyager.transitions.ScaleTransition import io.astrum.mindsync.app.ui.icon.ApplicationIcons -import io.astrum.mindsync.app.ui.screens.CurrentDay +import io.astrum.mindsync.app.ui.screens.CurrentDayScreen import kotlinx.datetime.Clock import kotlinx.datetime.LocalDateTime import kotlinx.datetime.TimeZone @@ -35,6 +37,11 @@ internal object CurrentDayTab : Tab { @Composable override fun Content() { - CurrentDay(localDateTime.date) +// Navigator(CurrentDayScreen(localDateTime.date)) { +// ScaleTransition(it) +// } + Navigator(CurrentDayScreen()) { + ScaleTransition(it) + } } } diff --git a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/platform/Resources.kt b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/platform/Resources.kt index ed80cc2..3d8a615 100755 --- a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/platform/Resources.kt +++ b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/platform/Resources.kt @@ -17,5 +17,6 @@ object Resources { const val mkLogo = "drawable/mklogo.xml" const val features = "drawable/features_overview_cuate.xml" const val productQuality = "drawable/product_quality_amico.xml" + const val matchCase = "drawable/match_case.xml" } } diff --git a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/DailyNoteEditor.kt b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/DailyNoteEditor.kt index 244c4f7..b562f65 100644 --- a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/DailyNoteEditor.kt +++ b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/DailyNoteEditor.kt @@ -1,16 +1,73 @@ package io.astrum.mindsync.app.ui.components +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.DragIndicator +import androidx.compose.material3.Checkbox +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import com.mohamedrejeb.richeditor.model.rememberRichTextState +import com.mohamedrejeb.richeditor.ui.material3.RichTextEditor +import com.mohamedrejeb.richeditor.ui.material3.RichTextEditorDefaults import io.astrum.mindsync.app.ui.screens.viewmodel.SimpleRowCalendarUiModel +@OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class) @Composable fun DailyNoteEditor(calendarUiModel: SimpleRowCalendarUiModel) { -// Text( -// text = """ -// day: ${calendarUiModel.selectedDate.day} -// isToday: ${calendarUiModel.selectedDate.isToday} -// isSelected: ${calendarUiModel.selectedDate.isSelected} -// """.trimIndent() -// ) - RichTextEditor() + val state = rememberRichTextState() + val keyboardController = LocalSoftwareKeyboardController.current + val inputModifier = Modifier + .fillMaxWidth() + val richTextEditorColors = RichTextEditorDefaults.richTextEditorColors( + containerColor = MaterialTheme.colorScheme.surface, + // test to fine the best color + cursorColor = MaterialTheme.colorScheme.primary, + focusedIndicatorColor = MaterialTheme.colorScheme.surface, + unfocusedIndicatorColor = MaterialTheme.colorScheme.surface, + ) + RichTextEditor( + state = state, + modifier = inputModifier, + singleLine = true, + colors = richTextEditorColors, + leadingIcon = { + Row( + // align the icons to the start without space between them + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Filled.Add, + contentDescription = "Insert Block", + ) + Icon( + imageVector = Icons.Filled.DragIndicator, + contentDescription = "Drag Block" + ) + } + } + ) + + Row( + modifier = Modifier + .fillMaxWidth(), + ) { + Checkbox( + checked = true, + onCheckedChange = { } + ) + Text( + text = "task number 1" + ) + } } diff --git a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/EditorToolbar.kt b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/EditorToolbar.kt index 9f8b821..05b8934 100644 --- a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/EditorToolbar.kt +++ b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/EditorToolbar.kt @@ -18,6 +18,7 @@ import androidx.compose.material3.Divider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier diff --git a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/RichTextEditor.kt b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/RichTextEditor.kt deleted file mode 100644 index aa2bd8d..0000000 --- a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/RichTextEditor.kt +++ /dev/null @@ -1,84 +0,0 @@ -package io.astrum.mindsync.app.ui.components - -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.text.BasicTextField -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.LocalTextStyle -import androidx.compose.material3.MaterialTheme -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.ExperimentalComposeUiApi -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.shadow -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.text.font.FontStyle -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.style.TextDecoration -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import io.astrum.mindsync.app.ui.screens.viewmodel.TextFormatOptionUiModel - -@OptIn(ExperimentalComposeUiApi::class) -@Composable -fun RichTextEditor() { - var textState by remember { mutableStateOf("Escribe aquí...") } - var textFormatOptionUiModel by remember { mutableStateOf(TextFormatOptionUiModel.Default) } - - val keyboardController = LocalSoftwareKeyboardController.current - - Column { - // Text Editor - BasicTextField( - value = textState, - onValueChange = { textState = it }, - keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Text, - imeAction = ImeAction.Done - ), - keyboardActions = KeyboardActions( - onDone = { - keyboardController?.hide() - } - ), - textStyle = LocalTextStyle.current.copy( - fontSize = 18.sp, - color = Color.Black, - fontWeight = if (textFormatOptionUiModel.isBold) FontWeight.Bold else null, - fontStyle = if (textFormatOptionUiModel.isItalic) FontStyle.Italic else null, - textDecoration = if (textFormatOptionUiModel.isUnderlined) TextDecoration.LineThrough else null - ), - modifier = Modifier - .padding(16.dp) - .fillMaxWidth() - .background(Color.White) - .clickable { } - .clip(MaterialTheme.shapes.small) - .shadow(1.dp) - ) - - EditorToolbar(textFormatOptionUiModel, keyboardController, - onBoldClick = { - textFormatOptionUiModel = textFormatOptionUiModel.copy(isBold = !it.isBold) - }, - onItalicClick = { - textFormatOptionUiModel = textFormatOptionUiModel.copy(isItalic = !it.isItalic) - }, - onUnderlinedClick = { - textFormatOptionUiModel = - textFormatOptionUiModel.copy(isUnderlined = !it.isUnderlined) - } - ) - } -} diff --git a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/SimpleRowCalendar.kt b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/SimpleRowCalendar.kt index a3eba0c..63afc70 100644 --- a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/SimpleRowCalendar.kt +++ b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/SimpleRowCalendar.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons @@ -23,6 +24,7 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ShapeDefaults import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -34,6 +36,7 @@ import io.astrum.mindsync.app.ui.screens.viewmodel.SimpleRowCalendarUiModel import kotlinx.datetime.LocalDate +@OptIn(ExperimentalMaterial3Api::class) @Composable fun SimpleRowCalendar( data: SimpleRowCalendarUiModel, @@ -42,7 +45,9 @@ fun SimpleRowCalendar( onNextClickListener: (LocalDate) -> Unit, onDateClickListener: (SimpleRowCalendarUiModel.Date) -> Unit, ) { - Row { + Row(modifier = Modifier.windowInsetsPadding( + TopAppBarDefaults.windowInsets + )) { IconButton(onClick = { onPrevClickListener(data.startDate.date) }) { Icon( imageVector = Icons.Filled.ChevronLeft, diff --git a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/CurrentDayScreen.kt b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/CurrentDayScreen.kt index fc55262..db30ec7 100755 --- a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/CurrentDayScreen.kt +++ b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/CurrentDayScreen.kt @@ -1,20 +1,47 @@ package io.astrum.mindsync.app.ui.screens +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.ime +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Divider +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold 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.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.platform.SoftwareKeyboardController +import androidx.compose.ui.unit.dp +import cafe.adriel.voyager.core.screen.Screen import io.astrum.mindsync.app.data.repositories.SimpleRowCalendarDataSource import io.astrum.mindsync.app.ui.components.DailyNote import io.astrum.mindsync.app.ui.components.SimpleRowCalendar +import io.astrum.mindsync.app.ui.icon.ApplicationIcons import io.astrum.mindsync.app.ui.screens.viewmodel.SimpleRowCalendarUiModel +import io.astrum.mindsync.app.ui.theme.ApplicationTheme import kotlinx.datetime.Clock import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.LocalDate @@ -22,67 +49,255 @@ import kotlinx.datetime.TimeZone import kotlinx.datetime.minus import kotlinx.datetime.plus import kotlinx.datetime.todayIn -import org.jetbrains.compose.resources.ExperimentalResourceApi -@OptIn(ExperimentalResourceApi::class) -@Composable -fun CurrentDay( - date: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault()), - modifier: Modifier = Modifier -) { -// val localization = getCurrentLocalization() - val dataSource = SimpleRowCalendarDataSource() - // we use `mutableStateOf` and `remember` inside composable function to schedules recomposition - var calendarUiModel by remember { mutableStateOf(dataSource.getData(lastSelectedDate = date)) } +class CurrentDayScreen( + private val date: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault()) +) : Screen { - fun onDateClickListener(date: SimpleRowCalendarUiModel.Date) { - // refresh the CalendarUiModel with new data - // by changing only the `selectedDate` with the date selected by User - calendarUiModel = calendarUiModel.copy( - selectedDate = date, - visibleDates = calendarUiModel.visibleDates.map { - it.copy( - isSelected = it.date == date.date - ) - } - ) - } - onDateClickListener(calendarUiModel.selectedDate) - fun onPreviousClickListener( - startDate: LocalDate - ) { - calendarUiModel = dataSource.getData( - startDate.minus(1, DateTimeUnit.DAY), - calendarUiModel.selectedDate.date - ) + @Composable + override fun Content() { + ApplicationTheme { + CurrentDayView(date) + } } - fun onNextClickListener( - startDate: LocalDate + @OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) + @Composable + fun CurrentDayView( + date: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault()) ) { - calendarUiModel = dataSource.getData( - startDate.plus(2, kotlinx.datetime.DateTimeUnit.DAY), - calendarUiModel.selectedDate.date - ) + val dataSource = SimpleRowCalendarDataSource() + // we use `mutableStateOf` and `remember` inside composable function to schedules recomposition + var calendarUiModel by remember { mutableStateOf(dataSource.getData(lastSelectedDate = date)) } + fun onDateClickListener(date: SimpleRowCalendarUiModel.Date) { + // refresh the CalendarUiModel with new data + // by changing only the `selectedDate` with the date selected by User + calendarUiModel = calendarUiModel.copy( + selectedDate = date, + visibleDates = calendarUiModel.visibleDates.map { + it.copy( + isSelected = it.date == date.date + ) + } + ) + } + onDateClickListener(calendarUiModel.selectedDate) + fun onPreviousClickListener( + startDate: LocalDate + ) { + calendarUiModel = dataSource.getData( + startDate.minus(1, DateTimeUnit.DAY), + calendarUiModel.selectedDate.date + ) + } + + fun onNextClickListener( + startDate: LocalDate + ) { + calendarUiModel = dataSource.getData( + startDate.plus(2, kotlinx.datetime.DateTimeUnit.DAY), + calendarUiModel.selectedDate.date + ) + } + + ApplicationTheme { + Scaffold( + topBar = { + SimpleRowCalendar(calendarUiModel, + onPrevClickListener = { startDate -> + onPreviousClickListener(startDate) + }, + onNextClickListener = { startDate -> + onNextClickListener(startDate) + }, + onDateClickListener = { date -> + onDateClickListener(date) + } + ) + }, + bottomBar = { + DailyNoteEditorToolbar() + } + ) { + val scrollState = rememberScrollState() + + Column( + modifier = Modifier + .padding(it).padding(bottom = 46.dp) + .background(MaterialTheme.colorScheme.background) + .verticalScroll(scrollState) + ) { + DailyNote(calendarUiModel) + Text(text = "This part is for the 'Created today' row") + + Column(modifier = Modifier.fillMaxWidth()) { + Text( + text = "Hello World! -> ${ + WindowInsets.Companion.ime.getBottom( + LocalDensity.current + ) + }" + ) + Text(text = "Not Visible") + } + } + } + } } - Column( - modifier = modifier.fillMaxSize(), - verticalArrangement = Arrangement.Top, - horizontalAlignment = Alignment.CenterHorizontally + @OptIn(ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class) + @Composable + private fun DailyNoteEditorToolbar( + keyboardController: SoftwareKeyboardController? + = LocalSoftwareKeyboardController.current ) { - SimpleRowCalendar(calendarUiModel, - onPrevClickListener = { startDate -> - onPreviousClickListener(startDate) - }, - onNextClickListener = { startDate -> - onNextClickListener(startDate) - }, - onDateClickListener = { date -> - onDateClickListener(date) + Column( + modifier = Modifier.windowInsetsPadding(WindowInsets.ime) + .imePadding() + .fillMaxWidth() + ) { + Divider( + color = MaterialTheme.colorScheme.primary, + modifier = Modifier + .height(0.8.dp) + .fillMaxHeight() + .fillMaxWidth() + ) + val scrollState = rememberScrollState() + Row( + modifier = Modifier + .background(MaterialTheme.colorScheme.surface) + .horizontalScroll(scrollState) + .fillMaxWidth() + .height(38.dp) + .padding(1.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Row { + IconButton( + onClick = {} + ) { + Icon( + imageVector = ApplicationIcons.Add, + contentDescription = "Insert Block", + ) + } + IconButton( + onClick = {} + ) { + Icon( + imageVector = ApplicationIcons.Repeat, + contentDescription = "Transform a Block to another", + ) + } + IconButton( + onClick = {} + ) { + Icon( + imageVector = ApplicationIcons.PhotoLibrary, + contentDescription = "Insert Image", + ) + } + IconButton( + onClick = {} + ) { + Icon( + imageVector = ApplicationIcons.TextFormat, + contentDescription = "Format Text", + ) + } + IconButton( + onClick = {} + ) { + Icon( + imageVector = ApplicationIcons.Undo, + contentDescription = "Undo", + ) + } + IconButton( + onClick = {} + ) { + Icon( + imageVector = ApplicationIcons.AlternateEmail, + contentDescription = "Mention someone", + ) + } + IconButton( + onClick = {} + ) { + Icon( + imageVector = ApplicationIcons.Delete, + contentDescription = "Delete Block", + ) + } + IconButton( + onClick = {} + ) { + Icon( + imageVector = ApplicationIcons.FormatIndentIncrease, + contentDescription = "Increase Indentation", + ) + } + IconButton( + onClick = {} + ) { + Icon( + imageVector = ApplicationIcons.FormatIndentDecrease, + contentDescription = "Decrease Indentation", + ) + } + IconButton( + onClick = {} + ) { + Icon( + imageVector = ApplicationIcons.FormatIndentDecrease, + contentDescription = "Decrease Indentation", + ) + } + IconButton( + onClick = {} + ) { + Icon( + imageVector = ApplicationIcons.VerticalAlignBottom, + contentDescription = "Align Bottom", + ) + } + IconButton( + onClick = {} + ) { + Icon( + imageVector = ApplicationIcons.VerticalAlignTop, + contentDescription = "Align Top", + ) + } + IconButton( + onClick = {} + ) { + Icon( + imageVector = ApplicationIcons.MoreHoriz, + contentDescription = "More Options", + ) + } + } + Row { + Divider( + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f), + modifier = Modifier + .fillMaxHeight() + .width(0.5.dp).padding(vertical = 5.dp) + ) + IconButton( + onClick = { + keyboardController?.hide() + } + ) { + Icon( + imageVector = ApplicationIcons.KeyboardHide, + contentDescription = "Hide Keyboard", + ) + } + } } - ) - DailyNote(calendarUiModel) - Text(text = "This part is for the 'Created today' row") + } } } diff --git a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/PetDetailScreen.kt b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/PetDetailScreen.kt index d0cfda9..461c3ca 100755 --- a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/PetDetailScreen.kt +++ b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/PetDetailScreen.kt @@ -1,5 +1,3 @@ -@file:OptIn(ExperimentalMaterial3Api::class) - package io.astrum.mindsync.app.ui.screens import androidx.compose.foundation.background @@ -57,6 +55,7 @@ class PetDetailScreen(private val petId: Int) : Screen { } } +@OptIn(ExperimentalMaterial3Api::class) @Composable fun PetDetailView(petModel: PetModel, onClose: () -> Unit) { val scrollState = rememberScrollState() diff --git a/shared/src/iosMain/kotlin/io/astrum/mindsync/app/main.ios.kt b/shared/src/iosMain/kotlin/io/astrum/mindsync/app/main.ios.kt index 6181dcc..e2b2dde 100755 --- a/shared/src/iosMain/kotlin/io/astrum/mindsync/app/main.ios.kt +++ b/shared/src/iosMain/kotlin/io/astrum/mindsync/app/main.ios.kt @@ -3,15 +3,10 @@ package io.astrum.mindsync.app -import androidx.compose.ui.uikit.OnFocusBehavior import androidx.compose.ui.window.ComposeUIViewController import platform.UIKit.UIViewController -fun homeScreenViewController(): UIViewController = ComposeUIViewController( - configure = { - onFocusBehavior = OnFocusBehavior.DoNothing - } -) { +fun homeScreenViewController(): UIViewController = ComposeUIViewController{ MainApp() } From 59219ebf873fae2fd5fce19091e8bbf6d25b5495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yuniel=20Acosta=20P=C3=A9rez?= Date: Wed, 29 Nov 2023 16:28:36 +0100 Subject: [PATCH 4/4] feat: created basic editor toolbar component sticky over keyboard - sticky keyboard button --- .../app/ui/screens/CurrentDayScreen.kt | 279 +++++++----------- 1 file changed, 114 insertions(+), 165 deletions(-) diff --git a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/CurrentDayScreen.kt b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/CurrentDayScreen.kt index db30ec7..548d956 100755 --- a/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/CurrentDayScreen.kt +++ b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/CurrentDayScreen.kt @@ -2,8 +2,6 @@ package io.astrum.mindsync.app.ui.screens import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background -import androidx.compose.foundation.horizontalScroll -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets @@ -13,8 +11,9 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Divider @@ -31,6 +30,10 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.SoftwareKeyboardController @@ -53,6 +56,60 @@ import kotlinx.datetime.todayIn class CurrentDayScreen( private val date: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault()) ) : Screen { + private val toolbarButtons = listOf( + ToolbarButton(id = "ai_block", + icon = ApplicationIcons.Sparkle, + contentDescription = "Insert Block", + onClick = {}), + ToolbarButton(id = "insert_block", + icon = ApplicationIcons.Add, + contentDescription = "Insert Block", + onClick = {}), + ToolbarButton(id = "transform_block", + icon = ApplicationIcons.Repeat, + contentDescription = "Transform a Block to another", + onClick = {}), + ToolbarButton(id = "image_block", + icon = ApplicationIcons.PhotoLibrary, + contentDescription = "Insert Image", + onClick = {}), + ToolbarButton(id = "format_text", + icon = ApplicationIcons.TextFormat, + contentDescription = "Format Text", + onClick = {}), + ToolbarButton(id = "undo", + icon = ApplicationIcons.Undo, + contentDescription = "Undo", + onClick = {}), + ToolbarButton(id = "mention_someone", + icon = ApplicationIcons.AlternateEmail, + contentDescription = "Mention someone", + onClick = {}), + ToolbarButton(id = "delete_block", + icon = ApplicationIcons.Delete, + contentDescription = "Delete Block", + onClick = {}), + ToolbarButton(id = "increase_indentation", + icon = ApplicationIcons.FormatIndentIncrease, + contentDescription = "Increase Indentation", + onClick = {}), + ToolbarButton(id = "decrease_indentation", + icon = ApplicationIcons.FormatIndentDecrease, + contentDescription = "Decrease Indentation", + onClick = {}), + ToolbarButton(id = "align_bottom", + icon = ApplicationIcons.VerticalAlignBottom, + contentDescription = "Align Bottom", + onClick = {}), + ToolbarButton(id = "align_top", + icon = ApplicationIcons.VerticalAlignTop, + contentDescription = "Align Top", + onClick = {}), + ToolbarButton(id = "more_options", + icon = ApplicationIcons.MoreHoriz, + contentDescription = "More Options", + onClick = {}), + ) @Composable override fun Content() { @@ -72,22 +129,19 @@ class CurrentDayScreen( fun onDateClickListener(date: SimpleRowCalendarUiModel.Date) { // refresh the CalendarUiModel with new data // by changing only the `selectedDate` with the date selected by User - calendarUiModel = calendarUiModel.copy( - selectedDate = date, + calendarUiModel = calendarUiModel.copy(selectedDate = date, visibleDates = calendarUiModel.visibleDates.map { it.copy( isSelected = it.date == date.date ) - } - ) + }) } onDateClickListener(calendarUiModel.selectedDate) fun onPreviousClickListener( startDate: LocalDate ) { calendarUiModel = dataSource.getData( - startDate.minus(1, DateTimeUnit.DAY), - calendarUiModel.selectedDate.date + startDate.minus(1, DateTimeUnit.DAY), calendarUiModel.selectedDate.date ) } @@ -95,35 +149,27 @@ class CurrentDayScreen( startDate: LocalDate ) { calendarUiModel = dataSource.getData( - startDate.plus(2, kotlinx.datetime.DateTimeUnit.DAY), + startDate.plus(2, DateTimeUnit.DAY), calendarUiModel.selectedDate.date ) } ApplicationTheme { - Scaffold( - topBar = { - SimpleRowCalendar(calendarUiModel, - onPrevClickListener = { startDate -> - onPreviousClickListener(startDate) - }, - onNextClickListener = { startDate -> - onNextClickListener(startDate) - }, - onDateClickListener = { date -> - onDateClickListener(date) - } - ) - }, - bottomBar = { - DailyNoteEditorToolbar() - } - ) { + Scaffold(topBar = { + SimpleRowCalendar(calendarUiModel, onPrevClickListener = { startDate -> + onPreviousClickListener(startDate) + }, onNextClickListener = { startDate -> + onNextClickListener(startDate) + }, onDateClickListener = { date -> + onDateClickListener(date) + }) + }, bottomBar = { + DailyNoteEditorToolbar() + }) { val scrollState = rememberScrollState() Column( - modifier = Modifier - .padding(it).padding(bottom = 46.dp) + modifier = Modifier.padding(it).padding(bottom = 46.dp) .background(MaterialTheme.colorScheme.background) .verticalScroll(scrollState) ) { @@ -148,149 +194,48 @@ class CurrentDayScreen( @OptIn(ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class) @Composable private fun DailyNoteEditorToolbar( - keyboardController: SoftwareKeyboardController? - = LocalSoftwareKeyboardController.current + keyboardController: SoftwareKeyboardController? = LocalSoftwareKeyboardController.current ) { Column( - modifier = Modifier.windowInsetsPadding(WindowInsets.ime) - .imePadding() - .fillMaxWidth() + modifier = Modifier.windowInsetsPadding(WindowInsets.ime).imePadding().fillMaxWidth() ) { + val dividerColor: Color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f) Divider( - color = MaterialTheme.colorScheme.primary, - modifier = Modifier - .height(0.8.dp) - .fillMaxHeight() - .fillMaxWidth() + color = dividerColor, + modifier = Modifier.height(0.8.dp).fillMaxHeight().fillMaxWidth() ) - val scrollState = rememberScrollState() - Row( - modifier = Modifier - .background(MaterialTheme.colorScheme.surface) - .horizontalScroll(scrollState) - .fillMaxWidth() - .height(38.dp) - .padding(1.dp), - horizontalArrangement = Arrangement.SpaceBetween - ) { - Row { - IconButton( - onClick = {} - ) { - Icon( - imageVector = ApplicationIcons.Add, - contentDescription = "Insert Block", - ) - } - IconButton( - onClick = {} - ) { - Icon( - imageVector = ApplicationIcons.Repeat, - contentDescription = "Transform a Block to another", - ) - } - IconButton( - onClick = {} - ) { - Icon( - imageVector = ApplicationIcons.PhotoLibrary, - contentDescription = "Insert Image", - ) - } - IconButton( - onClick = {} - ) { - Icon( - imageVector = ApplicationIcons.TextFormat, - contentDescription = "Format Text", - ) - } - IconButton( - onClick = {} - ) { - Icon( - imageVector = ApplicationIcons.Undo, - contentDescription = "Undo", - ) - } - IconButton( - onClick = {} - ) { - Icon( - imageVector = ApplicationIcons.AlternateEmail, - contentDescription = "Mention someone", - ) - } - IconButton( - onClick = {} - ) { - Icon( - imageVector = ApplicationIcons.Delete, - contentDescription = "Delete Block", - ) - } - IconButton( - onClick = {} - ) { - Icon( - imageVector = ApplicationIcons.FormatIndentIncrease, - contentDescription = "Increase Indentation", - ) - } - IconButton( - onClick = {} - ) { - Icon( - imageVector = ApplicationIcons.FormatIndentDecrease, - contentDescription = "Decrease Indentation", - ) - } - IconButton( - onClick = {} - ) { - Icon( - imageVector = ApplicationIcons.FormatIndentDecrease, - contentDescription = "Decrease Indentation", - ) - } - IconButton( - onClick = {} - ) { - Icon( - imageVector = ApplicationIcons.VerticalAlignBottom, - contentDescription = "Align Bottom", - ) - } - IconButton( - onClick = {} - ) { - Icon( - imageVector = ApplicationIcons.VerticalAlignTop, - contentDescription = "Align Top", - ) + + Row(modifier = Modifier.fillMaxWidth()) { + LazyRow(modifier = Modifier.weight(1F)) { + items(toolbarButtons) { button -> + IconButton( + onClick = button.onClick + ) { + Icon( + imageVector = button.icon, + contentDescription = button.contentDescription, + ) + } } - IconButton( - onClick = {} - ) { - Icon( - imageVector = ApplicationIcons.MoreHoriz, - contentDescription = "More Options", + } + + Row( + modifier = Modifier.drawBehind { + val strokeWidth = 1 * density + val topSpace = 8.dp.toPx() + val bottomSpace = 8.dp.toPx() + // Draw line function for left border with space at top and bottom + drawLine( + dividerColor, + start = Offset(0f, topSpace), + end = Offset(0f, size.height - bottomSpace), + strokeWidth ) } - } - Row { - Divider( - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f), - modifier = Modifier - .fillMaxHeight() - .width(0.5.dp).padding(vertical = 5.dp) - ) - IconButton( - onClick = { - keyboardController?.hide() - } - ) { + ) { + IconButton(onClick = { + keyboardController?.hide() + }) { Icon( imageVector = ApplicationIcons.KeyboardHide, contentDescription = "Hide Keyboard", @@ -301,3 +246,7 @@ class CurrentDayScreen( } } } + +data class ToolbarButton( + val id: String, val icon: ImageVector, val contentDescription: String, val onClick: () -> Unit +)