Skip to content

Commit

Permalink
Merge pull request #52 from schachi5000/dev
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
schachi5000 authored Apr 15, 2024
2 parents ed6f9eb + ad5ee92 commit b9d08da
Show file tree
Hide file tree
Showing 26 changed files with 641 additions and 322 deletions.
10 changes: 5 additions & 5 deletions androidApp/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<application
android:allowBackup="true"
Expand All @@ -12,10 +12,10 @@
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:exported="true"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|mnc|colorMode|density|fontScale|fontWeightAdjustment|keyboard|layoutDirection|locale|mcc|navigation|smallestScreenSize|touchscreen|uiMode"
android:name="net.schacher.mcc.MainActivity"
>
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|mnc|colorMode|density|fontScale|fontWeightAdjustment|keyboard|layoutDirection|locale|mcc|navigation|smallestScreenSize|touchscreen|uiMode"
android:exported="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
Expand Down
11 changes: 0 additions & 11 deletions androidApp/src/androidMain/kotlin/net/schacher/mcc/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import net.schacher.mcc.shared.auth.AuthHandler
import net.schacher.mcc.shared.auth.AuthHandler.APP_SCHEME

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
Expand All @@ -17,15 +15,6 @@ class MainActivity : AppCompatActivity() {
MainView()
}
}

override fun onResume() {
super.onResume()

if (intent.data.toString().startsWith(APP_SCHEME)) {
AuthHandler.handleCallbackUrl(intent.data.toString())
intent.data = null
}
}
}

