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/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/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..513cf2c 100755 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,15 +3,16 @@ 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" 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" @@ -20,14 +21,16 @@ 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" +androidxComposeBom = "2023.10.01" [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" } @@ -72,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 8d7c2a9..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,14 +70,19 @@ 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 { + dependsOn(commonMain) dependencies { implementation(libs.ktor.client.darwin) } @@ -112,3 +117,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, + 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/SimpleRowCalendar.kt b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/SimpleRowCalendar.kt new file mode 100644 index 0000000..63afc70 --- /dev/null +++ b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/components/SimpleRowCalendar.kt @@ -0,0 +1,143 @@ +package io.astrum.mindsync.app.ui.components + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +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.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ChevronLeft +import androidx.compose.material.icons.filled.ChevronRight +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.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 +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import io.astrum.mindsync.app.ui.screens.viewmodel.SimpleRowCalendarUiModel +import kotlinx.datetime.LocalDate + + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SimpleRowCalendar( + data: SimpleRowCalendarUiModel, + // calbacks to click previous & back button should be registered outside + onPrevClickListener: (LocalDate) -> Unit, + onNextClickListener: (LocalDate) -> Unit, + onDateClickListener: (SimpleRowCalendarUiModel.Date) -> Unit, +) { + Row(modifier = Modifier.windowInsetsPadding( + TopAppBarDefaults.windowInsets + )) { + 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(1.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 = 14.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..548d956 --- /dev/null +++ b/shared/src/commonMain/kotlin/io/astrum/mindsync/app/ui/screens/CurrentDayScreen.kt @@ -0,0 +1,252 @@ +package io.astrum.mindsync.app.ui.screens + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +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.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 +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.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 +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 +import kotlinx.datetime.TimeZone +import kotlinx.datetime.minus +import kotlinx.datetime.plus +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() { + ApplicationTheme { + CurrentDayView(date) + } + } + + @OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) + @Composable + fun CurrentDayView( + date: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault()) + ) { + 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, 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") + } + } + } + } + } + + @OptIn(ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class) + @Composable + private fun DailyNoteEditorToolbar( + keyboardController: SoftwareKeyboardController? = LocalSoftwareKeyboardController.current + ) { + Column( + modifier = Modifier.windowInsetsPadding(WindowInsets.ime).imePadding().fillMaxWidth() + ) { + val dividerColor: Color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f) + Divider( + color = dividerColor, + modifier = Modifier.height(0.8.dp).fillMaxHeight().fillMaxWidth() + ) + + Row(modifier = Modifier.fillMaxWidth()) { + LazyRow(modifier = Modifier.weight(1F)) { + items(toolbarButtons) { button -> + IconButton( + onClick = button.onClick + ) { + Icon( + imageVector = button.icon, + contentDescription = button.contentDescription, + ) + } + } + } + + 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 + ) + } + ) { + IconButton(onClick = { + keyboardController?.hide() + }) { + Icon( + imageVector = ApplicationIcons.KeyboardHide, + contentDescription = "Hide Keyboard", + ) + } + } + } + } + } +} + +data class ToolbarButton( + val id: String, val icon: ImageVector, val contentDescription: String, val onClick: () -> Unit +) 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/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/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/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/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) 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..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 @@ -4,10 +4,9 @@ package io.astrum.mindsync.app import androidx.compose.ui.window.ComposeUIViewController -import io.astrum.mindsync.app.MainApp import platform.UIKit.UIViewController -fun homeScreenViewController(): UIViewController = ComposeUIViewController { +fun homeScreenViewController(): UIViewController = ComposeUIViewController{ MainApp() }