Skip to content

Commit

Permalink
Move intent callback handling to login viewmodel
Browse files Browse the repository at this point in the history
  • Loading branch information
wingio committed Nov 26, 2023
1 parent fb2351e commit b19ddfd
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 60 deletions.
50 changes: 0 additions & 50 deletions app/src/main/java/xyz/wingio/dimett/ui/activity/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,38 +1,24 @@
package xyz.wingio.dimett.ui.activity

import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.transitions.SlideTransition
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import xyz.wingio.dimett.domain.manager.AccountManager
import xyz.wingio.dimett.domain.manager.InstanceManager
import xyz.wingio.dimett.domain.manager.PreferenceManager
import xyz.wingio.dimett.domain.repository.MastodonRepository
import xyz.wingio.dimett.rest.utils.ifSuccessful
import xyz.wingio.dimett.ui.screens.auth.LoginScreen
import xyz.wingio.dimett.ui.screens.main.MainScreen
import xyz.wingio.dimett.ui.theme.DimettTheme
import xyz.wingio.dimett.ui.viewmodels.auth.instanceUrl

class MainActivity : ComponentActivity(), KoinComponent {

private val accountManager: AccountManager by inject()
private val instanceManager: InstanceManager by inject()
private val repo: MastodonRepository by inject()
private val prefs: PreferenceManager by inject()

private lateinit var navigator: Navigator

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand All @@ -50,43 +36,7 @@ class MainActivity : ComponentActivity(), KoinComponent {

Navigator(startScreen) {
SlideTransition(it)
navigator = it
}
}
}
}
}
}

