Skip to content

Commit

Permalink
Settings screens (#125)
Browse files Browse the repository at this point in the history
  • Loading branch information
krizzu committed Jan 20, 2023
1 parent 3a67ee6 commit ee7fea1
Show file tree
Hide file tree
Showing 27 changed files with 376 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,9 @@ interface DodoAuthStorage {
* Get the Access token for @param server
*/
fun getAccessToken(server: String): String?

/**
* Remove stored access token for selected @param server
*/
fun clearAccessToken(server: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ internal class DodoAuthStorageImpl(
}
}

override fun clearAccessToken(server: String) {
lock.withLock {
memCache.remove(server)
diskCache = memCache
}
}

private companion object {
private const val KEY_DOMAIN_CACHE = "key_domain_cache"
private const val KEY_ACCESS_TOKENS_CACHE = "key_access_tokens_cache"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,9 @@ internal class AuthenticationRepositoryImpl(
settings.authorizedServersFlow.transform { servers ->
emit(servers.isNotEmpty())
}

override fun removeAccessToken(server: String) {
settings.currentDomain = null
settings.clearAccessToken(server)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ class FakeAuthStorage(serversFlow: Flow<List<String>> = flowOf(listOf())) : Dodo
}

override fun getAccessToken(server: String): String = "FakeToken"

override fun clearAccessToken(server: String) {
TODO("Not yet implemented")
}
}

class FakeAuthDatabase : AuthenticationDatabase {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import social.androiddev.domain.authentication.usecase.AuthenticateClient
import social.androiddev.domain.authentication.usecase.CreateAccessToken
import social.androiddev.domain.authentication.usecase.GetAuthStatus
import social.androiddev.domain.authentication.usecase.GetSelectedApplicationOAuthToken
import social.androiddev.domain.authentication.usecase.LogoutFromCurrentServer

/**
* Koin module containing all koin/bean definitions for
Expand Down Expand Up @@ -48,4 +49,10 @@ val domainAuthModule: Module = module {
authenticationRepository = get()
)
}

factory {
LogoutFromCurrentServer(
authenticationRepository = get()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,6 @@ interface AuthenticationRepository {
suspend fun getApplicationOAuthToken(server: String): ApplicationOAuthToken?

fun isAccessTokenPresent(): Flow<Boolean>

fun removeAccessToken(server: String)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* This file is part of Dodo.
*
* Dodo 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.
*
* Dodo 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 Dodo.
* If not, see <https://www.gnu.org/licenses/>.
*/
package social.androiddev.domain.authentication.usecase

import social.androiddev.domain.authentication.repository.AuthenticationRepository

class LogoutFromCurrentServer(private val authenticationRepository: AuthenticationRepository) {
operator fun invoke() {
val server = authenticationRepository.selectedServer
if (server != null) {
authenticationRepository.removeAccessToken(server)
}
}
}
7 changes: 7 additions & 0 deletions kotlin-utils/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
plugins {
id("social.androiddev.library")
}

android {
namespace = "social.androiddev.kotlinutils"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* This file is part of Dodo.
*
* Dodo 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.
*
* Dodo 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 Dodo.
* If not, see <https://www.gnu.org/licenses/>.
*/
package social.androiddev.kotlinutils

import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

/**
* Only return the result of [block] if [expression] is true, otherwise always returns null
*/
@OptIn(ExperimentalContracts::class)
inline fun <T> nullUnless(expression: Boolean, block: () -> T): T? {
contract {
returnsNotNull() implies (expression)
callsInPlace(block, InvocationKind.AT_MOST_ONCE)
}
return if (!expression) {
null
} else {
block()
}
}
5 changes: 4 additions & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ dependencyResolutionManagement {
}
}

rootProject.name="DodoForMastodon"
rootProject.name = "DodoForMastodon"

include(":di")
include(":logging")
Expand All @@ -32,10 +32,13 @@ include(":ui:root")
include(":ui:signed-in")
include(":ui:signed-out")
include(":ui:desktop-webview")
include(":ui:settings")

include(":domain:timeline")
include(":domain:authentication")

include(":data:persistence")
include(":data:network")
include(":data:repository")

include(":kotlin-utils")
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ class DefaultRootComponent(

override val authStatus = viewModel.authState

override fun navigateToSignedIn() {
navigation.replaceCurrent(Config.SignedIn)
}

override fun navigateToSignedOut() {
navigation.replaceCurrent(Config.SignedOut)
}

private fun createChild(config: Config, componentContext: ComponentContext): RootComponent.Child =
when (config) {
Config.Splash -> RootComponent.Child.Splash(createSplashComponent(componentContext))
Expand Down Expand Up @@ -87,12 +95,6 @@ class DefaultRootComponent(
componentContext: ComponentContext,
) = DefaultSplashComponent(
componentContext = componentContext,
navigateToTimelineInternal = {
navigation.replaceCurrent(Config.SignedIn)
},
navigateToLandingInternal = {
navigation.replaceCurrent(Config.SignedOut)
},
)

private fun getInitialStack(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ interface RootComponent {
// current authorization status
val authStatus: StateFlow<UiAuthStatus>

/**
* Replaces the navigation stack to be root of SignedIn navigation flow
*/
fun navigateToSignedIn()

/**
* Replaces the navigation stack to be root of SignedOut navigation flow
*/
fun navigateToSignedOut()

/**
* Supported "Child"s in this navigation stack. These are created from a configuration that
* contains any arguments for this particular child in the navigation stack.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ package social.androiddev.root.root
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.arkivanov.decompose.extensions.compose.jetbrains.stack.Children
import com.arkivanov.decompose.extensions.compose.jetbrains.subscribeAsState
import social.androiddev.root.splash.SplashComponent
import social.androiddev.root.splash.SplashContent
import social.androiddev.signedin.composables.SignedInRootContent
import social.androiddev.signedin.navigation.SignedInRootComponent
Expand All @@ -40,6 +40,20 @@ fun RootContent(
val childStack by component.childStack.subscribeAsState()
val authStatus by component.authStatus.collectAsState()

LaunchedEffect(authStatus) {
when (authStatus) {
is UiAuthStatus.Authorized -> {
component.navigateToSignedIn()
}

is UiAuthStatus.Unauthorized -> {
component.navigateToSignedOut()
}

is UiAuthStatus.Loading -> {}
}
}

Box(
modifier = modifier,
contentAlignment = Alignment.Center,
Expand All @@ -50,10 +64,7 @@ fun RootContent(
) { createdChild ->
when (val child = createdChild.instance) {
is RootComponent.Child.Splash -> {
SplashScreen(
component = child.component,
authStatus = authStatus,
)
SplashContent()
}

is RootComponent.Child.SignedIn -> {
Expand Down Expand Up @@ -91,15 +102,3 @@ private fun SignedInRoot(
component = component,
)
}

@Composable
private fun SplashScreen(
component: SplashComponent,
authStatus: UiAuthStatus,
) {
SplashContent(
authStatus = authStatus,
modifier = Modifier.fillMaxSize(),
component = component,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,4 @@ import com.arkivanov.decompose.ComponentContext

class DefaultSplashComponent(
private val componentContext: ComponentContext,
private val navigateToLandingInternal: () -> Unit,
private val navigateToTimelineInternal: () -> Unit,
) : SplashComponent, ComponentContext by componentContext {

override fun navigateToTimeline() {
navigateToTimelineInternal()
}

override fun navigateToLanding() {
navigateToLandingInternal()
}
}
) : SplashComponent, ComponentContext by componentContext
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,4 @@ package social.androiddev.root.splash
/**
* The base component describing all business logic needed for the splash screen
*/
interface SplashComponent {

/**
* Callback invoked when the logged-in user should be taken to the timeline screen
*/
fun navigateToTimeline()

/**
* Callback invoked when the logged-out user should be taken to the landing screen
*/
fun navigateToLanding()
}
interface SplashComponent
Original file line number Diff line number Diff line change
Expand Up @@ -13,62 +13,24 @@
package social.androiddev.root.splash

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import social.androiddev.root.root.UiAuthStatus

/**
* Stateful SplashScreen composable using [SplashComponent] for
* decompose navigation and business logic.
*/
@Composable
fun SplashContent(
component: SplashComponent,
authStatus: UiAuthStatus,
modifier: Modifier = Modifier,
) {
SplashContent(
authStatus = authStatus,
modifier = modifier,
navigateToWelcome = {
component.navigateToLanding()
},
navigateToTimeline = {
component.navigateToTimeline()
},
)
}

/**
* Stateless composable for rendering a simple Splash Screen
* upon app launch.
*/
@Composable
fun SplashContent(
authStatus: UiAuthStatus,
navigateToTimeline: () -> Unit,
navigateToWelcome: () -> Unit,
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier,
modifier = modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
Text("Loading")

LaunchedEffect(authStatus) {
when (authStatus) {
// TODO(krzysztof): Do not navigate to timeline, until logout func is implemented
// https://github.com/AndroidDev-social/DodoForMastodon/issues/107
is UiAuthStatus.Authorized,
is UiAuthStatus.Unauthorized,
-> navigateToWelcome()

is UiAuthStatus.Loading -> {}
}
}
}
}
21 changes: 21 additions & 0 deletions ui/settings/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
plugins {
id("social.androiddev.library.ui")
}

android {
namespace = "social.androiddev.ui.settings"
}

kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation(projects.ui.common)
implementation(projects.domain.authentication)
implementation(projects.data.persistence)
implementation(projects.data.repository)
implementation(libs.io.insert.koin.core)
}
}
}
}
Loading

0 comments on commit ee7fea1

Please sign in to comment.