Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import androidx.activity.enableEdgeToEdge
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import zed.rainxch.githubstore.core.presentation.utils.AppContextHolder

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
Expand All @@ -18,9 +17,8 @@ class MainActivity : ComponentActivity() {
}

enableEdgeToEdge()
super.onCreate(savedInstanceState)

AppContextHolder.appContext = applicationContext
super.onCreate(savedInstanceState)

setContent {
App(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import org.koin.android.ext.koin.androidContext
import org.koin.core.module.Module
import org.koin.dsl.module
import zed.rainxch.githubstore.core.data.local.data_store.createDataStore
import zed.rainxch.githubstore.core.presentation.utils.AndroidBrowserHelper
import zed.rainxch.githubstore.core.presentation.utils.AndroidClipboardHelper
import zed.rainxch.githubstore.core.presentation.utils.BrowserHelper
import zed.rainxch.githubstore.core.presentation.utils.ClipboardHelper
import zed.rainxch.githubstore.feature.auth.data.AndroidTokenStore
import zed.rainxch.githubstore.feature.auth.data.TokenStore
import zed.rainxch.githubstore.feature.details.data.AndroidDownloader
Expand Down Expand Up @@ -37,6 +41,14 @@ actual val platformModule: Module = module {
createDataStore(androidContext())
}

single<BrowserHelper> {
AndroidBrowserHelper(androidContext())
}

single<ClipboardHelper> {
AndroidClipboardHelper(androidContext())
}

single<TokenStore> {
AndroidTokenStore(
dataStore = get()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package zed.rainxch.githubstore.core.presentation.utils

import android.content.Intent
import android.content.Context
import androidx.core.net.toUri

class AndroidBrowserHelper(
private val context: Context
) : BrowserHelper {
override fun openUrl(
url: String,
onFailure: (error: String) -> Unit
) {
val intent = Intent(Intent.ACTION_VIEW, url.toUri()).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(intent)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package zed.rainxch.githubstore.core.presentation.utils

import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context

class AndroidClipboardHelper(
private val context: Context
) : ClipboardHelper {
override fun copy(label: String, text: String) {
val cm = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
cm.setPrimaryClip(ClipData.newPlainText(label, text))
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,35 +1,14 @@
package zed.rainxch.githubstore.feature.auth.data

import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey.*
import zed.rainxch.githubstore.BuildConfig
import kotlinx.serialization.json.Json
import zed.rainxch.githubstore.core.presentation.utils.AppContextHolder
import androidx.core.content.edit
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.first
import kotlinx.serialization.json.Json
import zed.rainxch.githubstore.BuildConfig
import zed.rainxch.githubstore.core.domain.model.DeviceTokenSuccess

actual fun getGithubClientId(): String = BuildConfig.GITHUB_CLIENT_ID

actual fun copyToClipboard(label: String, text: String): Boolean {
return try {
val ctx: Context = AppContextHolder.appContext
val cm = ctx.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
cm.setPrimaryClip(ClipData.newPlainText(label, text))
true
} catch (_: Throwable) {
false
}
}

class AndroidTokenStore(
private val dataStore: DataStore<Preferences>,
) : TokenStore {
Expand All @@ -55,3 +34,5 @@ class AndroidTokenStore(
dataStore.edit { it.remove(TOKEN_KEY) }
}
}

actual fun getGithubClientId(): String = BuildConfig.GITHUB_CLIENT_ID
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import zed.rainxch.githubstore.core.data.TokenDataSource
import zed.rainxch.githubstore.core.data.data_source.TokenDataSource
import zed.rainxch.githubstore.core.domain.repository.ThemesRepository

class MainViewModel(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import org.koin.core.module.dsl.viewModel
import org.koin.core.module.dsl.viewModelOf
import org.koin.dsl.module
import zed.rainxch.githubstore.MainViewModel
import zed.rainxch.githubstore.core.data.DefaultTokenDataSource
import zed.rainxch.githubstore.core.data.TokenDataSource
import zed.rainxch.githubstore.core.data.data_source.DefaultTokenDataSource
import zed.rainxch.githubstore.core.data.data_source.TokenDataSource
import zed.rainxch.githubstore.core.data.repository.ThemesRepositoryImpl
import zed.rainxch.githubstore.core.domain.getPlatform
import zed.rainxch.githubstore.core.domain.repository.ThemesRepository
Expand Down Expand Up @@ -58,7 +58,7 @@ val authModule: Module = module {
factory { ObserveAccessTokenUseCase(get()) }
factory { LogoutUseCase(get()) }

viewModel { AuthenticationViewModel(get(), get(), get(), get()) }
viewModelOf(::AuthenticationViewModel)
}

val homeModule: Module = module {
Expand Down Expand Up @@ -93,7 +93,8 @@ val detailsModule: Module = module {
detailsRepository = get(),
downloader = get<Downloader>(),
installer = get<Installer>(),
platform = getPlatform()
platform = getPlatform(),
helper = get()
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package zed.rainxch.githubstore.core.data
package zed.rainxch.githubstore.core.data.data_source

import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
Expand Down Expand Up @@ -27,7 +27,6 @@ class DefaultTokenDataSource(
private val _flow = MutableStateFlow<DeviceTokenSuccess?>(null)
override val tokenFlow: StateFlow<DeviceTokenSuccess?> = _flow

// Track if initial load is complete
private val isInitialized = CompletableDeferred<Unit>()

init {
Expand All @@ -36,7 +35,6 @@ class DefaultTokenDataSource(
val token = tokenStore.load()
_flow.value = token
} finally {
// Mark as initialized regardless of success/failure
isInitialized.complete(Unit)
}
}
Expand All @@ -48,7 +46,6 @@ class DefaultTokenDataSource(
}

override suspend fun reloadFromStore(): DeviceTokenSuccess? {
// Wait for initial load to complete first!
isInitialized.await()
return _flow.value
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package zed.rainxch.githubstore.core.presentation.utils

expect fun openBrowser(
url: String,
onError: (error: String) -> Unit = { },
)
interface BrowserHelper {
fun openUrl(
url: String,
onFailure: (error: String) -> Unit = { },
)
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package zed.rainxch.githubstore.core.presentation.utils

interface ClipboardHelper {
fun copy(
label: String,
text: String
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,4 @@ interface TokenStore {
suspend fun clear()
}

expect fun getGithubClientId(): String

expect fun copyToClipboard(label: String, text: String): Boolean
expect fun getGithubClientId(): String
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import zed.rainxch.githubstore.core.data.TokenDataSource
import zed.rainxch.githubstore.core.data.data_source.TokenDataSource
import zed.rainxch.githubstore.core.domain.model.DeviceStart
import zed.rainxch.githubstore.core.domain.model.DeviceTokenSuccess
import zed.rainxch.githubstore.feature.auth.data.network.GitHubAuthApi
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package zed.rainxch.githubstore.feature.auth.presentation

sealed interface AuthenticationEvents {
data class OpenBrowser(val url: String) : AuthenticationEvents
data class CopyToClipboard(val label: String, val text: String) : AuthenticationEvents
data object OnNavigateToMain : AuthenticationEvents
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,10 @@ import githubstore.composeapp.generated.resources.ic_github
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.ui.tooling.preview.Preview
import org.koin.compose.viewmodel.koinViewModel
import zed.rainxch.githubstore.core.domain.model.DeviceStart
import zed.rainxch.githubstore.core.presentation.components.GithubStoreButton
import zed.rainxch.githubstore.feature.auth.data.copyToClipboard
import zed.rainxch.githubstore.core.presentation.utils.openBrowser
import zed.rainxch.githubstore.core.presentation.utils.ObserveAsEvents
import zed.rainxch.githubstore.core.presentation.theme.GithubStoreTheme
import zed.rainxch.githubstore.core.domain.model.DeviceStart
import zed.rainxch.githubstore.core.presentation.utils.ObserveAsEvents

@Composable
fun AuthenticationRoot(
Expand All @@ -56,16 +54,6 @@ fun AuthenticationRoot(

ObserveAsEvents(viewModel.events) { event ->
when (event) {
is AuthenticationEvents.OpenBrowser -> {
openBrowser(
url = event.url,
onError = { info ->
viewModel.onAction(AuthenticationAction.OnInfo(info))
}
)
}

is AuthenticationEvents.CopyToClipboard -> copyToClipboard(event.label, event.text)
AuthenticationEvents.OnNavigateToMain -> {
onNavigateToHome()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import zed.rainxch.githubstore.core.domain.model.DeviceStart
import zed.rainxch.githubstore.core.presentation.utils.BrowserHelper
import zed.rainxch.githubstore.core.presentation.utils.ClipboardHelper
import zed.rainxch.githubstore.feature.auth.domain.AwaitDeviceTokenUseCase
import zed.rainxch.githubstore.feature.auth.domain.LogoutUseCase
import zed.rainxch.githubstore.feature.auth.domain.ObserveAccessTokenUseCase
Expand All @@ -25,6 +27,8 @@ class AuthenticationViewModel(
private val awaitDeviceToken: AwaitDeviceTokenUseCase,
private val logoutUc: LogoutUseCase,
observeAccessToken: ObserveAccessTokenUseCase,
private val browserHelper: BrowserHelper,
private val clipboardHelper: ClipboardHelper,
private val scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
) : ViewModel() {

Expand Down Expand Up @@ -92,15 +96,15 @@ class AuthenticationViewModel(
)
}

_events.trySend(
AuthenticationEvents.CopyToClipboard(
"GitHub Code",
start.userCode
)
clipboardHelper.copy(
label = "GitHub Code",
text = start.userCode
)

awaitDeviceToken(start)

_state.update { it.copy(loginState = AuthLoginState.LoggedIn) }

_events.trySend(AuthenticationEvents.OnNavigateToMain)
} catch (_: CancellationException) {
_state.update { it.copy(loginState = AuthLoginState.Error("Cancelled")) }
Expand All @@ -118,7 +122,8 @@ class AuthenticationViewModel(

private fun openGitHub(start: DeviceStart) {
val url = start.verificationUriComplete ?: start.verificationUri
_events.trySend(AuthenticationEvents.OpenBrowser(url))

browserHelper.openUrl(url)
}

private fun copyCode(start: DeviceStart) {
Expand All @@ -128,7 +133,11 @@ class AuthenticationViewModel(
copied = true
)
}
_events.trySend(AuthenticationEvents.CopyToClipboard("GitHub Code", start.userCode))

clipboardHelper.copy(
label = "GitHub Code",
text = start.userCode
)
}

private fun logout() {
Expand Down
Loading