override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
intent?.data?.let {
if (it.scheme == "dimett") {
val code = it.getQueryParameter("code")
if (code != null) {
lifecycleScope.launch(Dispatchers.IO) {
val instance = instanceManager[instanceUrl!!]
if (instance != null) {
repo.getToken(
instanceUrl = instance.url,
clientId = instance.clientId,
clientSecret = instance.clientSecret,
code = code
).ifSuccessful { token ->
repo.verifyCredentials(instance.url, token.accessToken)
.ifSuccessful { user ->
accountManager.addAccount(
user,
token.accessToken,
instance.url
)

prefs.currentAccount = user.id
navigator.replaceAll(MainScreen())
}
}
}
instanceUrl = null
}
}
}
Expand Down
35 changes: 35 additions & 0 deletions app/src/main/java/xyz/wingio/dimett/ui/components/IntentHandler.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package xyz.wingio.dimett.ui.components

import android.content.Intent
import androidx.activity.ComponentActivity
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.core.util.Consumer

@Composable
fun IntentHandler(enabled: Boolean = true, onIntent: (intent: Intent) -> Unit) {
val activity = LocalContext.current as? ComponentActivity ?: return // Make sure we are actually in a `ComponentActivity`
val currentOnIntent by rememberUpdatedState(onIntent) // Handle new `onIntent` lambdas safely

// Try not to recreate the callback on every composition
val intentCallback = remember {
Consumer<Intent> { intent -> if(enabled) currentOnIntent(intent) }
}

val lifecycleOwner = LocalLifecycleOwner.current

DisposableEffect(lifecycleOwner) {
// Add callback to the current activity
activity.addOnNewIntentListener(intentCallback)

// Remove callback when effect leaves the composition
onDispose {
activity.removeOnNewIntentListener(intentCallback)
}
}
}
14 changes: 10 additions & 4 deletions app/src/main/java/xyz/wingio/dimett/ui/screens/auth/LoginScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
Expand All @@ -27,11 +26,14 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.koin.getScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import kotlinx.coroutines.delay
import xyz.wingio.dimett.R
import xyz.wingio.dimett.ui.components.Text
import xyz.wingio.dimett.ui.viewmodels.auth.LoginViewModel
import xyz.wingio.dimett.ui.widgets.auth.InstancePreview
import xyz.wingio.dimett.ui.components.IntentHandler
import xyz.wingio.dimett.utils.getString
import kotlin.time.Duration.Companion.seconds

Expand All @@ -40,14 +42,14 @@ class LoginScreen : Screen {
@Composable
override fun Content() = Screen()

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun Screen(
viewModel: LoginViewModel = getScreenModel()
) {
val navigator = LocalNavigator.currentOrThrow
val ctx = LocalContext.current
var lastTyped by remember {
mutableStateOf(0L)
mutableLongStateOf(0L)
}

// Only send request when user is done typing
Expand All @@ -61,6 +63,10 @@ class LoginScreen : Screen {
}
}

IntentHandler { intent ->
viewModel.handleIntent(intent, navigator)
}

Scaffold { pv ->
Column(
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterVertically),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,33 @@
package xyz.wingio.dimett.ui.viewmodels.auth

import android.content.Context
import android.content.Intent
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.coroutineScope
import cafe.adriel.voyager.navigator.Navigator
import io.ktor.http.URLBuilder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import xyz.wingio.dimett.domain.db.entities.Instance
import xyz.wingio.dimett.domain.manager.AccountManager
import xyz.wingio.dimett.domain.manager.InstanceManager
import xyz.wingio.dimett.domain.manager.PreferenceManager
import xyz.wingio.dimett.domain.repository.MastodonRepository
import xyz.wingio.dimett.rest.dto.meta.NodeInfo
import xyz.wingio.dimett.rest.utils.fold
import xyz.wingio.dimett.rest.utils.ifSuccessful
import xyz.wingio.dimett.ui.screens.main.MainScreen
import xyz.wingio.dimett.utils.openCustomTab

var instanceUrl: String? = null

class LoginViewModel(
private val repo: MastodonRepository,
private val mastodonRepository: MastodonRepository,
private val instanceManager: InstanceManager,
private val am: AccountManager
private val accountManager: AccountManager,
private val preferenceManager: PreferenceManager
) : ScreenModel {

var instance by mutableStateOf("")
Expand All @@ -31,6 +36,8 @@ class LoginViewModel(

var nodeInfoLoading by mutableStateOf(false)

private var instanceUrl: String? = null

fun login(context: Context) {
if (instance.isEmpty()) return
coroutineScope.launch(Dispatchers.IO) {
Expand All @@ -55,10 +62,10 @@ class LoginViewModel(
nodeInfoLoading = true

coroutineScope.launch(Dispatchers.IO) {
repo.getNodeInfoLocation(url).fold(
mastodonRepository.getNodeInfoLocation(url).fold(
success = {
it.links.firstOrNull()?.let { link ->
repo.getNodeInfo(link.href).fold(
mastodonRepository.getNodeInfo(link.href).fold(
success = { node ->
nodeInfo = node
nodeInfoLoading = false
Expand All @@ -80,7 +87,7 @@ class LoginViewModel(
val url = fixUrl(instance)
if (instanceManager.exists(url)) return instanceManager[url]
var instance: Instance? = null
repo.createApp(url).fold(
mastodonRepository.createApp(url).fold(
success = { app ->
instance = instanceManager.addInstance(
url,
Expand All @@ -97,6 +104,38 @@ class LoginViewModel(
return instance
}

fun handleIntent(intent: Intent, navigator: Navigator) {
val data = intent.data ?: return
if (data.scheme != "dimett") return // only respond to dimett://
val code = data.getQueryParameter("code") ?: return
val url = instanceUrl ?: return

coroutineScope.launch {
val instance = instanceManager[url] ?: return@launch

mastodonRepository.getToken(
instanceUrl = instance.url,
clientId = instance.clientId,
clientSecret = instance.clientSecret,
code = code
).ifSuccessful { token ->
mastodonRepository.verifyCredentials(instance.url, token.accessToken)
.ifSuccessful { user ->
accountManager.addAccount(
user = user,
token = token.accessToken,
instance = instance.url
)

preferenceManager.currentAccount = user.id
navigator.replaceAll(MainScreen())
}
}

instanceUrl = null // Don't let duplicate intents happen
}
}

private fun fixUrl(url: String): String {
var s = url.replaceFirst("http://", "")
s = s.replaceFirst("https://", "")
Expand Down

0 comments on commit b19ddfd

Please sign in to comment.