Skip to content

Commit

Permalink
Merge pull request #49 from leinardi/dev
Browse files Browse the repository at this point in the history
Update dependencies + remote config support + allow multiple app instances
  • Loading branch information
leinardi committed Mar 13, 2024
2 parents 8098e12 + 9e5975f commit 3a3526e
Show file tree
Hide file tree
Showing 451 changed files with 5,795 additions and 14,090 deletions.
2 changes: 1 addition & 1 deletion .github/ci-gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright 2023 Roberto Leinardi.
# Copyright 2024 Roberto Leinardi.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 2 additions & 0 deletions .idea/detekt.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 29 additions & 4 deletions apps/forlago/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 Roberto Leinardi.
* Copyright 2024 Roberto Leinardi.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -14,10 +14,14 @@
* limitations under the License.
*/
import com.github.triplet.gradle.androidpublisher.ReleaseStatus
import io.github.reactivecircus.appversioning.SemVer
import java.time.Instant
import kotlin.math.log10
import kotlin.math.pow

plugins {
id("forlago.android-app-conventions")
id("forlago.app-versioning-conventions")
alias(libs.plugins.appversioning)
alias(libs.plugins.tripletplay)
}

Expand All @@ -27,7 +31,10 @@ println("Release keystore ${if (useReleaseKeystore) "" else "NOT "}found!")
android {
defaultConfig {
applicationId = config.apps.forlago.applicationId.get()
setProperty("archivesBaseName", "forlago")
setProperty("archivesBaseName", config.apps.forlago.baseName.get())

manifestPlaceholders["deepLinkScheme"] = config.apps.forlago.deepLinkScheme.get()
buildConfigField("String", "DEEP_LINK_SCHEME", "\"${config.apps.forlago.deepLinkScheme.get()}\"")

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" // https://github.com/google/dagger/issues/2033

Expand Down Expand Up @@ -80,6 +87,7 @@ android {
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules-benchmark.pro")
}
}
buildFeatures.buildConfig = true
}

val serviceAccountCredentialsFile: File = rootProject.file("release/play-account.json")
Expand All @@ -93,23 +101,40 @@ if (serviceAccountCredentialsFile.exists()) {
}
println("play-account.json ${if (serviceAccountCredentialsFile.exists()) "" else "NOT "}found!")

appVersioning {
overrideVersionCode { gitTag, _, _ ->
val semVer = SemVer.fromGitTag(gitTag)
val version = semVer.major * 10000 + semVer.minor * 100 + semVer.patch
val versionLength = (log10(version.toDouble()) + 1).toInt()
var epoch = Instant.now().epochSecond.toInt()
epoch -= epoch % 10.0.pow(versionLength.toDouble()).toInt()
version + epoch
}

overrideVersionName { gitTag, _, variantInfo ->
"${gitTag.rawTagName}${if (variantInfo.buildType == "debug") "-dev" else ""} (${gitTag.commitHash})"
}
}

