forked from Writand/writand
feat: welcome screens
This commit is contained in:
parent
c822126703
commit
5c56947c4f
36 changed files with 1176 additions and 65 deletions
|
@ -90,8 +90,10 @@ dependencies {
|
||||||
testImplementation "org.mockito:mockito-android:4.1.0"
|
testImplementation "org.mockito:mockito-android:4.1.0"
|
||||||
|
|
||||||
// Hilt
|
// Hilt
|
||||||
implementation "com.google.dagger:hilt-android:2.48"
|
implementation "com.google.dagger:hilt-android:2.49"
|
||||||
kapt "com.google.dagger:hilt-compiler:2.48"
|
kapt "com.google.dagger:hilt-compiler:2.49"
|
||||||
|
|
||||||
|
implementation "androidx.hilt:hilt-navigation-compose:1.2.0"
|
||||||
|
|
||||||
// Room
|
// Room
|
||||||
implementation 'androidx.room:room-runtime:2.6.1'
|
implementation 'androidx.room:room-runtime:2.6.1'
|
||||||
|
@ -99,8 +101,11 @@ dependencies {
|
||||||
implementation 'androidx.room:room-ktx:2.6.1'
|
implementation 'androidx.room:room-ktx:2.6.1'
|
||||||
|
|
||||||
// Proto DataStore
|
// Proto DataStore
|
||||||
implementation 'androidx.datastore:datastore:1.1.1'
|
|
||||||
implementation 'com.google.protobuf:protobuf-javalite:3.21.7'
|
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-tooling:$compose_ui_version"
|
||||||
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_ui_version"
|
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_ui_version"
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".WApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
|
|
|
@ -3,18 +3,18 @@ package be.re.writand
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import be.re.writand.navigation.WNavGraph
|
import be.re.writand.navigation.WNavGraph
|
||||||
import be.re.writand.ui.theme.WritandTheme
|
import be.re.writand.ui.theme.WritandTheme
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
@AndroidEntryPoint
|
||||||
|
class MainActivity: ComponentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContent {
|
setContent {
|
||||||
|
@ -26,7 +26,7 @@ class MainActivity : ComponentActivity() {
|
||||||
) {
|
) {
|
||||||
WNavGraph(
|
WNavGraph(
|
||||||
navHostController = rememberNavController(),
|
navHostController = rememberNavController(),
|
||||||
modifier = Modifier.padding(5.dp)
|
modifier = Modifier.background(color = MaterialTheme.colorScheme.primary)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
7
app/src/main/java/be/re/writand/WApplication.kt
Normal file
7
app/src/main/java/be/re/writand/WApplication.kt
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package be.re.writand
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
|
|
||||||
|
@HiltAndroidApp
|
||||||
|
class WApplication : Application() {}
|
|
@ -1,7 +1,10 @@
|
||||||
package be.re.writand.data.local
|
package be.re.writand.data.local
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import androidx.datastore.core.CorruptionException
|
import androidx.datastore.core.CorruptionException
|
||||||
|
import androidx.datastore.core.DataStore
|
||||||
import androidx.datastore.core.Serializer
|
import androidx.datastore.core.Serializer
|
||||||
|
import androidx.datastore.dataStore
|
||||||
import be.re.writand.ProtoSettings
|
import be.re.writand.ProtoSettings
|
||||||
import com.google.protobuf.InvalidProtocolBufferException
|
import com.google.protobuf.InvalidProtocolBufferException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
@ -29,3 +32,11 @@ class SettingsSerializer @Inject constructor() : Serializer<ProtoSettings> {
|
||||||
) = t.writeTo(output)
|
) = t.writeTo(output)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Added field to Context to enable getting an instance of ProtoSettings where needed.
|
||||||
|
*/
|
||||||
|
val Context.protoSettingsDataStore: DataStore<ProtoSettings> by dataStore(
|
||||||
|
fileName = "settings.proto",
|
||||||
|
serializer = SettingsSerializer()
|
||||||
|
)
|
||||||
|
|
|
@ -5,7 +5,38 @@ package be.re.writand.data.local.models
|
||||||
*/
|
*/
|
||||||
enum class UserLanguage {
|
enum class UserLanguage {
|
||||||
ENGLISH,
|
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 {
|
enum class UserTheme {
|
||||||
DARK,
|
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 userTheme: UserTheme = UserTheme.DARK,
|
||||||
var maxSavedProjects: Int = 3,
|
var maxSavedProjects: Int = 3,
|
||||||
var maxSavedFiles: Int = 5,
|
var maxSavedFiles: Int = 5,
|
||||||
var fontSize: Int = 10
|
var fontSize: Float = 14.0f
|
||||||
)
|
)
|
|
@ -1,15 +1,18 @@
|
||||||
package be.re.writand.data.repos.settings
|
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.UserLanguage
|
||||||
import be.re.writand.data.local.models.UserSettings
|
import be.re.writand.data.local.models.UserSettings
|
||||||
import be.re.writand.data.local.models.UserTheme
|
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 {
|
interface IUserSettingsRepository {
|
||||||
|
|
||||||
suspend fun getUserSettings(): UserSettings
|
suspend fun initializeSettingsIfNull()
|
||||||
|
|
||||||
|
suspend fun toUserSettings(protoSettings: ProtoSettings): UserSettings
|
||||||
|
|
||||||
suspend fun setLanguage(userLanguage: UserLanguage)
|
suspend fun setLanguage(userLanguage: UserLanguage)
|
||||||
|
|
||||||
|
@ -19,5 +22,5 @@ interface IUserSettingsRepository {
|
||||||
|
|
||||||
suspend fun setMaxSavedFiles(maxSaved: Int)
|
suspend fun setMaxSavedFiles(maxSaved: Int)
|
||||||
|
|
||||||
suspend fun setFontSize(size: Int)
|
suspend fun setFontSize(size: Float)
|
||||||
}
|
}
|
|
@ -1,37 +1,70 @@
|
||||||
package be.re.writand.data.repos.settings
|
package be.re.writand.data.repos.settings
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import androidx.datastore.core.DataStore
|
import androidx.datastore.core.DataStore
|
||||||
|
import androidx.datastore.core.IOException
|
||||||
|
import be.re.writand.ProtoLanguage
|
||||||
import be.re.writand.ProtoSettings
|
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.UserLanguage
|
||||||
import be.re.writand.data.local.models.UserSettings
|
import be.re.writand.data.local.models.UserSettings
|
||||||
import be.re.writand.data.local.models.UserTheme
|
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.toProtoLanguage
|
||||||
import be.re.writand.utils.toProtoTheme
|
import be.re.writand.utils.toProtoTheme
|
||||||
import be.re.writand.utils.toUserLanguage
|
import be.re.writand.utils.toUserLanguage
|
||||||
import be.re.writand.utils.toUserTheme
|
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.first
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class UserSettingsRepository @Inject constructor(
|
class UserSettingsRepository @Inject constructor(
|
||||||
private val store: DataStore<ProtoSettings>
|
context: Context
|
||||||
) : IUserSettingsRepository {
|
) : IUserSettingsRepository {
|
||||||
|
|
||||||
private val userSettings = store.data
|
private val dataStore: DataStore<ProtoSettings> = context.protoSettingsDataStore
|
||||||
|
val userSettings: Flow<UserSettings> = 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(
|
return UserSettings(
|
||||||
userLanguage = userSettings.first().language.toUserLanguage(),
|
userLanguage = protoSettings.language.toUserLanguage(),
|
||||||
userTheme = userSettings.first().theme.toUserTheme(),
|
userTheme = protoSettings.theme.toUserTheme(),
|
||||||
maxSavedProjects = userSettings.first().maxSavedProjects,
|
maxSavedProjects = protoSettings.maxSavedProjects,
|
||||||
maxSavedFiles = userSettings.first().maxSavedFiles,
|
maxSavedFiles = protoSettings.maxSavedFiles,
|
||||||
fontSize = userSettings.first().fontSize
|
fontSize = protoSettings.fontSize
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setLanguage(userLanguage: UserLanguage) {
|
override suspend fun setLanguage(userLanguage: UserLanguage) {
|
||||||
store.updateData {
|
dataStore.updateData {
|
||||||
val builder = it.toBuilder()
|
val builder = it.toBuilder()
|
||||||
builder.setLanguage(userLanguage.toProtoLanguage())
|
builder.setLanguage(userLanguage.toProtoLanguage())
|
||||||
builder.build()
|
builder.build()
|
||||||
|
@ -39,7 +72,7 @@ class UserSettingsRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setTheme(userTheme: UserTheme) {
|
override suspend fun setTheme(userTheme: UserTheme) {
|
||||||
store.updateData {
|
dataStore.updateData {
|
||||||
val builder = it.toBuilder()
|
val builder = it.toBuilder()
|
||||||
builder.setTheme(userTheme.toProtoTheme())
|
builder.setTheme(userTheme.toProtoTheme())
|
||||||
builder.build()
|
builder.build()
|
||||||
|
@ -47,7 +80,7 @@ class UserSettingsRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setMaxSavedProjects(maxSaved: Int) {
|
override suspend fun setMaxSavedProjects(maxSaved: Int) {
|
||||||
store.updateData {
|
dataStore.updateData {
|
||||||
val builder = it.toBuilder()
|
val builder = it.toBuilder()
|
||||||
builder.setMaxSavedProjects(maxSaved)
|
builder.setMaxSavedProjects(maxSaved)
|
||||||
builder.build()
|
builder.build()
|
||||||
|
@ -55,15 +88,15 @@ class UserSettingsRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setMaxSavedFiles(maxSaved: Int) {
|
override suspend fun setMaxSavedFiles(maxSaved: Int) {
|
||||||
store.updateData {
|
dataStore.updateData {
|
||||||
val builder = it.toBuilder()
|
val builder = it.toBuilder()
|
||||||
builder.setMaxSavedFiles(maxSaved)
|
builder.setMaxSavedFiles(maxSaved)
|
||||||
builder.build()
|
builder.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setFontSize(size: Int) {
|
override suspend fun setFontSize(size: Float) {
|
||||||
store.updateData {
|
dataStore.updateData {
|
||||||
val builder = it.toBuilder()
|
val builder = it.toBuilder()
|
||||||
builder.setFontSize(size)
|
builder.setFontSize(size)
|
||||||
builder.build()
|
builder.build()
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
package be.re.writand.data.repos.tos
|
package be.re.writand.data.repos.tos
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
|
||||||
import be.re.writand.data.local.models.TermsOfService
|
import be.re.writand.data.local.models.TermsOfService
|
||||||
import be.re.writand.utils.Version
|
import be.re.writand.utils.Version
|
||||||
import java.io.File
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.util.concurrent.Flow
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class TOSRepository @Inject constructor(
|
class TOSRepository @Inject constructor(
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
package be.re.writand.di
|
package be.re.writand.di
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.AssetManager
|
|
||||||
import be.re.writand.data.local.db.ProjectsDao
|
import be.re.writand.data.local.db.ProjectsDao
|
||||||
import be.re.writand.data.local.db.ProjectsDatabase
|
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.ITOSRepository
|
||||||
import be.re.writand.data.repos.tos.TOSRepository
|
import be.re.writand.data.repos.tos.TOSRepository
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
|
@ -38,7 +39,12 @@ class DatabaseModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideTOSRepository(repo: TOSRepository): ITOSRepository {
|
fun provideUserSettingsRepository(@ApplicationContext context: Context): IUserSettingsRepository {
|
||||||
return repo
|
return UserSettingsRepository(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun provideTOSRepository(@ApplicationContext context: Context): ITOSRepository {
|
||||||
|
return TOSRepository(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
17
app/src/main/java/be/re/writand/domain/tos/GetTOSUseCase.kt
Normal file
17
app/src/main/java/be/re/writand/domain/tos/GetTOSUseCase.kt
Normal file
|
@ -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<String> {
|
||||||
|
return tosRepository.getTOS().text
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
31
app/src/main/java/be/re/writand/screens/SettingsViewModel.kt
Normal file
31
app/src/main/java/be/re/writand/screens/SettingsViewModel.kt
Normal file
|
@ -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<UserSettings?>(null)
|
||||||
|
val settings: StateFlow<UserSettings?> = _settings
|
||||||
|
|
||||||
|
init {
|
||||||
|
launchCatching {
|
||||||
|
userSettingsRepository.userSettings.collect { settings ->
|
||||||
|
_settings.value = settings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
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.
|
* A standard button with predefined color and shape.
|
||||||
* @param[text] the text that should be shown in the button.
|
* @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[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.
|
* @param[enabled] boolean to express if the button is enabled to be clicked or not.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun WButton(
|
fun WButton(
|
||||||
text: String,
|
text: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
enabled: Boolean = true
|
enabled: Boolean = true
|
||||||
) {
|
) {
|
||||||
Button(
|
Button(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
|
modifier = modifier,
|
||||||
colors = ButtonDefaults.buttonColors(containerColor = MainGreen),
|
colors = ButtonDefaults.buttonColors(containerColor = MainGreen),
|
||||||
shape = RoundedCornerShape(10.dp),
|
shape = RoundedCornerShape(10.dp),
|
||||||
enabled = enabled
|
enabled = enabled
|
||||||
) {
|
) {
|
||||||
Text(text = text, color = Color.Black, modifier = Modifier.padding(5.dp))
|
WText(text = text, color = Color.Black, modifier = Modifier.padding(5.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
|
@ -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<String>,
|
||||||
|
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<String>,
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
80
app/src/main/java/be/re/writand/screens/components/WText.kt
Normal file
80
app/src/main/java/be/re/writand/screens/components/WText.kt
Normal file
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
|
@ -5,13 +5,20 @@ import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import be.re.writand.navigation.WAppDestinations
|
import be.re.writand.navigation.WAppDestinations
|
||||||
import be.re.writand.screens.components.WButton
|
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
|
@Composable
|
||||||
fun SplashScreen(
|
fun SplashScreen(
|
||||||
navHostController: NavHostController
|
navHostController: NavHostController,
|
||||||
|
vM: SplashViewModel = hiltViewModel()
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Box(modifier = Modifier.size(20.dp, 20.dp)) {
|
Box(modifier = Modifier.size(20.dp, 20.dp)) {
|
||||||
|
|
|
@ -1,4 +1,26 @@
|
||||||
package be.re.writand.screens.splash
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,24 +1,160 @@
|
||||||
package be.re.writand.screens.welcome
|
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.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.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.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.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.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.navigation.NavHostController
|
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.navigation.WAppDestinations
|
||||||
import be.re.writand.screens.components.WButton
|
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
|
@Composable
|
||||||
fun WelcomeSettingsScreen(
|
fun WelcomeSettingsScreen(
|
||||||
navHostController: NavHostController
|
navHostController: NavHostController,
|
||||||
|
vM: WelcomeSettingsViewModel = hiltViewModel()
|
||||||
) {
|
) {
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
|
||||||
Box(modifier = Modifier.size(20.dp, 20.dp)) {
|
|
||||||
WButton(
|
WButton(
|
||||||
text = "Finish",
|
text = "Finish",
|
||||||
onClick = { navHostController.navigate(WAppDestinations.PROJECT_PICKER) }
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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<UserSettings?>(null)
|
||||||
|
val settings: StateFlow<UserSettings?> = _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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,24 +1,103 @@
|
||||||
package be.re.writand.screens.welcome
|
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.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.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.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import be.re.writand.navigation.WAppDestinations
|
import be.re.writand.navigation.WAppDestinations
|
||||||
import be.re.writand.screens.components.WButton
|
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
|
@Composable
|
||||||
fun WelcomeStartScreen(
|
fun WelcomeStartScreen(
|
||||||
navHostController: NavHostController
|
navHostController: NavHostController
|
||||||
) {
|
) {
|
||||||
|
Box(
|
||||||
Box(modifier = Modifier.size(20.dp, 20.dp)) {
|
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(
|
WButton(
|
||||||
text = "Next",
|
text = "Next",
|
||||||
onClick = { navHostController.navigate(WAppDestinations.WELCOME_TOS) }
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,24 +1,152 @@
|
||||||
package be.re.writand.screens.welcome
|
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.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.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.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.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.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import be.re.writand.navigation.WAppDestinations
|
import be.re.writand.navigation.WAppDestinations
|
||||||
import be.re.writand.screens.components.WButton
|
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
|
@Composable
|
||||||
fun WelcomeTOSScreen(
|
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)) {
|
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(
|
WButton(
|
||||||
text = "Next",
|
text = "Next",
|
||||||
|
enabled = checked,
|
||||||
onClick = { navHostController.navigate(WAppDestinations.WELCOME_SETTINGS) }
|
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 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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<String>): WelcomeTOSUiState
|
||||||
|
data class Failed(val message: String): WelcomeTOSUiState
|
||||||
|
}
|
|
@ -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<WelcomeTOSUiState> = MutableStateFlow(WelcomeTOSUiState.Loading)
|
||||||
|
val uiState: StateFlow<WelcomeTOSUiState> = _uiState
|
||||||
|
|
||||||
|
init {
|
||||||
|
launchCatching {
|
||||||
|
val tosList: List<String> = getTOSUseCase()
|
||||||
|
if (tosList.isNotEmpty()) {
|
||||||
|
_uiState.value = WelcomeTOSUiState.Success(tosList)
|
||||||
|
} else {
|
||||||
|
_uiState.value = WelcomeTOSUiState.Failed("Terms of Services could not be loaded.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,14 +11,14 @@ private val DarkColorPalette = darkColorScheme(
|
||||||
primary = MainGrey,
|
primary = MainGrey,
|
||||||
secondary = VariantDarkGrey,
|
secondary = VariantDarkGrey,
|
||||||
tertiary = MainGreen,
|
tertiary = MainGreen,
|
||||||
onBackground = Color.White
|
onPrimary = Color.White
|
||||||
)
|
)
|
||||||
|
|
||||||
private val LightColorPalette = lightColorScheme(
|
private val LightColorPalette = lightColorScheme(
|
||||||
primary = Color.White,
|
primary = Color.White,
|
||||||
secondary = VariantLightGrey,
|
secondary = VariantLightGrey,
|
||||||
tertiary = MainGreen,
|
tertiary = MainGreen,
|
||||||
onBackground = Color.Black
|
onPrimary = Color.Black
|
||||||
|
|
||||||
/* Other default colors to override
|
/* Other default colors to override
|
||||||
background = Color.White,
|
background = Color.White,
|
||||||
|
|
|
@ -28,5 +28,5 @@ message ProtoSettings {
|
||||||
ProtoTheme theme = 2;
|
ProtoTheme theme = 2;
|
||||||
uint32 max_saved_projects = 3;
|
uint32 max_saved_projects = 3;
|
||||||
uint32 max_saved_files = 4;
|
uint32 max_saved_files = 4;
|
||||||
uint32 font_size = 5;
|
float font_size = 5;
|
||||||
};
|
};
|
BIN
app/src/main/res/drawable/writand_no_background.png
Normal file
BIN
app/src/main/res/drawable/writand_no_background.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
BIN
app/src/main/res/drawable/writand_with_background.png
Normal file
BIN
app/src/main/res/drawable/writand_with_background.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
|
@ -8,6 +8,6 @@ plugins {
|
||||||
id 'com.android.application' version '8.5.1' apply false
|
id 'com.android.application' version '8.5.1' apply false
|
||||||
id 'com.android.library' 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 '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'
|
id 'org.jetbrains.kotlin.plugin.serialization' version '1.6.21'
|
||||||
}
|
}
|
Loading…
Reference in a new issue