diff --git a/README.md b/README.md index 3d3810af1..d403fcfd4 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Note that Orca isn't a fork of the official app. Its overall structure has been ## Structure
- +
Each module represents the context to which its underlying structures are related. diff --git a/composite/status/build.gradle.kts b/composite/status/build.gradle.kts new file mode 100644 index 000000000..c163f498b --- /dev/null +++ b/composite/status/build.gradle.kts @@ -0,0 +1,36 @@ +/* + * Copyright © 2024 Orca + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If + * not, see https://www.gnu.org/licenses. + */ + +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) +} + +android { + buildFeatures.compose = true + composeOptions.kotlinCompilerExtensionVersion = libs.versions.android.compose.compiler.get() + defaultConfig.testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" +} + +dependencies { + androidTestImplementation(libs.android.compose.ui.test.junit) + androidTestImplementation(libs.android.compose.ui.test.manifest) + androidTestImplementation(libs.assertk) + androidTestImplementation(libs.kotlin.test) + + implementation(project(":core:sample")) + implementation(project(":platform:autos")) + implementation(libs.android.core) +} diff --git a/composite/status/src/androidTest/java/com/jeanbarrossilva/orca/composite/status/state/StatusCardStateExtensionsTests.kt b/composite/status/src/androidTest/java/com/jeanbarrossilva/orca/composite/status/state/StatusCardStateExtensionsTests.kt new file mode 100644 index 000000000..d072fa682 --- /dev/null +++ b/composite/status/src/androidTest/java/com/jeanbarrossilva/orca/composite/status/state/StatusCardStateExtensionsTests.kt @@ -0,0 +1,39 @@ +/* + * Copyright © 2024 Orca + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If + * not, see https://www.gnu.org/licenses. + */ + +package com.jeanbarrossilva.orca.composite.status.state + +import androidx.compose.runtime.DisposableEffect +import androidx.compose.ui.test.junit4.createComposeRule +import assertk.assertThat +import assertk.assertions.isSameAs +import kotlin.test.Test +import org.junit.Rule + +internal class StatusCardStateExtensionsTests { + @get:Rule val composeRule = createComposeRule() + + @Test + fun remembersEverLoadingState() { + composeRule.setContent { + val state = rememberStatusCardState() + + DisposableEffect(state) { + assertThat(state).isSameAs(StatusCardState.EverLoading) + onDispose {} + } + } + } +} diff --git a/composite/status/src/androidTest/java/com/jeanbarrossilva/orca/composite/status/state/StatusCardStateTests.kt b/composite/status/src/androidTest/java/com/jeanbarrossilva/orca/composite/status/state/StatusCardStateTests.kt new file mode 100644 index 000000000..f3293d6dd --- /dev/null +++ b/composite/status/src/androidTest/java/com/jeanbarrossilva/orca/composite/status/state/StatusCardStateTests.kt @@ -0,0 +1,39 @@ +/* + * Copyright © 2024 Orca + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If + * not, see https://www.gnu.org/licenses. + */ + +package com.jeanbarrossilva.orca.composite.status.state + +import assertk.assertThat +import assertk.assertions.isSameAs +import com.jeanbarrossilva.orca.composite.status.Status +import kotlin.test.Test + +internal class StatusCardStateTests { + @Test + fun changesStatusToNonLoadingOnes() { + Status.entries + .filter { it != Status.Loading } + .forEach { + assertThat(StatusCardState(targetStatus = it).apply(StatusCardState::loadStatus).status) + .isSameAs(it) + } + } + + @Test + fun everLoadingStateNeverLoads() { + StatusCardState.EverLoading.loadStatus() + assertThat(StatusCardState.EverLoading.status).isSameAs(Status.Loading) + } +} diff --git a/composite/status/src/main/AndroidManifest.xml b/composite/status/src/main/AndroidManifest.xml new file mode 100644 index 000000000..a51def919 --- /dev/null +++ b/composite/status/src/main/AndroidManifest.xml @@ -0,0 +1,16 @@ + + + diff --git a/composite/status/src/main/java/com/jeanbarrossilva/orca/composite/status/Status.kt b/composite/status/src/main/java/com/jeanbarrossilva/orca/composite/status/Status.kt new file mode 100644 index 000000000..190b4c3e8 --- /dev/null +++ b/composite/status/src/main/java/com/jeanbarrossilva/orca/composite/status/Status.kt @@ -0,0 +1,122 @@ +/* + * Copyright © 2024 Orca + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If + * not, see https://www.gnu.org/licenses. + */ + +package com.jeanbarrossilva.orca.composite.status + +import androidx.compose.foundation.layout.size +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.jeanbarrossilva.orca.core.instance.Instance +import com.jeanbarrossilva.orca.platform.autos.colors.asColor +import com.jeanbarrossilva.orca.platform.autos.theme.AutosTheme + +/** Account registration stage that a [StatusCard] can represent. */ +@Immutable +enum class Status { + /** Denotes that an [Instance] at which an account can be registered is being searched for. */ + Loading { + @Composable + override fun Indicator(modifier: Modifier) { + CircularProgressIndicator( + Modifier.size(24.dp), + AutosTheme.colors.secondary.asColor, + strokeCap = StrokeCap.Round + ) + } + + @Composable + override fun Description(modifier: Modifier) { + Text(stringResource(R.string.composite_status_loading)) + } + }, + + /** + * Denotes that an available [Instance] has been found and an account been registered + * successfully. + */ + Succeeded { + @Composable + override fun Indicator(modifier: Modifier) { + StatusIndicator( + com.jeanbarrossilva.orca.platform.autos.R.drawable.icon_selected, + AutosTheme.colors.activation.reposted.asColor, + Color.White + ) + } + + @Composable + override fun Description(modifier: Modifier) { + Text(stringResource(R.string.composite_status_succeeded)) + } + }, + + /** Denotes that an account couldn't be registered at a given [Instance]. */ + Failed { + @Composable + override fun Indicator(modifier: Modifier) { + StatusIndicator( + com.jeanbarrossilva.orca.platform.autos.R.drawable.icon_close, + AutosTheme.colors.activation.favorite.asColor, + Color.White + ) + } + + @Composable + override fun Description(modifier: Modifier) { + Text(stringResource(R.string.composite_status_failed)) + } + }; + + /** + * [StatusIndicator] that matches the [Description] and helps to easily identify whether an + * account could be registered or if the process itself hasn't yet completed. + */ + @Composable + fun Indicator() { + Indicator(Modifier) + } + + /** + * [Text] that clearly describes whether an account has been registered, hasn't or the process is + * still ongoing. + */ + @Composable + fun Description() { + Description(Modifier) + } + + /** + * [StatusIndicator] that matches the [Description] and helps to easily identify whether an + * account could be registered or if the process itself hasn't yet completed. + * + * @param modifier [Modifier] that is applied to the [StatusIndicator]. + */ + @Composable abstract fun Indicator(modifier: Modifier) + + /** + * [Text] that clearly describes whether an account has been registered, hasn't or the process is + * still ongoing. + * + * @param modifier [Modifier] that is applied to the [Text]. + */ + @Composable abstract fun Description(modifier: Modifier) +} diff --git a/composite/status/src/main/java/com/jeanbarrossilva/orca/composite/status/StatusCard.kt b/composite/status/src/main/java/com/jeanbarrossilva/orca/composite/status/StatusCard.kt new file mode 100644 index 000000000..b2230997a --- /dev/null +++ b/composite/status/src/main/java/com/jeanbarrossilva/orca/composite/status/StatusCard.kt @@ -0,0 +1,109 @@ +/* + * Copyright © 2024 Orca + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If + * not, see https://www.gnu.org/licenses. + */ + +package com.jeanbarrossilva.orca.composite.status + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ProvideTextStyle +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.jeanbarrossilva.orca.composite.status.state.StatusCardState +import com.jeanbarrossilva.orca.composite.status.state.rememberStatusCardState +import com.jeanbarrossilva.orca.core.instance.domain.Domain +import com.jeanbarrossilva.orca.core.sample.instance.domain.sample +import com.jeanbarrossilva.orca.platform.autos.borders.asBorderStroke +import com.jeanbarrossilva.orca.platform.autos.theme.AutosTheme +import com.jeanbarrossilva.orca.platform.autos.theme.MultiThemePreview + +/** + * [Card] for displaying the progress of an account registration process. + * + * @param state [StatusCardState] that holds the current operation [Status]. + * @param modifier [Modifier] that is applied to the [Card]. + * @param title Message shown alongside an indicator and a description of what the current [Status] + * is. + * @see rememberStatusCardState + * @see Status.Indicator + * @see Status.Description + */ +@Composable +fun StatusCard( + state: StatusCardState, + modifier: Modifier = Modifier, + title: @Composable () -> Unit +) { + val spacing = AutosTheme.spacings.large.dp + + Card( + modifier, + colors = CardDefaults.outlinedCardColors(), + border = AutosTheme.borders.default.asBorderStroke + ) { + Row( + Modifier.padding(spacing).fillMaxWidth(), + Arrangement.spacedBy(spacing), + Alignment.CenterVertically + ) { + state.status.Indicator() + + Column(verticalArrangement = Arrangement.spacedBy(AutosTheme.spacings.small.dp)) { + ProvideTextStyle(AutosTheme.typography.titleMedium, title) + ProvideTextStyle(AutosTheme.typography.labelMedium) { state.status.Description() } + } + } + } +} + +/** + * Preview of a [StatusCard] with a loading [Status]. + * + * @see Status.Loading + */ +@Composable +@MultiThemePreview +private fun LoadingStatusCardPreview() { + AutosTheme { StatusCard(rememberStatusCardState()) { Text("${Domain.sample}") } } +} + +/** + * Preview of a [StatusCard] with a succeeded [Status]. + * + * @see Status.Succeeded + */ +@Composable +@MultiThemePreview +private fun SucceededStatusCardPreview() { + AutosTheme { StatusCard(rememberStatusCardState(Status.Succeeded)) { Text("${Domain.sample}") } } +} + +/** + * Preview of a [StatusCard] with a failed [Status]. + * + * @see Status.Failed + */ +@Composable +@MultiThemePreview +private fun FailedStatusCardPreview() { + AutosTheme { StatusCard(rememberStatusCardState(Status.Failed)) { Text("${Domain.sample}") } } +} diff --git a/composite/status/src/main/java/com/jeanbarrossilva/orca/composite/status/StatusIndicator.kt b/composite/status/src/main/java/com/jeanbarrossilva/orca/composite/status/StatusIndicator.kt new file mode 100644 index 000000000..5b47c76a3 --- /dev/null +++ b/composite/status/src/main/java/com/jeanbarrossilva/orca/composite/status/StatusIndicator.kt @@ -0,0 +1,81 @@ +/* + * Copyright © 2024 Orca + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If + * not, see https://www.gnu.org/licenses. + */ + +package com.jeanbarrossilva.orca.composite.status + +import android.graphics.drawable.Drawable +import androidx.annotation.DrawableRes +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.drawscope.scale +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.toBitmap +import com.jeanbarrossilva.orca.platform.autos.R +import com.jeanbarrossilva.orca.platform.autos.colors.asColor +import com.jeanbarrossilva.orca.platform.autos.theme.AutosTheme + +/** + * Contained icon for facilitating the understanding of what a given [Status] means. + * + * @param iconResource [Drawable] resource of the icon. + * @param containerColor [Color] by which the container with the icon will be colored. + * @param contentColor [Color] of the icon itself. + * @param modifier [Modifier] that is applied to the [Canvas]. + */ +@Composable +internal fun StatusIndicator( + @DrawableRes iconResource: Int, + containerColor: Color, + contentColor: Color, + modifier: Modifier = Modifier +) { + val context = LocalContext.current + val contentColorInArgb = remember(contentColor, contentColor::toArgb) + val icon = + remember(context, iconResource, contentColorInArgb) { + ContextCompat.getDrawable(context, iconResource) + .let(::checkNotNull) + .apply { setTint(contentColorInArgb) } + .toBitmap() + .asImageBitmap() + } + + Canvas(modifier.size(24.dp)) { + drawCircle(containerColor) + scale(.7f) { drawImage(icon) } + } +} + +/** Preview of a [StatusIndicator]. */ +@Composable +@Preview +private fun StatusIndicatorPreview() { + AutosTheme { + StatusIndicator( + R.drawable.icon_profile_filled, + containerColor = AutosTheme.colors.activation.favorite.asColor, + contentColor = Color.White + ) + } +} diff --git a/composite/status/src/main/java/com/jeanbarrossilva/orca/composite/status/state/StatusCardState.extensions.kt b/composite/status/src/main/java/com/jeanbarrossilva/orca/composite/status/state/StatusCardState.extensions.kt new file mode 100644 index 000000000..d0e5957a4 --- /dev/null +++ b/composite/status/src/main/java/com/jeanbarrossilva/orca/composite/status/state/StatusCardState.extensions.kt @@ -0,0 +1,60 @@ +/* + * Copyright © 2024 Orca + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If + * not, see https://www.gnu.org/licenses. + */ + +package com.jeanbarrossilva.orca.composite.status.state + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import com.jeanbarrossilva.orca.composite.status.Status +import kotlin.time.Duration +import kotlinx.coroutines.delay + +/** + * Remembers a [StatusCardState]. + * + * @param targetStatus [Status] to which the current one will be changed when it loads. + * @param delay [Duration] to wait for before loading the [Status]. + * @see StatusCardState.status + * @see StatusCardState.loadStatus + */ +@Composable +fun rememberStatusCardState( + targetStatus: Status = Status.Loading, + delay: Duration = Duration.ZERO +): StatusCardState { + /* + * Regardless of the delay, given that the initial status of a state is the loading one, having it + * also be the target status means that the actual status won't ever change at all when it is + * requested to be "loaded" (that is, to be set as the specified target one). + */ + val isEverLoading = remember(targetStatus) { targetStatus == Status.Loading } + + return remember(isEverLoading) { + if (isEverLoading) { + StatusCardState.EverLoading + } else { + StatusCardState(targetStatus) + } + } + .also { + LaunchedEffect(it, isEverLoading, delay) { + if (!isEverLoading) { + delay(delay) + it.loadStatus() + } + } + } +} diff --git a/composite/status/src/main/java/com/jeanbarrossilva/orca/composite/status/state/StatusCardState.kt b/composite/status/src/main/java/com/jeanbarrossilva/orca/composite/status/state/StatusCardState.kt new file mode 100644 index 000000000..02e1259cd --- /dev/null +++ b/composite/status/src/main/java/com/jeanbarrossilva/orca/composite/status/state/StatusCardState.kt @@ -0,0 +1,65 @@ +/* + * Copyright © 2024 Orca + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If + * not, see https://www.gnu.org/licenses. + */ + +package com.jeanbarrossilva.orca.composite.status.state + +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import com.jeanbarrossilva.orca.composite.status.Status +import com.jeanbarrossilva.orca.composite.status.StatusCard + +/** + * State by which the [Status] of a [StatusCard] is held and loaded. + * + * @param targetStatus [Status] to which the current one will be changed when it loads. + * @see status + * @see loadStatus + */ +class StatusCardState internal constructor(private val targetStatus: Status) { + /** + * Current [Status] of the [StatusCard]. It starts as loading by default and can be changed to the + * target one through [loadStatus]. + * + * @see Status.Loading + * @see targetStatus + */ + internal var status by mutableStateOf(Status.Loading) + private set + + /** + * Defines the current [Status] as the target one with which this [StatusCardState] has been + * created if it's loading. + * + * @see status + * @see targetStatus + * @see Status.Loading + */ + internal fun loadStatus() { + if (status == Status.Loading) { + status = targetStatus + } + } + + companion object { + /** + * An immutable [StatusCardState], with a never-changing loading [status]. + * + * @see Status.Loading + */ + @Stable internal val EverLoading = StatusCardState(targetStatus = Status.Loading) + } +} diff --git a/composite/status/src/main/res/values-pt-rBR/strings.xml b/composite/status/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 000000000..8728ec48b --- /dev/null +++ b/composite/status/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,20 @@ + + + + Tentando criar conta… + Conta criada com sucesso. + Não foi possível criar uma conta. + diff --git a/composite/status/src/main/res/values/strings.xml b/composite/status/src/main/res/values/strings.xml new file mode 100644 index 000000000..9aa72dba6 --- /dev/null +++ b/composite/status/src/main/res/values/strings.xml @@ -0,0 +1,20 @@ + + + + Attempting to create account… + Account created successfully. + Couldn\'t create account. + diff --git a/feature/registration/build.gradle.kts b/feature/registration/build.gradle.kts new file mode 100644 index 000000000..602071efd --- /dev/null +++ b/feature/registration/build.gradle.kts @@ -0,0 +1,52 @@ +/* + * Copyright © 2024 Orca + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If + * not, see https://www.gnu.org/licenses. + */ + +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.symbolProcessor) +} + +android { + buildFeatures.compose = true + composeOptions.kotlinCompilerExtensionVersion = libs.versions.android.compose.compiler.get() + defaultConfig.testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + packagingOptions.resources.excludes += + arrayOf("META-INF/LICENSE.md", "META-INF/LICENSE-notice.md") +} + +dependencies { + androidTestImplementation(project(":platform:autos-test")) + androidTestImplementation(project(":platform:navigation")) + androidTestImplementation(project(":platform:navigation-test")) + androidTestImplementation(project(":std:injector-test")) + androidTestImplementation(libs.android.compose.ui.test.junit) + androidTestImplementation(libs.android.fragment.testing) + androidTestImplementation(libs.assertk) + androidTestImplementation(libs.kotlin.test) + androidTestImplementation(libs.mockk) + + api(project(":platform:navigation")) + api(project(":std:injector")) + + implementation(project(":composite:composable")) + implementation(project(":composite:status")) + implementation(project(":core:sample")) + implementation(project(":platform:animator")) + implementation(project(":platform:autos")) + implementation(project(":platform:stack")) + + ksp(project(":std:injector-processor")) +} diff --git a/feature/registration/credentials/build.gradle.kts b/feature/registration/credentials/build.gradle.kts new file mode 100644 index 000000000..2f466d236 --- /dev/null +++ b/feature/registration/credentials/build.gradle.kts @@ -0,0 +1,36 @@ +/* + * Copyright © 2024 Orca + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If + * not, see https://www.gnu.org/licenses. + */ + +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) +} + +android { + buildFeatures.compose = true + composeOptions.kotlinCompilerExtensionVersion = libs.versions.android.compose.compiler.get() + defaultConfig.testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" +} + +dependencies { + androidTestImplementation(project(":platform:navigation-test")) + androidTestImplementation(libs.assertk) + androidTestImplementation(libs.kotlin.test) + + api(project(":composite:composable")) + api(project(":platform:navigation")) + + implementation(project(":platform:autos")) +} diff --git a/feature/registration/credentials/src/androidTest/java/com/jeanbarrossilva/orca/feature/registration/credentials/CredentialsFragmentTests.kt b/feature/registration/credentials/src/androidTest/java/com/jeanbarrossilva/orca/feature/registration/credentials/CredentialsFragmentTests.kt new file mode 100644 index 000000000..a573de8e0 --- /dev/null +++ b/feature/registration/credentials/src/androidTest/java/com/jeanbarrossilva/orca/feature/registration/credentials/CredentialsFragmentTests.kt @@ -0,0 +1,34 @@ +/* + * Copyright © 2024 Orca + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If + * not, see https://www.gnu.org/licenses. + */ + +package com.jeanbarrossilva.orca.feature.registration.credentials + +import assertk.assertThat +import com.jeanbarrossilva.orca.platform.navigation.navigator +import com.jeanbarrossilva.orca.platform.navigation.test.activity.launchNavigationActivity +import com.jeanbarrossilva.orca.platform.navigation.test.isAt +import kotlin.test.Test + +internal class CredentialsFragmentTests { + @Test + fun navigates() { + launchNavigationActivity().use { scenario -> + scenario.onActivity { activity -> + CredentialsFragment.navigate(activity.navigator) + assertThat(activity).isAt(CredentialsFragment.ROUTE) + } + } + } +} diff --git a/feature/registration/credentials/src/main/AndroidManifest.xml b/feature/registration/credentials/src/main/AndroidManifest.xml new file mode 100644 index 000000000..e56813681 --- /dev/null +++ b/feature/registration/credentials/src/main/AndroidManifest.xml @@ -0,0 +1,16 @@ + + + diff --git a/feature/registration/credentials/src/main/java/com/jeanbarrossilva/orca/feature/registration/credentials/Credentials.kt b/feature/registration/credentials/src/main/java/com/jeanbarrossilva/orca/feature/registration/credentials/Credentials.kt new file mode 100644 index 000000000..1801bbdd6 --- /dev/null +++ b/feature/registration/credentials/src/main/java/com/jeanbarrossilva/orca/feature/registration/credentials/Credentials.kt @@ -0,0 +1,137 @@ +/* + * Copyright © 2024 Orca + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If + * not, see https://www.gnu.org/licenses. + */ + +package com.jeanbarrossilva.orca.feature.registration.credentials + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.jeanbarrossilva.orca.platform.autos.kit.action.button.PrimaryButton +import com.jeanbarrossilva.orca.platform.autos.kit.input.text.FormTextField +import com.jeanbarrossilva.orca.platform.autos.kit.scaffold.Scaffold +import com.jeanbarrossilva.orca.platform.autos.kit.scaffold.bar.button.ButtonBar +import com.jeanbarrossilva.orca.platform.autos.kit.scaffold.plus +import com.jeanbarrossilva.orca.platform.autos.theme.AutosTheme +import com.jeanbarrossilva.orca.platform.autos.theme.MultiThemePreview + +@Composable +internal fun Credentials(viewModel: CredentialsViewModel, modifier: Modifier = Modifier) { + val email by viewModel.emailFlow.collectAsState() + val password by viewModel.passwordFlow.collectAsState() + + Credentials( + email, + onEmailChange = viewModel::setEmail, + password, + onPasswordChange = viewModel::setPassword, + modifier + ) +} + +@Composable +private fun Credentials( + email: String, + onEmailChange: (email: String) -> Unit, + password: String, + onPasswordChange: (password: String) -> Unit, + modifier: Modifier = Modifier +) { + val spacing = AutosTheme.spacings.large.dp + val spacerModifier = remember { Modifier.height(spacing) } + + Scaffold( + modifier, + bottom = { + ButtonBar { + PrimaryButton(onClick = {}) { + Text(stringResource(R.string.feature_registration_credentials_confirm)) + } + } + } + ) { + expanded { + LazyColumn( + Modifier.fillMaxSize(), + contentPadding = it + PaddingValues(spacing), + verticalArrangement = Arrangement.SpaceBetween, + horizontalAlignment = Alignment.CenterHorizontally + ) { + item { Spacer(spacerModifier) } + + item { + Column( + Modifier.fillMaxWidth(), + Arrangement.spacedBy(AutosTheme.spacings.medium.dp), + Alignment.CenterHorizontally + ) { + Text( + stringResource(R.string.feature_registration_credentials), + textAlign = TextAlign.Center, + style = AutosTheme.typography.headlineLarge + ) + + Text( + stringResource(R.string.feature_registration_credentials_explanation), + textAlign = TextAlign.Center, + style = AutosTheme.typography.headlineSmall + ) + } + } + + item { + Column(verticalArrangement = Arrangement.spacedBy(AutosTheme.spacings.small.dp)) { + FormTextField(email, onEmailChange) { + Text(stringResource(R.string.feature_registration_credentials_email)) + } + + FormTextField(password, onPasswordChange) { + Text(stringResource(R.string.feature_registration_credentials_password)) + } + } + } + + item { Spacer(spacerModifier) } + } + } + } +} + +@Composable +@MultiThemePreview +private fun CredentialsPreview() { + AutosTheme { + Credentials( + email = "jean@orcinus.com.br", + onEmailChange = {}, + password = "password123", + onPasswordChange = {} + ) + } +} diff --git a/feature/registration/credentials/src/main/java/com/jeanbarrossilva/orca/feature/registration/credentials/CredentialsFragment.kt b/feature/registration/credentials/src/main/java/com/jeanbarrossilva/orca/feature/registration/credentials/CredentialsFragment.kt new file mode 100644 index 000000000..60d0f54c0 --- /dev/null +++ b/feature/registration/credentials/src/main/java/com/jeanbarrossilva/orca/feature/registration/credentials/CredentialsFragment.kt @@ -0,0 +1,40 @@ +/* + * Copyright © 2024 Orca + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If + * not, see https://www.gnu.org/licenses. + */ + +package com.jeanbarrossilva.orca.feature.registration.credentials + +import androidx.compose.runtime.Composable +import androidx.fragment.app.viewModels +import com.jeanbarrossilva.orca.composite.composable.ComposableFragment +import com.jeanbarrossilva.orca.platform.navigation.Navigator +import com.jeanbarrossilva.orca.platform.navigation.transition.opening + +class CredentialsFragment internal constructor() : ComposableFragment() { + private val viewModel by + viewModels(factoryProducer = CredentialsViewModel::createFactory) + + @Composable + override fun Content() { + Credentials(viewModel) + } + + companion object { + internal const val ROUTE = "credentials" + + fun navigate(navigator: Navigator) { + navigator.navigate(opening()) { to(ROUTE, ::CredentialsFragment) } + } + } +} diff --git a/feature/registration/credentials/src/main/java/com/jeanbarrossilva/orca/feature/registration/credentials/CredentialsViewModel.kt b/feature/registration/credentials/src/main/java/com/jeanbarrossilva/orca/feature/registration/credentials/CredentialsViewModel.kt new file mode 100644 index 000000000..0b540cb68 --- /dev/null +++ b/feature/registration/credentials/src/main/java/com/jeanbarrossilva/orca/feature/registration/credentials/CredentialsViewModel.kt @@ -0,0 +1,45 @@ +/* + * Copyright © 2024 Orca + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If + * not, see https://www.gnu.org/licenses. + */ + +package com.jeanbarrossilva.orca.feature.registration.credentials + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +internal class CredentialsViewModel : ViewModel() { + private val emailMutableFlow = MutableStateFlow("") + private val passwordMutableFlow = MutableStateFlow("") + + val emailFlow = emailMutableFlow.asStateFlow() + val passwordFlow = passwordMutableFlow.asStateFlow() + + fun setEmail(email: String) { + emailMutableFlow.value = email + } + + fun setPassword(password: String) { + passwordMutableFlow.value = password + } + + companion object { + fun createFactory(): ViewModelProvider.Factory { + return viewModelFactory { initializer { CredentialsViewModel() } } + } + } +} diff --git a/feature/registration/credentials/src/main/res/values-pt-rBR/strings.xml b/feature/registration/credentials/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 000000000..53bffa905 --- /dev/null +++ b/feature/registration/credentials/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,25 @@ + + + + Criar conta + Confirmar + E-mail + + Insira as credenciais a serem usadas para tentar criar uma conta numa instância que esteja + disponível. + + Senha + \ No newline at end of file diff --git a/feature/registration/credentials/src/main/res/values/strings.xml b/feature/registration/credentials/src/main/res/values/strings.xml new file mode 100644 index 000000000..3c614f6d1 --- /dev/null +++ b/feature/registration/credentials/src/main/res/values/strings.xml @@ -0,0 +1,24 @@ + + + + Create account + Confirm + E-mail + + Enter the credentials to be used for trying to create an account at an available instance. + + Password + \ No newline at end of file diff --git a/feature/registration/ongoing/build.gradle.kts b/feature/registration/ongoing/build.gradle.kts new file mode 100644 index 000000000..f15099f59 --- /dev/null +++ b/feature/registration/ongoing/build.gradle.kts @@ -0,0 +1,39 @@ +/* + * Copyright © 2024 Orca + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If + * not, see https://www.gnu.org/licenses. + */ + +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) +} + +android { + buildFeatures.compose = true + composeOptions.kotlinCompilerExtensionVersion = libs.versions.android.compose.compiler.get() + defaultConfig.testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" +} + +dependencies { + androidTestImplementation(project(":platform:navigation-test")) + androidTestImplementation(libs.assertk) + androidTestImplementation(libs.kotlin.test) + + api(project(":composite:composable")) + api(project(":platform:navigation")) + + implementation(project(":composite:status")) + implementation(project(":core:sample")) + implementation(project(":platform:autos")) + implementation(project(":platform:stack")) +} diff --git a/feature/registration/ongoing/src/androidTest/java/com/jeanbarrossilva/orca/feature/registration/ongoing/OngoingFragmentTests.kt b/feature/registration/ongoing/src/androidTest/java/com/jeanbarrossilva/orca/feature/registration/ongoing/OngoingFragmentTests.kt new file mode 100644 index 000000000..328eb0717 --- /dev/null +++ b/feature/registration/ongoing/src/androidTest/java/com/jeanbarrossilva/orca/feature/registration/ongoing/OngoingFragmentTests.kt @@ -0,0 +1,34 @@ +/* + * Copyright © 2024 Orca + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If + * not, see https://www.gnu.org/licenses. + */ + +package com.jeanbarrossilva.orca.feature.registration.ongoing + +import assertk.assertThat +import com.jeanbarrossilva.orca.platform.navigation.navigator +import com.jeanbarrossilva.orca.platform.navigation.test.activity.launchNavigationActivity +import com.jeanbarrossilva.orca.platform.navigation.test.isAt +import kotlin.test.Test + +internal class OngoingFragmentTests { + @Test + fun navigates() { + launchNavigationActivity().use { scenario -> + scenario.onActivity { activity -> + OngoingFragment.navigate(activity.navigator) + assertThat(activity).isAt(OngoingFragment.ROUTE) + } + } + } +} diff --git a/feature/registration/ongoing/src/main/AndroidManifest.xml b/feature/registration/ongoing/src/main/AndroidManifest.xml new file mode 100644 index 000000000..e56813681 --- /dev/null +++ b/feature/registration/ongoing/src/main/AndroidManifest.xml @@ -0,0 +1,16 @@ + + + diff --git a/feature/registration/ongoing/src/main/java/com/jeanbarrossilva/orca/feature/registration/ongoing/Ongoing.kt b/feature/registration/ongoing/src/main/java/com/jeanbarrossilva/orca/feature/registration/ongoing/Ongoing.kt new file mode 100644 index 000000000..1840ccd14 --- /dev/null +++ b/feature/registration/ongoing/src/main/java/com/jeanbarrossilva/orca/feature/registration/ongoing/Ongoing.kt @@ -0,0 +1,108 @@ +/* + * Copyright © 2024 Orca + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If + * not, see https://www.gnu.org/licenses. + */ + +package com.jeanbarrossilva.orca.feature.registration.ongoing + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.MutableTransitionState +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.scaleIn +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +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.Modifier +import androidx.compose.ui.res.stringResource +import com.jeanbarrossilva.orca.composite.status.Status +import com.jeanbarrossilva.orca.composite.status.StatusCard +import com.jeanbarrossilva.orca.composite.status.state.rememberStatusCardState +import com.jeanbarrossilva.orca.core.instance.domain.Domain +import com.jeanbarrossilva.orca.core.sample.instance.domain.sample +import com.jeanbarrossilva.orca.core.sample.instance.domain.samples +import com.jeanbarrossilva.orca.platform.autos.kit.scaffold.Scaffold +import com.jeanbarrossilva.orca.platform.autos.template.onboarding.Onboarding +import com.jeanbarrossilva.orca.platform.autos.theme.AutosTheme +import com.jeanbarrossilva.orca.platform.autos.theme.MultiThemePreview +import com.jeanbarrossilva.orca.platform.stack.Stack +import com.jeanbarrossilva.orca.platform.stack.StackScope +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds + +@Composable +internal fun Ongoing(viewModel: OngoingViewModel, modifier: Modifier = Modifier) { + val indexedDomain by viewModel.indexedDomainFlow.collectAsState() + Ongoing(indexedDomain, OngoingViewModel.perDomainDelay, modifier) +} + +@Composable +private fun Ongoing( + indexedDomain: IndexedValue?, + perDomainDelay: Duration, + modifier: Modifier = Modifier +) { + var statusCardStackScope by remember { mutableStateOf(null) } + val statusCardAnimationSpec = tween() + val statusCardEnterTransition = + remember(statusCardAnimationSpec) { + fadeIn(statusCardAnimationSpec) + scaleIn(statusCardAnimationSpec, initialScale = .8f) + } + + LaunchedEffect(indexedDomain, statusCardStackScope, statusCardEnterTransition) { + if (statusCardStackScope != null && indexedDomain != null) { + statusCardStackScope?.item { + AnimatedVisibility( + remember { MutableTransitionState(false).apply { targetState = true } }, + enter = statusCardEnterTransition + ) { + StatusCard( + rememberStatusCardState( + targetStatus = + if (indexedDomain.index == Domain.samples.lastIndex) { + Status.Succeeded + } else { + Status.Failed + }, + perDomainDelay / 2 + ) + ) { + Text("${indexedDomain.value}") + } + } + } + } + } + + Scaffold(modifier) { + expanded { + Onboarding( + illustration = { Stack { statusCardStackScope = this } }, + title = { Text(stringResource(R.string.feature_registration_ongoing)) }, + description = { Text(stringResource(R.string.feature_registration_ongoing_description)) }, + contentPadding = it + ) + } + } +} + +@Composable +@MultiThemePreview +private fun OngoingPreview() { + AutosTheme { Ongoing(IndexedValue(0, Domain.sample), perDomainDelay = 4.seconds) } +} diff --git a/feature/registration/ongoing/src/main/java/com/jeanbarrossilva/orca/feature/registration/ongoing/OngoingFragment.kt b/feature/registration/ongoing/src/main/java/com/jeanbarrossilva/orca/feature/registration/ongoing/OngoingFragment.kt new file mode 100644 index 000000000..296f4a0ee --- /dev/null +++ b/feature/registration/ongoing/src/main/java/com/jeanbarrossilva/orca/feature/registration/ongoing/OngoingFragment.kt @@ -0,0 +1,40 @@ +/* + * Copyright © 2024 Orca + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If + * not, see https://www.gnu.org/licenses. + */ + +package com.jeanbarrossilva.orca.feature.registration.ongoing + +import androidx.compose.runtime.Composable +import androidx.fragment.app.viewModels +import com.jeanbarrossilva.orca.composite.composable.ComposableFragment +import com.jeanbarrossilva.orca.platform.navigation.Navigator +import com.jeanbarrossilva.orca.platform.navigation.transition.opening + +internal class OngoingFragment : ComposableFragment() { + private val viewModel by + viewModels(factoryProducer = OngoingViewModel::createFactory) + + @Composable + override fun Content() { + Ongoing(viewModel) + } + + companion object { + const val ROUTE = "ONGOING" + + fun navigate(navigator: Navigator) { + navigator.navigate(opening()) { to(ROUTE, ::OngoingFragment) } + } + } +} diff --git a/feature/registration/ongoing/src/main/java/com/jeanbarrossilva/orca/feature/registration/ongoing/OngoingViewModel.kt b/feature/registration/ongoing/src/main/java/com/jeanbarrossilva/orca/feature/registration/ongoing/OngoingViewModel.kt new file mode 100644 index 000000000..b57554a70 --- /dev/null +++ b/feature/registration/ongoing/src/main/java/com/jeanbarrossilva/orca/feature/registration/ongoing/OngoingViewModel.kt @@ -0,0 +1,52 @@ +/* + * Copyright © 2024 Orca + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If + * not, see https://www.gnu.org/licenses. + */ + +package com.jeanbarrossilva.orca.feature.registration.ongoing + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import com.jeanbarrossilva.orca.core.instance.domain.Domain +import com.jeanbarrossilva.orca.core.sample.instance.domain.samples +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.withIndex + +internal class OngoingViewModel : ViewModel() { + val indexedDomainFlow = + Domain.samples + .asFlow() + .withIndex() + .onEach { (index, _) -> + if (index > 0) { + delay(perDomainDelay) + } + } + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), initialValue = null) + + companion object { + val perDomainDelay = 4.seconds + + fun createFactory(): ViewModelProvider.Factory { + return viewModelFactory { initializer { OngoingViewModel() } } + } + } +} diff --git a/feature/registration/ongoing/src/main/res/values-pt-rBR/strings.xml b/feature/registration/ongoing/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 000000000..d5664f959 --- /dev/null +++ b/feature/registration/ongoing/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,23 @@ + + + + Criando uma conta para você… + + O Orca está acessando cada uma das instâncias conhecidas e criará sua conta em uma que + estiver disponível. + + Concluir + diff --git a/feature/registration/ongoing/src/main/res/values/strings.xml b/feature/registration/ongoing/src/main/res/values/strings.xml new file mode 100644 index 000000000..836a1e4fb --- /dev/null +++ b/feature/registration/ongoing/src/main/res/values/strings.xml @@ -0,0 +1,23 @@ + + + + Creating an account for you… + + Orca is going through each of its known instances and will create your account at one that + is available. + + Done + diff --git a/feature/registration/src/androidTest/java/com/jeanbarrossilva/orca/feature/registration/RegistrationFragmentTests.kt b/feature/registration/src/androidTest/java/com/jeanbarrossilva/orca/feature/registration/RegistrationFragmentTests.kt new file mode 100644 index 000000000..5d42214f9 --- /dev/null +++ b/feature/registration/src/androidTest/java/com/jeanbarrossilva/orca/feature/registration/RegistrationFragmentTests.kt @@ -0,0 +1,62 @@ +/* + * Copyright © 2024 Orca + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If + * not, see https://www.gnu.org/licenses. + */ + +package com.jeanbarrossilva.orca.feature.registration + +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.runComposeUiTest +import androidx.fragment.app.testing.launchFragmentInContainer +import assertk.assertThat +import com.jeanbarrossilva.orca.platform.autos.test.kit.action.button.onPrimaryButton +import com.jeanbarrossilva.orca.platform.navigation.navigator +import com.jeanbarrossilva.orca.platform.navigation.test.activity.launchNavigationActivity +import com.jeanbarrossilva.orca.platform.navigation.test.isAt +import com.jeanbarrossilva.orca.std.injector.module.injection.injectionOf +import com.jeanbarrossilva.orca.std.injector.test.InjectorTestRule +import io.mockk.mockkObject +import io.mockk.verify +import kotlin.test.Test +import org.junit.Rule + +internal class RegistrationFragmentTests { + @get:Rule + val injectorRule = InjectorTestRule { + register(RegistrationModule(injectionOf { NoOpRegistrationBoundary })) + } + + @Test + fun navigates() { + launchNavigationActivity().use { scenario -> + scenario.onActivity { activity -> + RegistrationFragment.navigate(activity.navigator) + assertThat(activity).isAt(RegistrationFragment.ROUTE) + } + } + } + + @Test + fun navigatesToCredentialsWhenContinuing() { + @OptIn(ExperimentalTestApi::class) + runComposeUiTest { + mockkObject(NoOpRegistrationBoundary) { + launchFragmentInContainer(instantiate = ::RegistrationFragment).use { + onPrimaryButton().performClick() + verify { NoOpRegistrationBoundary.navigateToCredentials() } + } + } + } + } +} diff --git a/feature/registration/src/main/AndroidManifest.xml b/feature/registration/src/main/AndroidManifest.xml new file mode 100644 index 000000000..e56813681 --- /dev/null +++ b/feature/registration/src/main/AndroidManifest.xml @@ -0,0 +1,16 @@ + + + diff --git a/feature/registration/src/main/java/com/jeanbarrossilva/orca/feature/registration/NoOpRegistrationBoundary.kt b/feature/registration/src/main/java/com/jeanbarrossilva/orca/feature/registration/NoOpRegistrationBoundary.kt new file mode 100644 index 000000000..439431cb8 --- /dev/null +++ b/feature/registration/src/main/java/com/jeanbarrossilva/orca/feature/registration/NoOpRegistrationBoundary.kt @@ -0,0 +1,20 @@ +/* + * Copyright © 2024 Orca + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If + * not, see https://www.gnu.org/licenses. + */ + +package com.jeanbarrossilva.orca.feature.registration + +internal object NoOpRegistrationBoundary : RegistrationBoundary { + override fun navigateToCredentials() {} +} diff --git a/feature/registration/src/main/java/com/jeanbarrossilva/orca/feature/registration/Registration.kt b/feature/registration/src/main/java/com/jeanbarrossilva/orca/feature/registration/Registration.kt new file mode 100644 index 000000000..4c43ea5a3 --- /dev/null +++ b/feature/registration/src/main/java/com/jeanbarrossilva/orca/feature/registration/Registration.kt @@ -0,0 +1,157 @@ +/* + * Copyright © 2024 Orca + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If + * not, see https://www.gnu.org/licenses. + */ + +package com.jeanbarrossilva.orca.feature.registration + +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.scaleIn +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.jeanbarrossilva.orca.composite.status.Status +import com.jeanbarrossilva.orca.composite.status.StatusCard +import com.jeanbarrossilva.orca.composite.status.state.rememberStatusCardState +import com.jeanbarrossilva.orca.platform.animator.Animator +import com.jeanbarrossilva.orca.platform.animator.animation.Motion +import com.jeanbarrossilva.orca.platform.animator.animation.timing.after +import com.jeanbarrossilva.orca.platform.autos.colors.asColor +import com.jeanbarrossilva.orca.platform.autos.kit.action.button.PrimaryButton +import com.jeanbarrossilva.orca.platform.autos.kit.scaffold.Scaffold +import com.jeanbarrossilva.orca.platform.autos.kit.scaffold.bar.button.ButtonBar +import com.jeanbarrossilva.orca.platform.autos.kit.scaffold.plus +import com.jeanbarrossilva.orca.platform.autos.theme.AutosTheme +import com.jeanbarrossilva.orca.platform.autos.theme.MultiThemePreview +import com.jeanbarrossilva.orca.platform.stack.Stack +import kotlin.time.Duration.Companion.seconds + +@Composable +internal fun Registration(boundary: RegistrationBoundary, modifier: Modifier = Modifier) { + Registration(boundary, Motion.Moving, modifier) +} + +@Composable +private fun Registration( + boundary: RegistrationBoundary, + motion: Motion, + modifier: Modifier = Modifier +) { + val spacing = AutosTheme.spacings.large.dp + val spacerModifier = remember(spacing) { Modifier.height(spacing) } + val backdropColor = + if (isSystemInDarkTheme()) { + AutosTheme.colors.surface.container.asColor + } else { + AutosTheme.colors.placeholder.asColor + } + val statusCardAnimationSpec = remember { tween() } + val statusCardEnterTransition = + remember(statusCardAnimationSpec) { + fadeIn(statusCardAnimationSpec) + scaleIn(statusCardAnimationSpec, initialScale = .8f) + } + val statusCardDelay = remember { 2.seconds } + + Scaffold( + modifier, + bottom = { + ButtonBar { PrimaryButton(onClick = boundary::navigateToCredentials) { Text("Continue") } } + } + ) { + expanded { + LazyColumn( + Modifier.fillMaxHeight(), + verticalArrangement = Arrangement.SpaceBetween, + horizontalAlignment = Alignment.CenterHorizontally, + contentPadding = it + PaddingValues(spacing) + ) { + item { Spacer(spacerModifier) } + + item { + Box(contentAlignment = Alignment.Center) { + Canvas(Modifier.size(256.dp)) { drawCircle(backdropColor) } + + Animator(motion) { (failedStatusCard, succeededStatusCard) -> + Stack { + item { + failedStatusCard.Animate { + StatusCard(rememberStatusCardState(Status.Failed, statusCardDelay)) { + Text("Instance 1") + } + } + } + + item { + succeededStatusCard.Animate( + statusCardEnterTransition, + after(failedStatusCard) + statusCardDelay * 1.5 + ) { + StatusCard(rememberStatusCardState(Status.Succeeded, statusCardDelay)) { + Text("Instance 2") + } + } + } + } + } + } + } + + item { Spacer(spacerModifier) } + + item { + Column(verticalArrangement = Arrangement.spacedBy(AutosTheme.spacings.large.dp)) { + Text( + stringResource(R.string.feature_registration), + style = AutosTheme.typography.headlineLarge + ) + + Text( + stringResource(R.string.feature_registration_explanation), + style = AutosTheme.typography.headlineSmall + ) + } + } + + item { Spacer(spacerModifier) } + } + } + } +} + +@Composable +@MultiThemePreview +private fun StillRegistrationPreview() { + AutosTheme { Registration(NoOpRegistrationBoundary, Motion.Still) } +} + +@Composable +@MultiThemePreview +private fun MovingRegistrationPreview() { + AutosTheme { Registration(NoOpRegistrationBoundary) } +} diff --git a/feature/registration/src/main/java/com/jeanbarrossilva/orca/feature/registration/RegistrationBoundary.kt b/feature/registration/src/main/java/com/jeanbarrossilva/orca/feature/registration/RegistrationBoundary.kt new file mode 100644 index 000000000..fe0f609b9 --- /dev/null +++ b/feature/registration/src/main/java/com/jeanbarrossilva/orca/feature/registration/RegistrationBoundary.kt @@ -0,0 +1,20 @@ +/* + * Copyright © 2024 Orca + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If + * not, see https://www.gnu.org/licenses. + */ + +package com.jeanbarrossilva.orca.feature.registration + +interface RegistrationBoundary { + fun navigateToCredentials() +} diff --git a/feature/registration/src/main/java/com/jeanbarrossilva/orca/feature/registration/RegistrationFragment.kt b/feature/registration/src/main/java/com/jeanbarrossilva/orca/feature/registration/RegistrationFragment.kt new file mode 100644 index 000000000..77cc3ba9c --- /dev/null +++ b/feature/registration/src/main/java/com/jeanbarrossilva/orca/feature/registration/RegistrationFragment.kt @@ -0,0 +1,39 @@ +/* + * Copyright © 2024 Orca + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If + * not, see https://www.gnu.org/licenses. + */ + +package com.jeanbarrossilva.orca.feature.registration + +import androidx.compose.runtime.Composable +import com.jeanbarrossilva.orca.composite.composable.ComposableFragment +import com.jeanbarrossilva.orca.platform.navigation.Navigator +import com.jeanbarrossilva.orca.platform.navigation.transition.opening +import com.jeanbarrossilva.orca.std.injector.Injector + +class RegistrationFragment internal constructor() : ComposableFragment() { + private val module by lazy { Injector.from() } + + @Composable + override fun Content() { + Registration(module.boundary()) + } + + companion object { + internal const val ROUTE = "registration" + + fun navigate(navigator: Navigator) { + navigator.navigate(opening()) { to(ROUTE, ::RegistrationFragment) } + } + } +} diff --git a/feature/registration/src/main/java/com/jeanbarrossilva/orca/feature/registration/RegistrationModule.kt b/feature/registration/src/main/java/com/jeanbarrossilva/orca/feature/registration/RegistrationModule.kt new file mode 100644 index 000000000..96f33de59 --- /dev/null +++ b/feature/registration/src/main/java/com/jeanbarrossilva/orca/feature/registration/RegistrationModule.kt @@ -0,0 +1,22 @@ +/* + * Copyright © 2024 Orca + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If + * not, see https://www.gnu.org/licenses. + */ + +package com.jeanbarrossilva.orca.feature.registration + +import com.jeanbarrossilva.orca.std.injector.module.Inject +import com.jeanbarrossilva.orca.std.injector.module.Module +import com.jeanbarrossilva.orca.std.injector.module.injection.Injection + +open class RegistrationModule(@Inject val boundary: Injection) : Module() diff --git a/feature/registration/src/main/res/values-pt-rBR/strings.xml b/feature/registration/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 000000000..cd032a218 --- /dev/null +++ b/feature/registration/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,23 @@ + + + + Criar uma conta + + O Orca tentará encontrar uma instância que está disponível e criar uma conta nela para você + com as credenciais que inserir. Tudo isso de uma forma completamente segura, sem armazenar + nenhuma das informações privadas fornecidas (como a sua senha desejada). + + \ No newline at end of file diff --git a/feature/registration/src/main/res/values/strings.xml b/feature/registration/src/main/res/values/strings.xml new file mode 100644 index 000000000..8a49ff7ac --- /dev/null +++ b/feature/registration/src/main/res/values/strings.xml @@ -0,0 +1,23 @@ + + + + Create an account + + Orca will try to find an instance that is available and create an account for you with the + credentials you pass in. All of it in a completely secure way, not storing any of the given + private information (such as your desired password). + + \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 03db4d51c..6a0949167 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,6 +26,7 @@ includeBuild("build-src") include( ":app", ":composite:composable", + ":composite:status", ":composite:timeline", ":composite:timeline-test", ":core", @@ -46,6 +47,9 @@ include( ":feature:gallery-test", ":feature:post-details", ":feature:profile-details", + ":feature:registration", + ":feature:registration:credentials", + ":feature:registration:ongoing", ":feature:search", ":feature:settings", ":feature:settings:term-muting",