dependencies {
// Modules
implementation(projects.modules.featureAccount)
implementation(projects.modules.featureBar)
implementation(projects.modules.featureDebug)
implementation(projects.modules.featureFoo)
implementation(projects.modules.featureLogin)
implementation(projects.modules.featureLogout)
implementation(projects.modules.libraryAndroid)
implementation(projects.modules.libraryI18n)
implementation(projects.modules.libraryLogging)
implementation(projects.modules.libraryNavigation)
implementation(projects.modules.libraryNetwork)
implementation(projects.modules.libraryPreferences)
implementation(projects.modules.libraryRemoteConfig)
implementation(projects.modules.libraryUi)

implementation(libs.androidx.activity.compose)
implementation(libs.androidx.lifecycle.process)
implementation(libs.androidx.lifecycle.runtime)
implementation(libs.androidx.profileinstaller) // Need this to side load a Baseline Profile when Benchmarking
implementation(libs.androidx.startup)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 Roberto Leinardi.
* Copyright 2024 Roberto Leinardi.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion apps/forlago/src/debug/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2023 Roberto Leinardi.
~ Copyright 2024 Roberto Leinardi.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
Expand Down
9 changes: 2 additions & 7 deletions apps/forlago/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2023 Roberto Leinardi.
~ Copyright 2024 Roberto Leinardi.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -31,14 +31,9 @@
android:supportsRtl="true"
android:theme="@style/Ui.Theme.SplashScreen">

<!--
singleTask is used instead of singleInstance because the latter breaks Espresso tests:
https://github.com/android/android-test/issues/1115
-->
<activity
android:name=".ui.MainActivity"
android:exported="true"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand All @@ -51,7 +46,7 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data android:scheme="forlago" />
<data android:scheme="${deepLinkScheme}" />
</intent-filter>
</activity>
</application>
Expand Down
50 changes: 5 additions & 45 deletions apps/forlago/src/main/kotlin/com/leinardi/forlago/Forlago.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 Roberto Leinardi.
* Copyright 2024 Roberto Leinardi.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,17 +17,17 @@
package com.leinardi.forlago

import android.app.Application
import android.os.Build
import android.os.StrictMode
import android.os.SystemClock
import android.util.Log
import coil.ImageLoader
import coil.ImageLoaderFactory
import coil.disk.DiskCache
import coil.memory.MemoryCache
import coil.util.DebugLogger
import com.leinardi.forlago.library.android.api.strictmode.configureStrictMode
import com.leinardi.forlago.library.feature.Feature
import com.leinardi.forlago.library.feature.FeatureManager
import com.leinardi.forlago.library.navigation.api.destination.NavigationDestination
import com.leinardi.forlago.library.network.api.interactor.ReadCertificatePinningEnabledInteractor
import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.runBlocking
Expand All @@ -46,52 +46,12 @@ class Forlago : Application(), ImageLoaderFactory {

override fun onCreate() {
super.onCreate()
configureStrictMode()
NavigationDestination.DEEP_LINK_SCHEME = BuildConfig.DEEP_LINK_SCHEME
configureStrictMode(runBlocking { readCertificatePinningEnabledInteractor() })
registerFeatures()
simulateHeavyLoad()
}

private fun configureStrictMode() {
// This can't be initialized using `androidx.startup.Initializer` or it will cause crashes in 3rd party libs using Content Providers
// and writing data on the main thread (e.g. LeakCanary and AndroidTestRunner).
if (BuildConfig.DEBUG) {
val builderThread = StrictMode.ThreadPolicy.Builder()
.detectAll()
.permitDiskReads()
.permitCustomSlowCalls()
.penaltyLog()
.penaltyDeath()
.detectResourceMismatches()
StrictMode.setThreadPolicy(builderThread.build())

val builderVM = StrictMode.VmPolicy.Builder()
.detectActivityLeaks()
.detectLeakedSqlLiteObjects()
.detectLeakedRegistrationObjects()
.detectFileUriExposure()
.penaltyLog()
.penaltyDeath()
.detectContentUriWithoutPermission()
// .detectUntaggedSockets() // https://github.com/square/okhttp/issues/3537#issuecomment-974861679
.apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
detectCredentialProtectedWhileLocked()
detectImplicitDirectBoot()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
detectIncorrectContextUse()
detectUnsafeIntentLaunch()
}
runBlocking {
if (readCertificatePinningEnabledInteractor()) {
detectCleartextNetwork()
}
}
}
StrictMode.setVmPolicy(builderVM.build())
}
}

