From 5c56947c4f291b6d392739487ec7cd4292f18190 Mon Sep 17 00:00:00 2001 From: Emma Vandewalle Date: Fri, 16 Aug 2024 21:10:10 +0000 Subject: [PATCH] feat: welcome screens --- app/build.gradle | 11 +- app/src/main/AndroidManifest.xml | 1 + .../main/java/be/re/writand/MainActivity.kt | 10 +- .../main/java/be/re/writand/WApplication.kt | 7 + .../writand/data/local/SettingsSerializer.kt | 13 +- .../writand/data/local/models/UserSettings.kt | 70 +++++++- .../repos/settings/IUserSettingsRepository.kt | 9 +- .../repos/settings/UserSettingsRepository.kt | 61 +++++-- .../writand/data/repos/tos/TOSRepository.kt | 3 - .../java/be/re/writand/di/DatabaseModule.kt | 12 +- app/src/main/java/be/re/writand/domain/.keep | 0 .../settings/SetFontSizeSettingsUseCase.kt | 27 ++++ .../settings/SetLanguageSettingsUseCase.kt | 23 +++ .../settings/SetThemeSettingsUseCase.kt | 23 +++ .../be/re/writand/domain/tos/GetTOSUseCase.kt | 17 ++ .../re/writand/screens/SettingsViewModel.kt | 31 ++++ .../re/writand/screens/components/Buttons.kt | 6 +- .../writand/screens/components/TextFields.kt | 89 +++++++++++ .../writand/screens/components/WCheckbox.kt | 30 ++++ .../screens/components/WLoadingIndicator.kt | 30 ++++ .../writand/screens/components/WLogoImage.kt | 28 ++++ .../components/WRadioButtonsSelector.kt | 124 +++++++++++++++ .../be/re/writand/screens/components/WText.kt | 80 ++++++++++ .../re/writand/screens/splash/SplashScreen.kt | 9 +- .../writand/screens/splash/SplashViewModel.kt | 24 ++- .../screens/welcome/WelcomeSettingsScreen.kt | 150 +++++++++++++++++- .../welcome/WelcomeSettingsViewModel.kt | 64 ++++++++ .../screens/welcome/WelcomeStartScreen.kt | 93 ++++++++++- .../screens/welcome/WelcomeTOSScreen.kt | 142 ++++++++++++++++- .../screens/welcome/WelcomeTOSUiState.kt | 13 ++ .../screens/welcome/WelcomeTOSViewModel.kt | 33 ++++ .../main/java/be/re/writand/ui/theme/Theme.kt | 4 +- app/src/main/proto/settings.proto | 2 +- .../res/drawable/writand_no_background.png | Bin 0 -> 29370 bytes .../res/drawable/writand_with_background.png | Bin 0 -> 29552 bytes build.gradle | 2 +- 36 files changed, 1176 insertions(+), 65 deletions(-) create mode 100644 app/src/main/java/be/re/writand/WApplication.kt delete mode 100644 app/src/main/java/be/re/writand/domain/.keep create mode 100644 app/src/main/java/be/re/writand/domain/settings/SetFontSizeSettingsUseCase.kt create mode 100644 app/src/main/java/be/re/writand/domain/settings/SetLanguageSettingsUseCase.kt create mode 100644 app/src/main/java/be/re/writand/domain/settings/SetThemeSettingsUseCase.kt create mode 100644 app/src/main/java/be/re/writand/domain/tos/GetTOSUseCase.kt create mode 100644 app/src/main/java/be/re/writand/screens/SettingsViewModel.kt create mode 100644 app/src/main/java/be/re/writand/screens/components/TextFields.kt create mode 100644 app/src/main/java/be/re/writand/screens/components/WCheckbox.kt create mode 100644 app/src/main/java/be/re/writand/screens/components/WLoadingIndicator.kt create mode 100644 app/src/main/java/be/re/writand/screens/components/WLogoImage.kt create mode 100644 app/src/main/java/be/re/writand/screens/components/WRadioButtonsSelector.kt create mode 100644 app/src/main/java/be/re/writand/screens/components/WText.kt create mode 100644 app/src/main/java/be/re/writand/screens/welcome/WelcomeSettingsViewModel.kt create mode 100644 app/src/main/java/be/re/writand/screens/welcome/WelcomeTOSUiState.kt create mode 100644 app/src/main/java/be/re/writand/screens/welcome/WelcomeTOSViewModel.kt create mode 100644 app/src/main/res/drawable/writand_no_background.png create mode 100644 app/src/main/res/drawable/writand_with_background.png diff --git a/app/build.gradle b/app/build.gradle index 8d29d88..5dc4160 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -90,8 +90,10 @@ dependencies { testImplementation "org.mockito:mockito-android:4.1.0" // Hilt - implementation "com.google.dagger:hilt-android:2.48" - kapt "com.google.dagger:hilt-compiler:2.48" + implementation "com.google.dagger:hilt-android:2.49" + kapt "com.google.dagger:hilt-compiler:2.49" + + implementation "androidx.hilt:hilt-navigation-compose:1.2.0" // Room implementation 'androidx.room:room-runtime:2.6.1' @@ -99,8 +101,11 @@ dependencies { implementation 'androidx.room:room-ktx:2.6.1' // Proto DataStore - implementation 'androidx.datastore:datastore:1.1.1' implementation 'com.google.protobuf:protobuf-javalite:3.21.7' + implementation 'androidx.datastore:datastore:1.1.1' + implementation("androidx.datastore:datastore-core:1.1.1") +// implementation("androidx.datastore:datastore-preferences:1.1.1") +// implementation("androidx.datastore:datastore-preferences-core:1.1.1") debugImplementation "androidx.compose.ui:ui-tooling:$compose_ui_version" debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_ui_version" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ad9d132..7228303 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools"> { output: OutputStream ) = t.writeTo(output) -} \ No newline at end of file +} + +/** + * Added field to Context to enable getting an instance of ProtoSettings where needed. + */ +val Context.protoSettingsDataStore: DataStore by dataStore( + fileName = "settings.proto", + serializer = SettingsSerializer() +) diff --git a/app/src/main/java/be/re/writand/data/local/models/UserSettings.kt b/app/src/main/java/be/re/writand/data/local/models/UserSettings.kt index adf09f0..145ca6a 100644 --- a/app/src/main/java/be/re/writand/data/local/models/UserSettings.kt +++ b/app/src/main/java/be/re/writand/data/local/models/UserSettings.kt @@ -5,7 +5,38 @@ package be.re.writand.data.local.models */ enum class UserLanguage { ENGLISH, - DUTCH + DUTCH; + + /** + * Overwritten toString function for displaying the different languages as text on the screen. + */ + override fun toString(): String { + return when (this) { + ENGLISH -> "English" + DUTCH -> "Dutch" + } + } +} + +/** + * Checks whether the given string is a language supported by the enum class UserLanguage. + */ +fun String.isUserLanguage(): Boolean { + return UserLanguage.entries.map { entry -> entry.toString() }.contains(this) +} + +/** + * Converts a given string to a valid UserLanguage or English when the string isn't valid. + * The choice to give English back when invalid is chosen as the checks for input strings should be + * checked in a use-case/viewmodel with the function isUserLanguage() first. + */ +fun String.toUserLanguage(): UserLanguage { + // when i18n: first translate to English then get back UserLanguage + return when (this) { + "English" -> UserLanguage.ENGLISH + "Dutch" -> UserLanguage.DUTCH + else -> UserLanguage.ENGLISH + } } /** @@ -13,7 +44,38 @@ enum class UserLanguage { */ enum class UserTheme { DARK, - LIGHT + LIGHT; + + /** + * Overwritten toString function for displaying the different themes as text on the screen. + */ + override fun toString(): String { + return when (this) { + DARK -> "Dark" + LIGHT -> "Light" + } + } +} + +/** + * Checks whether the given string is a theme supported by the enum class UserTheme. + */ +fun String.isUserTheme(): Boolean { + return UserTheme.entries.map { entry -> entry.toString() }.contains(this) +} + +/** + * Converts a given string to a valid UserTheme or the Dark when the string isn't valid. + * The choice to give Dark back when invalid is chosen as the checks for input strings should be + * checked in a use-case/viewmodel with the function isUserTheme() first. + */ +fun String.toUserTheme(): UserTheme { + // when i18n: first translate to English then get back UserLanguage + return when (this) { + "Dark" -> UserTheme.DARK + "Light" -> UserTheme.LIGHT + else -> UserTheme.DARK // should not occur: checks should happen in view models + } } /** @@ -31,5 +93,5 @@ data class UserSettings( var userTheme: UserTheme = UserTheme.DARK, var maxSavedProjects: Int = 3, var maxSavedFiles: Int = 5, - var fontSize: Int = 10 -) + var fontSize: Float = 14.0f +) \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/data/repos/settings/IUserSettingsRepository.kt b/app/src/main/java/be/re/writand/data/repos/settings/IUserSettingsRepository.kt index 905b018..7af4f55 100644 --- a/app/src/main/java/be/re/writand/data/repos/settings/IUserSettingsRepository.kt +++ b/app/src/main/java/be/re/writand/data/repos/settings/IUserSettingsRepository.kt @@ -1,15 +1,18 @@ package be.re.writand.data.repos.settings +import be.re.writand.ProtoSettings import be.re.writand.data.local.models.UserLanguage import be.re.writand.data.local.models.UserSettings import be.re.writand.data.local.models.UserTheme /** - * Repository containing getters and setters to interact with all of the user settings + * Repository containing initializer, and setters to interact with all of the user settings. */ interface IUserSettingsRepository { - suspend fun getUserSettings(): UserSettings + suspend fun initializeSettingsIfNull() + + suspend fun toUserSettings(protoSettings: ProtoSettings): UserSettings suspend fun setLanguage(userLanguage: UserLanguage) @@ -19,5 +22,5 @@ interface IUserSettingsRepository { suspend fun setMaxSavedFiles(maxSaved: Int) - suspend fun setFontSize(size: Int) + suspend fun setFontSize(size: Float) } \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/data/repos/settings/UserSettingsRepository.kt b/app/src/main/java/be/re/writand/data/repos/settings/UserSettingsRepository.kt index 233e89d..8fc7645 100644 --- a/app/src/main/java/be/re/writand/data/repos/settings/UserSettingsRepository.kt +++ b/app/src/main/java/be/re/writand/data/repos/settings/UserSettingsRepository.kt @@ -1,37 +1,70 @@ package be.re.writand.data.repos.settings +import android.content.Context import androidx.datastore.core.DataStore +import androidx.datastore.core.IOException +import be.re.writand.ProtoLanguage import be.re.writand.ProtoSettings +import be.re.writand.ProtoTheme import be.re.writand.data.local.models.UserLanguage import be.re.writand.data.local.models.UserSettings import be.re.writand.data.local.models.UserTheme +import be.re.writand.data.local.protoSettingsDataStore import be.re.writand.utils.toProtoLanguage import be.re.writand.utils.toProtoTheme import be.re.writand.utils.toUserLanguage import be.re.writand.utils.toUserTheme +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map import javax.inject.Inject import javax.inject.Singleton @Singleton class UserSettingsRepository @Inject constructor( - private val store: DataStore + context: Context ) : IUserSettingsRepository { - private val userSettings = store.data + private val dataStore: DataStore = context.protoSettingsDataStore + val userSettings: Flow = dataStore.data + .catch { exception -> + if (exception is IOException) { + emit(ProtoSettings.getDefaultInstance()) + } else { + throw exception + } + } + .map { protoSettings -> + toUserSettings(protoSettings) + } - override suspend fun getUserSettings(): UserSettings { + override suspend fun initializeSettingsIfNull() { + val currentSettings = dataStore.data.first(); + if (currentSettings == ProtoSettings.getDefaultInstance()) { + val defaultSettings = ProtoSettings.newBuilder() + .setLanguage(ProtoLanguage.PROTO_LANGUAGE_ENGLISH) + .setTheme(ProtoTheme.PROTO_THEME_DARK) + .setMaxSavedProjects(3) + .setMaxSavedFiles(5) + .setFontSize(14.0f) + .build() + dataStore.updateData { defaultSettings } + } + } + + override suspend fun toUserSettings(protoSettings: ProtoSettings): UserSettings { return UserSettings( - userLanguage = userSettings.first().language.toUserLanguage(), - userTheme = userSettings.first().theme.toUserTheme(), - maxSavedProjects = userSettings.first().maxSavedProjects, - maxSavedFiles = userSettings.first().maxSavedFiles, - fontSize = userSettings.first().fontSize + userLanguage = protoSettings.language.toUserLanguage(), + userTheme = protoSettings.theme.toUserTheme(), + maxSavedProjects = protoSettings.maxSavedProjects, + maxSavedFiles = protoSettings.maxSavedFiles, + fontSize = protoSettings.fontSize ) } override suspend fun setLanguage(userLanguage: UserLanguage) { - store.updateData { + dataStore.updateData { val builder = it.toBuilder() builder.setLanguage(userLanguage.toProtoLanguage()) builder.build() @@ -39,7 +72,7 @@ class UserSettingsRepository @Inject constructor( } override suspend fun setTheme(userTheme: UserTheme) { - store.updateData { + dataStore.updateData { val builder = it.toBuilder() builder.setTheme(userTheme.toProtoTheme()) builder.build() @@ -47,7 +80,7 @@ class UserSettingsRepository @Inject constructor( } override suspend fun setMaxSavedProjects(maxSaved: Int) { - store.updateData { + dataStore.updateData { val builder = it.toBuilder() builder.setMaxSavedProjects(maxSaved) builder.build() @@ -55,15 +88,15 @@ class UserSettingsRepository @Inject constructor( } override suspend fun setMaxSavedFiles(maxSaved: Int) { - store.updateData { + dataStore.updateData { val builder = it.toBuilder() builder.setMaxSavedFiles(maxSaved) builder.build() } } - override suspend fun setFontSize(size: Int) { - store.updateData { + override suspend fun setFontSize(size: Float) { + dataStore.updateData { val builder = it.toBuilder() builder.setFontSize(size) builder.build() diff --git a/app/src/main/java/be/re/writand/data/repos/tos/TOSRepository.kt b/app/src/main/java/be/re/writand/data/repos/tos/TOSRepository.kt index 1eae875..b59beb3 100644 --- a/app/src/main/java/be/re/writand/data/repos/tos/TOSRepository.kt +++ b/app/src/main/java/be/re/writand/data/repos/tos/TOSRepository.kt @@ -1,12 +1,9 @@ package be.re.writand.data.repos.tos import android.content.Context -import android.util.Log import be.re.writand.data.local.models.TermsOfService import be.re.writand.utils.Version -import java.io.File import java.io.InputStream -import java.util.concurrent.Flow import javax.inject.Inject class TOSRepository @Inject constructor( diff --git a/app/src/main/java/be/re/writand/di/DatabaseModule.kt b/app/src/main/java/be/re/writand/di/DatabaseModule.kt index aa9e9f6..5fc206c 100644 --- a/app/src/main/java/be/re/writand/di/DatabaseModule.kt +++ b/app/src/main/java/be/re/writand/di/DatabaseModule.kt @@ -1,9 +1,10 @@ package be.re.writand.di import android.content.Context -import android.content.res.AssetManager import be.re.writand.data.local.db.ProjectsDao import be.re.writand.data.local.db.ProjectsDatabase +import be.re.writand.data.repos.settings.IUserSettingsRepository +import be.re.writand.data.repos.settings.UserSettingsRepository import be.re.writand.data.repos.tos.ITOSRepository import be.re.writand.data.repos.tos.TOSRepository import dagger.Module @@ -38,7 +39,12 @@ class DatabaseModule { } @Provides - fun provideTOSRepository(repo: TOSRepository): ITOSRepository { - return repo + fun provideUserSettingsRepository(@ApplicationContext context: Context): IUserSettingsRepository { + return UserSettingsRepository(context) + } + + @Provides + fun provideTOSRepository(@ApplicationContext context: Context): ITOSRepository { + return TOSRepository(context) } } \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/domain/.keep b/app/src/main/java/be/re/writand/domain/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/main/java/be/re/writand/domain/settings/SetFontSizeSettingsUseCase.kt b/app/src/main/java/be/re/writand/domain/settings/SetFontSizeSettingsUseCase.kt new file mode 100644 index 0000000..7d7ac85 --- /dev/null +++ b/app/src/main/java/be/re/writand/domain/settings/SetFontSizeSettingsUseCase.kt @@ -0,0 +1,27 @@ +package be.re.writand.domain.settings + +import be.re.writand.data.repos.settings.UserSettingsRepository +import javax.inject.Inject + +/** + * Use-case that takes in a string which will alter the current UserSettings if the string is a + * valid Float and above 0, otherwise nothing will be altered. + * @param[userSettingsRepository] repository to change the UserSettings when valid input is given. + */ +class SetFontSizeSettingsUseCase @Inject constructor( + private val userSettingsRepository: UserSettingsRepository +) { + + suspend operator fun invoke(fontSize: String): NumberFormatException? { + try { + val newFontSize: Float = fontSize.toFloat() + if (newFontSize > 0) { + userSettingsRepository.setFontSize(newFontSize) + } + return null + } catch (e: NumberFormatException) { + return e + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/domain/settings/SetLanguageSettingsUseCase.kt b/app/src/main/java/be/re/writand/domain/settings/SetLanguageSettingsUseCase.kt new file mode 100644 index 0000000..17b76dd --- /dev/null +++ b/app/src/main/java/be/re/writand/domain/settings/SetLanguageSettingsUseCase.kt @@ -0,0 +1,23 @@ +package be.re.writand.domain.settings + +import be.re.writand.data.local.models.isUserLanguage +import be.re.writand.data.local.models.toUserLanguage +import be.re.writand.data.repos.settings.UserSettingsRepository +import javax.inject.Inject + +/** + * Use-case that takes in a string which will alter the current UserSettings if the string is part + * of the defined UserLanguage fields, otherwise nothing will be altered. + * @param[userSettingsRepository] repository to change the UserSettings when valid input is given. + */ +class SetLanguageSettingsUseCase @Inject constructor( + private val userSettingsRepository: UserSettingsRepository +) { + + suspend operator fun invoke(language: String) { + if (language.isUserLanguage()) { + userSettingsRepository.setLanguage(language.toUserLanguage()) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/domain/settings/SetThemeSettingsUseCase.kt b/app/src/main/java/be/re/writand/domain/settings/SetThemeSettingsUseCase.kt new file mode 100644 index 0000000..08df511 --- /dev/null +++ b/app/src/main/java/be/re/writand/domain/settings/SetThemeSettingsUseCase.kt @@ -0,0 +1,23 @@ +package be.re.writand.domain.settings + +import be.re.writand.data.local.models.isUserTheme +import be.re.writand.data.local.models.toUserTheme +import be.re.writand.data.repos.settings.UserSettingsRepository +import javax.inject.Inject + +/** + * Use-case that takes in a string which will alter the current UserSettings if the string is part + * of the defined UserTheme fields, otherwise nothing will be altered. + * @param[userSettingsRepository] repository to change the UserSettings when valid input is given. + */ +class SetThemeSettingsUseCase @Inject constructor( + private val userSettingsRepository: UserSettingsRepository +) { + + suspend operator fun invoke(theme: String) { + if (theme.isUserTheme()) { + userSettingsRepository.setTheme(theme.toUserTheme()) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/domain/tos/GetTOSUseCase.kt b/app/src/main/java/be/re/writand/domain/tos/GetTOSUseCase.kt new file mode 100644 index 0000000..64d4f04 --- /dev/null +++ b/app/src/main/java/be/re/writand/domain/tos/GetTOSUseCase.kt @@ -0,0 +1,17 @@ +package be.re.writand.domain.tos + +import be.re.writand.data.repos.tos.ITOSRepository +import javax.inject.Inject + +/** + * Use-case for getting back the TOS file in a list. + */ +class GetTOSUseCase @Inject constructor( + private val tosRepository: ITOSRepository +) { + + operator fun invoke(): List { + return tosRepository.getTOS().text + } + +} \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/screens/SettingsViewModel.kt b/app/src/main/java/be/re/writand/screens/SettingsViewModel.kt new file mode 100644 index 0000000..1bebd61 --- /dev/null +++ b/app/src/main/java/be/re/writand/screens/SettingsViewModel.kt @@ -0,0 +1,31 @@ +package be.re.writand.screens + +import be.re.writand.data.local.models.UserSettings +import be.re.writand.data.repos.settings.UserSettingsRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +/** + * View model to be used by components needing the UserSettings for font size, theme etc. + * This view model should be a general model offering the UserSettings, and nothing more. + * @param[userSettingsRepository] providing the UserSettings. + */ +@HiltViewModel +class SettingsViewModel @Inject constructor( + private val userSettingsRepository: UserSettingsRepository +): WViewModel() { + + private val _settings = MutableStateFlow(null) + val settings: StateFlow = _settings + + init { + launchCatching { + userSettingsRepository.userSettings.collect { settings -> + _settings.value = settings + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/screens/components/Buttons.kt b/app/src/main/java/be/re/writand/screens/components/Buttons.kt index 990ebbd..b3def35 100644 --- a/app/src/main/java/be/re/writand/screens/components/Buttons.kt +++ b/app/src/main/java/be/re/writand/screens/components/Buttons.kt @@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -14,21 +13,24 @@ import be.re.writand.ui.theme.MainGreen /** * A standard button with predefined color and shape. * @param[text] the text that should be shown in the button. + * @param[modifier] compose.ui modifier to change the button, not the text. * @param[onClick] the callback function to execute when the button is clicked. * @param[enabled] boolean to express if the button is enabled to be clicked or not. */ @Composable fun WButton( text: String, + modifier: Modifier = Modifier, onClick: () -> Unit, enabled: Boolean = true ) { Button( onClick = onClick, + modifier = modifier, colors = ButtonDefaults.buttonColors(containerColor = MainGreen), shape = RoundedCornerShape(10.dp), enabled = enabled ) { - Text(text = text, color = Color.Black, modifier = Modifier.padding(5.dp)) + WText(text = text, color = Color.Black, modifier = Modifier.padding(5.dp)) } } \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/screens/components/TextFields.kt b/app/src/main/java/be/re/writand/screens/components/TextFields.kt new file mode 100644 index 0000000..1f8e47c --- /dev/null +++ b/app/src/main/java/be/re/writand/screens/components/TextFields.kt @@ -0,0 +1,89 @@ +package be.re.writand.screens.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +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.unit.Dp +import androidx.compose.ui.unit.dp + +/** + * Text field composable to use the themed colors and font size from settings. + * @param[title] label in text field itself instead of in front of text field. + * @param[value] initial value of the content of the text field. + * @param[onTextChange] lambda that is executed when text field is being edited. + * @param[keyboardOptions] options to have different keyboard layouts (numeric only etc). + * @param[leadingIcon] icon in the text field, in front of the 'value' parameter (e.g. search icon). + */ +@Composable +fun WTextField( + title: String, + value: String, + onTextChange: (String) -> Unit, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + leadingIcon: @Composable (() -> Unit)? = null +) { + TextField( + singleLine = true, + label = { WText(text = title, color = Color.Black) }, + value = value, + onValueChange = { onTextChange(it) }, + colors = TextFieldDefaults.colors( + focusedIndicatorColor = MaterialTheme.colorScheme.tertiary + ), + keyboardOptions = keyboardOptions, + modifier = Modifier + .padding(vertical = 8.dp) + .fillMaxWidth(), + leadingIcon = leadingIcon + ) +} + +/** + * Text field composable with label in front to use the themed colors and font size from settings. + * This is the same as [WTextField], but the label in the text field is gone, and the label is + * placed in front of the text field. + * @param[title] label in text field itself instead of in front of text field. + * @param[labelSize] the width of the label [title]. + * @param[value] initial value of the content of the text field. + * @param[fullSize] the full width of the label plus text field. + * @param[onTextChange] lambda that is executed when text field is being edited. + * @param[keyboardOptions] options to have different keyboard layouts (numeric only etc). + * @param[leadingIcon] icon in the text field, in front of the 'value' parameter (e.g. search icon). + */ +@Composable +fun WLabelAndTextField( + title: String, + labelSize: Dp = 75.dp, + value: String, + fullSize: Dp = 500.dp, + onTextChange: (String) -> Unit, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + leadingIcon: @Composable (() -> Unit)? = null +) { + Row( + modifier = Modifier.width(fullSize), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + WText(text = title, modifier = Modifier.width(labelSize)) + Spacer(modifier = Modifier.width(20.dp)) + WTextField( + title = "", + value = value, + onTextChange = onTextChange, + keyboardOptions = keyboardOptions, + leadingIcon = leadingIcon + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/screens/components/WCheckbox.kt b/app/src/main/java/be/re/writand/screens/components/WCheckbox.kt new file mode 100644 index 0000000..da108fe --- /dev/null +++ b/app/src/main/java/be/re/writand/screens/components/WCheckbox.kt @@ -0,0 +1,30 @@ +package be.re.writand.screens.components + +import androidx.compose.foundation.layout.Row +import androidx.compose.material3.Checkbox +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier + +/** + * Checkbox composable using the theme present in the users settings. + * @param[text] text present next to the checkbox. + * @param[modifier] compose.ui modifier to change the checkbox with its text. + * @param[checked] boolean that tells whether the checkbox is checked or not. + * @param[updateChecked] lambda that is called when a checkbox is clicked. + */ +@Composable +fun WCheckbox( + text: String, + modifier: Modifier = Modifier, + checked: Boolean, + updateChecked: (String) -> Unit +) { + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically + ) { + Checkbox(checked = checked, onCheckedChange = { updateChecked(text) }) + WText(text = text) + } +} \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/screens/components/WLoadingIndicator.kt b/app/src/main/java/be/re/writand/screens/components/WLoadingIndicator.kt new file mode 100644 index 0000000..2161452 --- /dev/null +++ b/app/src/main/java/be/re/writand/screens/components/WLoadingIndicator.kt @@ -0,0 +1,30 @@ +package be.re.writand.screens.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp + +/** + * Loading indicator composable having a progress indicator and a quote. + */ +@Composable +fun WLoadingIndicator() { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 30.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + CircularProgressIndicator(modifier = Modifier.padding(vertical = 10.dp)) + + WText(text = "while( !( succeed = try() ) );", textAlign = TextAlign.Center) + } +} \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/screens/components/WLogoImage.kt b/app/src/main/java/be/re/writand/screens/components/WLogoImage.kt new file mode 100644 index 0000000..3e16a92 --- /dev/null +++ b/app/src/main/java/be/re/writand/screens/components/WLogoImage.kt @@ -0,0 +1,28 @@ +package be.re.writand.screens.components + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import be.re.writand.R + +/** + * Logo composable having the background in the logo. + * @param[modifier] compose.ui modifier to change the Image itself, the .fillMaxSize() is always + * appended to the end of the modifier. + * @param[alignment] alignment parameter used to place the painterResource in the given bounds + * defined by the width and height. + */ +@Composable +fun WLogoImage(modifier: Modifier, alignment: Alignment) { + Image( + painterResource(id = R.drawable.writand_with_background), + contentDescription = "logo", + contentScale = ContentScale.Fit, + modifier = modifier.fillMaxSize(), + alignment = alignment + ) +} \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/screens/components/WRadioButtonsSelector.kt b/app/src/main/java/be/re/writand/screens/components/WRadioButtonsSelector.kt new file mode 100644 index 0000000..6d6dd1f --- /dev/null +++ b/app/src/main/java/be/re/writand/screens/components/WRadioButtonsSelector.kt @@ -0,0 +1,124 @@ +package be.re.writand.screens.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.selection.selectable +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.RadioButton +import androidx.compose.material3.RadioButtonDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +/** + * Given a list of options, it produces a composable of elements having a radio button with text + * on the left, elements are placed all together on a ROW. + * @param[enable] a boolean to disable all the buttons when value is set to false. + * @param[textTitle] text to explain the category of options. + * @param[labelSize] the width [textTitle] is allowed to use. + * @param[options] list of string options the user should be choosing of. + * @param[selectedOption] the current option that is selected by the radiobuttons. + * @param[onOptionSelected] lambda to change which button is selected. + * @param[vMChangeField] view model lambda to call when selected option is changed. + */ +@Composable +fun WRadioButtonsSelectorRowWise( + enable: Boolean, + textTitle: String, + labelSize: Dp = 75.dp, + options: List, + selectedOption: String, + onOptionSelected: (String) -> Unit, + vMChangeField: () -> Unit = {} +) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + WText(text = textTitle, modifier = Modifier.width(labelSize)) + options.forEach { text -> + Row( + modifier = Modifier + .padding(10.dp) + .selectable( + selected = (text == selectedOption), + onClick = { + onOptionSelected(text) + vMChangeField() + } + ), + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton( + selected = (text == selectedOption), + enabled = enable, + colors = RadioButtonDefaults.colors( + selectedColor = MaterialTheme.colorScheme.tertiary, + ), + onClick = { + onOptionSelected(text) + vMChangeField() + } + ) + WText(text = text) + } + } + } +} + +/** + * Given a list of options, it produces a composable of elements having a radio button with text + * on the left, elements are placed all together on a COLUMN. + * @param[enable] a boolean to disable all the buttons when value is set to false. + * @param[textTitle] text to explain the category of options. + * @param[labelSize] the width [textTitle] is allowed to use. + * @param[options] list of string options the user should be choosing of. + * @param[selectedOption] the current option that is selected by the radiobuttons. + * @param[onOptionSelected] lambda to change which button is selected. + * @param[vMChangeField] view model lambda to call when selected option is changed. + */ +@Composable +fun WRadioButtonsSelectorColumnWise( + enable: Boolean, + textTitle: String, + labelSize: Dp = 75.dp, + options: List, + selectedOption: String, + onOptionSelected: (String) -> Unit, + vMChangeField: () -> Unit = {} +) { + Row { + WText(text = textTitle, modifier = Modifier.width(labelSize)) + Column { + options.forEach { text -> + Row( + modifier = Modifier + .selectable( + selected = (text == selectedOption), + onClick = { + onOptionSelected(text) + vMChangeField() + } + ), + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton( + selected = (text == selectedOption), + enabled = enable, + colors = RadioButtonDefaults.colors( + selectedColor = MaterialTheme.colorScheme.tertiary + ), + onClick = { + onOptionSelected(text) + vMChangeField() + } + ) + WText(text = text) + } + } + } + } +} diff --git a/app/src/main/java/be/re/writand/screens/components/WText.kt b/app/src/main/java/be/re/writand/screens/components/WText.kt new file mode 100644 index 0000000..f8b69f0 --- /dev/null +++ b/app/src/main/java/be/re/writand/screens/components/WText.kt @@ -0,0 +1,80 @@ +package be.re.writand.screens.components + +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel +import be.re.writand.screens.SettingsViewModel + +/** + * Composable to use UserSettings in the standard compose.material3.Text, + * all parameters are explained in there, but the [settingsViewModel]. + * @param[settingsViewModel] view model that provides the UserSettings. + * @see[Text]. + */ +@Composable +fun WText( + text: String, + modifier: Modifier = Modifier, + color: Color = MaterialTheme.colorScheme.onPrimary, + fontSize: TextUnit = TextUnit.Unspecified, + fontStyle: FontStyle? = null, + fontWeight: FontWeight? = null, + fontFamily: FontFamily? = null, + letterSpacing: TextUnit = TextUnit.Unspecified, + textDecoration: TextDecoration? = null, + textAlign: TextAlign? = null, + lineHeight: TextUnit = TextUnit.Unspecified, + overflow: TextOverflow = TextOverflow.Clip, + softWrap: Boolean = true, + maxLines: Int = Int.MAX_VALUE, + minLines: Int = 1, + onTextLayout: ((TextLayoutResult) -> Unit)? = null, + style: TextStyle = LocalTextStyle.current, + settingsViewModel: SettingsViewModel = hiltViewModel(), +) { + + val settings by settingsViewModel.settings.collectAsState() + val size = if (fontSize == TextUnit.Unspecified && settings != null) { + settings!!.fontSize.sp + } else if (fontSize != TextUnit.Unspecified) { + fontSize + } else { + 14.sp + } + + Text( + text = text, + modifier = modifier, + color = color, + fontSize = size, + fontStyle = fontStyle, + fontWeight = fontWeight, + fontFamily = fontFamily, + letterSpacing = letterSpacing, + textDecoration = textDecoration, + textAlign = textAlign, + lineHeight = lineHeight, + overflow = overflow, + softWrap = softWrap, + maxLines = maxLines, + minLines = minLines, + onTextLayout = onTextLayout, + style = style + ) +} diff --git a/app/src/main/java/be/re/writand/screens/splash/SplashScreen.kt b/app/src/main/java/be/re/writand/screens/splash/SplashScreen.kt index 5a440b8..9b9ebf1 100644 --- a/app/src/main/java/be/re/writand/screens/splash/SplashScreen.kt +++ b/app/src/main/java/be/re/writand/screens/splash/SplashScreen.kt @@ -5,13 +5,20 @@ import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController import be.re.writand.navigation.WAppDestinations import be.re.writand.screens.components.WButton +/** + * Screen displayed on the startup of the app showing the logo while UserSettings are being loaded. + * @param[navHostController] controller to use the WNavGraph to change screen to right destination. + * @param[vM] view model corresponding to this screen. + */ @Composable fun SplashScreen( - navHostController: NavHostController + navHostController: NavHostController, + vM: SplashViewModel = hiltViewModel() ) { Box(modifier = Modifier.size(20.dp, 20.dp)) { diff --git a/app/src/main/java/be/re/writand/screens/splash/SplashViewModel.kt b/app/src/main/java/be/re/writand/screens/splash/SplashViewModel.kt index 8c7bf72..d5f1ba6 100644 --- a/app/src/main/java/be/re/writand/screens/splash/SplashViewModel.kt +++ b/app/src/main/java/be/re/writand/screens/splash/SplashViewModel.kt @@ -1,4 +1,26 @@ package be.re.writand.screens.splash -class SplashViewModel { +import android.util.Log +import be.re.writand.data.repos.settings.UserSettingsRepository +import be.re.writand.screens.WViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +/** + * View model to be used by SplashScreen that handles the UserSettings and navigation. + * @param[userSettingsRepository] repository to setup the settings if not initialized yet. + */ +@HiltViewModel +class SplashViewModel @Inject constructor( + userSettingsRepository: UserSettingsRepository +): WViewModel() { + + init { + // this is needed for every start of the app to direct the user: + // welcome screen, project picker, editor (--> version 2 is for the editor as well) + launchCatching { + userSettingsRepository.initializeSettingsIfNull() + } + } + } \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/screens/welcome/WelcomeSettingsScreen.kt b/app/src/main/java/be/re/writand/screens/welcome/WelcomeSettingsScreen.kt index 43f5e14..d534387 100644 --- a/app/src/main/java/be/re/writand/screens/welcome/WelcomeSettingsScreen.kt +++ b/app/src/main/java/be/re/writand/screens/welcome/WelcomeSettingsScreen.kt @@ -1,24 +1,160 @@ package be.re.writand.screens.welcome +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController +import be.re.writand.data.local.models.UserLanguage +import be.re.writand.data.local.models.UserTheme import be.re.writand.navigation.WAppDestinations import be.re.writand.screens.components.WButton +import be.re.writand.screens.components.WLabelAndTextField +import be.re.writand.screens.components.WLogoImage +import be.re.writand.screens.components.WRadioButtonsSelectorRowWise +import be.re.writand.screens.components.WText +/** + * Screen displayed for first time users to set their basic settings such as language etc. + * @param[navHostController] controller to use the WNavGraph to change screen to right destination. + * @param[vM] view model that handles everything for this screen for changes in settings and ui. + */ @Composable fun WelcomeSettingsScreen( - navHostController: NavHostController + navHostController: NavHostController, + vM: WelcomeSettingsViewModel = hiltViewModel() ) { - - Box(modifier = Modifier.size(20.dp, 20.dp)) { - WButton( - text = "Finish", - onClick = { navHostController.navigate(WAppDestinations.PROJECT_PICKER) } - ) + val languageOptions = UserLanguage.entries.map { entry -> entry.toString() } + val (languageSelected, onLanguageSelect) = remember { + mutableStateOf(languageOptions[0]) } + val themeOptions = UserTheme.entries.map { entry -> entry.toString() } + val (themeSelected, themeSelect) = remember { + mutableStateOf(themeOptions[0]) + } + + Box( + modifier = Modifier + .background(color = MaterialTheme.colorScheme.secondary), + contentAlignment = Alignment.Center + ) { + Scaffold(modifier = Modifier + .size(600.dp) + .border( + 1.dp, + color = MaterialTheme.colorScheme.tertiary, + shape = RoundedCornerShape(10.dp) + ) + .clip(shape = RoundedCornerShape(10.dp)), + topBar = { + Box(modifier = Modifier.height(75.dp)) { + WLogoImage( + modifier = Modifier.padding(25.dp), + alignment = Alignment.CenterStart + ) + } + }, + bottomBar = { + Row( + modifier = Modifier + .fillMaxWidth(1f) + .padding(15.dp), + verticalAlignment = Alignment.Bottom, + horizontalArrangement = Arrangement.End + ) { + WButton( + text = "Apply", + onClick = { + vM.onApply(languageSelected, themeSelected, vM.textFieldInput.value) + }, + modifier = Modifier.padding(end = 10.dp) + ) + + WButton( + text = "Finish", + onClick = { + navHostController.navigate(WAppDestinations.PROJECT_PICKER) + } + ) + } + } + ) { + Column( + modifier = Modifier + .padding(it) + .fillMaxSize(), + verticalArrangement = Arrangement.Center, + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(50.dp, 15.dp) + ) { + WText( + text = "Welcome", + color = MaterialTheme.colorScheme.tertiary, + fontWeight = FontWeight.ExtraBold, + fontSize = 25.sp + ) + WText( + text = " to settings", + color = MaterialTheme.colorScheme.onPrimary, + fontWeight = FontWeight.ExtraBold, + fontSize = 25.sp + ) + } + + Column( + modifier = Modifier.padding(start = 100.dp) + ) { + WRadioButtonsSelectorRowWise( + enable = true, + textTitle = "Language", + options = languageOptions, + selectedOption = languageSelected, + onOptionSelected = onLanguageSelect + ) + + WRadioButtonsSelectorRowWise( + enable = true, + textTitle = "Theme", + options = themeOptions, + selectedOption = themeSelected, + onOptionSelected = themeSelect + ) + + WLabelAndTextField( + title = "Fontsize", + value = vM.textFieldInput.value, + fullSize = 200.dp, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), + onTextChange = vM::onTextFieldChange + ) + } + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/screens/welcome/WelcomeSettingsViewModel.kt b/app/src/main/java/be/re/writand/screens/welcome/WelcomeSettingsViewModel.kt new file mode 100644 index 0000000..11730c1 --- /dev/null +++ b/app/src/main/java/be/re/writand/screens/welcome/WelcomeSettingsViewModel.kt @@ -0,0 +1,64 @@ +package be.re.writand.screens.welcome + +import androidx.compose.runtime.mutableStateOf +import be.re.writand.data.local.models.UserSettings +import be.re.writand.data.repos.settings.UserSettingsRepository +import be.re.writand.domain.settings.SetFontSizeSettingsUseCase +import be.re.writand.domain.settings.SetLanguageSettingsUseCase +import be.re.writand.domain.settings.SetThemeSettingsUseCase +import be.re.writand.screens.WViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +/** + * View model to be used by WelcomeSettingsScreen that handles the UserSettings and buttons. + * @param[userSettingsRepository] repository to setup the settings if not initialized yet. + * @param[setLanguage] use-case that alters the UserLanguage of the UserSettings if a valid String + * is given as argument. + * @param[setTheme] use-case that alters the UserTheme of the UserSettings if a valid String + * is given as argument. + * @param[setFontSize] use-case that alters the fontSize of the UserSettings if the field contains + * a String that can be converted to a Float above 0. + */ +@HiltViewModel +class WelcomeSettingsViewModel @Inject constructor( + private val userSettingsRepository: UserSettingsRepository, + private val setLanguage: SetLanguageSettingsUseCase, + private val setTheme: SetThemeSettingsUseCase, + private val setFontSize: SetFontSizeSettingsUseCase +) : WViewModel() { + + private val _settings = MutableStateFlow(null) + val settings: StateFlow = _settings + + val textFieldInput = mutableStateOf(_settings.value?.fontSize.toString()) + + init { + launchCatching { + userSettingsRepository.userSettings.collect { settings -> + _settings.value = settings + textFieldInput.value = settings.fontSize.toString() + } + } + } + + fun onTextFieldChange(newValue: String) { + textFieldInput.value = newValue + } + + fun onApply(language: String, theme: String, fontSize: String) { + launchCatching { + setLanguage(language) + setTheme(theme) + + val errorFontSize: NumberFormatException? = setFontSize(fontSize) + errorFontSize.let { + // reset the text field to its old value + textFieldInput.value = _settings.value?.fontSize.toString() + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/screens/welcome/WelcomeStartScreen.kt b/app/src/main/java/be/re/writand/screens/welcome/WelcomeStartScreen.kt index 352c3bf..99c72b1 100644 --- a/app/src/main/java/be/re/writand/screens/welcome/WelcomeStartScreen.kt +++ b/app/src/main/java/be/re/writand/screens/welcome/WelcomeStartScreen.kt @@ -1,24 +1,103 @@ package be.re.writand.screens.welcome +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.navigation.NavHostController import be.re.writand.navigation.WAppDestinations import be.re.writand.screens.components.WButton +import be.re.writand.screens.components.WLogoImage +import be.re.writand.screens.components.WText +/** + * Screen displayed for first time users to welcome them to the app. + * @param[navHostController] controller to use the WNavGraph to change screen to right destination. + */ @Composable fun WelcomeStartScreen( navHostController: NavHostController ) { - - Box(modifier = Modifier.size(20.dp, 20.dp)) { - WButton( - text = "Next", - onClick = { navHostController.navigate(WAppDestinations.WELCOME_TOS) } - ) + Box( + modifier = Modifier + .background(color = MaterialTheme.colorScheme.secondary), + contentAlignment = Alignment.Center + ) { + Scaffold(modifier = Modifier + .size(600.dp) + .border( + 1.dp, + color = MaterialTheme.colorScheme.tertiary, + shape = RoundedCornerShape(10.dp) + ) + .clip(shape = RoundedCornerShape(10.dp)), + topBar = { + Box(modifier = Modifier.height(75.dp)) { + WLogoImage( + modifier = Modifier.padding(25.dp), + alignment = Alignment.CenterStart + ) + } + }, + bottomBar = { + Box( + modifier = Modifier + .fillMaxWidth(1f) + .padding(15.dp), + contentAlignment = Alignment.BottomEnd + ) { + WButton( + text = "Next", + onClick = { navHostController.navigate(WAppDestinations.WELCOME_TOS) } + ) + } + } + ) { it -> + Column( + modifier = Modifier + .padding(it) + .fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(50.dp, 15.dp) + ) { + WText( + text = "Welcome", + color = MaterialTheme.colorScheme.tertiary, + fontWeight = FontWeight.ExtraBold, + fontSize = 25.sp + ) + WText( + text = " to Writand", + color = MaterialTheme.colorScheme.onPrimary, + fontWeight = FontWeight.ExtraBold, + fontSize = 25.sp + ) + } + WText(text = "Any fool can write code that a computer can understand.\nGood programmers can write code that humans can understand.") + WText(text = "~ Martin Fowler") + } + } } - } \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/screens/welcome/WelcomeTOSScreen.kt b/app/src/main/java/be/re/writand/screens/welcome/WelcomeTOSScreen.kt index df8b933..891d6ae 100644 --- a/app/src/main/java/be/re/writand/screens/welcome/WelcomeTOSScreen.kt +++ b/app/src/main/java/be/re/writand/screens/welcome/WelcomeTOSScreen.kt @@ -1,24 +1,152 @@ package be.re.writand.screens.welcome +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController import be.re.writand.navigation.WAppDestinations import be.re.writand.screens.components.WButton +import be.re.writand.screens.components.WCheckbox +import be.re.writand.screens.components.WLoadingIndicator +import be.re.writand.screens.components.WLogoImage +import be.re.writand.screens.components.WText +/** + * Screen displayed for first time users to set their basic settings such as language etc. + * @param[navHostController] controller to use the WNavGraph to change screen to right destination. + * @param[vM] view model that gives access to the TOS file content. + */ @Composable fun WelcomeTOSScreen( - navHostController: NavHostController + navHostController: NavHostController, + vM: WelcomeTOSViewModel = hiltViewModel() ) { + var checked by remember { mutableStateOf(false) } + val uiState by vM.uiState.collectAsState() - Box(modifier = Modifier.size(20.dp, 20.dp)) { - WButton( - text = "Next", - onClick = { navHostController.navigate(WAppDestinations.WELCOME_SETTINGS) } - ) + Box( + modifier = Modifier + .background(color = MaterialTheme.colorScheme.secondary), + contentAlignment = Alignment.Center + ) { + Scaffold(modifier = Modifier + .size(600.dp) + .border( + 1.dp, + color = MaterialTheme.colorScheme.tertiary, + shape = RoundedCornerShape(10.dp) + ) + .clip(shape = RoundedCornerShape(10.dp)), + topBar = { + Box(modifier = Modifier.height(75.dp)) { + WLogoImage( + modifier = Modifier.padding(25.dp), + alignment = Alignment.CenterStart + ) + } + }, + bottomBar = { + Box( + modifier = Modifier + .fillMaxWidth(1f) + .padding(15.dp), + contentAlignment = Alignment.BottomEnd + ) { + WButton( + text = "Next", + enabled = checked, + onClick = { navHostController.navigate(WAppDestinations.WELCOME_SETTINGS) } + ) + } + } + ) { it -> + Column( + modifier = Modifier + .padding(it) + .fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(50.dp, 15.dp) + ) { + WText( + text = "Welcome", + color = MaterialTheme.colorScheme.tertiary, + fontWeight = FontWeight.ExtraBold, + fontSize = 25.sp + ) + WText( + text = " to TOS", + color = MaterialTheme.colorScheme.onPrimary, + fontWeight = FontWeight.ExtraBold, + fontSize = 25.sp + ) + } + + Box( + modifier = Modifier + .height(350.dp) + .padding(horizontal = 50.dp, vertical = 25.dp) + .border(1.dp, color = MaterialTheme.colorScheme.secondary) + ) { + when (val value = uiState) { + WelcomeTOSUiState.Loading -> { + WLoadingIndicator() + } + + is WelcomeTOSUiState.Success -> { + LazyColumn { + items(value.tosStrings.size) { item -> + Text(text = value.tosStrings[item]) + } + } + } + + is WelcomeTOSUiState.Failed -> { + Text(text = value.message) + } + } + } + + WCheckbox( + text = "Accept", + modifier = Modifier + .padding(horizontal = 50.dp) + .fillMaxWidth(), + checked = checked, + updateChecked = { checked = !checked } + ) + } + } } - } \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/screens/welcome/WelcomeTOSUiState.kt b/app/src/main/java/be/re/writand/screens/welcome/WelcomeTOSUiState.kt new file mode 100644 index 0000000..f903cd8 --- /dev/null +++ b/app/src/main/java/be/re/writand/screens/welcome/WelcomeTOSUiState.kt @@ -0,0 +1,13 @@ +package be.re.writand.screens.welcome + +/** + * Interface to present the 3 differences stages of getting the TOS file content: + * - [Loading]: the content is pending. + * - [Success]: the TOS file is ready, and given in a list of Strings. + * - [Failed]: unable to get the TOS content from the file, give back an error message. + */ +sealed interface WelcomeTOSUiState { + object Loading: WelcomeTOSUiState + data class Success(val tosStrings: List): WelcomeTOSUiState + data class Failed(val message: String): WelcomeTOSUiState +} \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/screens/welcome/WelcomeTOSViewModel.kt b/app/src/main/java/be/re/writand/screens/welcome/WelcomeTOSViewModel.kt new file mode 100644 index 0000000..c5a3ef0 --- /dev/null +++ b/app/src/main/java/be/re/writand/screens/welcome/WelcomeTOSViewModel.kt @@ -0,0 +1,33 @@ +package be.re.writand.screens.welcome + +import be.re.writand.domain.tos.GetTOSUseCase +import be.re.writand.screens.WViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +/** + * View model to be used by WelcomeTOSScreen that handles the TOS content. + * This view model uses the WelcomeTOSUiState to enable handling failures of getting the file. + * @param[getTOSUseCase] use case to get the TOS content in a list of Strings. + */ +@HiltViewModel +class WelcomeTOSViewModel @Inject constructor( + private val getTOSUseCase: GetTOSUseCase +): WViewModel() { + + private val _uiState: MutableStateFlow = MutableStateFlow(WelcomeTOSUiState.Loading) + val uiState: StateFlow = _uiState + + init { + launchCatching { + val tosList: List = getTOSUseCase() + if (tosList.isNotEmpty()) { + _uiState.value = WelcomeTOSUiState.Success(tosList) + } else { + _uiState.value = WelcomeTOSUiState.Failed("Terms of Services could not be loaded.") + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/ui/theme/Theme.kt b/app/src/main/java/be/re/writand/ui/theme/Theme.kt index 1279751..614f474 100644 --- a/app/src/main/java/be/re/writand/ui/theme/Theme.kt +++ b/app/src/main/java/be/re/writand/ui/theme/Theme.kt @@ -11,14 +11,14 @@ private val DarkColorPalette = darkColorScheme( primary = MainGrey, secondary = VariantDarkGrey, tertiary = MainGreen, - onBackground = Color.White + onPrimary = Color.White ) private val LightColorPalette = lightColorScheme( primary = Color.White, secondary = VariantLightGrey, tertiary = MainGreen, - onBackground = Color.Black + onPrimary = Color.Black /* Other default colors to override background = Color.White, diff --git a/app/src/main/proto/settings.proto b/app/src/main/proto/settings.proto index 032e8d1..2a00429 100644 --- a/app/src/main/proto/settings.proto +++ b/app/src/main/proto/settings.proto @@ -28,5 +28,5 @@ message ProtoSettings { ProtoTheme theme = 2; uint32 max_saved_projects = 3; uint32 max_saved_files = 4; - uint32 font_size = 5; + float font_size = 5; }; \ No newline at end of file diff --git a/app/src/main/res/drawable/writand_no_background.png b/app/src/main/res/drawable/writand_no_background.png new file mode 100644 index 0000000000000000000000000000000000000000..02234c83294cb341decb81f6378739321f5b6ee1 GIT binary patch literal 29370 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 U){!U~@FS3-oSJO4v{~r?0k#j|{Qv*} literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/writand_with_background.png b/app/src/main/res/drawable/writand_with_background.png new file mode 100644 index 0000000000000000000000000000000000000000..c4fdb5518a29c385cd311d20bda786c40ad4c4e2 GIT binary patch literal 29552 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 literal 0 HcmV?d00001 diff --git a/build.gradle b/build.gradle index 343fae8..69a84fb 100644 --- a/build.gradle +++ b/build.gradle @@ -8,6 +8,6 @@ plugins { id 'com.android.application' version '8.5.1' apply false id 'com.android.library' version '8.5.1' apply false id 'org.jetbrains.kotlin.android' version '1.9.0' apply false - id 'com.google.dagger.hilt.android' version '2.44' apply false + id 'com.google.dagger.hilt.android' version '2.49' apply false id 'org.jetbrains.kotlin.plugin.serialization' version '1.6.21' } \ No newline at end of file