From 0c98426f985c239dda6d59a0799c37d63512caf0 Mon Sep 17 00:00:00 2001 From: Emma Vandewalle Date: Thu, 5 Sep 2024 19:20:53 +0000 Subject: [PATCH] feat: info popup screen implemented + some grammar fixed in other files --- app/build.gradle | 2 +- .../be/re/writand/domain/tos/GetTOSUseCase.kt | 7 +- .../editor/editorspace/EditorSpaceUiState.kt | 6 +- .../be/re/writand/screens/info/InfoPopup.kt | 256 ++++++++++++++++++ .../writand/screens/info/InfoPopupUiState.kt | 16 ++ .../screens/info/InfoPopupViewModel.kt | 45 +++ .../writand/screens/settings/SettingsPopup.kt | 2 +- .../screens/welcome/WelcomeTOSUiState.kt | 6 +- .../screens/welcome/WelcomeTOSViewModel.kt | 2 +- .../main/java/be/re/writand/utils/Version.kt | 5 + 10 files changed, 335 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/be/re/writand/screens/info/InfoPopup.kt create mode 100644 app/src/main/java/be/re/writand/screens/info/InfoPopupUiState.kt create mode 100644 app/src/main/java/be/re/writand/screens/info/InfoPopupViewModel.kt diff --git a/app/build.gradle b/app/build.gradle index dc84850..b93b4a0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -69,7 +69,7 @@ dependencies { implementation 'com.google.ar:core:1.45.0' implementation 'androidx.navigation:navigation-runtime-ktx:2.7.7' implementation 'androidx.navigation:navigation-compose:2.7.7' - implementation 'androidx.documentfile:documentfile:1.0.1' + implementation 'androidx.compose.foundation:foundation-layout-android:1.6.0' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.2.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' 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 index 64d4f04..3a2414b 100644 --- a/app/src/main/java/be/re/writand/domain/tos/GetTOSUseCase.kt +++ b/app/src/main/java/be/re/writand/domain/tos/GetTOSUseCase.kt @@ -1,17 +1,18 @@ package be.re.writand.domain.tos +import be.re.writand.data.local.models.TermsOfService import be.re.writand.data.repos.tos.ITOSRepository import javax.inject.Inject /** - * Use-case for getting back the TOS file in a list. + * Use-case for getting back the Terms of Services. */ class GetTOSUseCase @Inject constructor( private val tosRepository: ITOSRepository ) { - operator fun invoke(): List { - return tosRepository.getTOS().text + operator fun invoke(): TermsOfService { + return tosRepository.getTOS() } } \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/screens/editor/editorspace/EditorSpaceUiState.kt b/app/src/main/java/be/re/writand/screens/editor/editorspace/EditorSpaceUiState.kt index 231e472..7ab134a 100644 --- a/app/src/main/java/be/re/writand/screens/editor/editorspace/EditorSpaceUiState.kt +++ b/app/src/main/java/be/re/writand/screens/editor/editorspace/EditorSpaceUiState.kt @@ -3,9 +3,9 @@ package be.re.writand.screens.editor.editorspace /** * Interface to present the 3 differences stages of getting the contents of file: - * - [Loading]: the content is pending. - * - [Success]: the file content is ready, and is given in a String. - * - [Failed]: unable to get the content from the file, give back an error message. + * - [Loading] the content is pending. + * - [Success] the file content is ready, and is given in a String. + * - [Failed] unable to get the content from the file, give back an error message. */ sealed interface EditorSpaceUiState { object Loading : EditorSpaceUiState diff --git a/app/src/main/java/be/re/writand/screens/info/InfoPopup.kt b/app/src/main/java/be/re/writand/screens/info/InfoPopup.kt new file mode 100644 index 0000000..b7fbc1d --- /dev/null +++ b/app/src/main/java/be/re/writand/screens/info/InfoPopup.kt @@ -0,0 +1,256 @@ +package be.re.writand.screens.info + +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.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +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.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +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.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import androidx.hilt.navigation.compose.hiltViewModel +import be.re.writand.screens.components.WLoadingIndicator +import be.re.writand.screens.components.WLogoImage +import be.re.writand.screens.components.WText + +/** + * Popup screen to display all the information about the app, and provide links for support etc. + * @param[vM] view model corresponding to this screen. + */ +@Composable +fun InfoPopup( + vM: InfoPopupViewModel = hiltViewModel() +) { + val openDialog = remember { mutableStateOf(false) } + val buttonTitle = remember { + mutableStateOf("Show Info") + } + + Button( + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + onClick = { + openDialog.value = !openDialog.value + if (!openDialog.value) { + buttonTitle.value = "Show Info" + } + } + ) { + Text(text = buttonTitle.value, modifier = Modifier.padding(3.dp)) + } + + if (openDialog.value) { + val uiState by vM.uiState.collectAsState() + + buttonTitle.value = "Hide Info" + Dialog( + onDismissRequest = { openDialog.value = false }, + properties = DialogProperties(usePlatformDefaultWidth = false) + ) { + Box( + modifier = Modifier + .height(600.dp) + .width(800.dp), + contentAlignment = Alignment.Center + ) { + Scaffold( + modifier = Modifier + .fillMaxSize() + .clip(shape = RoundedCornerShape(10.dp)) + .border( + width = 1.dp, + color = MaterialTheme.colorScheme.tertiary, + shape = RoundedCornerShape(10.dp) + ), + topBar = { + Row( + modifier = Modifier + .fillMaxWidth() + .background(color = MaterialTheme.colorScheme.tertiary), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + // spacer to divide the row in 3 parts and spread them over the width + Spacer(modifier = Modifier.size(0.dp)) + + WText(text = "About Writand", fontSize = 25.sp) + + IconButton( + onClick = { openDialog.value = false }, + modifier = Modifier.padding(start = 10.dp), + ) { + Icon( + modifier = Modifier.size(35.dp), + imageVector = Icons.Default.Close, + contentDescription = "Close" + ) + } + } + } + ) { + Row( + modifier = Modifier.padding(it) + ) { + WLogoImage( + modifier = Modifier + .padding(25.dp) + .fillMaxHeight() + .size(100.dp), + alignment = Alignment.CenterStart, + ) + + Column( + modifier = Modifier.padding(start = 5.dp).verticalScroll( + rememberScrollState() + ) + ) { + // Writand (short description) + version + Row( + modifier = Modifier.padding(vertical = 15.dp), + verticalAlignment = Alignment.Bottom + ) { + WText( + text = "Writand", + color = MaterialTheme.colorScheme.tertiary, + fontWeight = FontWeight.ExtraBold, + fontSize = 30.sp + ) + WText( + text = " | version ${vM.version}", + fontSize = 20.sp + ) + } + WText(text = + "Writand is a new integrated development environment (IDE) designed specifically for Android devices.\n" + + "Whether you're a seasoned developer or just starting, Writand offers an intuitive and powerful\n" + + "platform to code on your Android tablet.\n" + ) + // change log / what's new: version 2 will have this + /*Column { + WText(text = "What's new", fontSize = 25.sp) + WText("upcoming...") + }*/ + // TOS + Column { + WText( + modifier = Modifier.padding(vertical = 10.dp), + text = "Terms Of Services", + fontSize = 25.sp + ) + + Box( + modifier = Modifier + .height(300.dp) + .padding(horizontal = 10.dp) + .border(1.dp, color = MaterialTheme.colorScheme.secondary) + ) { + when (val value = uiState) { + InfoPopupUiState.Loading -> { + WLoadingIndicator() + } + + is InfoPopupUiState.Success -> { + LazyColumn { + items(value.tosStrings.size) { item -> + Text(text = value.tosStrings[item]) + } + } + } + + is InfoPopupUiState.Failed -> { + WText(text = value.message) + } + } + } + } + // support + feedback (link site / contact gmail) + Column { + WText( + modifier = Modifier.padding(vertical = 15.dp), + text = "Support & Feedback", + fontSize = 25.sp + ) + WText( + modifier = Modifier.padding(bottom = 5.dp), + text = "We’re here to assist you with any issues you might encounter, and we’d love to hear your thoughts on how we can improve our app. Whether you need support or want to share feedback, we're just a click away." + ) + WText( + modifier = Modifier.padding(start = 10.dp), + text = "* Email us at emro-dev@gmail.com" + ) + WText( + modifier = Modifier.padding(start = 10.dp), + text = "* More information at emro-dev.com" + ) + WText( + modifier = Modifier.padding(top = 5.dp), + text = "Thank you for helping us make the app better!" + ) + } + // (developer information +?) support us (patreon link) + Column { + WText( + modifier = Modifier.padding(vertical = 15.dp), + text = "Support Us", + fontSize = 25.sp + ) + WText(text = "Support us via patreon and get more features: (link)") + } + // rate the app: for version 2 as well + Column( + modifier = Modifier.padding(bottom = 25.dp) + ) { + WText( + modifier = Modifier.padding(vertical = 15.dp), + text = "Rate The App", + fontSize = 25.sp + ) + WText( + modifier = Modifier.padding(bottom = 5.dp), + text = "If you love using our app, we’d really appreciate it if you could take a moment to rate us! Your feedback helps us improve and reach more users like you." + ) + WText( + modifier = Modifier.padding(bottom = 5.dp), + text = "(link)" + ) + WText(text = "Thank you for your support!") + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/screens/info/InfoPopupUiState.kt b/app/src/main/java/be/re/writand/screens/info/InfoPopupUiState.kt new file mode 100644 index 0000000..dc83b35 --- /dev/null +++ b/app/src/main/java/be/re/writand/screens/info/InfoPopupUiState.kt @@ -0,0 +1,16 @@ +package be.re.writand.screens.info + +/** + * Interface to present the 3 differences stages of getting the contents of file: + * - [Loading] the content is pending. + * - [Success] all the info for the popup is ready. + * - [Failed] unable to get the content for the info popup, give back an error message. + */ +sealed interface InfoPopupUiState { + object Loading: InfoPopupUiState + data class Success( + val changeLog: List, + val tosStrings: List + ): InfoPopupUiState + data class Failed(val message: String): InfoPopupUiState +} \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/screens/info/InfoPopupViewModel.kt b/app/src/main/java/be/re/writand/screens/info/InfoPopupViewModel.kt new file mode 100644 index 0000000..686f79b --- /dev/null +++ b/app/src/main/java/be/re/writand/screens/info/InfoPopupViewModel.kt @@ -0,0 +1,45 @@ +package be.re.writand.screens.info + +import be.re.writand.data.local.models.TermsOfService +import be.re.writand.domain.tos.GetTOSUseCase +import be.re.writand.screens.WViewModel +import be.re.writand.utils.Version +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 InfoPopup that handles all the needed information to be displayed. + * This view model uses the InfoPopupUiState to enable handling failures. + * @param[getTOSUseCase] use case to get the Terms of Services. + */ +@HiltViewModel +class InfoPopupViewModel @Inject constructor( + getTOSUseCase: GetTOSUseCase +) : WViewModel() { + + private val _uiState: MutableStateFlow = + MutableStateFlow(InfoPopupUiState.Loading) + val uiState: StateFlow = _uiState + + var version: String = "" + + init { + launchCatching { + val tos: TermsOfService = getTOSUseCase() + + version = tos.version.toString() + + val tosList: List = tos.text + val changeLog = listOf() // this will be used in version 2 + if (tosList.isNotEmpty()) { + _uiState.value = + InfoPopupUiState.Success(changeLog = changeLog, tosStrings = tosList) + } else { + _uiState.value = InfoPopupUiState.Failed("Terms of Services could not be loaded.") + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/screens/settings/SettingsPopup.kt b/app/src/main/java/be/re/writand/screens/settings/SettingsPopup.kt index e596a1c..45cee72 100644 --- a/app/src/main/java/be/re/writand/screens/settings/SettingsPopup.kt +++ b/app/src/main/java/be/re/writand/screens/settings/SettingsPopup.kt @@ -52,7 +52,7 @@ import be.re.writand.screens.components.WRadioButtonsSelectorRowWise import be.re.writand.screens.components.WText /** - * Popup screen to handle let the user change the settings once the welcome part of the app has + * Popup screen to let the user change the settings once the welcome part of the app has * gone through. This should involve all the settings possible to be changed. * @param[vM] view model corresponding to this screen. */ 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 index f903cd8..cb8598e 100644 --- a/app/src/main/java/be/re/writand/screens/welcome/WelcomeTOSUiState.kt +++ b/app/src/main/java/be/re/writand/screens/welcome/WelcomeTOSUiState.kt @@ -2,9 +2,9 @@ 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. + * - [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 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 index c5a3ef0..301c5ae 100644 --- a/app/src/main/java/be/re/writand/screens/welcome/WelcomeTOSViewModel.kt +++ b/app/src/main/java/be/re/writand/screens/welcome/WelcomeTOSViewModel.kt @@ -22,7 +22,7 @@ class WelcomeTOSViewModel @Inject constructor( init { launchCatching { - val tosList: List = getTOSUseCase() + val tosList: List = getTOSUseCase().text if (tosList.isNotEmpty()) { _uiState.value = WelcomeTOSUiState.Success(tosList) } else { diff --git a/app/src/main/java/be/re/writand/utils/Version.kt b/app/src/main/java/be/re/writand/utils/Version.kt index c2137b4..6c4e9ae 100644 --- a/app/src/main/java/be/re/writand/utils/Version.kt +++ b/app/src/main/java/be/re/writand/utils/Version.kt @@ -3,6 +3,11 @@ package be.re.writand.utils import android.util.Log data class Version(var major: Int, var minor: Int, var patch: Int) { + + override fun toString(): String { + return "${major}.${minor}.${patch}" + } + companion object { /**