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
26 changes: 20 additions & 6 deletions .github/workflows/build_linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ jobs:
- name: Checkout branch
uses: actions/checkout@v2

- run: |
echo "${{ secrets.KEYSTORE }}" > keystore.jks.asc
gpg -d --passphrase "${{ secrets.KEYSTORE_PASSPHRASE }}" --batch keystore.jks.asc > keystore.jks

- name: set up JDK 17
uses: actions/setup-java@v3
with:
Expand All @@ -28,11 +32,21 @@ jobs:
- name: Grant execute permission for gradlew
run: chmod +x gradlew

# local properties not needed now
# - name: set up LOCAL_PROPERTIES
# env:
# LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
# run: echo "$LOCAL_PROPERTIES" > ./local.properties
- name: Configure Keystore
env:
KEYSTORE_KEY_ALIAS: ${{ secrets.KEYSTORE_KEY_ALIAS }}
KEYSTORE_KEY_PASSWORD: ${{ secrets.KEYSTORE_KEY_PASSWORD }}
KEYSTORE_STORE_PASSWORD: ${{ secrets.KEYSTORE_STORE_PASSWORD }}
run: |
echo "storeFile=keystore.jks" >> keystore.properties
echo "keyAlias=$KEYSTORE_KEY_ALIAS" >> keystore.properties
echo "storePassword=$KEYSTORE_STORE_PASSWORD" >> keystore.properties
echo "keyPassword=$KEYSTORE_KEY_PASSWORD" >> keystore.properties

- name: set up LOCAL_PROPERTIES
env:
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
run: echo "$LOCAL_PROPERTIES" > ./local.properties

- name: Build with Gradle
run: ./gradlew assembleAndroidTest
run: ./gradlew assembleAndroidTest
17 changes: 16 additions & 1 deletion .github/workflows/build_mac_os.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ jobs:
- name: Checkout branch
uses: actions/checkout@v4

- run: |
echo "${{ secrets.KEYSTORE }}" > keystore.jks.asc
gpg -d --passphrase "${{ secrets.KEYSTORE_PASSPHRASE }}" --batch keystore.jks.asc > keystore.jks

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
Expand All @@ -28,6 +32,17 @@ jobs:
- name: Set Xcode version
run: sudo xcode-select -s /Applications/Xcode_15.3.app/Contents/Developer

- name: Configure Keystore
env:
KEYSTORE_KEY_ALIAS: ${{ secrets.KEYSTORE_KEY_ALIAS }}
KEYSTORE_KEY_PASSWORD: ${{ secrets.KEYSTORE_KEY_PASSWORD }}
KEYSTORE_STORE_PASSWORD: ${{ secrets.KEYSTORE_STORE_PASSWORD }}
run: |
echo "storeFile=keystore.jks" >> keystore.properties
echo "keyAlias=$KEYSTORE_KEY_ALIAS" >> keystore.properties
echo "storePassword=$KEYSTORE_STORE_PASSWORD" >> keystore.properties
echo "keyPassword=$KEYSTORE_KEY_PASSWORD" >> keystore.properties

- name: set up LOCAL_PROPERTIES
env:
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
Expand All @@ -37,4 +52,4 @@ jobs:
run: ./gradlew kspKotlinMetadata

- name: Build with Gradle
run: cd iosApp && xcodebuild -workspace ./iosApp.xcworkspace -scheme iosApp -configuration Debug -destination 'platform=iOS Simulator,OS=latest,name=iPhone 15' CODE_SIGNING_ALLOWED='NO'
run: cd iosApp && xcodebuild -workspace ./iosApp.xcworkspace -scheme iosApp -configuration Debug -destination 'platform=iOS Simulator,OS=latest,name=iPhone 15' CODE_SIGNING_ALLOWED='NO'
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import com.stslex.atten.convention.configureKsp
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import java.io.File
import java.io.FileInputStream
import java.io.InputStreamReader
import java.util.Properties

class KMPApplicationConventionPlugin : Plugin<Project> {

Expand Down Expand Up @@ -43,5 +47,61 @@ class KMPApplicationConventionPlugin : Plugin<Project> {
versionCode = libs.findVersionInt("versionCode")
}
}
configureSigning()
}
}

