Skip to content

Commit

Permalink
feat: Authentication (#39)
Browse files Browse the repository at this point in the history
## <sup><sup>&#x1F534; [Mandatory]</sup></sup> What's the change?
- Sign in, Register actions added with Firebase Authentication
- Email, Google and Anonymous types added
- Credential manager added to safe and quick login

## <sup><sup>&#x1F534; [Mandatory]</sup></sup> Why do we need this
change?
- To authenticate users for the app

## Related issues/PRs/Jira ticket links
- #40

## <sup><sup>&#x1F534; [Mandatory if it's UI related]</sup></sup>
Screenshots
-
  • Loading branch information
enesky authored Nov 25, 2023
2 parents 51475fa + f4bd719 commit 8a1f1f6
Show file tree
Hide file tree
Showing 40 changed files with 1,298 additions and 188 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,4 @@ proguard/
*.log

# AjCore files
ajcore.*
ajcore.*
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ Doodle demonstrates modern Android development with Koin, Coroutines, Flow, Jetp

```properties
doodle.api.url="https://api.jikan.moe/v4/"
doodle.api.key="sample api key"
#Jikan API doesn't require an API key, but I'm using it as an example for later usages.
doodle.api.key="sample api key" #Jikan API doesn't require an API key, but I'm using it as an example for later usages.
doodle.google.api.key="sample google api ket" #Google API key is required for Google Authentication
```

- <a href="https://github.com/pinterest/ktlint" target="_blank">Ktlint</a> should be added using brew
Expand Down
2 changes: 1 addition & 1 deletion app/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,4 @@ proguard/
*.log

# AjCore files
ajcore.*
ajcore.*
25 changes: 20 additions & 5 deletions app/google-services.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@
],
"api_key": [
{
"current_key": "AIzaSyADM5K8M_9pE7nU-OkFFeadYLWK0MexOsc"
"current_key": "AIzaSyAGd3e4gg1HrUbHqBHWDiAFj_iqN4P_Hf4"
},
{
"current_key": "AIzaSyAFs09_-yOtdWuK7olSYjQdgGQxjQOIrfs"
}
],
"services": {
Expand Down Expand Up @@ -65,7 +68,10 @@
],
"api_key": [
{
"current_key": "AIzaSyADM5K8M_9pE7nU-OkFFeadYLWK0MexOsc"
"current_key": "AIzaSyAGd3e4gg1HrUbHqBHWDiAFj_iqN4P_Hf4"
},
{
"current_key": "AIzaSyAFs09_-yOtdWuK7olSYjQdgGQxjQOIrfs"
}
],
"services": {
Expand Down Expand Up @@ -102,7 +108,10 @@
],
"api_key": [
{
"current_key": "AIzaSyADM5K8M_9pE7nU-OkFFeadYLWK0MexOsc"
"current_key": "AIzaSyAGd3e4gg1HrUbHqBHWDiAFj_iqN4P_Hf4"
},
{
"current_key": "AIzaSyAFs09_-yOtdWuK7olSYjQdgGQxjQOIrfs"
}
],
"services": {
Expand Down Expand Up @@ -139,7 +148,10 @@
],
"api_key": [
{
"current_key": "AIzaSyADM5K8M_9pE7nU-OkFFeadYLWK0MexOsc"
"current_key": "AIzaSyAGd3e4gg1HrUbHqBHWDiAFj_iqN4P_Hf4"
},
{
"current_key": "AIzaSyAFs09_-yOtdWuK7olSYjQdgGQxjQOIrfs"
}
],
"services": {
Expand Down Expand Up @@ -176,7 +188,10 @@
],
"api_key": [
{
"current_key": "AIzaSyADM5K8M_9pE7nU-OkFFeadYLWK0MexOsc"
"current_key": "AIzaSyAGd3e4gg1HrUbHqBHWDiAFj_iqN4P_Hf4"
},
{
"current_key": "AIzaSyAFs09_-yOtdWuK7olSYjQdgGQxjQOIrfs"
}
],
"services": {
Expand Down
8 changes: 7 additions & 1 deletion app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,10 @@

# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken

# Keep Credentials API
-if class androidx.credentials.CredentialManager
-keep class androidx.credentials.playservices.** {
*;
}
4 changes: 4 additions & 0 deletions app/src/main/java/dev/enesky/doodle/app/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package dev.enesky.doodle.app.di

import dev.enesky.core.network.di.networkModule
import dev.enesky.feature.login.di.loginManagerModule
import org.koin.core.annotation.KoinExperimentalAPI
import org.koin.core.module.includes
import org.koin.dsl.lazyModule
Expand All @@ -30,4 +31,7 @@ val appModule = lazyModule {

// Includes all the lazy modules from the core modules
includes(networkModule, viewModelModule)

// Includes all the lazy modules from the feature modules
includes(loginManagerModule)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import androidx.navigation.compose.NavHost
import dev.enesky.core.navigation.DoodleNavigationDestination
import dev.enesky.feature.details.navigation.DetailsDestination
import dev.enesky.feature.details.navigation.detailsGraph
import dev.enesky.feature.login.navigation.loginGraph
import dev.enesky.feature.login.loginGraph
import dev.enesky.feature.main.navigation.HomeDestination
import dev.enesky.feature.main.navigation.homeGraph

Expand Down
21 changes: 20 additions & 1 deletion app/src/main/java/dev/enesky/doodle/app/ui/DoodleApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@ import dev.enesky.core.design_system.DoodleTheme
import dev.enesky.doodle.app.navigation.DoodleNavHost
import dev.enesky.doodle.app.ui.component.DoodleSnackbarHost
import dev.enesky.doodle.app.ui.component.LocalSnackbarHostState
import dev.enesky.feature.login.manager.AuthManager
import dev.enesky.feature.main.navigation.HomeDestination
import org.koin.compose.koinInject

@OptIn(ExperimentalLayoutApi::class)
@Suppress("ModifierMissing")
@Composable
fun DoodleApp(
modifier: Modifier = Modifier,
appState: DoodleAppState = rememberDoodleAppState(),
// TODO: add new parameter -> systemBarsColor: Color = DoodleTheme.colors.primaryDark
) {
Expand Down Expand Up @@ -64,6 +67,22 @@ fun DoodleApp(
// Use this when you want to use all the screen
// contentWindowInsets = WindowInsets(0.dp, 0.dp, 0.dp, 0.dp),
) { innerPadding ->

/**
* Update the start destination according to the user's login status
*/
val authManager: AuthManager = koinInject<AuthManager>()
if (authManager.isUserLoggedIn()) {
appState.startDestination = HomeDestination
}

/**
* TODO: Loading screen
* if (appState.showLoading) {
* LoadingWithTriangleDots()
* }
*/

DoodleNavHost(
modifier = Modifier
.padding(paddingValues = innerPadding)
Expand Down
8 changes: 3 additions & 5 deletions app/src/main/java/dev/enesky/doodle/app/ui/DoodleAppState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import androidx.navigation.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import dev.enesky.core.navigation.DoodleNavigationDestination
import dev.enesky.feature.login.navigation.LoginDestination
import dev.enesky.feature.login.LoginDestination
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
Expand All @@ -25,8 +25,7 @@ fun rememberDoodleAppState(
snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
coroutineScope: CoroutineScope = rememberCoroutineScope(),
navController: NavHostController = rememberNavController(),
// TODO: Check if user is logged in or not
startDestination: LoginDestination = LoginDestination,
startDestination: DoodleNavigationDestination = LoginDestination,
) = remember(
snackbarHostState,
coroutineScope,
Expand All @@ -46,7 +45,7 @@ class DoodleAppState(
val snackbarHostState: SnackbarHostState,
val coroutineScope: CoroutineScope,
val navController: NavHostController,
val startDestination: LoginDestination,
var startDestination: DoodleNavigationDestination,
) {
init {
coroutineScope.launch {
Expand All @@ -61,7 +60,6 @@ class DoodleAppState(
}
}
}

val currentDestination: NavDestination?
@Composable get() = navController.currentBackStackEntryAsState().value?.destination

Expand Down
4 changes: 4 additions & 0 deletions build-logic/convention/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ gradlePlugin {
id = libs.plugins.common.spotless.get().pluginId
implementationClass = "$rootPath.common.SpotlessPlugin"
}
register("authentication") {
id = libs.plugins.common.authentication.get().pluginId
implementationClass = "$rootPath.common.AuthenticationPlugin"
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ internal fun Project.configureAndroidCompose(commonExtension: CommonExtension<*,
val composeBomPlatform = platform(libs.compose.bom.get().toString())
add("implementation", composeBomPlatform)
add("implementation", libs.bundles.compose.materials)
add("implementation", libs.androidx.navigation.compose)
add("androidTestImplementation", composeBomPlatform)

add("implementation", libs.androidx.navigation.compose)
add("implementation", libs.lifecycle.runtime.compose)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,20 @@ import org.gradle.api.Project
import org.gradle.api.plugins.ExtensionAware
import org.gradle.kotlin.dsl.the
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
import java.util.Properties

internal val Project.libs get() = the<LibrariesForLibs>()

internal fun CommonExtension<*, *, *, *, *>.kotlinOptions(block: KotlinJvmOptions.() -> Unit) =
(this as ExtensionAware).extensions.configure("kotlinOptions", block)

internal fun getLocalProperties(rootProject: Project): Properties {
val localProperties = Properties()
val localPropertiesFile = rootProject.file("local.properties") // It's ignored by git
if (localPropertiesFile.exists() && localPropertiesFile.isFile) {
localPropertiesFile.inputStream().use { input ->
localProperties.load(input)
}
}
return localProperties
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,18 @@
package dev.enesky.build_logic.convention.plugins.common

import com.android.build.gradle.LibraryExtension
import dev.enesky.build_logic.convention.getLocalProperties
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.getByType
import java.util.Properties

/**
* Configure Api Key Provider
* -> Only for core/network/build.gradle.kts <-
*/
class ApiKeyProviderPlugin : Plugin<Project> {
override fun apply(target: Project) = with(target) {
val localProperties = Properties()
val localPropertiesFile = rootProject.file("local.properties") // It's ignored by git
if (localPropertiesFile.exists() && localPropertiesFile.isFile) {
localPropertiesFile.inputStream().use { input ->
localProperties.load(input)
}
}

val localProperties = getLocalProperties(rootProject)
val doodleApiUrl: String = checkNotNull(
localProperties.getProperty("doodle.api.url") ?: System.getenv("DOODLE_API_URL") ?: "\"\"",
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package dev.enesky.build_logic.convention.plugins.common

import com.android.build.gradle.LibraryExtension
import dev.enesky.build_logic.convention.getLocalProperties
import dev.enesky.build_logic.convention.libs
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.getByType

/**
* Created by Enes Kamil YILMAZ on 19/11/2023
*/
class AuthenticationPlugin : Plugin<Project> {
override fun apply(target: Project) = with(target) {
val localProperties = getLocalProperties(rootProject)
val googleApiKey: String = checkNotNull(
localProperties.getProperty("doodle.google.api.key")
?: System.getenv("DOODLE_GOOGLE_API_KEY")
?: "\"\"",
)

with(extensions.getByType<LibraryExtension>()) {
buildFeatures.buildConfig = true
defaultConfig.buildConfigField("String", "DOODLE_GOOGLE_API_KEY", googleApiKey)
}

dependencies {
// Firebase Authentication
add("implementation", platform(libs.firebase.bom))
add("implementation", libs.firebase.authentication)

// Google Sign In
add("implementation", libs.google.auth)

// Credential Manager
add("implementation", libs.credential.manager)
add("implementation", libs.credential.manager.play.services.auth)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ class FeaturePlugin : Plugin<Project> {
override fun apply(target: Project) = with(target) {
dependencies {
add("implementation", project(":core:design-system"))
add("implementation", project(":core:common"))
add("implementation", project(":core:data"))
add("implementation", project(":core:ui"))
}
}
}
2 changes: 1 addition & 1 deletion config/detekt/compose-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Compose:
# You can optionally disable the checks in this rule for regex matches against the composable name (e.g. molecule presenters)
# allowedComposableFunctionNames: .*Presenter,.*MoleculePresenter
ComposableParamOrder:
active: true
active: false
PreviewAnnotationNaming:
active: true
PreviewPublic:
Expand Down
2 changes: 1 addition & 1 deletion config/detekt/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ exceptions:
ThrowingNewInstanceOfSameException:
active: true
TooGenericExceptionCaught:
active: true
active: false
excludes: ['**/test/**', '**/androidTest/**']
exceptionNames:
- ArrayIndexOutOfBoundsException
Expand Down
4 changes: 4 additions & 0 deletions core/common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ plugins {
}

android.namespace = "dev.enesky.core.common"

dependencies {
implementation(projects.core.data)
}
Loading

0 comments on commit 8a1f1f6

Please sign in to comment.