@Suppress("MagicNumber")
private fun simulateHeavyLoad() {
SystemClock.sleep(1500)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 Roberto Leinardi.
* Copyright 2024 Roberto Leinardi.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down
20 changes: 3 additions & 17 deletions apps/forlago/src/main/kotlin/com/leinardi/forlago/di/AppModule.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 Roberto Leinardi.
* Copyright 2024 Roberto Leinardi.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -18,12 +18,7 @@ package com.leinardi.forlago.di

import android.app.Application
import com.leinardi.forlago.feature.account.AccountFeature
import com.leinardi.forlago.feature.account.api.interactor.account.RemoveAccountsInteractor
import com.leinardi.forlago.feature.account.api.interactor.token.InvalidateAccessTokenInteractor
import com.leinardi.forlago.feature.account.api.interactor.token.InvalidateRefreshTokenInteractor
import com.leinardi.forlago.library.android.api.interactor.android.DeleteWebViewDataInteractor
import com.leinardi.forlago.library.feature.Feature
import com.leinardi.forlago.library.navigation.api.navigator.ForlagoNavigator
import com.leinardi.forlago.ui.MainActivity
import dagger.Module
import dagger.Provides
Expand All @@ -40,17 +35,8 @@ object AppModule {
@IntoSet
fun provideAccountFeature(
application: Application,
deleteWebViewDataInteractor: DeleteWebViewDataInteractor,
invalidateAccessTokenInteractor: InvalidateAccessTokenInteractor,
invalidateRefreshTokenInteractor: InvalidateRefreshTokenInteractor,
navigator: ForlagoNavigator,
removeAccountsInteractor: RemoveAccountsInteractor,
): Feature = AccountFeature(
deleteWebViewDataInteractor = deleteWebViewDataInteractor,
invalidateAccessTokenInteractor = invalidateAccessTokenInteractor,
invalidateRefreshTokenInteractor = invalidateRefreshTokenInteractor,
accountFeatureFactory: AccountFeature.Factory,
): Feature = accountFeatureFactory.create(
mainActivityIntent = MainActivity.createIntent(application),
navigator = navigator,
removeAccountsInteractor = removeAccountsInteractor,
)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 Roberto Leinardi.
* Copyright 2024 Roberto Leinardi.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 Roberto Leinardi.
* Copyright 2024 Roberto Leinardi.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -40,6 +40,8 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LifecycleEventEffect
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController
import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi
Expand Down Expand Up @@ -95,8 +97,9 @@ class MainActivity : AppCompatActivity() { // AppCompatActivity is needed to be
ForlagoTheme(dynamicColor = viewModel.viewState.value.dynamicColors) {
ForlagoMainScreen(
effectFlow = viewModel.effect,
startDestination = viewModel.viewState.value.startDestination,
forlagoNavigator = forlagoNavigator,
sendEvent = { viewModel.onUiEvent(it) },
startDestination = viewModel.viewState.value.startDestination,
)
}
}
Expand All @@ -109,11 +112,6 @@ class MainActivity : AppCompatActivity() { // AppCompatActivity is needed to be
viewModel.onUiEvent(Event.OnIntentReceived(intent, true))
}

override fun onResume() {
super.onResume()
viewModel.onUiEvent(Event.OnShown)
}

override fun onDestroy() {
// Workaround to prevent executing a deep link again on activity recreated (e.g. theme change)
if (intent.action == Intent.ACTION_VIEW) {
Expand Down Expand Up @@ -157,14 +155,18 @@ class MainActivity : AppCompatActivity() { // AppCompatActivity is needed to be
}

@OptIn(ExperimentalMaterialNavigationApi::class)
@Suppress("ReusedModifierInstance")
@Suppress("ReusedModifierInstance", "ModifierNotUsedAtRoot")
@Composable
fun ForlagoMainScreen(
effectFlow: Flow<Effect>,
forlagoNavigator: ForlagoNavigator,
sendEvent: (event: Event) -> Unit,
startDestination: String,
modifier: Modifier = Modifier,
) {
LifecycleEventEffect(Lifecycle.Event.ON_RESUME) {
sendEvent(Event.OnActivityResumed)
}
val bottomSheetNavigator = rememberBottomSheetNavigator(skipHalfExpanded = true)
val navHostController = rememberNavController(bottomSheetNavigator)
val activity = LocalContext.current.requireActivity() as MainActivity
Expand All @@ -178,8 +180,14 @@ fun ForlagoMainScreen(
).also { Timber.d("Navigate to ${event.destination}") }

is NavigatorEvent.HandleDeepLink -> navHostController.handleDeepLink(event.intent)
is NavigatorEvent.NavigateUp -> navHostController.navigateUp().also { Timber.d("Navigate Up successful = $it") }
is NavigatorEvent.NavigateBack -> navHostController.popBackStack().also { Timber.d("NavigateBack successful = $it") }
is NavigatorEvent.NavigateBackOrHome -> if (navHostController.previousBackStackEntry != null) {
navHostController.popBackStack()
} else {
forlagoNavigator.navigateHome()
}

is NavigatorEvent.NavigateUp -> navHostController.navigateUp().also { Timber.d("Navigate Up successful = $it") }
}
}.launchIn(this)
}
Expand Down

0 comments on commit 3a3526e

Please sign in to comment.