fun Project.configureSigning() = extensions.configure<ApplicationExtension> {
signingConfigs {
val keystoreProperties = gradleKeystoreProperties(project.rootProject.projectDir)
create("release") {
keyAlias = keystoreProperties.getProperty("keyAlias")
keyPassword = keystoreProperties.getProperty("keyPassword")
storeFile = project.getFile(keystoreProperties.getProperty("storeFile"))
storePassword = keystoreProperties.getProperty("storePassword")
}
with(getByName("debug")) {
keyAlias = keystoreProperties.getProperty("keyAlias")
keyPassword = keystoreProperties.getProperty("keyPassword")
storeFile = project.getFile(keystoreProperties.getProperty("storeFile"))
storePassword = keystoreProperties.getProperty("storePassword")
}
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
signingConfig = signingConfigs.getByName("release")
isDebuggable = false
}
getByName("debug") {
signingConfig = signingConfigs.getByName("debug")
isDebuggable = true
}
}
}


fun Project.getFile(path: String): File {
val file = File(project.rootProject.projectDir, path)
if (file.isFile) {
return file
} else {
throw IllegalStateException("${file.name} is inValid")
}
}

fun gradleKeystoreProperties(projectRootDir: File): Properties {
val properties = Properties()
val localProperties = File(projectRootDir, "keystore.properties")

if (localProperties.isFile) {
InputStreamReader(FileInputStream(localProperties), Charsets.UTF_8).use { reader ->
properties.load(reader)
}
}
return properties
}
2 changes: 2 additions & 0 deletions commonApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ kotlin {
implementation(project(":core:database"))
implementation(project(":core:paging"))
implementation(project(":core:todo"))
implementation(project(":core:auth"))

implementation(project(":feature:home"))
implementation(project(":feature:details"))
implementation(project(":feature:settings"))
}
}
}
Expand Down
11 changes: 11 additions & 0 deletions commonApp/src/androidMain/kotlin/com/stslex/atten/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,21 @@ import androidx.core.view.WindowCompat
import androidx.lifecycle.compose.LocalLifecycleOwner
import com.arkivanov.decompose.DefaultComponentContext
import com.arkivanov.decompose.defaultComponentContext
import com.stslex.atten.core.ui.kit.utils.ActivityHolderProducer
import com.stslex.atten.host.DefaultRootComponent
import org.koin.android.ext.android.getKoin

class MainActivity : ComponentActivity() {

private val activityProducer: ActivityHolderProducer by lazy { getKoin().get() }

override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
val rootComponent = DefaultRootComponent(defaultComponentContext())
val windowController = WindowCompat.getInsetsController(window, window.decorView)

activityProducer.produce(this)
setContent {
App(
rootComponent = rootComponent,
Expand All @@ -28,6 +34,11 @@ class MainActivity : ComponentActivity() {
)
}
}

override fun onDestroy() {
super.onDestroy()
activityProducer.produce(null)
}
}

@Preview
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.stslex.atten.di

import com.stslex.atten.core.auth.di.ModuleCoreAuth
import com.stslex.atten.core.core.di.ModuleCore
import com.stslex.atten.core.database.di.ModuleCoreDatabase
import com.stslex.atten.core.paging.di.ModuleCorePaging
import com.stslex.atten.core.todo.di.ModuleCoreToDo
import com.stslex.atten.core.ui.kit.utils.ModuleCoreUiUtils
import com.stslex.atten.feature.details.di.ModuleFeatureDetails
import com.stslex.atten.feature.home.di.ModuleFeatureHome
import com.stslex.atten.feature.settings.di.ModuleFeatureSettings
import org.koin.core.module.Module
import org.koin.ksp.generated.module

