Skip to content

Commit

Permalink
Merge pull request #119 from CrisisCleanup/tutorial
Browse files Browse the repository at this point in the history
Tutorial
  • Loading branch information
hueachilles authored Aug 15, 2024
2 parents 4b4b434 + e802df4 commit e3499bf
Show file tree
Hide file tree
Showing 58 changed files with 1,754 additions and 602 deletions.
5 changes: 4 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ plugins {
alias(libs.plugins.nowinandroid.android.application.flavors)
alias(libs.plugins.nowinandroid.android.application.jacoco)
alias(libs.plugins.nowinandroid.android.application.firebase)
alias(libs.plugins.firebase.crashlytics)
alias(libs.plugins.nowinandroid.hilt)
id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
}

android {
defaultConfig {
val buildVersion = 214
val buildVersion = 222
applicationId = "com.crisiscleanup"
versionCode = buildVersion
versionName = "0.9.${buildVersion - 168}"
Expand Down Expand Up @@ -42,6 +43,7 @@ android {
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
"proguard-playservices.pro",
"proguard-retrofit2.pro",
"proguard-crashlytics.pro",
)

Expand All @@ -53,6 +55,7 @@ android {
}

configure<CrashlyticsExtension> {
mappingFileUploadEnabled = true
// Enable processing and uploading of native symbols to Firebase servers.
// By default, this is disabled to improve build speeds.
// This flag must be enabled to see properly-symbolicated native
Expand Down
48 changes: 48 additions & 0 deletions app/proguard-retrofit2.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
# EnclosingMethod is required to use InnerClasses.
-keepattributes Signature, InnerClasses, EnclosingMethod

# Retrofit does reflection on method and parameter annotations.
-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations

# Keep annotation default values (e.g., retrofit2.http.Field.encoded).
-keepattributes AnnotationDefault

# Retain service method parameters when optimizing.
-keepclassmembers,allowshrinking,allowobfuscation interface * {
@retrofit2.http.* <methods>;
}

# Ignore annotation used for build tooling.
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement

# Ignore JSR 305 annotations for embedding nullability information.
-dontwarn javax.annotation.**

# Guarded by a NoClassDefFoundError try/catch and only used when on the classpath.
-dontwarn kotlin.Unit

# Top-level functions that can only be used by Kotlin.
-dontwarn retrofit2.KotlinExtensions
-dontwarn retrofit2.KotlinExtensions$*

# With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy
# and replaces all potential values with null. Explicitly keeping the interfaces prevents this.
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface <1>

# Keep inherited services.
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface * extends <1>

# With R8 full mode generic signatures are stripped for classes that are not
# kept. Suspend functions are wrapped in continuations where the type argument
# is used.
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation

# R8 full mode strips generic signatures from return types if not kept.
-if interface * { @retrofit2.http.* public *** *(...); }
-keep,allowoptimization,allowshrinking,allowobfuscation class <3>

# With R8 full mode generic signatures are stripped for classes that are not kept.
-keep,allowobfuscation,allowshrinking class retrofit2.Response
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.crisiscleanup

import androidx.compose.runtime.snapshots.SnapshotStateMap
import com.crisiscleanup.core.model.data.TutorialViewId
import com.crisiscleanup.core.model.data.TutorialViewId.AccountToggle
import com.crisiscleanup.core.model.data.TutorialViewId.AppNavBar
import com.crisiscleanup.core.model.data.TutorialViewId.IncidentSelectDropdown
import com.crisiscleanup.core.ui.LayoutSizePosition
import com.crisiscleanup.core.ui.TutorialViewTracker
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class CrisisCleanupTutorialViewTracker @Inject constructor() : TutorialViewTracker {
override val viewSizePositionLookup =
SnapshotStateMap<TutorialViewId, LayoutSizePosition>().also {
it[AppNavBar] = LayoutSizePosition()
it[IncidentSelectDropdown] = LayoutSizePosition()
it[AccountToggle] = LayoutSizePosition()
}
}
32 changes: 29 additions & 3 deletions app/src/main/java/com/crisiscleanup/MainActivityViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ import androidx.lifecycle.viewModelScope
import com.crisiscleanup.core.common.AppEnv
import com.crisiscleanup.core.common.AppSettingsProvider
import com.crisiscleanup.core.common.AppVersionProvider
import com.crisiscleanup.core.common.CrisisCleanupTutorialDirectors.Menu
import com.crisiscleanup.core.common.KeyResourceTranslator
import com.crisiscleanup.core.common.NetworkMonitor
import com.crisiscleanup.core.common.TutorialDirector
import com.crisiscleanup.core.common.Tutorials
import com.crisiscleanup.core.common.event.AuthEventBus
import com.crisiscleanup.core.common.event.ExternalEventBus
import com.crisiscleanup.core.common.event.UserPersistentInvite
Expand All @@ -35,6 +38,7 @@ import com.crisiscleanup.core.model.data.EarlybirdEndOfLifeFallback
import com.crisiscleanup.core.model.data.EmptyIncident
import com.crisiscleanup.core.model.data.MinSupportedAppVersion
import com.crisiscleanup.core.model.data.UserData
import com.crisiscleanup.core.ui.TutorialViewTracker
import com.google.firebase.analytics.FirebaseAnalytics
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher
Expand All @@ -60,13 +64,15 @@ import kotlin.time.Duration.Companion.hours

@HiltViewModel
class MainActivityViewModel @Inject constructor(
localAppPreferencesRepository: LocalAppPreferencesRepository,
private val appPreferencesRepository: LocalAppPreferencesRepository,
private val appMetricsRepository: LocalAppMetricsRepository,
accountDataRepository: AccountDataRepository,
incidentSelector: IncidentSelector,
appDataRepository: AppDataManagementRepository,
private val accountDataRefresher: AccountDataRefresher,
private val accountUpdateRepository: AccountUpdateRepository,
@Tutorials(Menu) private val menuTutorialDirector: TutorialDirector,
val tutorialViewTracker: TutorialViewTracker,
val translator: KeyResourceTranslator,
private val syncPuller: SyncPuller,
private val appVersionProvider: AppVersionProvider,
Expand All @@ -87,7 +93,7 @@ class MainActivityViewModel @Inject constructor(
private val initialAppOpen = AtomicReference<AppOpenInstant>(null)

val viewState = combine(
localAppPreferencesRepository.userPreferences,
appPreferencesRepository.userPreferences,
appMetricsRepository.metrics.distinctUntilChanged(),
::Pair,
)
Expand Down Expand Up @@ -188,6 +194,8 @@ class MainActivityViewModel @Inject constructor(
val isSwitchingToProduction: StateFlow<Boolean>
val productionSwitchMessage: StateFlow<String>

val menuTutorialStep = menuTutorialDirector.tutorialStep

init {
accountDataRepository.accountData
.onEach {
Expand Down Expand Up @@ -216,7 +224,7 @@ class MainActivityViewModel @Inject constructor(
.flowOn(ioDispatcher)
.launchIn(viewModelScope)

localAppPreferencesRepository.userPreferences.onEach {
appPreferencesRepository.userPreferences.onEach {
firebaseAnalytics.setAnalyticsCollectionEnabled(it.allowAllAnalytics)
}
.launchIn(viewModelScope)
Expand Down Expand Up @@ -307,6 +315,24 @@ class MainActivityViewModel @Inject constructor(
}
}
}

private fun setMenuTutorialDone() {
viewModelScope.launch(ioDispatcher) {
appPreferencesRepository.setMenuTutorialDone(true)
}
}

fun onMenuTutorialNext() {
val hasNextStep = menuTutorialDirector.onNextStep()
if (!hasNextStep) {
setMenuTutorialDone()
}
}

fun closeMenuTutorial() {
menuTutorialDirector.skipTutorial()
setMenuTutorialDone()
}
}

sealed interface MainActivityViewState {
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/com/crisiscleanup/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.crisiscleanup.AndroidPermissionManager
import com.crisiscleanup.AndroidPhoneNumberPicker
import com.crisiscleanup.AppVisualAlertManager
import com.crisiscleanup.CrisisCleanupAppEnv
import com.crisiscleanup.CrisisCleanupTutorialViewTracker
import com.crisiscleanup.ZxingQrCodeGenerator
import com.crisiscleanup.core.common.AppEnv
import com.crisiscleanup.core.common.LocationProvider
Expand All @@ -18,6 +19,7 @@ import com.crisiscleanup.core.common.VisualAlertManager
import com.crisiscleanup.core.common.log.TagLogger
import com.crisiscleanup.core.network.AuthInterceptorProvider
import com.crisiscleanup.core.network.RetrofitInterceptorProvider
import com.crisiscleanup.core.ui.TutorialViewTracker
import com.crisiscleanup.log.CrisisCleanupAppLogger
import com.crisiscleanup.network.CrisisCleanupAuthInterceptorProvider
import com.crisiscleanup.network.CrisisCleanupInterceptorProvider
Expand Down Expand Up @@ -68,6 +70,9 @@ interface AppModule {
@Singleton
@Binds
fun bindsPhoneNumberPicker(picker: AndroidPhoneNumberPicker): PhoneNumberPicker

@Binds
fun bindsTutorialViewTracker(tracker: CrisisCleanupTutorialViewTracker): TutorialViewTracker
}

@Module
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ fun CrisisCleanupAuthNavHost(
val navToVolunteerOrg = navController::navigateToVolunteerOrg
val navToForgotPassword = navController::navigateToForgotPassword
val navToEmailMagicLink = navController::navigateToEmailLoginLink
val navToLoginWithPhoneClearStack = remember(navController) {
{
navController.popToAuth()
navController.navigateToLoginWithPhone()
}
}

NavHost(
navController = navController,
Expand All @@ -66,6 +72,7 @@ fun CrisisCleanupAuthNavHost(
closeAuthentication = closeAuthentication,
openForgotPassword = navToForgotPassword,
openEmailMagicLink = navToEmailMagicLink,
openPhoneLogin = navToLoginWithPhoneClearStack,
nestedGraphs = {
forgotPasswordScreen(
onBack = onBack,
Expand Down
35 changes: 31 additions & 4 deletions app/src/main/java/com/crisiscleanup/ui/CrisisCleanupApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,11 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
Expand All @@ -50,15 +52,19 @@ import com.crisiscleanup.AuthState
import com.crisiscleanup.MainActivityViewModel
import com.crisiscleanup.MainActivityViewState
import com.crisiscleanup.core.common.NetworkMonitor
import com.crisiscleanup.core.common.TutorialStep
import com.crisiscleanup.core.designsystem.LayoutProvider
import com.crisiscleanup.core.designsystem.LocalAppTranslator
import com.crisiscleanup.core.designsystem.LocalLayoutProvider
import com.crisiscleanup.core.designsystem.component.BusyIndicatorFloatingTopCenter
import com.crisiscleanup.core.designsystem.component.CrisisCleanupBackground
import com.crisiscleanup.core.designsystem.theme.LocalDimensions
import com.crisiscleanup.core.model.data.TutorialViewId
import com.crisiscleanup.core.ui.AppLayoutArea
import com.crisiscleanup.core.ui.LayoutSizePosition
import com.crisiscleanup.core.ui.LocalAppLayout
import com.crisiscleanup.core.ui.rememberIsKeyboardOpen
import com.crisiscleanup.core.ui.sizePosition
import com.crisiscleanup.feature.authentication.navigation.navigateToMagicLinkLogin
import com.crisiscleanup.feature.authentication.navigation.navigateToOrgPersistentInvite
import com.crisiscleanup.feature.authentication.navigation.navigateToPasswordReset
Expand Down Expand Up @@ -209,10 +215,15 @@ private fun BoxScope.LoadedContent(
?: true
val isOnboarding = !hideOnboarding

val menuTutorialStep by viewModel.menuTutorialStep.collectAsStateWithLifecycle()

NavigableContent(
snackbarHostState,
appState,
isOnboarding,
menuTutorialStep,
viewModel.tutorialViewTracker.viewSizePositionLookup,
viewModel::onMenuTutorialNext,
) { openAuthentication = true }

if (
Expand Down Expand Up @@ -315,11 +326,19 @@ private fun NavigableContent(
snackbarHostState: SnackbarHostState,
appState: CrisisCleanupAppState,
isOnboarding: Boolean,
menuTutorialStep: TutorialStep,
tutorialViewLookup: SnapshotStateMap<TutorialViewId, LayoutSizePosition>,
advanceMenuTutorial: () -> Unit,
openAuthentication: () -> Unit,
) {
val showNavigation = appState.isTopLevelRoute
val layoutBottomNav = LocalLayoutProvider.current.isBottomNav
val isFullscreen = appState.isFullscreenRoute

val navBarSizePositionModifier = Modifier.onGloballyPositioned { coordinates ->
tutorialViewLookup[TutorialViewId.AppNavBar] = coordinates.sizePosition
}

Scaffold(
modifier = Modifier.semantics {
testTagsAsResourceId = true
Expand All @@ -337,7 +356,7 @@ private fun NavigableContent(
) {
AppNavigationBar(
appState,
Modifier.testTag("AppNavigationBottomBar"),
navBarSizePositionModifier.testTag("AppNavigationBottomBar"),
)
}

Expand Down Expand Up @@ -367,9 +386,9 @@ private fun NavigableContent(
if (showNavigation && !layoutBottomNav) {
AppNavigationBar(
appState,
Modifier
.testTag("AppNavigationSideRail")
.safeDrawingPadding(),
navBarSizePositionModifier
.safeDrawingPadding()
.testTag("AppNavigationSideRail"),
true,
)
}
Expand Down Expand Up @@ -406,6 +425,14 @@ private fun NavigableContent(
}
}
}

if (menuTutorialStep != TutorialStep.End) {
TutorialOverlay(
tutorialStep = menuTutorialStep,
onNextStep = advanceMenuTutorial,
tutorialViewLookup = tutorialViewLookup,
)
}
}

@Composable
Expand Down
Loading

0 comments on commit e3499bf

Please sign in to comment.