Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app-android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ dependencies {
implementation(libs.io.insert.koin.android)
implementation(libs.com.arkivanov.decompose)
implementation(libs.com.arkivanov.decompose.extensions.compose.jetbrains)
implementation(libs.androidx.browser)
}
2 changes: 1 addition & 1 deletion app-android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data android:scheme="dodooauth2redirect" />
<data android:scheme="dodooauth2redirect" android:host="callback" />
</intent-filter>
</activity>
</application>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,29 @@
package social.androiddev.dodo

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
import com.arkivanov.decompose.defaultComponentContext
import kotlinx.coroutines.Dispatchers
import org.koin.core.context.loadKoinModules
import org.koin.core.context.unloadKoinModules
import org.koin.dsl.module
import social.androiddev.common.theme.DodoTheme
import social.androiddev.root.composables.RootContent
import social.androiddev.root.navigation.DefaultRootComponent

class MainActivity : AppCompatActivity() {

private val activityModule = module {
factory<ComponentActivity> { this@MainActivity }
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loadKoinModules(activityModule)

// Create the root component before starting Compose
val root = DefaultRootComponent(
Expand All @@ -40,4 +49,9 @@ class MainActivity : AppCompatActivity() {
}
}
}

override fun onDestroy() {
super.onDestroy()
unloadKoinModules(activityModule)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,15 @@
package social.androiddev.dodo.di

import org.koin.dsl.module
import social.androiddev.common.web.WebAuth
import social.androiddev.dodo.web.CustomTabWebAuth

/**
* The Dodo Android app Koin module holding koin definitions
* specific to the android app
*/
val androidModule = module { }
val androidModule = module {
factory<WebAuth> {
CustomTabWebAuth(activity = get())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package social.androiddev.dodo.web

import android.content.Intent
import android.net.Uri
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
import androidx.browser.customtabs.CustomTabColorSchemeParams
import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.util.Consumer
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import social.androiddev.common.web.WebAuth
import social.androiddev.common.web.WebOpenExtras

class CustomTabWebAuth(private val activity: ComponentActivity) : WebAuth {

private val _state = MutableSharedFlow<WebAuth.State>(replay = 1)
override val state: Flow<WebAuth.State> = _state

override suspend fun start(): String {
val onNewIntentListener = Consumer<Intent> { intent ->
activity.lifecycleScope.launch {
val code = intent.data?.getQueryParameter("code")
if (code != null) {
_state.emit(WebAuth.State.Success(code))
}
val error = intent.data?.getQueryParameter("error")
val errorDescription = intent.data?.getQueryParameter("error_description")
if (error != null) {
_state.emit(WebAuth.State.Error(errorDescription))
}
}
}
activity.addOnNewIntentListener(onNewIntentListener)
return "dodooauth2redirect://callback"
}

override fun open(uri: String, extras: WebOpenExtras) {
CustomTabsIntent.Builder()
.setDefaultColorSchemeParams(
CustomTabColorSchemeParams.Builder()
.setToolbarColor(extras.primaryColor)
.setSecondaryToolbarColor(extras.secondaryColor)
.build()
)
.build()
.launchUrl(activity, Uri.parse(uri))
}
}
2 changes: 2 additions & 0 deletions app-desktop/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ kotlin {
implementation(libs.io.insert.koin.core)
implementation(libs.kotlinx.coroutines.javafx)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.io.ktor.server.core)
implementation(libs.io.ktor.server.netty)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ import kotlinx.coroutines.Dispatchers
import org.koin.core.context.startKoin
import social.androiddev.common.di.appModule
import social.androiddev.common.theme.DodoTheme
import social.androiddev.desktop.di.desktopModule
import social.androiddev.root.composables.RootContent
import social.androiddev.root.navigation.DefaultRootComponent

@OptIn(ExperimentalDecomposeApi::class) // Using LifecycleController
fun main() {

startKoin {
modules(appModule())
modules(appModule() + desktopModule)
}

val lifecycle = LifecycleRegistry()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package social.androiddev.desktop.di

import org.koin.dsl.module
import social.androiddev.common.web.WebAuth
import social.androiddev.desktop.web.ExternalBrowserWebAuth

val desktopModule = module {
factory<WebAuth> { ExternalBrowserWebAuth() }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package social.androiddev.desktop.web

import androidx.compose.ui.res.painterResource
import io.ktor.server.application.call
import io.ktor.server.response.respond
import io.ktor.server.routing.get
import io.ktor.server.routing.routing
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import social.androiddev.common.web.WebAuth
import social.androiddev.common.web.WebOpenExtras

private val Browsers = arrayOf(
"xdg-open",
"google-chrome",
"firefox",
"opera",
"konqueror",
"mozilla"
)

class ExternalBrowserWebAuth : WebAuth {

private val _state = MutableSharedFlow<WebAuth.State>(replay = 1)
override val state: Flow<WebAuth.State> = _state

override suspend fun start(): String {
val server = io.ktor.server.engine.embeddedServer(io.ktor.server.netty.Netty, port = 0) {
routing {
get("/callback") {
val code = call.request.queryParameters["code"]
if (code != null) {
_state.emit(WebAuth.State.Success(code))
this@embeddedServer.dispose()
call.respond("Success! You may close the tab")
}
val error = call.request.queryParameters["error"]
val errorDescription = call.request.queryParameters["error_description"]
if (error != null) {
_state.emit(WebAuth.State.Error(errorDescription))
call.respond("$error: $errorDescription")
}
}
}
}
server.start()
val port = server.resolvedConnectors().first().port
return "http://localhost:${port}/callback"
}

override fun open(uri: String, extras: WebOpenExtras) {
val osName = System.getProperty("os.name")
try {
if (osName.startsWith("Mac OS")) {
Runtime.getRuntime().exec(
"open $uri"
)
} else if (osName.startsWith("Windows")) {
Runtime.getRuntime().exec(
"rundll32 url.dll,FileProtocolHandler $uri"
)
} else { //assume Unix or Linux
var browser: String? = null
for (b in Browsers) {
if (browser == null && Runtime.getRuntime()
.exec(arrayOf("which", b)).inputStream.read() != -1
) {
Runtime.getRuntime().exec(arrayOf(b.also { browser = it }, uri))
}
}
if (browser == null) {
throw Exception("No web browser found")
}
}
} catch (e: Exception) {
// should not happen
// dump stack for debug purpose
e.printStackTrace()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ android {
compileSdk = 33

defaultConfig {
minSdk = 33
minSdk = 23
}

// targetSdk is in a different interface for library and application projects
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,5 +108,5 @@ internal class AuthenticationRepositoryImpl(
}
}

override val selectedServer: String? = settings.currentDomain
override val selectedServer: String? get() = settings.currentDomain
}
1 change: 1 addition & 0 deletions di/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ kotlin {
implementation(projects.data.repository)
implementation(projects.domain.authentication)
implementation(libs.io.insert.koin.core)
implementation(libs.kotlinx.coroutines.core)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package social.androiddev.common.web

actual class WebOpenExtras(val primaryColor: Int, val secondaryColor: Int)
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package social.androiddev.common.web

import kotlinx.coroutines.flow.Flow

interface WebAuth {

val state: Flow<State>

suspend fun start(): String

fun open(uri: String, extras: WebOpenExtras)

sealed interface State {
data class Success(val code: String) : State

data class Error(val error: String? = null) : State
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package social.androiddev.common.web

expect class WebOpenExtras
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package social.androiddev.common.web

actual class WebOpenExtras
7 changes: 7 additions & 0 deletions domain/authentication/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,12 @@ kotlin {
implementation(libs.org.jetbrains.kotlin.test.annotations.common)
}
}

val desktopMain by getting {
dependencies {
implementation(libs.io.ktor.server.core)
implementation(libs.io.ktor.server.netty)
}
}
}
}
3 changes: 3 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,14 @@ io-ktor-client-mock-jvm = { module = "io.ktor:ktor-client-mock-jvm", version.ref
io-ktor-client-mock = { module = "io.ktor:ktor-client-mock", version.ref = "io-ktor" }
io-ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "io-ktor" }
io-ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "io-ktor" }
io-ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "io-ktor" }
io-ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "io-ktor" }
io-insert-koin-core = { module = "io.insert-koin:koin-core", version.ref = "io-insert-koin" }
io-insert-koin-test = { module = "io.insert-koin:koin-test", version.ref = "io-insert-koin" }
io-insert-koin-android = { module = "io.insert-koin:koin-android", version.ref = "io-insert-koin" }

kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "org-jetbrains-kotlinx-coroutines" }
kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "org-jetbrains-kotlinx-coroutines" }
kotlinx-coroutines-javafx = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-javafx", version.ref = "org-jetbrains-kotlinx-coroutines" }
org-jetbrains-kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "org-jetbrains-kotlinx-coroutines" }
org-jetbrains-kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "org-jetbrains-kotlinx-serialization" }
Expand Down
1 change: 1 addition & 0 deletions ui/signed-out/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ kotlin {
val commonMain by getting {
dependencies {
implementation(projects.ui.common)
implementation(projects.di)
implementation(projects.domain.authentication)
implementation(libs.io.insert.koin.core)
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package social.androiddev.signedout.selectserver

import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.toArgb
import social.androiddev.common.web.WebOpenExtras

@Composable
actual fun webOpenExtras(): WebOpenExtras {
return WebOpenExtras(
primaryColor = MaterialTheme.colors.primary.toArgb(),
secondaryColor = MaterialTheme.colors.secondary.toArgb(),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.childStack
import com.arkivanov.decompose.router.stack.pop
import com.arkivanov.decompose.router.stack.push
import com.arkivanov.decompose.router.stack.replaceAll
import com.arkivanov.decompose.value.Value
import com.arkivanov.essenty.parcelable.Parcelable
import com.arkivanov.essenty.parcelable.Parcelize
Expand Down Expand Up @@ -78,9 +79,7 @@ class DefaultSignedOutRootComponent(
) = DefaultSelectServerComponent(
componentContext = componentContext,
mainContext = mainContext,
launchOAuth = {
navigation.push(Config.SignIn)
}
onAuthenticated = navigateToTimeLine,
)

private fun createSignInComponent(
Expand Down
Loading