Expand All @@ -14,6 +17,9 @@ val appModules: List<Module> = listOf(
ModuleCoreDatabase().module,
ModuleCoreToDo().module,
ModuleCorePaging().module,
ModuleCoreUiUtils().module,
ModuleCoreAuth().module,
ModuleFeatureHome().module,
ModuleFeatureDetails().module,
ModuleFeatureSettings().module
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.arkivanov.decompose.extensions.compose.stack.Children
import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation
import com.stslex.atten.feature.settings.ui.SettingsScreen
import com.stslex.atten.feature.details.ui.DetailsScreen
import com.stslex.atten.feature.home.ui.HomeScreen

Expand All @@ -21,6 +22,7 @@ internal fun AppNavigationHost(
when (val instance = created.instance) {
is RootComponent.Child.Details -> DetailsScreen(instance.component)
is RootComponent.Child.Home -> HomeScreen(instance.component)
is RootComponent.Child.Settings -> SettingsScreen(instance.component)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.arkivanov.decompose.router.stack.childStack
import com.arkivanov.decompose.router.stack.navigate
import com.arkivanov.decompose.router.stack.pop
import com.arkivanov.decompose.value.Value
import com.stslex.atten.feature.settings.mvi.SettingsComponent
import com.stslex.atten.core.ui.navigation.Config
import com.stslex.atten.core.ui.navigation.Router
import com.stslex.atten.feature.details.ui.mvi.DetailsComponent
Expand Down Expand Up @@ -40,6 +41,7 @@ class DefaultRootComponent(
): Child = when (config) {
is Config.Home -> Child.Home(HomeComponent.create(context.router))
is Config.Detail -> Child.Details(DetailsComponent.create(context.router, config.uuid))
is Config.Settings -> Child.Settings(SettingsComponent.create(context.router))
}

@OptIn(DelicateDecomposeApi::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.stslex.atten.host
import com.arkivanov.decompose.Cancellation
import com.arkivanov.decompose.router.stack.ChildStack
import com.arkivanov.decompose.value.Value
import com.stslex.atten.feature.settings.mvi.SettingsComponent
import com.stslex.atten.core.ui.navigation.Config
import com.stslex.atten.feature.details.ui.mvi.DetailsComponent
import com.stslex.atten.feature.home.ui.mvi.HomeComponent
Expand All @@ -18,6 +19,8 @@ interface RootComponent {
data class Home(val component: HomeComponent) : Child

data class Details(val component: DetailsComponent) : Child

data class Settings(val component: SettingsComponent) : Child
}

}
15 changes: 15 additions & 0 deletions core/auth/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
plugins {
alias(libs.plugins.convention.kmp.library.compose)
}

kotlin {
sourceSets.apply {
commonMain.dependencies {
implementation(project(":core:core"))
implementation(project(":core:ui:kit"))
}
androidMain.dependencies {
implementation(libs.gms.auth)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package com.stslex.atten.core.auth.controller

import android.app.Activity
import android.content.IntentSender.SendIntentException
import androidx.activity.ComponentActivity
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.ActivityResult
import androidx.activity.result.IntentSenderRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.Composable
import com.google.android.gms.auth.api.identity.AuthorizationRequest
import com.google.android.gms.auth.api.identity.Identity
import com.google.android.gms.common.Scopes
import com.google.android.gms.common.api.Scope
import com.stslex.atten.core.auth.callback.GoogleAuthCallback
import com.stslex.atten.core.auth.model.GoogleAuthResult
import com.stslex.atten.core.core.logger.Log
import com.stslex.atten.core.ui.kit.utils.ActivityHolder

internal actual class GoogleAuthControllerImpl actual constructor(
private val callback: GoogleAuthCallback,
private val activityHolder: ActivityHolder
) : GoogleAuthController {

private lateinit var launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
private val activity: ComponentActivity
get() = requireNotNull(activityHolder.activity as? ComponentActivity)

@Composable
actual override fun RegisterLauncher() {
launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartIntentSenderForResult()
) { result ->
logger.d("activity_result: $result, ${result.data}")
val activity = checkNotNull(activityHolder.activity as? ComponentActivity)
val result = if (result.resultCode == Activity.RESULT_OK) {
val authorizationResult = Identity
.getAuthorizationClient(activity)
.getAuthorizationResultFromIntent(result.data)
val uiResult = GoogleAuthResult(
accessToken = authorizationResult.accessToken,
serverAuthCode = authorizationResult.serverAuthCode
)
Result.success(uiResult)
} else {
val msg = "auth fail with ${result.resultCode} code"
Result.failure(IllegalStateException(msg))
}
callback.process(result)
}
}

actual override fun auth(
block: (Result<GoogleAuthResult>) -> Unit
) {
callback { block(it) }

val authRequest = AuthorizationRequest.Builder()
.setRequestedScopes(listOf(Scope(Scopes.EMAIL)))
.build()

Identity.getAuthorizationClient(activity)
.authorize(authRequest)
.addOnSuccessListener { result ->
logger.d("on success: $result")
if (result.hasResolution()) {
try {
val pendingIntent = checkNotNull(result.pendingIntent)
val intentSenderRequest = IntentSenderRequest.Builder(pendingIntent).build()
launcher.launch(intentSenderRequest)
} catch (e: SendIntentException) {
logger.e(e, "Couldn't start Authorization UI: " + e.localizedMessage)
}
} else {
val uiResult = GoogleAuthResult(
accessToken = result.accessToken,
serverAuthCode = result.serverAuthCode
)
callback.process(Result.success(uiResult))
}
}
.addOnFailureListener {
logger.e(it)
callback.process(Result.failure(it))
}
}

companion object {

private val logger = Log.tag(TAG)
private const val TAG = "GOOGLE_AUTH_CONTROLLER"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.stslex.atten.core.auth.callback

internal interface GoogleAuthCallback : GoogleAuthReceiverCallback, GoogleAuthTransmitterCallback
Loading