@Preview
Expand Down
4 changes: 1 addition & 3 deletions iosApp/iosApp/iOSApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ import shared
struct iOSApp: App {
var body: some Scene {
WindowGroup {
ContentView().onOpenURL(perform: { url in
AuthHandler.shared.handleCallbackUrl(callbackUrl: url.absoluteString)
})
ContentView()
}
}
}
1 change: 1 addition & 0 deletions shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ kotlin {
implementation(libs.moko.mvvm.compose)
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
implementation(compose.components.resources)
api("io.github.kevinnzou:compose-webview-multiplatform:1.9.2")
}
}
val androidMain by getting {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<resources>
<string name="app_name">Marvel Champions Companion</string>
<string name="app_name">MCC</string>
<string name="database">Datenbank</string>
<string name="delete">Löschen</string>
<string name="decks">Decks</string>
Expand Down
2 changes: 1 addition & 1 deletion shared/src/commonMain/composeResources/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<resources>
<string name="app_name">Marvel Champions Companion</string>
<string name="app_name">MCC</string>
<string name="database">Database</string>
<string name="delete">Delete</string>
<string name="decks">Decks</string>
Expand Down
49 changes: 25 additions & 24 deletions shared/src/commonMain/kotlin/App.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import androidx.compose.runtime.Composable
import net.schacher.mcc.shared.auth.AuthHandler
import net.schacher.mcc.shared.auth.PersistingAuthHandler
import net.schacher.mcc.shared.datasource.database.CardDatabaseDao
import net.schacher.mcc.shared.datasource.database.DatabaseDao
import net.schacher.mcc.shared.datasource.database.DeckDatabaseDao
Expand All @@ -11,7 +13,8 @@ import net.schacher.mcc.shared.platform.platformModule
import net.schacher.mcc.shared.repositories.CardRepository
import net.schacher.mcc.shared.repositories.DeckRepository
import net.schacher.mcc.shared.repositories.PackRepository
import net.schacher.mcc.shared.screens.main.MainScreen
import net.schacher.mcc.shared.screens.app.AppScreen
import net.schacher.mcc.shared.screens.app.AppViewModel
import net.schacher.mcc.shared.screens.main.MainViewModel
import net.schacher.mcc.shared.screens.mydecks.MyDecksViewModel
import net.schacher.mcc.shared.screens.newdeck.NewDeckViewModel
Expand All @@ -25,7 +28,7 @@ import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module

val network = module {
singleOf<MarvelCDbDataSource>(::KtorMarvelCDbDataSource)
single<MarvelCDbDataSource> { KtorMarvelCDbDataSource(get()) }
}

val repositories = module {
Expand All @@ -35,6 +38,7 @@ val repositories = module {
}

val viewModels = module {
singleOf(::AppViewModel)
singleOf(::MainViewModel)
singleOf(::MyDecksViewModel)
singleOf(::NewDeckViewModel)
Expand All @@ -45,28 +49,25 @@ val viewModels = module {
}

@Composable
fun App(
databaseDao: DatabaseDao,
onKoinStart: KoinApplication.() -> Unit = {}
) {
KoinApplication(
application = {
onKoinStart()
modules(
platformModule,
module {
single<CardDatabaseDao> { databaseDao }
single<DeckDatabaseDao> { databaseDao }
single<PackDatabaseDao> { databaseDao }
single<SettingsDao> { databaseDao }
},
network,
repositories,
viewModels
)
}) {
fun App(databaseDao: DatabaseDao, onKoinStart: KoinApplication.() -> Unit = {}) {
val authHandler = PersistingAuthHandler(databaseDao as SettingsDao)
KoinApplication(application = {
onKoinStart()
modules(platformModule, module {
single<CardDatabaseDao> { databaseDao }
single<DeckDatabaseDao> { databaseDao }
single<PackDatabaseDao> { databaseDao }
single<SettingsDao> { databaseDao }
}, module {
single<AuthHandler> { authHandler }
}, network, repositories, viewModels
)
}) {
MccTheme {
MainScreen()
AppScreen()
}
}
}
}



10 changes: 0 additions & 10 deletions shared/src/commonMain/kotlin/net/schacher/mcc/shared/TestClass.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,56 +1,16 @@
package net.schacher.mcc.shared.auth

import co.touchlab.kermit.Logger
import io.ktor.http.Url
import net.schacher.mcc.shared.time.Time
import net.schacher.mcc.shared.utils.debug
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.flow.StateFlow

object AuthHandler {
interface AuthHandler {

const val APP_SCHEME = "mccapp"

val loggedIn: Boolean
get() = this.accessToken != null &&
(this.accessToken?.expiresAt ?: 0) > Time.currentTimeMillis
val loginState: StateFlow<Boolean>

val authHeader: String
get() = "Bearer ${this.accessToken?.token ?: throw IllegalStateException("No access token available")}"

var accessToken: AccessToken? = null
private set(value) {
field = value
Logger.d { "Access token set to $value" }
}


fun handleCallbackUrl(callbackUrl: String) {
val fixedCallbackUrl = callbackUrl.replace("#", "?")
Logger.debug { "Handling callback url: $fixedCallbackUrl" }
this.accessToken = try {
TokenUtils.parseData(fixedCallbackUrl)
} catch (e: Exception) {
Logger.e(e) { "Error parsing access token from $fixedCallbackUrl" }
null
}
}
}

fun isLoggedIn(): Boolean

object TokenUtils {
fun handleCallbackUrl(callbackUrl: String): Boolean

fun parseData(callbackUrl: String): AccessToken = Url(callbackUrl).let {
AccessToken(
token = it.parameters["access_token"]
?: throw IllegalArgumentException("No access token found"),
expiresAt = it.parameters["expires_in"]?.toLongOrNull()
?.let { Time.currentTimeMillis + it.seconds.inWholeMilliseconds }
?: throw IllegalArgumentException("No expiration time found")
)
}
fun logout()
}

data class AccessToken(
val token: String,
val expiresAt: Long
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package net.schacher.mcc.shared.auth

import co.touchlab.kermit.Logger
import io.ktor.http.Url
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import net.schacher.mcc.shared.datasource.database.SettingsDao
import net.schacher.mcc.shared.time.Time
import net.schacher.mcc.shared.utils.debug
import kotlin.time.Duration.Companion.seconds

class PersistingAuthHandler(private val settingsDao: SettingsDao) : AuthHandler {
companion object {
const val APP_SCHEME = "mccapp"
private const val ACCESS_TOKEN = "access_token"
private const val EXPIRES_AT = "access_token_expires_at"
}

private var accessToken: AccessToken? = null
set(value) {
field = value
Logger.debug { "Access token set to $value" }

this._loginState.value = this.isLoggedIn()
}

private val _loginState = MutableStateFlow(this.isLoggedIn())

override val loginState: StateFlow<Boolean> = _loginState.asStateFlow()

init {
this.restoreAccessToken()
}

override fun isLoggedIn(): Boolean = this.accessToken != null &&
(this.accessToken?.expiresAt ?: 0) > Time.currentTimeMillis

override val authHeader: String
get() = "Bearer ${this.accessToken?.token ?: throw IllegalStateException("No access token available")}"

private fun restoreAccessToken() {
val token = this.settingsDao.getString(ACCESS_TOKEN)
val expiresAt = this.settingsDao.getString(EXPIRES_AT)?.toLongOrNull()

if (token != null && expiresAt != null) {
this.accessToken = AccessToken(token, expiresAt)
}
}

override fun handleCallbackUrl(callbackUrl: String): Boolean {
val fixedCallbackUrl = callbackUrl.replace("#", "?")
Logger.debug { "Handling callback url: $fixedCallbackUrl" }

this.accessToken = try {
this.parseData(fixedCallbackUrl)
} catch (e: Exception) {
Logger.e(throwable = e) { "Error parsing access token from $fixedCallbackUrl" }
null
}?.also {
this.storeAccessToken(it)
}

return this.accessToken != null
}

private fun parseData(callbackUrl: String): AccessToken = Url(callbackUrl).let {
AccessToken(
token = it.parameters["access_token"]
?: throw IllegalArgumentException("No access token found"),
expiresAt = it.parameters["expires_in"]?.toLongOrNull()
?.let { Time.currentTimeMillis + it.seconds.inWholeMilliseconds }
?: throw IllegalArgumentException("No expiration time found")
)
}

override fun logout() {
this.accessToken = null

this.settingsDao.remove(ACCESS_TOKEN)
this.settingsDao.remove(EXPIRES_AT)
}


private fun storeAccessToken(accessToken: AccessToken) {
this.settingsDao.putString(ACCESS_TOKEN, accessToken.token)
this.settingsDao.putString(EXPIRES_AT, accessToken.expiresAt.toString())
}
}

data class AccessToken(
val token: String,
val expiresAt: Long
) {
val remainingTime: Long
get() = this.expiresAt - Time.currentTimeMillis

override fun toString(): String {
return "AccessToken(token='$token', expiresAt=$expiresAt, remainingTime=$remainingTime)"
}
}
Loading

0 comments on commit b9d08da

Please sign in to comment.