From 60e64f5ded5a0572e8699b4938aa36b4b0659087 Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Thu, 25 May 2023 00:07:52 -0400
Subject: [PATCH 01/33] Target SDK 33

---
 app/build.gradle.kts        | 4 ++--
 attendance/build.gradle.kts | 4 ++--
 auth/build.gradle.kts       | 4 ++--
 base/build.gradle.kts       | 4 ++--
 build.gradle.kts            | 4 ++--
 navigation/build.gradle.kts | 4 ++--
 6 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 253413f..fb6bd83 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -71,11 +71,11 @@ android {
         create("release") {
         }
     }
-    compileSdk = 32
+    compileSdk = 33
     defaultConfig {
         applicationId = "org.robojackets.apiary"
         minSdk = 21
-        targetSdk = 32
+        targetSdk = 33
         versionCode = 12
         versionName = "1.0.0"
         vectorDrawables {
diff --git a/attendance/build.gradle.kts b/attendance/build.gradle.kts
index 4953318..cbcb6a4 100644
--- a/attendance/build.gradle.kts
+++ b/attendance/build.gradle.kts
@@ -46,10 +46,10 @@ dependencies {
 }
 
 android {
-    compileSdk = 32
+    compileSdk = 33
     defaultConfig {
         minSdk = 21
-        targetSdk = 32
+        targetSdk = 33
         vectorDrawables {
             useSupportLibrary = true
         }
diff --git a/auth/build.gradle.kts b/auth/build.gradle.kts
index e6c79a8..1406388 100644
--- a/auth/build.gradle.kts
+++ b/auth/build.gradle.kts
@@ -52,10 +52,10 @@ dependencies {
 }
 
 android {
-    compileSdk = 32
+    compileSdk = 33
     defaultConfig {
         minSdk = 21
-        targetSdk = 32
+        targetSdk = 33
 
         vectorDrawables {
             useSupportLibrary = true
diff --git a/base/build.gradle.kts b/base/build.gradle.kts
index ac41121..aba2aaf 100644
--- a/base/build.gradle.kts
+++ b/base/build.gradle.kts
@@ -44,10 +44,10 @@ hilt {
 }
 
 android {
-    compileSdk = 32
+    compileSdk = 33
     defaultConfig {
         minSdk = 21
-        targetSdk = 32
+        targetSdk = 33
 
         vectorDrawables {
             useSupportLibrary = true
diff --git a/build.gradle.kts b/build.gradle.kts
index 9114a2d..8d8f272 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -9,8 +9,8 @@ buildscript {
         classpath("com.android.tools.build:gradle:7.3.1")
         classpath("com.google.dagger:hilt-android-gradle-plugin:2.40.1")
         classpath("com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1")
-        classpath("com.google.gms:google-services:4.3.10")
-        classpath("com.google.android.gms:oss-licenses-plugin:0.10.5")
+        classpath("com.google.gms:google-services:4.3.15")
+        classpath("com.google.android.gms:oss-licenses-plugin:0.10.6")
     }
 }
 
diff --git a/navigation/build.gradle.kts b/navigation/build.gradle.kts
index 643e28b..980ebbf 100644
--- a/navigation/build.gradle.kts
+++ b/navigation/build.gradle.kts
@@ -31,10 +31,10 @@ dependencies {
 }
 
 android {
-    compileSdk = 32
+    compileSdk = 33
     defaultConfig {
         minSdk = 21
-        targetSdk = 32
+        targetSdk = 33
 
         vectorDrawables {
             useSupportLibrary = true

From 18e0b499deee0c90315f0cb1ddeee14cc1c75747 Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Thu, 25 May 2023 01:35:22 -0400
Subject: [PATCH 02/33] Update dependency versions

---
 app/build.gradle.kts                          | 10 ++---
 .../apiary/di/MainActivityModule.kt           |  4 +-
 attendance/build.gradle.kts                   | 10 ++---
 auth/build.gradle.kts                         |  8 ++--
 base/build.gradle.kts                         | 10 ++---
 build.gradle.kts                              | 16 +++----
 buildSrc/src/main/java/Dependencies.kt        | 45 +++++++++----------
 gradle.properties                             |  5 ++-
 gradle/wrapper/gradle-wrapper.properties      |  2 +-
 navigation/build.gradle.kts                   |  8 ++--
 10 files changed, 59 insertions(+), 59 deletions(-)

diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index fb6bd83..7ac1cdf 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -54,7 +54,7 @@ dependencies {
     implementation(NetworkDependencies.sandwich) // yum yum
 
     implementation(platform(NfcDependencies.nfc_firebase_bom))
-    implementation(NfcDependencies.nfc_firebase_core) // Firebase BoM and Core are required when including TapLinx (line below) manually
+    implementation(NfcDependencies.nfc_firebase_analytics) // Firebase BoM and Analytics (f/k/a Core) are required when including TapLinx (line below) manually
     implementation(files(NfcDependencies.nxp_nfc_android_aar_path))
 
     // Test dependencies
@@ -92,15 +92,15 @@ android {
         compose = true
     }
     compileOptions {
-        sourceCompatibility = JavaVersion.VERSION_1_8
-        targetCompatibility = JavaVersion.VERSION_1_8
+        sourceCompatibility = JavaVersion.VERSION_17
+        targetCompatibility = JavaVersion.VERSION_17
         isCoreLibraryDesugaringEnabled = true
     }
     kotlinOptions {
-        jvmTarget = "1.8"
+        jvmTarget = "17"
     }
     composeOptions {
-        kotlinCompilerExtensionVersion = "1.2.0-beta03"
+        kotlinCompilerExtensionVersion = "1.4.7"
     }
     namespace = "org.robojackets.apiary"
     hilt {
diff --git a/app/src/main/java/org/robojackets/apiary/di/MainActivityModule.kt b/app/src/main/java/org/robojackets/apiary/di/MainActivityModule.kt
index e63cb91..e6be8c7 100644
--- a/app/src/main/java/org/robojackets/apiary/di/MainActivityModule.kt
+++ b/app/src/main/java/org/robojackets/apiary/di/MainActivityModule.kt
@@ -2,7 +2,7 @@ package org.robojackets.apiary.di
 
 import android.content.Context
 import com.nxp.nfclib.NxpNfcLib
-import com.skydoves.sandwich.coroutines.CoroutinesResponseCallAdapterFactory
+import com.skydoves.sandwich.adapters.ApiResponseCallAdapterFactory
 import com.squareup.moshi.Moshi
 import com.squareup.moshi.Types
 import dagger.Module
@@ -76,7 +76,7 @@ object MainActivityModule {
     ): Retrofit = Retrofit.Builder()
         .client(okHttpClient)
         .baseUrl(globalSettings.appEnv.apiBaseUrl.toString())
-        .addCallAdapterFactory(CoroutinesResponseCallAdapterFactory.create())
+        .addCallAdapterFactory(ApiResponseCallAdapterFactory.create())
         .addConverterFactory(MoshiConverterFactory.create(moshi))
         .build()
 
diff --git a/attendance/build.gradle.kts b/attendance/build.gradle.kts
index cbcb6a4..d623c34 100644
--- a/attendance/build.gradle.kts
+++ b/attendance/build.gradle.kts
@@ -36,7 +36,7 @@ dependencies {
     implementation(NetworkDependencies.sandwich)
 
     implementation(platform(NfcDependencies.nfc_firebase_bom))
-    implementation(NfcDependencies.nfc_firebase_core) // Firebase BoM and Core are required when including TapLinx (line below) manually
+    implementation(NfcDependencies.nfc_firebase_analytics) // Firebase BoM and Analytics (f/k/a Core) are required when including TapLinx (line below) manually
     compileOnly(files(NfcDependencies.nxp_nfc_android_aar_path))
 
     // Test dependencies
@@ -63,15 +63,15 @@ android {
         compose = true
     }
     compileOptions {
-        sourceCompatibility = JavaVersion.VERSION_1_8
-        targetCompatibility = JavaVersion.VERSION_1_8
+        sourceCompatibility = JavaVersion.VERSION_17
+        targetCompatibility = JavaVersion.VERSION_17
         isCoreLibraryDesugaringEnabled = true
     }
     kotlinOptions {
-        jvmTarget = "1.8"
+        jvmTarget = "17"
     }
     composeOptions {
-        kotlinCompilerExtensionVersion = "1.2.0-beta03"
+        kotlinCompilerExtensionVersion = "1.4.7"
     }
     namespace = "org.robojackets.apiary.attendance"
     hilt {
diff --git a/auth/build.gradle.kts b/auth/build.gradle.kts
index 1406388..7cb2b00 100644
--- a/auth/build.gradle.kts
+++ b/auth/build.gradle.kts
@@ -71,15 +71,15 @@ android {
         compose = true
     }
     compileOptions {
-        sourceCompatibility = JavaVersion.VERSION_1_8
-        targetCompatibility = JavaVersion.VERSION_1_8
+        sourceCompatibility = JavaVersion.VERSION_17
+        targetCompatibility = JavaVersion.VERSION_17
         isCoreLibraryDesugaringEnabled = true
     }
     kotlinOptions {
-        jvmTarget = "1.8"
+        jvmTarget = "17"
     }
     composeOptions {
-        kotlinCompilerExtensionVersion = "1.2.0-beta03"
+        kotlinCompilerExtensionVersion = "1.4.7"
     }
     namespace = "org.robojackets.apiary.auth"
     hilt {
diff --git a/base/build.gradle.kts b/base/build.gradle.kts
index aba2aaf..f4a15c0 100644
--- a/base/build.gradle.kts
+++ b/base/build.gradle.kts
@@ -31,7 +31,7 @@ dependencies {
     implementation(NetworkDependencies.sandwich)
 
     implementation(platform(NfcDependencies.nfc_firebase_bom))
-    implementation(NfcDependencies.nfc_firebase_core) // Firebase BoM and Core are required when including TapLinx (line below) manually
+    implementation(NfcDependencies.nfc_firebase_analytics) // Firebase BoM and Analytics (f/k/a Core) are required when including TapLinx (line below) manually
     compileOnly(files(NfcDependencies.nxp_nfc_android_aar_path))
 
     // Test dependencies
@@ -62,15 +62,15 @@ android {
         compose = true
     }
     compileOptions {
-        sourceCompatibility = JavaVersion.VERSION_1_8
-        targetCompatibility = JavaVersion.VERSION_1_8
+        sourceCompatibility = JavaVersion.VERSION_17
+        targetCompatibility = JavaVersion.VERSION_17
         isCoreLibraryDesugaringEnabled = true
     }
     kotlinOptions {
-        jvmTarget = "1.8"
+        jvmTarget = "17"
     }
     composeOptions {
-        kotlinCompilerExtensionVersion = "1.2.0-beta03"
+        kotlinCompilerExtensionVersion = "1.4.7"
     }
     namespace = "org.robojackets.apiary.base"
     hilt {
diff --git a/build.gradle.kts b/build.gradle.kts
index 8d8f272..353df25 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -5,9 +5,9 @@ buildscript {
         mavenCentral()
     }
     dependencies {
-        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21")
-        classpath("com.android.tools.build:gradle:7.3.1")
-        classpath("com.google.dagger:hilt-android-gradle-plugin:2.40.1")
+        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.21")
+        classpath("com.android.tools.build:gradle:8.0.1")
+        classpath("com.google.dagger:hilt-android-gradle-plugin:2.46.1")
         classpath("com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1")
         classpath("com.google.gms:google-services:4.3.15")
         classpath("com.google.android.gms:oss-licenses-plugin:0.10.6")
@@ -29,14 +29,14 @@ allprojects {
 }
 
 plugins {
-    id("io.gitlab.arturbosch.detekt").version("1.20.0")
-    id("com.autonomousapps.dependency-analysis").version("1.4.0")
-    id("com.github.ben-manes.versions").version("0.42.0")
+    id("io.gitlab.arturbosch.detekt").version("1.23.0")
+    id("com.autonomousapps.dependency-analysis").version("1.20.0")
+    id("com.github.ben-manes.versions").version("0.46.0")
 }
 
 tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
     kotlinOptions {
-        jvmTarget = "1.8"
+        jvmTarget = "17"
     }
 }
 
@@ -71,7 +71,7 @@ tasks.register("detektAll", io.gitlab.arturbosch.detekt.Detekt::class) {
 }
 
 dependencies {
-    detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.16.0")
+    detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.22.0")
 }
 
 // from https://github.com/autonomousapps/dependency-analysis-android-gradle-plugin/wiki/ABI-filtering
diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt
index e14fabe..056f4db 100644
--- a/buildSrc/src/main/java/Dependencies.kt
+++ b/buildSrc/src/main/java/Dependencies.kt
@@ -1,9 +1,9 @@
 object ComposeDependencies {
     object Versions {
-        const val accompanist_version = "0.25.0"
-        const val compose_settings_version = "0.7.2"
-        const val compose_version = "1.1.1"
-        const val lifecycle_viewmodel_compose_version = "2.4.1"
+        const val accompanist_version = "0.30.1"
+        const val compose_settings_version = "0.27.0"
+        const val compose_version = "1.4.3"
+        const val lifecycle_viewmodel_compose_version = "2.6.1"
     }
 
     const val accompanist_systemuicontroller =
@@ -37,7 +37,7 @@ object ComposeDependencies {
 
 object MaterialDependencies {
     object Versions {
-        const val material_android_version = "1.6.1"
+        const val material_android_version = "1.9.0"
     }
 
     const val material_android =
@@ -46,11 +46,11 @@ object MaterialDependencies {
 
 object AndroidXDependencies {
     object Versions {
-        const val androidx_activity_compose_version = "1.4.0"
-        const val androidx_appcompat_version = "1.4.2"
-        const val androidx_browser_version = "1.4.0"
-        const val androidx_lifecycle_runtime_ktx_version = "2.4.1"
-        const val androidx_navigation_compose_version = "2.4.2"
+        const val androidx_activity_compose_version = "1.7.2"
+        const val androidx_appcompat_version = "1.6.1"
+        const val androidx_browser_version = "1.5.0"
+        const val androidx_lifecycle_runtime_ktx_version = "2.6.1"
+        const val androidx_navigation_compose_version = "2.5.3"
     }
 
     const val androidx_activity_compose =
@@ -66,19 +66,16 @@ object AndroidXDependencies {
 
 object FirebaseDependencies {
     object Versions {
-        const val firebase_bom_version = "30.1.0"
+        const val firebase_bom_version = "32.0.0"
     }
 
     const val firebase_bom = "com.google.firebase:firebase-bom:${Versions.firebase_bom_version}"
-    const val firebase_core = "com.google.firebase:firebase-core" // No explicit version specified
-    // because of the inclusion of the Firebase BOM
+    const val firebase_analytics = "com.google.firebase:firebase-analytics-ktx" // versioned by BOM
 }
 
 object NfcDependencies {
     const val nfc_firebase_bom = FirebaseDependencies.firebase_bom
-    const val nfc_firebase_core = FirebaseDependencies.firebase_core // Duplicate definition
-    // with the goal of clarifying that the inclusion of Firebase Core here is specifically for
-    // the NXP NFC (TapLinx) library
+    const val nfc_firebase_analytics = FirebaseDependencies.firebase_analytics
     const val nxp_nfc_android_aar_path = "../libs/nxpnfcandroidlib-release.aar"
 }
 
@@ -93,7 +90,7 @@ object AuthDependencies {
 object HiltDependencies {
     object Versions {
         const val hilt_navigation_compose_version = "1.0.0"
-        const val hilt_version = "2.42"
+        const val hilt_version = "2.44"
     }
 
     const val hilt = "com.google.dagger:hilt-android:${Versions.hilt_version}"
@@ -107,10 +104,10 @@ object AndroidToolDependencies {
     object Versions {
         const val android_tools_desugar_version = "1.1.5"
         const val krate_version = "2.0.0"
-        const val gson_version = "2.9.0"
-        const val in_app_update_compose_version = "0.0.16"
-        const val open_source_licenses_version = "17.0.0"
-        const val sentry_version = "5.7.3"
+        const val gson_version = "2.10.1"
+        const val in_app_update_compose_version = "0.0.17"
+        const val open_source_licenses_version = "17.0.1"
+        const val sentry_version = "6.19.1"
         const val timber_version = "5.0.1"
     }
 
@@ -129,12 +126,12 @@ object AndroidToolDependencies {
 
 object NetworkDependencies {
     object Versions {
-        const val moshi_version = "1.13.0"
+        const val moshi_version = "1.15.0"
         const val moshi_converter_factory_version = "2.9.0"
-        const val okhttp_bom_version = "4.9.3"
+        const val okhttp_bom_version = "4.11.0"
         const val retrofit_version = "2.9.0"
         const val retrofuture_version = "1.7.4"
-        const val sandwich_version = "1.2.5"
+        const val sandwich_version = "1.3.6"
     }
 
     const val moshi = "com.squareup.moshi:moshi:${Versions.moshi_version}"
diff --git a/gradle.properties b/gradle.properties
index 1728bde..20edcb1 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -5,4 +5,7 @@ org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M"
 kotlin.code.style=official
 
 #Android
-android.useAndroidX=true
\ No newline at end of file
+android.useAndroidX=true
+android.defaults.buildfeatures.buildconfig=true
+android.nonTransitiveRClass=true
+android.nonFinalResIds=true
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 6ee0b26..142d4cd 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
 #Wed Dec 15 17:03:27 EST 2021
 distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip
 distributionPath=wrapper/dists
 zipStorePath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
diff --git a/navigation/build.gradle.kts b/navigation/build.gradle.kts
index 980ebbf..f293d8a 100644
--- a/navigation/build.gradle.kts
+++ b/navigation/build.gradle.kts
@@ -50,14 +50,14 @@ android {
     }
     compileOptions {
         isCoreLibraryDesugaringEnabled = true
-        sourceCompatibility = JavaVersion.VERSION_1_8
-        targetCompatibility = JavaVersion.VERSION_1_8
+        sourceCompatibility = JavaVersion.VERSION_17
+        targetCompatibility = JavaVersion.VERSION_17
     }
     kotlinOptions {
-        jvmTarget = "1.8"
+        jvmTarget = "17"
     }
     composeOptions {
-        kotlinCompilerExtensionVersion = "1.2.0-beta03"
+        kotlinCompilerExtensionVersion = "1.4.7"
     }
     namespace = "org.robojackets.apiary.navigation"
     hilt {

From 3f9a8d932f4c194ed47359d4d1fab0a9f85aa804 Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Sun, 20 Aug 2023 01:26:46 -0400
Subject: [PATCH 03/33] First pass at M3 update

---
 app/build.gradle.kts                          |  2 +-
 .../apiary/ApiaryMobileApplication.kt         |  4 +-
 .../org/robojackets/apiary/MainActivity.kt    | 17 ++++++---
 .../robojackets/apiary/ui/global/AppTopBar.kt | 17 +++++----
 .../apiary/ui/settings/Settings.kt            |  8 ++--
 .../apiary/ui/update/UpdateAvailable.kt       | 16 ++++++--
 .../apiary/ui/update/UpdateGate.kt            |  2 +-
 attendance/build.gradle.kts                   |  1 +
 .../apiary/attendance/Attendance.kt           | 11 ++++--
 .../attendance/ui/AttendableSelection.kt      | 17 ++++++---
 .../ui/AttendableTypeSelectionScreen.kt       | 37 +++++++++++++------
 auth/build.gradle.kts                         |  1 +
 .../apiary/auth/ui/Authentication.kt          | 27 +++++++++++---
 .../ui/permissions/InsufficientPermissions.kt | 27 +++++++++-----
 .../permissions/MissingHiddenTeamsCallout.kt  |  4 +-
 base/build.gradle.kts                         |  3 ++
 .../apiary/base/ui/ActionPrompt.kt            | 14 ++++---
 .../apiary/base/ui/IconWithText.kt            |  2 +-
 .../apiary/base/ui/callout/Callout.kt         | 11 ++++--
 .../apiary/base/ui/error/ErrorMessage.kt      | 14 +++++--
 .../apiary/base/ui/icons/ExtraIcons.kt        | 23 ++++++------
 .../apiary/base/ui/nfc/BuzzCardPrompt.kt      | 14 ++++++-
 .../apiary/base/ui/nfc/NfcRequired.kt         | 13 +++++--
 .../robojackets/apiary/base/ui/theme/Shape.kt |  2 +-
 .../robojackets/apiary/base/ui/theme/Theme.kt | 21 +++++++++--
 .../robojackets/apiary/base/ui/theme/Type.kt  |  7 ++--
 .../apiary/base/ui/util/LoadingSpinner.kt     |  2 +-
 .../apiary/base/ui/util/MadeWithLove.kt       |  2 +-
 build.gradle.kts                              |  2 +-
 buildSrc/src/main/java/Dependencies.kt        |  6 ++-
 30 files changed, 223 insertions(+), 104 deletions(-)

diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 7ac1cdf..9edde41 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -37,11 +37,11 @@ dependencies {
     implementation(ComposeDependencies.compose_ui_tooling)
     implementation(ComposeDependencies.compose_foundation)
     implementation(ComposeDependencies.compose_material)
+    implementation(ComposeDependencies.compose_material3)
     implementation(ComposeDependencies.compose_material_icons_core)
     implementation(ComposeDependencies.compose_material_icons_extended)
     implementation(ComposeDependencies.compose_settings)
 
-
     implementation(HiltDependencies.hilt)
     kapt(HiltDependencies.hilt_android_compiler)
     implementation(HiltDependencies.hilt_navigation_compose)
diff --git a/app/src/main/java/org/robojackets/apiary/ApiaryMobileApplication.kt b/app/src/main/java/org/robojackets/apiary/ApiaryMobileApplication.kt
index 535df50..27fc2f8 100644
--- a/app/src/main/java/org/robojackets/apiary/ApiaryMobileApplication.kt
+++ b/app/src/main/java/org/robojackets/apiary/ApiaryMobileApplication.kt
@@ -8,8 +8,8 @@ import io.sentry.android.timber.SentryTimberIntegration
 import timber.log.Timber
 
 // Note: this class has to be in the same module as the @AndroidEntryPoint annotated class, which
-// is MainActivity.  In other words, you can't move this class to another module to solve
-// dependency issues (use dependency injection instead!).
+// is MainActivity. In other words, you can't move this class to another module to solve dependency
+// issues (use dependency injection instead!).
 @HiltAndroidApp
 class ApiaryMobileApplication : Application() {
     override fun onCreate() {
diff --git a/app/src/main/java/org/robojackets/apiary/MainActivity.kt b/app/src/main/java/org/robojackets/apiary/MainActivity.kt
index ae3f25e..69036fc 100644
--- a/app/src/main/java/org/robojackets/apiary/MainActivity.kt
+++ b/app/src/main/java/org/robojackets/apiary/MainActivity.kt
@@ -8,10 +8,17 @@ import androidx.activity.compose.setContent
 import androidx.annotation.StringRes
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.padding
-import androidx.compose.material.*
+import androidx.compose.material.ExperimentalMaterialApi
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Settings
 import androidx.compose.material.icons.outlined.Contactless
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.NavigationBar
+import androidx.compose.material3.NavigationBarItem
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
 import androidx.compose.runtime.*
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.toArgb
@@ -137,7 +144,7 @@ class MainActivity : ComponentActivity() {
 
         setContent {
             Apiary_MobileTheme {
-                window.statusBarColor = MaterialTheme.colors.primaryVariant.toArgb()
+                window.statusBarColor = MaterialTheme.colorScheme.secondary.toArgb()
                 val navController = rememberNavController()
                 val bottomSheetNavigator = rememberBottomSheetNavigator()
                 navController.navigatorProvider += bottomSheetNavigator
@@ -155,7 +162,7 @@ class MainActivity : ComponentActivity() {
                 }
 
                 // A surface container using the 'background' color from the theme
-                Surface(color = MaterialTheme.colors.background) {
+                Surface(color = MaterialTheme.colorScheme.background) {
                     ModalBottomSheetLayout(bottomSheetNavigator) {
                         UpdateGate(
                             navReady = navReady,
@@ -180,9 +187,9 @@ class MainActivity : ComponentActivity() {
                                 bottomBar = {
                                     val current = currentRoute(navController)
                                     if (shouldShowBottomNav(nfcEnabled, current)) {
-                                        BottomNavigation {
+                                        NavigationBar {
                                             navItems.forEach { screen ->
-                                                BottomNavigationItem(
+                                                NavigationBarItem(
                                                     icon = {
                                                         Icon(
                                                             screen.icon,
diff --git a/app/src/main/java/org/robojackets/apiary/ui/global/AppTopBar.kt b/app/src/main/java/org/robojackets/apiary/ui/global/AppTopBar.kt
index bf23577..4d42b22 100644
--- a/app/src/main/java/org/robojackets/apiary/ui/global/AppTopBar.kt
+++ b/app/src/main/java/org/robojackets/apiary/ui/global/AppTopBar.kt
@@ -5,18 +5,19 @@ import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Text
-import androidx.compose.material.TopAppBar
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.dp
 import org.robojackets.apiary.base.ui.IconWithText
 import org.robojackets.apiary.base.ui.icons.WarningIcon
 
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun AppTopBar(isProdEnv: Boolean) {
     Column {
@@ -24,7 +25,7 @@ fun AppTopBar(isProdEnv: Boolean) {
             title = {
                 Text(
                     text = "MyRoboJackets",
-                    style = MaterialTheme.typography.h5,
+                    style = MaterialTheme.typography.headlineSmall,
                     fontWeight = FontWeight.W800
                 )
             },
@@ -34,16 +35,16 @@ fun AppTopBar(isProdEnv: Boolean) {
             Box(
                 Modifier
                     .fillMaxWidth()
-                    .background(MaterialTheme.colors.error)
+                    .background(MaterialTheme.colorScheme.error)
                     .align(Alignment.CenterHorizontally)
                     .padding(vertical = 4.dp)
             ) {
                 IconWithText(
-                    icon = { WarningIcon(tint = Color.White) },
+                    icon = { WarningIcon(tint = MaterialTheme.colorScheme.onError) },
                     text = { Text(
                         "Non-production server",
                         modifier = Modifier.padding(start = 4.dp),
-                        color = Color.White
+                        color = MaterialTheme.colorScheme.onError
                     ) }
                 )
             }
diff --git a/app/src/main/java/org/robojackets/apiary/ui/settings/Settings.kt b/app/src/main/java/org/robojackets/apiary/ui/settings/Settings.kt
index 374875f..481aba8 100644
--- a/app/src/main/java/org/robojackets/apiary/ui/settings/Settings.kt
+++ b/app/src/main/java/org/robojackets/apiary/ui/settings/Settings.kt
@@ -8,11 +8,11 @@ import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.Icon
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Text
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.*
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.collectAsState
@@ -143,7 +143,7 @@ fun SettingsScreen(
     }
 
     val state by viewModel.state.collectAsState()
-    val secondaryThemeColor = MaterialTheme.colors.background
+    val secondaryThemeColor = MaterialTheme.colorScheme.background
     ContentPadding {
        Settings(
            appEnv = viewModel.globalSettings.appEnv,
diff --git a/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt b/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt
index d214674..0bed163 100644
--- a/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt
+++ b/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt
@@ -2,7 +2,12 @@ package org.robojackets.apiary.ui.update
 
 import android.app.Activity
 import androidx.compose.foundation.layout.*
-import androidx.compose.material.*
+import androidx.compose.material.AlertDialog
+import androidx.compose.material3.Button
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
 import androidx.compose.runtime.*
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -16,6 +21,9 @@ import org.robojackets.apiary.base.ui.util.getActivity
 import se.warting.inappupdate.compose.rememberInAppUpdateState
 import timber.log.Timber
 
+// TODO: M3 upgrade:
+// - Implement M3 AlertDialog
+
 // Just a random number so we can identify our update request later if necessary
 const val UPDATE_REQUEST_CODE = 1999
 
@@ -87,7 +95,7 @@ fun RequiredUpdatePrompt() {
         UpdateIcon(Modifier
             .padding(bottom = 18.dp)
             .size(96.dp))
-        Text("Update to continue", style = MaterialTheme.typography.h4)
+        Text("Update to continue", style = MaterialTheme.typography.headlineMedium)
         Text("To continue using MyRoboJackets, install the latest version. It'll only take a minute.",
             textAlign = TextAlign.Center,
             modifier = Modifier.padding(24.dp)
@@ -110,7 +118,7 @@ fun OptionalUpdatePrompt(
         UpdateIcon(Modifier
             .padding(bottom = 9.dp)
             .size(72.dp))
-        Text("Update available", style = MaterialTheme.typography.h5)
+        Text("Update available", style = MaterialTheme.typography.headlineSmall)
         Text("Install the latest version of MyRoboJackets for the latest features and " +
                 "bug fixes. It'll only take a minute.",
             textAlign = TextAlign.Center,
@@ -133,7 +141,7 @@ fun UpdateInProgress() {
         modifier = Modifier.fillMaxHeight(),
     ) {
         CircularProgressIndicator(Modifier.padding(bottom = 28.dp))
-        Text("Please wait...", style = MaterialTheme.typography.h5)
+        Text("Please wait...", style = MaterialTheme.typography.headlineSmall)
         Text("We're finishing installing an update. It'll just be a minute or two!",
             textAlign = TextAlign.Center,
             modifier = Modifier.padding(20.dp)
diff --git a/app/src/main/java/org/robojackets/apiary/ui/update/UpdateGate.kt b/app/src/main/java/org/robojackets/apiary/ui/update/UpdateGate.kt
index 14a32d0..2a7cbec 100644
--- a/app/src/main/java/org/robojackets/apiary/ui/update/UpdateGate.kt
+++ b/app/src/main/java/org/robojackets/apiary/ui/update/UpdateGate.kt
@@ -1,6 +1,6 @@
 package org.robojackets.apiary.ui.update
 
-import androidx.compose.material.Text
+import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.rememberCoroutineScope
diff --git a/attendance/build.gradle.kts b/attendance/build.gradle.kts
index d623c34..415502e 100644
--- a/attendance/build.gradle.kts
+++ b/attendance/build.gradle.kts
@@ -19,6 +19,7 @@ dependencies {
     implementation(AndroidToolDependencies.timber)
 
     implementation(ComposeDependencies.compose_material)
+    implementation(ComposeDependencies.compose_material3)
     implementation(ComposeDependencies.compose_ui)
     implementation(ComposeDependencies.lifecycle_viewmodel_compose)
 
diff --git a/attendance/src/main/java/org/robojackets/apiary/attendance/Attendance.kt b/attendance/src/main/java/org/robojackets/apiary/attendance/Attendance.kt
index ea6ac4c..ac53f75 100644
--- a/attendance/src/main/java/org/robojackets/apiary/attendance/Attendance.kt
+++ b/attendance/src/main/java/org/robojackets/apiary/attendance/Attendance.kt
@@ -1,8 +1,13 @@
 package org.robojackets.apiary.attendance
 
-import androidx.compose.foundation.layout.*
-import androidx.compose.material.Button
-import androidx.compose.material.Text
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.collectAsState
diff --git a/attendance/src/main/java/org/robojackets/apiary/attendance/ui/AttendableSelection.kt b/attendance/src/main/java/org/robojackets/apiary/attendance/ui/AttendableSelection.kt
index bb59cb3..3c69000 100644
--- a/attendance/src/main/java/org/robojackets/apiary/attendance/ui/AttendableSelection.kt
+++ b/attendance/src/main/java/org/robojackets/apiary/attendance/ui/AttendableSelection.kt
@@ -4,7 +4,13 @@ import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.*
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.itemsIndexed
-import androidx.compose.material.*
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material3.Button
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.Divider
+import androidx.compose.material3.ListItem
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.collectAsState
@@ -36,12 +42,11 @@ private fun <T> AttendableList(
         LazyColumn {
             itemsIndexed(attendables) { idx, attendable ->
                 ListItem(
+                    headlineContent = { attendableContent(attendable) },
                     Modifier.clickable {
                         onAttendableSelected(attendable)
                     }
-                ) {
-                    attendableContent(attendable)
-                }
+                )
                 if (idx < attendables.size - 1) {
                     Divider()
                 }
@@ -102,7 +107,7 @@ fun AttendableSelectionScreen(
                     onAttendableSelected = {
                         viewModel.onAttendableSelected(it.toAttendable())
                     },
-                    title = { Text("Select a team", style = MaterialTheme.typography.h5) },
+                    title = { Text("Select a team", style = MaterialTheme.typography.headlineSmall) },
                     callout = {
                         if (state.missingHiddenTeams == true) {
                             Spacer(Modifier.height(4.dp))
@@ -121,7 +126,7 @@ fun AttendableSelectionScreen(
                     onAttendableSelected = {
                         viewModel.onAttendableSelected(it.toAttendable())
                     },
-                    title = { Text("Select an event", style = MaterialTheme.typography.h5) }
+                    title = { Text("Select an event", style = MaterialTheme.typography.headlineSmall) }
                 ) {
                     Text(it.name)
                 }
diff --git a/attendance/src/main/java/org/robojackets/apiary/attendance/ui/AttendableTypeSelectionScreen.kt b/attendance/src/main/java/org/robojackets/apiary/attendance/ui/AttendableTypeSelectionScreen.kt
index 52b637c..9543a34 100644
--- a/attendance/src/main/java/org/robojackets/apiary/attendance/ui/AttendableTypeSelectionScreen.kt
+++ b/attendance/src/main/java/org/robojackets/apiary/attendance/ui/AttendableTypeSelectionScreen.kt
@@ -1,8 +1,17 @@
 package org.robojackets.apiary.attendance.ui
 
 import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.*
-import androidx.compose.material.*
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material3.Divider
+import androidx.compose.material3.ListItem
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.collectAsState
@@ -58,26 +67,32 @@ fun AttendableTypeSelectionScreen(
             Modifier
                 .fillMaxWidth()
                 .fillMaxHeight()) {
-            Text("What do you want to take attendance for?", style = MaterialTheme.typography.h5)
+            Text("What do you want to take attendance for?", style = MaterialTheme.typography.headlineSmall)
             Spacer(Modifier.defaultMinSize(minHeight = 16.dp))
             Divider()
             ListItem(
-                icon = { GroupsIcon(Modifier.size(36.dp)) },
+                leadingContent = {
+                    GroupsIcon(Modifier.size(36.dp))
+                },
+                headlineContent = {
+                    Text("Team", style = MaterialTheme.typography.titleLarge)
+                },
                 modifier = Modifier
                     .defaultMinSize(minHeight = 80.dp)
                     .clickable { viewModel.navigateToAttendableSelection(AttendableType.Team) }
-            ) {
-                Text("Team", style = MaterialTheme.typography.h6)
-            }
+            )
             Divider()
             ListItem(
-                icon = { EventIcon(Modifier.size(36.dp)) },
+                leadingContent = {
+                    EventIcon(Modifier.size(36.dp))
+                },
+                headlineContent = {
+                    Text("Event", style = MaterialTheme.typography.titleLarge)
+                },
                 modifier = Modifier
                     .defaultMinSize(minHeight = 80.dp)
                     .clickable { viewModel.navigateToAttendableSelection(AttendableType.Event) }
-            ) {
-                Text("Event", style = MaterialTheme.typography.h6)
-            }
+            )
             Divider()
         }
     }
diff --git a/auth/build.gradle.kts b/auth/build.gradle.kts
index 7cb2b00..e6a7fa7 100644
--- a/auth/build.gradle.kts
+++ b/auth/build.gradle.kts
@@ -29,6 +29,7 @@ dependencies {
     implementation(ComposeDependencies.accompanist_systemuicontroller)
     implementation(ComposeDependencies.compose_foundation)
     implementation(ComposeDependencies.compose_material)
+    implementation(ComposeDependencies.compose_material3)
     implementation(ComposeDependencies.compose_ui)
     implementation(ComposeDependencies.compose_ui_tooling)
 
diff --git a/auth/src/main/java/org/robojackets/apiary/auth/ui/Authentication.kt b/auth/src/main/java/org/robojackets/apiary/auth/ui/Authentication.kt
index fdb287b..793be11 100644
--- a/auth/src/main/java/org/robojackets/apiary/auth/ui/Authentication.kt
+++ b/auth/src/main/java/org/robojackets/apiary/auth/ui/Authentication.kt
@@ -7,7 +7,19 @@ import androidx.compose.foundation.Image
 import androidx.compose.foundation.layout.*
 import androidx.compose.foundation.selection.selectable
 import androidx.compose.foundation.selection.selectableGroup
-import androidx.compose.material.*
+import androidx.compose.material.AlertDialog
+import androidx.compose.material.BottomSheetScaffold
+import androidx.compose.material.BottomSheetScaffoldState
+import androidx.compose.material.BottomSheetValue
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.rememberBottomSheetScaffoldState
+import androidx.compose.material.rememberBottomSheetState
+import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.RadioButton
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
 import androidx.compose.runtime.*
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -27,6 +39,11 @@ import org.robojackets.apiary.base.AppEnvironment
 import org.robojackets.apiary.base.ui.theme.BottomSheetShape
 import org.robojackets.apiary.base.ui.util.MadeWithLove
 
+// TODO: M3 upgrade
+// - Update to M3 BottomSheet
+// -  Update to M3 AlertDialog
+// - Test auth behavior with new theme
+
 @OptIn(ExperimentalMaterialApi::class, androidx.compose.ui.ExperimentalComposeUiApi::class)
 @Suppress("LongMethod", "MagicNumber")
 @Composable
@@ -46,7 +63,7 @@ private fun Authentication(
     val coroutineScope = rememberCoroutineScope()
 
     val systemUiController = rememberSystemUiController()
-    val backgroundColor = MaterialTheme.colors.background
+    val backgroundColor = MaterialTheme.colorScheme.background
     SideEffect {
         systemUiController.setSystemBarsColor(backgroundColor)
     }
@@ -115,7 +132,7 @@ private fun Authentication(
             )
         }) {
         Surface(
-            color = MaterialTheme.colors.background,
+            color = MaterialTheme.colorScheme.background,
             modifier = Modifier.padding(8.dp)
         ) {
             Column(
@@ -207,7 +224,7 @@ private fun ChangeEnvironmentBottomSheetContent(
         "Change server",
         modifier = Modifier
             .padding(16.dp),
-        style = MaterialTheme.typography.h5
+        style = MaterialTheme.typography.headlineSmall
     )
 
     Column(Modifier.selectableGroup()) {
@@ -230,7 +247,7 @@ private fun ChangeEnvironmentBottomSheetContent(
                 )
                 Text(
                     text = "${it.name} (${it.apiBaseUrl})",
-                    style = MaterialTheme.typography.body1.merge(),
+                    style = MaterialTheme.typography.bodyLarge.merge(),
                     modifier = Modifier.padding(start = 16.dp)
                 )
             }
diff --git a/auth/src/main/java/org/robojackets/apiary/auth/ui/permissions/InsufficientPermissions.kt b/auth/src/main/java/org/robojackets/apiary/auth/ui/permissions/InsufficientPermissions.kt
index f904049..a3cc943 100644
--- a/auth/src/main/java/org/robojackets/apiary/auth/ui/permissions/InsufficientPermissions.kt
+++ b/auth/src/main/java/org/robojackets/apiary/auth/ui/permissions/InsufficientPermissions.kt
@@ -3,9 +3,17 @@ package org.robojackets.apiary.auth.ui.permissions
 import androidx.compose.foundation.layout.*
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.items
-import androidx.compose.material.*
+import androidx.compose.material.AlertDialog
+import androidx.compose.material.ExperimentalMaterialApi
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.CheckCircle
+import androidx.compose.material3.Divider
+import androidx.compose.material3.Icon
+import androidx.compose.material3.ListItem
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
 import androidx.compose.runtime.*
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -21,6 +29,9 @@ import org.robojackets.apiary.base.ui.theme.danger
 import org.robojackets.apiary.base.ui.theme.success
 import org.robojackets.apiary.base.ui.util.ContentPadding
 
+// TODO: M3 upgrade
+// - Update to M3 AlertDialog
+
 @Composable
 fun InsufficientPermissions(
     featureName: String,
@@ -40,7 +51,7 @@ fun InsufficientPermissions(
         ErrorIcon(Modifier.size(90.dp), tint = danger)
         Text(
             text = "$featureName unavailable",
-            style = MaterialTheme.typography.h4
+            style = MaterialTheme.typography.headlineMedium,
         )
         Text(
             text = "You don't have permission to use this feature. Please ask in #it-helpdesk for assistance.",
@@ -88,8 +99,8 @@ fun PermissionDetailsDialog(
             ) {
                 Text(
                     text = "Required permissions",
-                    style = MaterialTheme.typography.h5,
-                    color = MaterialTheme.colors.onBackground,
+                    style = MaterialTheme.typography.headlineSmall,
+                    color = MaterialTheme.colorScheme.onBackground,
                     modifier = Modifier.padding(bottom = 20.dp)
                 )
                 Divider()
@@ -123,9 +134,8 @@ fun PermissionDetailsDialog(
 @OptIn(ExperimentalMaterialApi::class)
 @Composable
 fun PermissionsListItem(hasPermission: Boolean, permissionName: String) {
-
     ListItem(
-        icon = {
+        leadingContent = {
             when (hasPermission) {
                 true -> Icon(
                     Icons.Outlined.CheckCircle,
@@ -136,9 +146,8 @@ fun PermissionsListItem(hasPermission: Boolean, permissionName: String) {
                 false -> ErrorIcon(Modifier.size(28.dp), tint = danger)
             }
         },
-    ) {
-        Text(permissionName)
-    }
+        headlineContent = { Text(permissionName) },
+    )
 }
 
 @Composable
diff --git a/auth/src/main/java/org/robojackets/apiary/auth/ui/permissions/MissingHiddenTeamsCallout.kt b/auth/src/main/java/org/robojackets/apiary/auth/ui/permissions/MissingHiddenTeamsCallout.kt
index e1e107e..ffbe887 100644
--- a/auth/src/main/java/org/robojackets/apiary/auth/ui/permissions/MissingHiddenTeamsCallout.kt
+++ b/auth/src/main/java/org/robojackets/apiary/auth/ui/permissions/MissingHiddenTeamsCallout.kt
@@ -3,8 +3,8 @@ package org.robojackets.apiary.auth.ui.permissions
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.padding
-import androidx.compose.material.OutlinedButton
-import androidx.compose.material.Text
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
diff --git a/base/build.gradle.kts b/base/build.gradle.kts
index f4a15c0..bfeca8d 100644
--- a/base/build.gradle.kts
+++ b/base/build.gradle.kts
@@ -18,10 +18,13 @@ dependencies {
 
     implementation(ComposeDependencies.compose_foundation)
     implementation(ComposeDependencies.compose_material)
+    implementation(ComposeDependencies.compose_material3)
     implementation(ComposeDependencies.compose_ui)
     implementation(ComposeDependencies.compose_ui_tooling)
     implementation(ComposeDependencies.accompanist_nav_material)
 
+    implementation(MaterialDependencies.material_android)
+
     implementation(HiltDependencies.hilt)
     kapt(HiltDependencies.hilt_android_compiler)
 
diff --git a/base/src/main/java/org/robojackets/apiary/base/ui/ActionPrompt.kt b/base/src/main/java/org/robojackets/apiary/base/ui/ActionPrompt.kt
index f4bd538..7d48683 100644
--- a/base/src/main/java/org/robojackets/apiary/base/ui/ActionPrompt.kt
+++ b/base/src/main/java/org/robojackets/apiary/base/ui/ActionPrompt.kt
@@ -1,8 +1,12 @@
 package org.robojackets.apiary.base.ui
 
-import androidx.compose.foundation.layout.*
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Text
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -28,7 +32,7 @@ fun ActionPrompt(
         icon()
         Text(
             text = title,
-            style = MaterialTheme.typography.h4,
+            style = MaterialTheme.typography.headlineMedium,
             modifier = Modifier.padding(top = 6.dp),
             textAlign = TextAlign.Center,
         )
@@ -40,7 +44,7 @@ fun ActionPrompt(
         subtitle?.let {
             Text(
                 text = subtitle,
-                style = MaterialTheme.typography.subtitle1,
+                style = MaterialTheme.typography.titleMedium,
                 textAlign = TextAlign.Center,
             )
         }
diff --git a/base/src/main/java/org/robojackets/apiary/base/ui/IconWithText.kt b/base/src/main/java/org/robojackets/apiary/base/ui/IconWithText.kt
index 55ba14f..bc90d7d 100644
--- a/base/src/main/java/org/robojackets/apiary/base/ui/IconWithText.kt
+++ b/base/src/main/java/org/robojackets/apiary/base/ui/IconWithText.kt
@@ -4,7 +4,7 @@ import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
-import androidx.compose.material.Text
+import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
diff --git a/base/src/main/java/org/robojackets/apiary/base/ui/callout/Callout.kt b/base/src/main/java/org/robojackets/apiary/base/ui/callout/Callout.kt
index a48fcc4..0b3c333 100644
--- a/base/src/main/java/org/robojackets/apiary/base/ui/callout/Callout.kt
+++ b/base/src/main/java/org/robojackets/apiary/base/ui/callout/Callout.kt
@@ -4,8 +4,8 @@ import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.layout.*
 import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Text
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -19,6 +19,9 @@ import org.robojackets.apiary.base.ui.theme.warningDarkSubtle
 import org.robojackets.apiary.base.ui.theme.warningLightMuted
 import org.robojackets.apiary.base.ui.theme.warningLightSubtle
 
+// TODO: M3 upgrade
+// - Implement replacement for isLight
+
 @Composable
 fun Callout(
     title: @Composable () -> Unit,
@@ -49,7 +52,7 @@ fun WarningCallout(
     padding: PaddingValues? = null,
     body: @Composable () -> Unit,
 ) {
-    val isLightTheme = MaterialTheme.colors.isLight
+    val isLightTheme = true // TODO: M3 upgrade, previously: MaterialTheme.colors.isLight
 
     Callout(
         title = {
@@ -57,7 +60,7 @@ fun WarningCallout(
                 Text(
                     titleText,
                     modifier = Modifier.padding(start = 8.dp),
-                    style = MaterialTheme.typography.h6
+                    style = MaterialTheme.typography.titleLarge
                 )
             }
         },
diff --git a/base/src/main/java/org/robojackets/apiary/base/ui/error/ErrorMessage.kt b/base/src/main/java/org/robojackets/apiary/base/ui/error/ErrorMessage.kt
index 7354e7b..c8e9f2a 100644
--- a/base/src/main/java/org/robojackets/apiary/base/ui/error/ErrorMessage.kt
+++ b/base/src/main/java/org/robojackets/apiary/base/ui/error/ErrorMessage.kt
@@ -2,10 +2,16 @@ package org.robojackets.apiary.base.ui.error
 
 import android.content.Intent
 import android.net.Uri
-import androidx.compose.foundation.layout.*
-import androidx.compose.material.Button
-import androidx.compose.material.OutlinedButton
-import androidx.compose.material.Text
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Button
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
diff --git a/base/src/main/java/org/robojackets/apiary/base/ui/icons/ExtraIcons.kt b/base/src/main/java/org/robojackets/apiary/base/ui/icons/ExtraIcons.kt
index 058fabd..baf992f 100644
--- a/base/src/main/java/org/robojackets/apiary/base/ui/icons/ExtraIcons.kt
+++ b/base/src/main/java/org/robojackets/apiary/base/ui/icons/ExtraIcons.kt
@@ -1,10 +1,9 @@
 package org.robojackets.apiary.base.ui.icons
 
-import androidx.compose.material.Icon
-import androidx.compose.material.LocalContentAlpha
-import androidx.compose.material.LocalContentColor
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Warning
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
@@ -14,7 +13,7 @@ import org.robojackets.apiary.base.R
 @Composable
 fun ContactlessIcon(
     modifier: Modifier,
-    tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
+    tint: Color = MaterialTheme.colorScheme.onSurface
 ) {
     Icon(
         painter = painterResource(id = R.drawable.ic_outline_contactless_24dp),
@@ -27,7 +26,7 @@ fun ContactlessIcon(
 @Composable
 fun WarningIcon(
     modifier: Modifier = Modifier,
-    tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
+    tint: Color = MaterialTheme.colorScheme.onSurface
 ) {
     Icon(
         Icons.Default.Warning,
@@ -40,7 +39,7 @@ fun WarningIcon(
 @Composable
 fun ErrorIcon(
     modifier: Modifier = Modifier,
-    tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
+    tint: Color = MaterialTheme.colorScheme.onSurface
 ) {
     Icon(
         painter = painterResource(id = R.drawable.ic_baseline_error_outline_24),
@@ -53,7 +52,7 @@ fun ErrorIcon(
 @Composable
 fun CreditCardIcon(
     modifier: Modifier = Modifier,
-    tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
+    tint: Color = MaterialTheme.colorScheme.onSurface
 ) {
     Icon(
         painter = painterResource(id = R.drawable.ic_baseline_credit_card_24),
@@ -66,7 +65,7 @@ fun CreditCardIcon(
 @Composable
 fun PendingIcon(
     modifier: Modifier = Modifier,
-    tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
+    tint: Color = MaterialTheme.colorScheme.onSurface
 ) {
     Icon(
         painter = painterResource(id = R.drawable.ic_outline_pending_24dp),
@@ -79,7 +78,7 @@ fun PendingIcon(
 @Composable
 fun GroupsIcon(
     modifier: Modifier = Modifier,
-    tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
+    tint: Color = MaterialTheme.colorScheme.onSurface,
 ) {
     Icon(
         painter = painterResource(id = R.drawable.ic_outline_groups_24dp),
@@ -92,7 +91,7 @@ fun GroupsIcon(
 @Composable
 fun EventIcon(
     modifier: Modifier = Modifier,
-    tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
+    tint: Color = MaterialTheme.colorScheme.onSurface
 ) {
     Icon(
         painter = painterResource(id = R.drawable.ic_outline_event_24dp),
@@ -105,7 +104,7 @@ fun EventIcon(
 @Composable
 fun UpdateIcon(
     modifier: Modifier = Modifier,
-    tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
+    tint: Color = MaterialTheme.colorScheme.onSurface
 ) {
     Icon(
         painter = painterResource(id = R.drawable.ic_baseline_update_24),
@@ -118,7 +117,7 @@ fun UpdateIcon(
 @Composable
 fun TaskAltIcon(
     modifier: Modifier = Modifier,
-    tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
+    tint: Color = MaterialTheme.colorScheme.onSurface
 ) {
     Icon(
         painter = painterResource(id = R.drawable.ic_outline_task_alt_24dp),
diff --git a/base/src/main/java/org/robojackets/apiary/base/ui/nfc/BuzzCardPrompt.kt b/base/src/main/java/org/robojackets/apiary/base/ui/nfc/BuzzCardPrompt.kt
index 6f034a9..b53e4ee 100644
--- a/base/src/main/java/org/robojackets/apiary/base/ui/nfc/BuzzCardPrompt.kt
+++ b/base/src/main/java/org/robojackets/apiary/base/ui/nfc/BuzzCardPrompt.kt
@@ -5,7 +5,12 @@ import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.material.*
+import androidx.compose.material.AlertDialog
+import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
 import androidx.compose.runtime.*
 import androidx.compose.ui.Alignment.Companion.CenterHorizontally
 import androidx.compose.ui.Modifier
@@ -27,6 +32,11 @@ import org.robojackets.apiary.base.ui.theme.danger
 import timber.log.Timber
 import java.nio.charset.StandardCharsets
 
+// TODO: M3 upgrade
+// - Switch to M3 AlertDialog
+// - Verify icon/text colors are correct in dark mode
+// - Verify Contactless icon color is correct in light mode
+
 /**
  * Show a prompt for BuzzCards and call a callback function each time a valid GTID is obtained.
  *
@@ -185,7 +195,7 @@ fun ManualGtidEntryPrompt(
                 Text("Submit")
             }
         },
-        title = { Text(text = "Manual GTID entry", style = MaterialTheme.typography.h5) },
+        title = { Text(text = "Manual GTID entry", style = MaterialTheme.typography.headlineSmall) },
         text = {
             Column {
                 Text("Type the entire 9-digit GTID, starting with 90")
diff --git a/base/src/main/java/org/robojackets/apiary/base/ui/nfc/NfcRequired.kt b/base/src/main/java/org/robojackets/apiary/base/ui/nfc/NfcRequired.kt
index 06f0df7..c1f2a36 100644
--- a/base/src/main/java/org/robojackets/apiary/base/ui/nfc/NfcRequired.kt
+++ b/base/src/main/java/org/robojackets/apiary/base/ui/nfc/NfcRequired.kt
@@ -6,9 +6,13 @@ import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.size
-import androidx.compose.material.Button
-import androidx.compose.material.Text
-import androidx.compose.runtime.*
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
@@ -18,6 +22,9 @@ import org.robojackets.apiary.base.ui.icons.ErrorIcon
 import org.robojackets.apiary.base.ui.theme.danger
 import org.robojackets.apiary.base.ui.theme.warningLightEmphasis
 
+// TODO: M3 upgrade
+// - Opening the app with NFC disabled causes a crash
+
 @Composable
 fun NfcRequired(nfcEnabled: Boolean, gatedComposable: @Composable () -> Unit) {
     val context = LocalContext.current
diff --git a/base/src/main/java/org/robojackets/apiary/base/ui/theme/Shape.kt b/base/src/main/java/org/robojackets/apiary/base/ui/theme/Shape.kt
index 20bcac2..ddad505 100644
--- a/base/src/main/java/org/robojackets/apiary/base/ui/theme/Shape.kt
+++ b/base/src/main/java/org/robojackets/apiary/base/ui/theme/Shape.kt
@@ -1,7 +1,7 @@
 package org.robojackets.apiary.base.ui.theme
 
 import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.Shapes
+import androidx.compose.material3.Shapes
 import androidx.compose.ui.unit.dp
 
 val Shapes = Shapes(
diff --git a/base/src/main/java/org/robojackets/apiary/base/ui/theme/Theme.kt b/base/src/main/java/org/robojackets/apiary/base/ui/theme/Theme.kt
index 1f76d50..3e2fd5a 100644
--- a/base/src/main/java/org/robojackets/apiary/base/ui/theme/Theme.kt
+++ b/base/src/main/java/org/robojackets/apiary/base/ui/theme/Theme.kt
@@ -1,9 +1,11 @@
 package org.robojackets.apiary.base.ui.theme
 
 import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.material.MaterialTheme
 import androidx.compose.material.darkColors
 import androidx.compose.material.lightColors
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.lightColorScheme
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.graphics.Color
 
@@ -28,19 +30,30 @@ private val LightColorPalette = lightColors(
     */
 )
 
+val LightColorScheme = lightColorScheme(
+    primary = Gold,
+    secondary = GoldDark,
+    tertiary = Color.Black,
+)
+val DarkColorScheme = darkColorScheme(
+    primary = Gold,
+    secondary = GoldDark,
+    tertiary = Color.White,
+)
+
 @Composable
 fun Apiary_MobileTheme(
     darkTheme: Boolean = isSystemInDarkTheme(),
     content: @Composable() () -> Unit
 ) {
     val colors = if (darkTheme) {
-        DarkColorPalette
+        DarkColorScheme
     } else {
-        LightColorPalette
+        LightColorScheme
     }
 
     MaterialTheme(
-        colors = colors,
+        colorScheme = colors,
         typography = Typography,
         shapes = Shapes,
         content = content
diff --git a/base/src/main/java/org/robojackets/apiary/base/ui/theme/Type.kt b/base/src/main/java/org/robojackets/apiary/base/ui/theme/Type.kt
index d67ef35..2398b76 100644
--- a/base/src/main/java/org/robojackets/apiary/base/ui/theme/Type.kt
+++ b/base/src/main/java/org/robojackets/apiary/base/ui/theme/Type.kt
@@ -1,6 +1,6 @@
 package org.robojackets.apiary.base.ui.theme
 
-import androidx.compose.material.Typography
+import androidx.compose.material3.Typography
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.text.font.FontWeight
@@ -8,13 +8,14 @@ import androidx.compose.ui.unit.sp
 
 // Set of Material typography styles to start with
 val Typography = Typography(
-    body1 = TextStyle(
+    bodyLarge = TextStyle(
         fontFamily = FontFamily.Default,
         fontWeight = FontWeight.Normal,
         fontSize = 16.sp
     ),
     /* Other default text styles to override */
-    button = TextStyle(
+    labelLarge = TextStyle(
+        fontFamily = FontFamily.Default,
         fontWeight = FontWeight.Medium,
         fontSize = 14.sp,
 //        letterSpacing = 1.25.sp
diff --git a/base/src/main/java/org/robojackets/apiary/base/ui/util/LoadingSpinner.kt b/base/src/main/java/org/robojackets/apiary/base/ui/util/LoadingSpinner.kt
index 7c49776..2ecc73a 100644
--- a/base/src/main/java/org/robojackets/apiary/base/ui/util/LoadingSpinner.kt
+++ b/base/src/main/java/org/robojackets/apiary/base/ui/util/LoadingSpinner.kt
@@ -4,7 +4,7 @@ import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.material.CircularProgressIndicator
+import androidx.compose.material3.CircularProgressIndicator
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
diff --git a/base/src/main/java/org/robojackets/apiary/base/ui/util/MadeWithLove.kt b/base/src/main/java/org/robojackets/apiary/base/ui/util/MadeWithLove.kt
index c6da7b1..4e7f57e 100644
--- a/base/src/main/java/org/robojackets/apiary/base/ui/util/MadeWithLove.kt
+++ b/base/src/main/java/org/robojackets/apiary/base/ui/util/MadeWithLove.kt
@@ -1,6 +1,6 @@
 package org.robojackets.apiary.base.ui.util
 
-import androidx.compose.material.Text
+import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 
 @Composable
diff --git a/build.gradle.kts b/build.gradle.kts
index 353df25..a81b354 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -6,7 +6,7 @@ buildscript {
     }
     dependencies {
         classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.21")
-        classpath("com.android.tools.build:gradle:8.0.1")
+        classpath("com.android.tools.build:gradle:8.1.0")
         classpath("com.google.dagger:hilt-android-gradle-plugin:2.46.1")
         classpath("com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1")
         classpath("com.google.gms:google-services:4.3.15")
diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt
index 056f4db..9d3a185 100644
--- a/buildSrc/src/main/java/Dependencies.kt
+++ b/buildSrc/src/main/java/Dependencies.kt
@@ -4,6 +4,7 @@ object ComposeDependencies {
         const val compose_settings_version = "0.27.0"
         const val compose_version = "1.4.3"
         const val lifecycle_viewmodel_compose_version = "2.6.1"
+        const val compose_material3_version = "1.1.1"
     }
 
     const val accompanist_systemuicontroller =
@@ -25,7 +26,7 @@ object ComposeDependencies {
         "androidx.compose.runtime:runtime-livedata:${Versions.compose_version}"
 
     const val compose_settings =
-        "com.github.alorma:compose-settings-ui:${Versions.compose_settings_version}"
+        "com.github.alorma:compose-settings-ui-m3:${Versions.compose_settings_version}"
 
     const val compose_ui = "androidx.compose.ui:ui:${Versions.compose_version}"
     const val compose_ui_test = "androidx.compose.ui:ui-test-junit4:${Versions.compose_version}"
@@ -33,6 +34,9 @@ object ComposeDependencies {
 
     const val lifecycle_viewmodel_compose =
         "androidx.lifecycle:lifecycle-viewmodel-compose:${Versions.lifecycle_viewmodel_compose_version}"
+
+    const val compose_material3 =
+        "androidx.compose.material3:material3:${Versions.compose_material3_version}"
 }
 
 object MaterialDependencies {

From 54edda1cf4e080e83868051fc35bcb018e4a41aa Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Sun, 20 Aug 2023 01:28:07 -0400
Subject: [PATCH 04/33] add app launch todo

---
 app/src/main/java/org/robojackets/apiary/MainActivity.kt | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/app/src/main/java/org/robojackets/apiary/MainActivity.kt b/app/src/main/java/org/robojackets/apiary/MainActivity.kt
index 69036fc..97348f8 100644
--- a/app/src/main/java/org/robojackets/apiary/MainActivity.kt
+++ b/app/src/main/java/org/robojackets/apiary/MainActivity.kt
@@ -62,6 +62,8 @@ import org.robojackets.apiary.ui.update.UpdateInProgress
 import timber.log.Timber
 import javax.inject.Inject
 
+// TODO: see if we can make app launch screen match light/dark mode?
+
 sealed class Screen(
     val navigationDestination: String,
     @StringRes val resourceId: Int,

From f56d5e0679a4a75ca58f4b5ab33160c672ec68af Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Sun, 20 Aug 2023 17:41:05 -0400
Subject: [PATCH 05/33] Remainder of M3 upgrade

---
 app/build.gradle.kts                          |   1 -
 .../org/robojackets/apiary/MainActivity.kt    |   3 -
 .../apiary/ui/update/UpdateAvailable.kt       |   2 +-
 attendance/build.gradle.kts                   |   3 -
 .../attendance/ui/AttendableSelection.kt      |   3 -
 .../ui/AttendableTypeSelectionScreen.kt       |   2 -
 auth/build.gradle.kts                         |   1 -
 .../apiary/auth/ui/Authentication.kt          | 215 +++++++++---------
 .../ui/permissions/InsufficientPermissions.kt |  22 +-
 base/build.gradle.kts                         |   1 -
 .../apiary/base/ui/nfc/BuzzCardPrompt.kt      |   2 +-
 .../robojackets/apiary/base/ui/theme/Theme.kt |  23 --
 buildSrc/src/main/java/Dependencies.kt        |   6 +-
 13 files changed, 127 insertions(+), 157 deletions(-)

diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 9edde41..8fe5451 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -36,7 +36,6 @@ dependencies {
     implementation(ComposeDependencies.lifecycle_viewmodel_compose)
     implementation(ComposeDependencies.compose_ui_tooling)
     implementation(ComposeDependencies.compose_foundation)
-    implementation(ComposeDependencies.compose_material)
     implementation(ComposeDependencies.compose_material3)
     implementation(ComposeDependencies.compose_material_icons_core)
     implementation(ComposeDependencies.compose_material_icons_extended)
diff --git a/app/src/main/java/org/robojackets/apiary/MainActivity.kt b/app/src/main/java/org/robojackets/apiary/MainActivity.kt
index 97348f8..0ae1af0 100644
--- a/app/src/main/java/org/robojackets/apiary/MainActivity.kt
+++ b/app/src/main/java/org/robojackets/apiary/MainActivity.kt
@@ -8,7 +8,6 @@ import androidx.activity.compose.setContent
 import androidx.annotation.StringRes
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.padding
-import androidx.compose.material.ExperimentalMaterialApi
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Settings
 import androidx.compose.material.icons.outlined.Contactless
@@ -125,7 +124,6 @@ class MainActivity : ComponentActivity() {
     }
 
     @OptIn(ExperimentalMaterialNavigationApi::class)
-    @ExperimentalMaterialApi
     @Suppress("LongMethod")
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
@@ -242,7 +240,6 @@ class MainActivity : ComponentActivity() {
 
     @Suppress("LongMethod")
     @OptIn(ExperimentalMaterialNavigationApi::class)
-    @ExperimentalMaterialApi
     @Composable
     private fun AppNavigation(
         navController: NavHostController,
diff --git a/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt b/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt
index 0bed163..1fc505e 100644
--- a/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt
+++ b/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt
@@ -2,7 +2,7 @@ package org.robojackets.apiary.ui.update
 
 import android.app.Activity
 import androidx.compose.foundation.layout.*
-import androidx.compose.material.AlertDialog
+import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.Button
 import androidx.compose.material3.CircularProgressIndicator
 import androidx.compose.material3.MaterialTheme
diff --git a/attendance/build.gradle.kts b/attendance/build.gradle.kts
index 415502e..4df0cbe 100644
--- a/attendance/build.gradle.kts
+++ b/attendance/build.gradle.kts
@@ -18,7 +18,6 @@ dependencies {
     coreLibraryDesugaring(AndroidToolDependencies.android_tools_desugar_jdk)
     implementation(AndroidToolDependencies.timber)
 
-    implementation(ComposeDependencies.compose_material)
     implementation(ComposeDependencies.compose_material3)
     implementation(ComposeDependencies.compose_ui)
     implementation(ComposeDependencies.lifecycle_viewmodel_compose)
@@ -26,8 +25,6 @@ dependencies {
     implementation(HiltDependencies.hilt)
     kapt(HiltDependencies.hilt_android_compiler)
 
-    implementation(MaterialDependencies.material_android)
-
     implementation(NetworkDependencies.moshi)
     kapt(NetworkDependencies.moshi_kotlin_codegen)
     implementation(NetworkDependencies.okhttp)
diff --git a/attendance/src/main/java/org/robojackets/apiary/attendance/ui/AttendableSelection.kt b/attendance/src/main/java/org/robojackets/apiary/attendance/ui/AttendableSelection.kt
index 3c69000..ad127f5 100644
--- a/attendance/src/main/java/org/robojackets/apiary/attendance/ui/AttendableSelection.kt
+++ b/attendance/src/main/java/org/robojackets/apiary/attendance/ui/AttendableSelection.kt
@@ -4,7 +4,6 @@ import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.*
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.itemsIndexed
-import androidx.compose.material.ExperimentalMaterialApi
 import androidx.compose.material3.Button
 import androidx.compose.material3.CircularProgressIndicator
 import androidx.compose.material3.Divider
@@ -27,7 +26,6 @@ import org.robojackets.apiary.base.ui.icons.WarningIcon
 import org.robojackets.apiary.base.ui.theme.danger
 import org.robojackets.apiary.base.ui.util.ContentPadding
 
-@ExperimentalMaterialApi
 @Composable
 private fun <T> AttendableList(
     attendables: List<T>,
@@ -56,7 +54,6 @@ private fun <T> AttendableList(
 }
 
 @Suppress("LongMethod")
-@ExperimentalMaterialApi
 @Composable
 fun AttendableSelectionScreen(
     viewModel: AttendanceViewModel,
diff --git a/attendance/src/main/java/org/robojackets/apiary/attendance/ui/AttendableTypeSelectionScreen.kt b/attendance/src/main/java/org/robojackets/apiary/attendance/ui/AttendableTypeSelectionScreen.kt
index 9543a34..5f475cc 100644
--- a/attendance/src/main/java/org/robojackets/apiary/attendance/ui/AttendableTypeSelectionScreen.kt
+++ b/attendance/src/main/java/org/robojackets/apiary/attendance/ui/AttendableTypeSelectionScreen.kt
@@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.defaultMinSize
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.size
-import androidx.compose.material.ExperimentalMaterialApi
 import androidx.compose.material3.Divider
 import androidx.compose.material3.ListItem
 import androidx.compose.material3.MaterialTheme
@@ -27,7 +26,6 @@ import org.robojackets.apiary.base.ui.icons.GroupsIcon
 import org.robojackets.apiary.base.ui.util.ContentPadding
 import org.robojackets.apiary.base.ui.util.LoadingSpinner
 
-@ExperimentalMaterialApi
 @Composable
 fun AttendableTypeSelectionScreen(
     viewModel: AttendableTypeSelectionViewModel,
diff --git a/auth/build.gradle.kts b/auth/build.gradle.kts
index e6a7fa7..daf62ac 100644
--- a/auth/build.gradle.kts
+++ b/auth/build.gradle.kts
@@ -28,7 +28,6 @@ dependencies {
 
     implementation(ComposeDependencies.accompanist_systemuicontroller)
     implementation(ComposeDependencies.compose_foundation)
-    implementation(ComposeDependencies.compose_material)
     implementation(ComposeDependencies.compose_material3)
     implementation(ComposeDependencies.compose_ui)
     implementation(ComposeDependencies.compose_ui_tooling)
diff --git a/auth/src/main/java/org/robojackets/apiary/auth/ui/Authentication.kt b/auth/src/main/java/org/robojackets/apiary/auth/ui/Authentication.kt
index 793be11..e59ed6f 100644
--- a/auth/src/main/java/org/robojackets/apiary/auth/ui/Authentication.kt
+++ b/auth/src/main/java/org/robojackets/apiary/auth/ui/Authentication.kt
@@ -7,19 +7,16 @@ import androidx.compose.foundation.Image
 import androidx.compose.foundation.layout.*
 import androidx.compose.foundation.selection.selectable
 import androidx.compose.foundation.selection.selectableGroup
-import androidx.compose.material.AlertDialog
-import androidx.compose.material.BottomSheetScaffold
-import androidx.compose.material.BottomSheetScaffoldState
-import androidx.compose.material.BottomSheetValue
-import androidx.compose.material.ExperimentalMaterialApi
-import androidx.compose.material.rememberBottomSheetScaffoldState
-import androidx.compose.material.rememberBottomSheetState
+import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.Button
+import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.ModalBottomSheet
 import androidx.compose.material3.RadioButton
-import androidx.compose.material3.Surface
 import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
+import androidx.compose.material3.rememberBottomSheetScaffoldState
+import androidx.compose.material3.rememberModalBottomSheetState
 import androidx.compose.runtime.*
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -27,7 +24,6 @@ import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.unit.dp
 import com.google.accompanist.systemuicontroller.rememberSystemUiController
-import kotlinx.coroutines.launch
 import net.openid.appauth.AuthorizationException
 import net.openid.appauth.AuthorizationResponse
 import org.robojackets.apiary.auth.R
@@ -36,7 +32,6 @@ import org.robojackets.apiary.auth.model.AuthenticationViewModel
 import org.robojackets.apiary.auth.model.LoginStatus.*
 import org.robojackets.apiary.auth.oauth2.AuthManager
 import org.robojackets.apiary.base.AppEnvironment
-import org.robojackets.apiary.base.ui.theme.BottomSheetShape
 import org.robojackets.apiary.base.ui.util.MadeWithLove
 
 // TODO: M3 upgrade
@@ -44,7 +39,7 @@ import org.robojackets.apiary.base.ui.util.MadeWithLove
 // -  Update to M3 AlertDialog
 // - Test auth behavior with new theme
 
-@OptIn(ExperimentalMaterialApi::class, androidx.compose.ui.ExperimentalComposeUiApi::class)
+@OptIn(ExperimentalMaterial3Api::class)
 @Suppress("LongMethod", "MagicNumber")
 @Composable
 private fun Authentication(
@@ -53,13 +48,7 @@ private fun Authentication(
     onAppEnvChange: (newEnv: AppEnvironment) -> Unit,
     viewModel: AuthenticationViewModel,
 ) {
-    // You have to `remember` two things here for some reason
-    // In any case, thanks to https://proandroiddev.com/getting-your-bottomsheetscaffold-working-on-jetpack-compose-beta-03-aa829b0c9b6c
-    val scaffoldState = rememberBottomSheetScaffoldState(
-        bottomSheetState = rememberBottomSheetState(
-            initialValue = BottomSheetValue.Collapsed
-        )
-    )
+    val scaffoldState = rememberBottomSheetScaffoldState()
     val coroutineScope = rememberCoroutineScope()
 
     val systemUiController = rememberSystemUiController()
@@ -99,11 +88,13 @@ private fun Authentication(
                                         viewModel.navigateToAttendance()
                                     }
                                 }
+
                                 ex != null -> viewModel.recordAuthError(ex)
                                 else -> viewModel.recordAuthError(null)
                             }
                         }
                     }
+
                     authException != null -> viewModel.recordAuthError(authException)
                 }
             } else {
@@ -111,113 +102,125 @@ private fun Authentication(
             }
         }
 
-    BottomSheetScaffold(
-        scaffoldState = scaffoldState,
-        sheetShape = BottomSheetShape,
-        sheetPeekHeight = 0.dp,
-        sheetContent = {
-            Column(
-                Modifier
-                    .fillMaxWidth()
-                    .defaultMinSize(minHeight = 56.dp),
-                horizontalAlignment = Alignment.CenterHorizontally,
-                verticalArrangement = Arrangement.SpaceEvenly,
-                content = {
-                    ChangeEnvironmentBottomSheetContent(
-                        viewState,
-                        onAppEnvChange,
-                        scaffoldState
-                    )
-                }
-            )
-        }) {
-        Surface(
-            color = MaterialTheme.colorScheme.background,
-            modifier = Modifier.padding(8.dp)
+    var showChangeEnvBottomSheet by remember { mutableStateOf(false) }
+
+
+    if (showChangeEnvBottomSheet) {
+        ChangeEnvBottomSheet(
+            onDismiss = { showChangeEnvBottomSheet = false },
+            viewState = viewState,
+            onAppEnvChange = {
+                showChangeEnvBottomSheet = false
+                onAppEnvChange(it)
+            }
+        )
+    }
+
+    Column(
+        modifier = Modifier
+            .fillMaxWidth()
+            .fillMaxHeight(),
+        horizontalAlignment = Alignment.CenterHorizontally,
+        verticalArrangement = Arrangement.SpaceBetween
+    ) {
+        Image(
+            painter = painterResource(id = R.drawable.ic_robobuzz_white_outline),
+            contentDescription = "RoboJackets logo",
+            modifier = Modifier
+                .fillMaxWidth(.45f)
+                .weight(1.0f)
+        )
+        Column(
+            modifier = Modifier.weight(.5f),
+            horizontalAlignment = Alignment.CenterHorizontally,
+            verticalArrangement = Arrangement.SpaceAround
         ) {
-            Column(
-                modifier = Modifier
-                    .fillMaxWidth()
-                    .fillMaxHeight(),
-                horizontalAlignment = Alignment.CenterHorizontally,
-                verticalArrangement = Arrangement.SpaceBetween
+
+            Button(
+                onClick = {
+                    val authRequest = authManager.getAuthRequest()
+                    launcher.launch(
+                        authManager.authService.getAuthorizationRequestIntent(
+                            authRequest
+                        )
+                    )
+                },
             ) {
-                Image(
-                    painter = painterResource(id = R.drawable.ic_robobuzz_white_outline),
-                    contentDescription = "RoboJackets logo",
-                    modifier = Modifier
-                        .fillMaxWidth(.45f)
-                        .weight(1.0f)
-                )
-                Column(
-                    modifier = Modifier.weight(.5f),
-                    horizontalAlignment = Alignment.CenterHorizontally,
-                    verticalArrangement = Arrangement.SpaceAround
-                ) {
+                Text("Sign in with MyRoboJackets")
+            }
+        }
 
-                    Button(
-                        onClick = {
-                            val authRequest = authManager.getAuthRequest()
-                            launcher.launch(
-                                authManager.authService.getAuthorizationRequestIntent(
-                                    authRequest
-                                )
-                            )
-                        },
-                    ) {
-                        Text("Sign in with MyRoboJackets")
+        if (viewState.loginStatus == ERROR) {
+            AlertDialog(
+                onDismissRequest = {
+                    viewModel.setLoginStatus(NOT_STARTED)
+                },
+                confirmButton = {
+                    TextButton(onClick = { viewModel.setLoginStatus(NOT_STARTED) }) {
+                        Text("Close")
                     }
-                }
-
-                if (viewState.loginStatus == ERROR) {
-                    AlertDialog(
-                        onDismissRequest = {
-                            viewModel.setLoginStatus(NOT_STARTED)
-                        },
-                        confirmButton = {
-                            TextButton(onClick = { viewModel.setLoginStatus(NOT_STARTED) }) {
-                                Text("Close")
-                            }
-                        },
-                        title = { Text("Login failed") },
-                        text = {
-                            Text(
-                                "${viewState.loginErrorMessage}\n\nTry logging in again. " +
-                                        "If that does not work, please post in #it-helpdesk in Slack."
-                            )
-                        },
+                },
+                title = { Text("Login failed") },
+                text = {
+                    Text(
+                        "${viewState.loginErrorMessage}\n\nTry logging in again. " +
+                                "If that does not work, please post in #it-helpdesk in Slack."
                     )
-                }
+                },
+            )
+        }
 
-                TextButton(onClick = {
-                    coroutineScope.launch {
-                        scaffoldState.bottomSheetState.expand()
-                    }
-                }) {
-                    Text("Change server")
-                }
-                Text("Server: ${viewState.appEnv.name} (${viewState.appEnv.apiBaseUrl})")
-                MadeWithLove()
-            }
+        TextButton(onClick = {
+            showChangeEnvBottomSheet = true
+        }) {
+            Text("Change server")
         }
+        Text("Server: ${viewState.appEnv.name} (${viewState.appEnv.apiBaseUrl})")
+        MadeWithLove()
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ChangeEnvBottomSheet(
+    onDismiss: () -> Unit,
+    viewState: AuthenticationState,
+    onAppEnvChange: (newEnv: AppEnvironment) -> Unit
+) {
+    val modalBottomSheetState = rememberModalBottomSheetState()
+
+    ModalBottomSheet(
+        onDismissRequest = { onDismiss() },
+        sheetState = modalBottomSheetState,
+    ) {
+        Column(
+            Modifier
+                .padding(bottom = 40.dp)
+                .fillMaxWidth()
+                .defaultMinSize(minHeight = 56.dp),
+            horizontalAlignment = Alignment.CenterHorizontally,
+            verticalArrangement = Arrangement.SpaceEvenly,
+            content = {
+                ChangeEnvironmentBottomSheetContent(
+                    viewState,
+                    onAppEnvChange,
+                )
+            }
+        )
     }
 }
 
-@OptIn(ExperimentalMaterialApi::class)
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 private fun ChangeEnvironmentBottomSheetContent(
     viewState: AuthenticationState,
     onAppEnvChange: (newEnv: AppEnvironment) -> Unit,
-    scaffoldState: BottomSheetScaffoldState
 ) {
     val coroutineScope = rememberCoroutineScope()
     var unsavedAppEnvSelection by remember { mutableStateOf(viewState.appEnv) }
     val appEnvChoices = AppEnvironment.values()
     val saveNewAppEnvChoice: (() -> Unit) = {
         onAppEnvChange(unsavedAppEnvSelection)
-        coroutineScope.launch {
-            scaffoldState.bottomSheetState.collapse()
-        }
     }
 
     Text(
@@ -265,7 +268,7 @@ private fun ChangeEnvironmentBottomSheetContent(
 @Composable
 fun AuthenticationScreen(
     viewModel: AuthenticationViewModel,
-    authManager: AuthManager
+    authManager: AuthManager,
 ) {
     val viewState by viewModel.state.collectAsState()
     Authentication(
diff --git a/auth/src/main/java/org/robojackets/apiary/auth/ui/permissions/InsufficientPermissions.kt b/auth/src/main/java/org/robojackets/apiary/auth/ui/permissions/InsufficientPermissions.kt
index a3cc943..54faec2 100644
--- a/auth/src/main/java/org/robojackets/apiary/auth/ui/permissions/InsufficientPermissions.kt
+++ b/auth/src/main/java/org/robojackets/apiary/auth/ui/permissions/InsufficientPermissions.kt
@@ -1,12 +1,17 @@
 package org.robojackets.apiary.auth.ui.permissions
 
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.items
-import androidx.compose.material.AlertDialog
-import androidx.compose.material.ExperimentalMaterialApi
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.CheckCircle
+import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.Divider
 import androidx.compose.material3.Icon
 import androidx.compose.material3.ListItem
@@ -14,13 +19,16 @@ import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
-import androidx.compose.runtime.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
-import androidx.core.content.ContextCompat.*
 import org.robojackets.apiary.auth.model.Permission
 import org.robojackets.apiary.auth.model.Permission.*
 import org.robojackets.apiary.base.ui.error.GoToItHelpdesk
@@ -29,9 +37,6 @@ import org.robojackets.apiary.base.ui.theme.danger
 import org.robojackets.apiary.base.ui.theme.success
 import org.robojackets.apiary.base.ui.util.ContentPadding
 
-// TODO: M3 upgrade
-// - Update to M3 AlertDialog
-
 @Composable
 fun InsufficientPermissions(
     featureName: String,
@@ -131,7 +136,6 @@ fun PermissionDetailsDialog(
     )
 }
 
-@OptIn(ExperimentalMaterialApi::class)
 @Composable
 fun PermissionsListItem(hasPermission: Boolean, permissionName: String) {
     ListItem(
diff --git a/base/build.gradle.kts b/base/build.gradle.kts
index bfeca8d..cd90e77 100644
--- a/base/build.gradle.kts
+++ b/base/build.gradle.kts
@@ -17,7 +17,6 @@ dependencies {
     implementation(AndroidToolDependencies.timber)
 
     implementation(ComposeDependencies.compose_foundation)
-    implementation(ComposeDependencies.compose_material)
     implementation(ComposeDependencies.compose_material3)
     implementation(ComposeDependencies.compose_ui)
     implementation(ComposeDependencies.compose_ui_tooling)
diff --git a/base/src/main/java/org/robojackets/apiary/base/ui/nfc/BuzzCardPrompt.kt b/base/src/main/java/org/robojackets/apiary/base/ui/nfc/BuzzCardPrompt.kt
index b53e4ee..94e32ea 100644
--- a/base/src/main/java/org/robojackets/apiary/base/ui/nfc/BuzzCardPrompt.kt
+++ b/base/src/main/java/org/robojackets/apiary/base/ui/nfc/BuzzCardPrompt.kt
@@ -5,7 +5,7 @@ import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.material.AlertDialog
+import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.Button
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedTextField
diff --git a/base/src/main/java/org/robojackets/apiary/base/ui/theme/Theme.kt b/base/src/main/java/org/robojackets/apiary/base/ui/theme/Theme.kt
index 3e2fd5a..deb2e15 100644
--- a/base/src/main/java/org/robojackets/apiary/base/ui/theme/Theme.kt
+++ b/base/src/main/java/org/robojackets/apiary/base/ui/theme/Theme.kt
@@ -1,35 +1,12 @@
 package org.robojackets.apiary.base.ui.theme
 
 import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.material.darkColors
-import androidx.compose.material.lightColors
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.darkColorScheme
 import androidx.compose.material3.lightColorScheme
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.graphics.Color
 
-private val DarkColorPalette = darkColors(
-    primary = GoldLight,
-    primaryVariant = GoldDark,
-    secondary = Color.White,
-)
-
-private val LightColorPalette = lightColors(
-    primary = Gold,
-    primaryVariant = GoldDark,
-    secondary = Color.Black,
-
-    /* Other default colors to override
-    background = Color.White,
-    surface = Color.White,
-    onPrimary = Color.White,
-    onSecondary = Color.Black,
-    onBackground = Color.Black,
-    onSurface = Color.Black,
-    */
-)
-
 val LightColorScheme = lightColorScheme(
     primary = Gold,
     secondary = GoldDark,
diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt
index 9d3a185..74470a1 100644
--- a/buildSrc/src/main/java/Dependencies.kt
+++ b/buildSrc/src/main/java/Dependencies.kt
@@ -15,7 +15,8 @@ object ComposeDependencies {
     const val compose_foundation =
         "androidx.compose.foundation:foundation:${Versions.compose_version}"
 
-    const val compose_material = "androidx.compose.material:material:${Versions.compose_version}"
+    const val compose_material3 =
+        "androidx.compose.material3:material3:${Versions.compose_material3_version}"
     const val compose_material_icons_core =
         "androidx.compose.material:material-icons-core:${Versions.compose_version}"
     const val compose_material_icons_extended =
@@ -35,8 +36,7 @@ object ComposeDependencies {
     const val lifecycle_viewmodel_compose =
         "androidx.lifecycle:lifecycle-viewmodel-compose:${Versions.lifecycle_viewmodel_compose_version}"
 
-    const val compose_material3 =
-        "androidx.compose.material3:material3:${Versions.compose_material3_version}"
+
 }
 
 object MaterialDependencies {

From b7eb39c34591d421e171553d2ddf046d086ed8a7 Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Sun, 20 Aug 2023 19:27:00 -0400
Subject: [PATCH 06/33] Dependency updates

---
 app/build.gradle.kts                                     | 3 ++-
 app/src/main/java/org/robojackets/apiary/MainActivity.kt | 6 +++---
 attendance/build.gradle.kts                              | 6 +++---
 auth/build.gradle.kts                                    | 6 +++---
 base/build.gradle.kts                                    | 6 +++---
 build.gradle.kts                                         | 5 +++--
 buildSrc/src/main/java/Dependencies.kt                   | 6 +++---
 navigation/build.gradle.kts                              | 4 ++--
 8 files changed, 22 insertions(+), 20 deletions(-)

diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 8fe5451..fbf6dad 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -3,6 +3,7 @@ plugins {
     kotlin("android")
     id("kotlin-android")
     kotlin("kapt")
+    id("com.google.devtools.ksp")
     id("dagger.hilt.android.plugin")
     id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
     id("com.google.android.gms.oss-licenses-plugin")
@@ -99,7 +100,7 @@ android {
         jvmTarget = "17"
     }
     composeOptions {
-        kotlinCompilerExtensionVersion = "1.4.7"
+        kotlinCompilerExtensionVersion = "1.5.1"
     }
     namespace = "org.robojackets.apiary"
     hilt {
diff --git a/app/src/main/java/org/robojackets/apiary/MainActivity.kt b/app/src/main/java/org/robojackets/apiary/MainActivity.kt
index 0ae1af0..a192ae7 100644
--- a/app/src/main/java/org/robojackets/apiary/MainActivity.kt
+++ b/app/src/main/java/org/robojackets/apiary/MainActivity.kt
@@ -272,7 +272,7 @@ class MainActivity : ComponentActivity() {
                         navArgument("attendableType") { type = NavType.StringType }
                     ),
                 ) {
-                    val attendableType = it.arguments?.get("attendableType")
+                    val attendableType = it.arguments?.getString("attendableType")
 
                     AttendableSelectionScreen(
                         hiltViewModel(),
@@ -287,8 +287,8 @@ class MainActivity : ComponentActivity() {
                         navArgument("attendableId") { type = NavType.IntType },
                     )
                 ) {
-                    val attendableType = it.arguments?.get("attendableType")
-                    val attendableId = it.arguments?.get("attendableId")
+                    val attendableType = it.arguments?.getString("attendableType")
+                    val attendableId = it.arguments?.getInt("attendableId")
 
                     AttendanceScreen(
                         hiltViewModel(),
diff --git a/attendance/build.gradle.kts b/attendance/build.gradle.kts
index 4df0cbe..878df10 100644
--- a/attendance/build.gradle.kts
+++ b/attendance/build.gradle.kts
@@ -3,6 +3,7 @@ plugins {
     kotlin("android")
     id("kotlin-android")
     kotlin("kapt")
+    id("com.google.devtools.ksp")
     id("dagger.hilt.android.plugin")
     id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
 }
@@ -26,7 +27,7 @@ dependencies {
     kapt(HiltDependencies.hilt_android_compiler)
 
     implementation(NetworkDependencies.moshi)
-    kapt(NetworkDependencies.moshi_kotlin_codegen)
+    ksp(NetworkDependencies.moshi_kotlin_codegen)
     implementation(NetworkDependencies.okhttp)
     implementation(platform(NetworkDependencies.okhttp_bom))
     implementation(NetworkDependencies.retrofit)
@@ -47,7 +48,6 @@ android {
     compileSdk = 33
     defaultConfig {
         minSdk = 21
-        targetSdk = 33
         vectorDrawables {
             useSupportLibrary = true
         }
@@ -69,7 +69,7 @@ android {
         jvmTarget = "17"
     }
     composeOptions {
-        kotlinCompilerExtensionVersion = "1.4.7"
+        kotlinCompilerExtensionVersion = "1.5.1"
     }
     namespace = "org.robojackets.apiary.attendance"
     hilt {
diff --git a/auth/build.gradle.kts b/auth/build.gradle.kts
index daf62ac..785301f 100644
--- a/auth/build.gradle.kts
+++ b/auth/build.gradle.kts
@@ -3,6 +3,7 @@ plugins {
     kotlin("android")
     id("kotlin-android")
     kotlin("kapt")
+    id("com.google.devtools.ksp")
     id("dagger.hilt.android.plugin")
     id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
 }
@@ -38,7 +39,7 @@ dependencies {
     implementation(MaterialDependencies.material_android)
 
     implementation(NetworkDependencies.moshi)
-    kapt(NetworkDependencies.moshi_kotlin_codegen)
+    ksp(NetworkDependencies.moshi_kotlin_codegen)
     implementation(NetworkDependencies.okhttp)
     implementation(platform(NetworkDependencies.okhttp_bom))
     implementation(NetworkDependencies.retrofit)
@@ -55,7 +56,6 @@ android {
     compileSdk = 33
     defaultConfig {
         minSdk = 21
-        targetSdk = 33
 
         vectorDrawables {
             useSupportLibrary = true
@@ -79,7 +79,7 @@ android {
         jvmTarget = "17"
     }
     composeOptions {
-        kotlinCompilerExtensionVersion = "1.4.7"
+        kotlinCompilerExtensionVersion = "1.5.1"
     }
     namespace = "org.robojackets.apiary.auth"
     hilt {
diff --git a/base/build.gradle.kts b/base/build.gradle.kts
index cd90e77..1373472 100644
--- a/base/build.gradle.kts
+++ b/base/build.gradle.kts
@@ -3,6 +3,7 @@ plugins {
     kotlin("android")
     id("kotlin-android")
     kotlin("kapt")
+    id("com.google.devtools.ksp")
     id("dagger.hilt.android.plugin")
     id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
 }
@@ -28,7 +29,7 @@ dependencies {
     kapt(HiltDependencies.hilt_android_compiler)
 
     implementation(NetworkDependencies.moshi)
-    kapt(NetworkDependencies.moshi_kotlin_codegen)
+    ksp(NetworkDependencies.moshi_kotlin_codegen)
     implementation(NetworkDependencies.retrofit)
     implementation(NetworkDependencies.sandwich)
 
@@ -49,7 +50,6 @@ android {
     compileSdk = 33
     defaultConfig {
         minSdk = 21
-        targetSdk = 33
 
         vectorDrawables {
             useSupportLibrary = true
@@ -72,7 +72,7 @@ android {
         jvmTarget = "17"
     }
     composeOptions {
-        kotlinCompilerExtensionVersion = "1.4.7"
+        kotlinCompilerExtensionVersion = "1.5.1"
     }
     namespace = "org.robojackets.apiary.base"
     hilt {
diff --git a/build.gradle.kts b/build.gradle.kts
index a81b354..64ac937 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -5,7 +5,7 @@ buildscript {
         mavenCentral()
     }
     dependencies {
-        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.21")
+        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0")
         classpath("com.android.tools.build:gradle:8.1.0")
         classpath("com.google.dagger:hilt-android-gradle-plugin:2.46.1")
         classpath("com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1")
@@ -29,8 +29,9 @@ allprojects {
 }
 
 plugins {
+    id("com.google.devtools.ksp").version("1.9.0-1.0.13").apply(false)
     id("io.gitlab.arturbosch.detekt").version("1.23.0")
-    id("com.autonomousapps.dependency-analysis").version("1.20.0")
+    id("com.autonomousapps.dependency-analysis").version("1.21.0")
     id("com.github.ben-manes.versions").version("0.46.0")
 }
 
diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt
index 74470a1..2426b55 100644
--- a/buildSrc/src/main/java/Dependencies.kt
+++ b/buildSrc/src/main/java/Dependencies.kt
@@ -94,7 +94,7 @@ object AuthDependencies {
 object HiltDependencies {
     object Versions {
         const val hilt_navigation_compose_version = "1.0.0"
-        const val hilt_version = "2.44"
+        const val hilt_version = "2.46.1"
     }
 
     const val hilt = "com.google.dagger:hilt-android:${Versions.hilt_version}"
@@ -111,7 +111,7 @@ object AndroidToolDependencies {
         const val gson_version = "2.10.1"
         const val in_app_update_compose_version = "0.0.17"
         const val open_source_licenses_version = "17.0.1"
-        const val sentry_version = "6.19.1"
+        const val sentry_version = "6.28.0"
         const val timber_version = "5.0.1"
     }
 
@@ -135,7 +135,7 @@ object NetworkDependencies {
         const val okhttp_bom_version = "4.11.0"
         const val retrofit_version = "2.9.0"
         const val retrofuture_version = "1.7.4"
-        const val sandwich_version = "1.3.6"
+        const val sandwich_version = "1.3.8"
     }
 
     const val moshi = "com.squareup.moshi:moshi:${Versions.moshi_version}"
diff --git a/navigation/build.gradle.kts b/navigation/build.gradle.kts
index f293d8a..716c8a1 100644
--- a/navigation/build.gradle.kts
+++ b/navigation/build.gradle.kts
@@ -3,6 +3,7 @@ plugins {
     kotlin("android")
     id("kotlin-android")
     kotlin("kapt")
+    id("com.google.devtools.ksp")
     id("dagger.hilt.android.plugin")
     id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
 }
@@ -34,7 +35,6 @@ android {
     compileSdk = 33
     defaultConfig {
         minSdk = 21
-        targetSdk = 33
 
         vectorDrawables {
             useSupportLibrary = true
@@ -57,7 +57,7 @@ android {
         jvmTarget = "17"
     }
     composeOptions {
-        kotlinCompilerExtensionVersion = "1.4.7"
+        kotlinCompilerExtensionVersion = "1.5.1"
     }
     namespace = "org.robojackets.apiary.navigation"
     hilt {

From 9d1cc96285c9b5511ff7a909836ee9a3c5a6db65 Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Sun, 20 Aug 2023 19:31:31 -0400
Subject: [PATCH 07/33] Cleanup M3 upgrade todos

---
 .../java/org/robojackets/apiary/ui/update/UpdateAvailable.kt | 3 ---
 .../java/org/robojackets/apiary/auth/ui/Authentication.kt    | 5 -----
 .../org/robojackets/apiary/base/ui/nfc/BuzzCardPrompt.kt     | 5 -----
 3 files changed, 13 deletions(-)

diff --git a/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt b/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt
index 1fc505e..3717ace 100644
--- a/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt
+++ b/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt
@@ -21,9 +21,6 @@ import org.robojackets.apiary.base.ui.util.getActivity
 import se.warting.inappupdate.compose.rememberInAppUpdateState
 import timber.log.Timber
 
-// TODO: M3 upgrade:
-// - Implement M3 AlertDialog
-
 // Just a random number so we can identify our update request later if necessary
 const val UPDATE_REQUEST_CODE = 1999
 
diff --git a/auth/src/main/java/org/robojackets/apiary/auth/ui/Authentication.kt b/auth/src/main/java/org/robojackets/apiary/auth/ui/Authentication.kt
index e59ed6f..f785a01 100644
--- a/auth/src/main/java/org/robojackets/apiary/auth/ui/Authentication.kt
+++ b/auth/src/main/java/org/robojackets/apiary/auth/ui/Authentication.kt
@@ -34,11 +34,6 @@ import org.robojackets.apiary.auth.oauth2.AuthManager
 import org.robojackets.apiary.base.AppEnvironment
 import org.robojackets.apiary.base.ui.util.MadeWithLove
 
-// TODO: M3 upgrade
-// - Update to M3 BottomSheet
-// -  Update to M3 AlertDialog
-// - Test auth behavior with new theme
-
 @OptIn(ExperimentalMaterial3Api::class)
 @Suppress("LongMethod", "MagicNumber")
 @Composable
diff --git a/base/src/main/java/org/robojackets/apiary/base/ui/nfc/BuzzCardPrompt.kt b/base/src/main/java/org/robojackets/apiary/base/ui/nfc/BuzzCardPrompt.kt
index 94e32ea..1ca0477 100644
--- a/base/src/main/java/org/robojackets/apiary/base/ui/nfc/BuzzCardPrompt.kt
+++ b/base/src/main/java/org/robojackets/apiary/base/ui/nfc/BuzzCardPrompt.kt
@@ -32,11 +32,6 @@ import org.robojackets.apiary.base.ui.theme.danger
 import timber.log.Timber
 import java.nio.charset.StandardCharsets
 
-// TODO: M3 upgrade
-// - Switch to M3 AlertDialog
-// - Verify icon/text colors are correct in dark mode
-// - Verify Contactless icon color is correct in light mode
-
 /**
  * Show a prompt for BuzzCards and call a callback function each time a valid GTID is obtained.
  *

From ad28be1e14fde45482f09fb3313159247af1b3e8 Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Sun, 20 Aug 2023 19:46:00 -0400
Subject: [PATCH 08/33] Use android-build-box 1.25 for Java 17 support

---
 ci/dockerfiles/Dockerfile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ci/dockerfiles/Dockerfile b/ci/dockerfiles/Dockerfile
index a51916d..b2ddf9d 100644
--- a/ci/dockerfiles/Dockerfile
+++ b/ci/dockerfiles/Dockerfile
@@ -1,6 +1,6 @@
 # This value is overridden during Concourse jobs to use a local copy of the image.
 # If you are changing the base image here, you should also change it in the Concourse pipeline configuration.
-ARG base_image=mingc/android-build-box:1.24.0
+ARG base_image=mingc/android-build-box:1.25.0
 
 FROM ${base_image}
 LABEL maintainer="RoboJackets <hello@robojackets.org>"

From cdfa2c57f3d9d9de34f567c868ef960484f6603e Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Fri, 1 Sep 2023 00:12:49 -0400
Subject: [PATCH 09/33] Update detekt config to remove deprecated rules

---
 ci/detekt/detekt.yml | 57 +++++++++++++-------------------------------
 1 file changed, 17 insertions(+), 40 deletions(-)

diff --git a/ci/detekt/detekt.yml b/ci/detekt/detekt.yml
index 418dc11..c2b990c 100644
--- a/ci/detekt/detekt.yml
+++ b/ci/detekt/detekt.yml
@@ -85,7 +85,7 @@ complexity:
     threshold: 10
     includeStaticDeclarations: false
     includePrivateDeclarations: false
-  ComplexMethod:
+  CyclomaticComplexMethod:
     active: true
     threshold: 15
     ignoreSingleWhenExpression: false
@@ -302,7 +302,6 @@ formatting:
     active: false
     autoCorrect: true
     indentSize: 4
-    continuationIndentSize: 4
   MaximumLineLength:
     active: true
     maxLineLength: 120
@@ -411,7 +410,6 @@ naming:
     parameterPattern: '[a-z][A-Za-z0-9]*'
     privateParameterPattern: '[a-z][A-Za-z0-9]*'
     excludeClassPattern: '$^'
-    ignoreOverridden: true
   EnumNaming:
     active: true
     excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
@@ -433,7 +431,6 @@ naming:
     excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
     functionPattern: '([a-z][a-zA-Z0-9]*)|(`.*`)'
     excludeClassPattern: '$^'
-    ignoreOverridden: true
     ignoreAnnotated:
       - 'Composable'
   FunctionParameterNaming:
@@ -441,7 +438,6 @@ naming:
     excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
     parameterPattern: '[a-z][A-Za-z0-9]*'
     excludeClassPattern: '$^'
-    ignoreOverridden: true
   InvalidPackageDeclaration:
     active: false
     excludes: ['**/*.kts']
@@ -487,7 +483,6 @@ naming:
     variablePattern: '[a-z][A-Za-z0-9]*'
     privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
     excludeClassPattern: '$^'
-    ignoreOverridden: true
 
 performance:
   active: true
@@ -512,8 +507,6 @@ potential-bugs:
     active: false
   DoubleMutabilityForCollection:
     active: false
-  DuplicateCaseInWhenExpression:
-    active: true
   EqualsAlwaysReturnsTrueOrFalse:
     active: true
   EqualsWithHashCodeExist:
@@ -526,7 +519,7 @@ potential-bugs:
     active: false
   IgnoredReturnValue:
     active: false
-    restrictToAnnotatedMethods: true
+    restrictToConfig: true
     returnValueAnnotations:
       - '*.CheckReturnValue'
       - '*.CheckResult'
@@ -544,17 +537,12 @@ potential-bugs:
   LateinitUsage:
     active: false
     excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
-    excludeAnnotatedProperties: []
+    ignoreAnnotated: []
     ignoreOnClassesPattern: ''
   MapGetWithNotNullAssertionOperator:
     active: false
-  MissingWhenCase:
-    active: true
-    allowElseExpression: true
   NullableToStringCall:
     active: false
-  RedundantElseInWhen:
-    active: true
   UnconditionalJumpStatementInLoop:
     active: false
   UnnecessaryNotNullOperator:
@@ -584,7 +572,8 @@ style:
     active: false
   DataClassContainsFunctions:
     active: false
-    conversionFunctionPrefix: 'to'
+    conversionFunctionPrefix:
+      - to
   DataClassShouldBeImmutable:
     active: false
   DestructuringDeclarationWithTooManyEntries:
@@ -603,10 +592,11 @@ style:
     includeLineWrapping: false
   ForbiddenComment:
     active: true
-    values:
-      - 'TODO:'
-      - 'FIXME:'
-      - 'STOPSHIP:'
+    comments:
+      - reason: 'Forbidden FIXME todo marker in comment, please fix the problem.'
+        value: 'FIXME:'
+      - reason: 'Forbidden STOPSHIP todo marker in comment, please address the problem before shipping the code.'
+        value: 'STOPSHIP:'
     allowedPatterns: ''
   ForbiddenImport:
     active: false
@@ -617,12 +607,6 @@ style:
     methods:
       - 'kotlin.io.println'
       - 'kotlin.io.print'
-  ForbiddenPublicDataClass:
-    active: true
-    excludes: ['**']
-    ignorePackages:
-      - '*.internal'
-      - '*.internal.*'
   ForbiddenVoid:
     active: false
     ignoreOverridden: false
@@ -631,15 +615,9 @@ style:
     active: true
     ignoreOverridableFunction: true
     ignoreActualFunction: true
-    excludedFunctions: ''
-    excludeAnnotatedFunction:
+    excludedFunctions: []
+    ignoreAnnotated:
       - 'dagger.Provides'
-  LibraryCodeMustSpecifyReturnType:
-    active: true
-    excludes: ['**']
-  LibraryEntitiesShouldNotBePublic:
-    active: true
-    excludes: ['**']
   LoopWithTooManyJumpStatements:
     active: true
     maxJumpCount: 1
@@ -661,8 +639,6 @@ style:
     ignoreEnums: false
     ignoreRanges: false
     ignoreExtensionFunctions: true
-  MandatoryBracesIfStatements:
-    active: false
   MandatoryBracesLoops:
     active: false
   MaxLineLength:
@@ -704,7 +680,8 @@ style:
   ReturnCount:
     active: true
     max: 2
-    excludedFunctions: 'equals'
+    excludedFunctions:
+      - equals
     excludeLabeled: false
     excludeReturnFromLambda: true
     excludeGuardClauses: false
@@ -722,10 +699,10 @@ style:
     active: false
   UnderscoresInNumericLiterals:
     active: false
-    acceptableDecimalLength: 5
+    acceptableLength: 5
   UnnecessaryAbstractClass:
     active: true
-    excludeAnnotatedClasses:
+    ignoreAnnotated:
       - 'dagger.Module'
   UnnecessaryAnnotationUseSiteTarget:
     active: false
@@ -756,7 +733,7 @@ style:
     active: false
   UseDataClass:
     active: false
-    excludeAnnotatedClasses: []
+    ignoreAnnotated: []
     allowVars: false
   UseEmptyCounterpart:
     active: false

From 9f3ee8df790427027d3966e044db4e8978a3e4da Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Fri, 1 Sep 2023 00:13:32 -0400
Subject: [PATCH 10/33] Update detekt plugin version

---
 build.gradle.kts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index 64ac937..8d61bf1 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -72,7 +72,7 @@ tasks.register("detektAll", io.gitlab.arturbosch.detekt.Detekt::class) {
 }
 
 dependencies {
-    detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.22.0")
+    detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.0")
 }
 
 // from https://github.com/autonomousapps/dependency-analysis-android-gradle-plugin/wiki/ABI-filtering

From 0f7e0862c963f3616005e81826ff8d6982de97dc Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Fri, 1 Sep 2023 00:13:52 -0400
Subject: [PATCH 11/33] Update comment

---
 ci/dockerfiles/Dockerfile | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/ci/dockerfiles/Dockerfile b/ci/dockerfiles/Dockerfile
index b2ddf9d..c596687 100644
--- a/ci/dockerfiles/Dockerfile
+++ b/ci/dockerfiles/Dockerfile
@@ -1,5 +1,6 @@
 # This value is overridden during Concourse jobs to use a local copy of the image.
-# If you are changing the base image here, you should also change it in the Concourse pipeline configuration.
+# If you are changing the base image here, you should also change it in the Concourse pipeline configuration
+# in https://github.com/RoboJackets/concourse-pipeline-library/blob/main/pipelines/information-technology/apiary-mobile-docker-build.yml
 ARG base_image=mingc/android-build-box:1.25.0
 
 FROM ${base_image}

From bddb438f7fb7c661a8822d5daccb13e716165ca3 Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Fri, 1 Sep 2023 00:14:12 -0400
Subject: [PATCH 12/33] Style fixes

---
 .../org/robojackets/apiary/MainActivity.kt    |  7 ++--
 .../apiary/di/MainActivityModule.kt           |  7 ++--
 .../apiary/network/UserAgentInterceptor.kt    | 10 ++++--
 .../robojackets/apiary/ui/global/AppTopBar.kt |  6 ++--
 .../apiary/ui/settings/Settings.kt            | 14 +++++---
 .../apiary/ui/settings/SettingsViewModel.kt   | 15 ++++++---
 .../apiary/ui/update/UpdateAvailable.kt       | 33 ++++++++++++-------
 .../apiary/ui/update/UpdateGate.kt            | 15 ++++++---
 .../model/AttendableTypeSelectionViewModel.kt |  9 +++--
 .../attendance/model/AttendanceViewModel.kt   | 15 ++++++---
 .../ui/AttendableTypeSelectionScreen.kt       |  7 ++--
 .../apiary/auth/ui/Authentication.kt          |  6 ----
 .../permissions/MissingHiddenTeamsCallout.kt  |  6 ++--
 .../apiary/base/ui/ActionPrompt.kt            |  6 ++--
 .../apiary/base/ui/callout/Callout.kt         |  6 ++--
 .../apiary/base/ui/nfc/NfcRequired.kt         |  3 +-
 buildSrc/src/main/java/Dependencies.kt        |  2 --
 17 files changed, 109 insertions(+), 58 deletions(-)

diff --git a/app/src/main/java/org/robojackets/apiary/MainActivity.kt b/app/src/main/java/org/robojackets/apiary/MainActivity.kt
index a192ae7..0f926ec 100644
--- a/app/src/main/java/org/robojackets/apiary/MainActivity.kt
+++ b/app/src/main/java/org/robojackets/apiary/MainActivity.kt
@@ -246,8 +246,11 @@ class MainActivity : ComponentActivity() {
         modifier: Modifier = Modifier,
     ) {
         val startDestination =
-            if (!authStateManager.current.isAuthorized) NavigationDestinations.authentication
-            else NavigationDestinations.attendanceSubgraph
+            if (!authStateManager.current.isAuthorized) {
+                NavigationDestinations.authentication
+            } else {
+                NavigationDestinations.attendanceSubgraph
+            }
 
         NavHost(
             navController = navController,
diff --git a/app/src/main/java/org/robojackets/apiary/di/MainActivityModule.kt b/app/src/main/java/org/robojackets/apiary/di/MainActivityModule.kt
index e6be8c7..b5aa650 100644
--- a/app/src/main/java/org/robojackets/apiary/di/MainActivityModule.kt
+++ b/app/src/main/java/org/robojackets/apiary/di/MainActivityModule.kt
@@ -45,8 +45,11 @@ object MainActivityModule {
     ): OkHttpClient {
         val loggingInterceptor = HttpLoggingInterceptor()
         loggingInterceptor.setLevel(
-            if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY
-            else HttpLoggingInterceptor.Level.BASIC
+            if (BuildConfig.DEBUG) {
+                HttpLoggingInterceptor.Level.BODY
+            } else {
+                HttpLoggingInterceptor.Level.BASIC
+            }
         ) // Only log detailed
         // network requests in debug builds
         loggingInterceptor.redactHeader("Authorization") // Redact access tokens in headers
diff --git a/app/src/main/java/org/robojackets/apiary/network/UserAgentInterceptor.kt b/app/src/main/java/org/robojackets/apiary/network/UserAgentInterceptor.kt
index dcd8d26..cafceb1 100644
--- a/app/src/main/java/org/robojackets/apiary/network/UserAgentInterceptor.kt
+++ b/app/src/main/java/org/robojackets/apiary/network/UserAgentInterceptor.kt
@@ -24,9 +24,13 @@ class UserAgentInterceptor(context: Context) : Interceptor {
     private fun getApplicationName(context: Context): String {
         val applicationInfo = context.applicationInfo
         val stringId = applicationInfo.labelRes
-        return if (stringId == 0) applicationInfo.nonLocalizedLabel.toString() else context.getString(
+        return if (stringId == 0) {
+            applicationInfo.nonLocalizedLabel.toString()
+        } else {
+            context.getString(
             stringId
         )
+        }
     }
 
     private fun getDeviceName(): String {
@@ -34,7 +38,9 @@ class UserAgentInterceptor(context: Context) : Interceptor {
         val model = Build.MODEL
         return if (model.startsWith(manufacturer)) {
             model.replaceFirstChar { it.titlecase() }
-        } else manufacturer.replaceFirstChar { it.titlecase() } + " " + model
+        } else {
+            manufacturer.replaceFirstChar { it.titlecase() } + " " + model
+        }
     }
 
     override fun intercept(chain: Interceptor.Chain): Response {
diff --git a/app/src/main/java/org/robojackets/apiary/ui/global/AppTopBar.kt b/app/src/main/java/org/robojackets/apiary/ui/global/AppTopBar.kt
index 4d42b22..d077626 100644
--- a/app/src/main/java/org/robojackets/apiary/ui/global/AppTopBar.kt
+++ b/app/src/main/java/org/robojackets/apiary/ui/global/AppTopBar.kt
@@ -41,11 +41,13 @@ fun AppTopBar(isProdEnv: Boolean) {
             ) {
                 IconWithText(
                     icon = { WarningIcon(tint = MaterialTheme.colorScheme.onError) },
-                    text = { Text(
+                    text = {
+                        Text(
                         "Non-production server",
                         modifier = Modifier.padding(start = 4.dp),
                         color = MaterialTheme.colorScheme.onError
-                    ) }
+                    )
+                    }
                 )
             }
         }
diff --git a/app/src/main/java/org/robojackets/apiary/ui/settings/Settings.kt b/app/src/main/java/org/robojackets/apiary/ui/settings/Settings.kt
index 481aba8..9e8b3ec 100644
--- a/app/src/main/java/org/robojackets/apiary/ui/settings/Settings.kt
+++ b/app/src/main/java/org/robojackets/apiary/ui/settings/Settings.kt
@@ -34,7 +34,7 @@ import org.robojackets.apiary.ui.update.UpdateStatus
 
 @Suppress("LongMethod", "LongParameterList")
 @Composable
- private fun Settings(
+private fun Settings(
      appEnv: AppEnvironment,
      user: UserInfo?,
      onLogout: () -> Unit,
@@ -76,17 +76,21 @@ import org.robojackets.apiary.ui.update.UpdateStatus
             SettingsMenuLink(
                 icon = { Icon(Icons.Outlined.Home, contentDescription = "home") },
                 title = { Text(text = "Server") },
-                subtitle = { Text(
+                subtitle = {
+                    Text(
                     text = "${appEnv.name} (${appEnv.apiBaseUrl})"
-                ) },
+                )
+                },
                 onClick = {}
             )
             SettingsMenuLink(
                 icon = { Icon(Icons.Outlined.Build, contentDescription = "build") },
                 title = { Text(text = "Version") },
-                subtitle = { Text(
+                subtitle = {
+                    Text(
                     text = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})"
-                ) },
+                )
+                },
                 onClick = {}
             )
             SettingsMenuLink(
diff --git a/app/src/main/java/org/robojackets/apiary/ui/settings/SettingsViewModel.kt b/app/src/main/java/org/robojackets/apiary/ui/settings/SettingsViewModel.kt
index 8734317..e6c543e 100644
--- a/app/src/main/java/org/robojackets/apiary/ui/settings/SettingsViewModel.kt
+++ b/app/src/main/java/org/robojackets/apiary/ui/settings/SettingsViewModel.kt
@@ -35,9 +35,11 @@ class SettingsViewModel @Inject constructor(
     val authStateManager: AuthStateManager,
 ) : ViewModel() {
     val privacyPolicyUrl: Uri = Uri.withAppendedPath(globalSettings.appEnv.apiBaseUrl, "privacy")
-    val makeAWishUrl: Uri = Uri.parse("https://docs.google.com/forms/d/e/1FAIpQLSelERsYq3" +
+    val makeAWishUrl: Uri = Uri.parse(
+        "https://docs.google.com/forms/d/e/1FAIpQLSelERsYq3" +
             "gLmHbWvVCWha5iCU8z3r9VYC0hCN4ArLpMAiysaQ/viewform?entry.1338203640=MyRoboJackets%20" +
-            "Android")
+            "Android"
+    )
     var customTabsClient: CustomTabsClient? = null
 
     val customTabsServiceConnection = object : CustomTabsServiceConnection() {
@@ -67,10 +69,13 @@ class SettingsViewModel @Inject constructor(
 
     init {
         viewModelScope.launch {
-            combine(listOf(
+            combine(
+                listOf(
                 user,
-            )) {
-                flows -> SettingsState(
+            )
+            ) {
+                flows ->
+                    SettingsState(
                     flows[0] as UserInfo?
                 )
             }.catch { throwable -> throw throwable }
diff --git a/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt b/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt
index 3717ace..1926b98 100644
--- a/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt
+++ b/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt
@@ -72,10 +72,14 @@ fun InstallUpdateButton(onIgnoreUpdate: () -> Unit = {}) {
             },
             title = { Text("Update failed") },
             text = {
-                Text("${
-                    updateError ?: ("An unknown error occurred while starting the " +
-                            "update.")
-                }\n\nPlease try again, or post in #it-helpdesk for assistance.")
+                Text(
+                    "${
+                    updateError ?: (
+                        "An unknown error occurred while starting the " +
+                            "update."
+                    )
+                }\n\nPlease try again, or post in #it-helpdesk for assistance."
+                )
             }
         )
     }
@@ -89,11 +93,14 @@ fun RequiredUpdatePrompt() {
         verticalArrangement = Arrangement.Center,
         modifier = Modifier.fillMaxHeight(),
     ) {
-        UpdateIcon(Modifier
+        UpdateIcon(
+            Modifier
             .padding(bottom = 18.dp)
-            .size(96.dp))
+            .size(96.dp)
+        )
         Text("Update to continue", style = MaterialTheme.typography.headlineMedium)
-        Text("To continue using MyRoboJackets, install the latest version. It'll only take a minute.",
+        Text(
+            "To continue using MyRoboJackets, install the latest version. It'll only take a minute.",
             textAlign = TextAlign.Center,
             modifier = Modifier.padding(24.dp)
             )
@@ -112,11 +119,14 @@ fun OptionalUpdatePrompt(
         verticalArrangement = Arrangement.Center,
         modifier = Modifier.fillMaxHeight(.5F),
     ) {
-        UpdateIcon(Modifier
+        UpdateIcon(
+            Modifier
             .padding(bottom = 9.dp)
-            .size(72.dp))
+            .size(72.dp)
+        )
         Text("Update available", style = MaterialTheme.typography.headlineSmall)
-        Text("Install the latest version of MyRoboJackets for the latest features and " +
+        Text(
+            "Install the latest version of MyRoboJackets for the latest features and " +
                 "bug fixes. It'll only take a minute.",
             textAlign = TextAlign.Center,
             modifier = Modifier.padding(20.dp)
@@ -139,7 +149,8 @@ fun UpdateInProgress() {
     ) {
         CircularProgressIndicator(Modifier.padding(bottom = 28.dp))
         Text("Please wait...", style = MaterialTheme.typography.headlineSmall)
-        Text("We're finishing installing an update. It'll just be a minute or two!",
+        Text(
+            "We're finishing installing an update. It'll just be a minute or two!",
             textAlign = TextAlign.Center,
             modifier = Modifier.padding(20.dp)
         )
diff --git a/app/src/main/java/org/robojackets/apiary/ui/update/UpdateGate.kt b/app/src/main/java/org/robojackets/apiary/ui/update/UpdateGate.kt
index 2a7cbec..168005b 100644
--- a/app/src/main/java/org/robojackets/apiary/ui/update/UpdateGate.kt
+++ b/app/src/main/java/org/robojackets/apiary/ui/update/UpdateGate.kt
@@ -80,7 +80,8 @@ fun UpdateGate(
             Lifecycle.Event.ON_RESUME -> {
                 if (result is AppUpdateResult.Available) {
                     if (result.updateInfo.updateAvailability()
-                        == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
+                        == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS
+                    ) {
                         context.getActivity()
                             ?.let { result.startImmediateUpdate(it, UPDATE_REQUEST_CODE) }
                     }
@@ -160,10 +161,14 @@ fun UpdateStatus() {
                     isImmediateUpdateOptional(priority, staleness)
 
             when {
-                immediateRequired -> Text("Required update available (priority: " +
-                        "$priority, staleness: $staleness)")
-                immediateOptional -> Text("Optional update available (priority: " +
-                        "$priority, staleness: $staleness)")
+                immediateRequired -> Text(
+                    "Required update available (priority: " +
+                        "$priority, staleness: $staleness)"
+                )
+                immediateOptional -> Text(
+                    "Optional update available (priority: " +
+                        "$priority, staleness: $staleness)"
+                )
                 else -> Text("Available")
             }
         }
diff --git a/attendance/src/main/java/org/robojackets/apiary/attendance/model/AttendableTypeSelectionViewModel.kt b/attendance/src/main/java/org/robojackets/apiary/attendance/model/AttendableTypeSelectionViewModel.kt
index 844e487..a87359d 100644
--- a/attendance/src/main/java/org/robojackets/apiary/attendance/model/AttendableTypeSelectionViewModel.kt
+++ b/attendance/src/main/java/org/robojackets/apiary/attendance/model/AttendableTypeSelectionViewModel.kt
@@ -3,7 +3,11 @@ package org.robojackets.apiary.attendance.model
 import androidx.lifecycle.SavedStateHandle
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
-import com.skydoves.sandwich.*
+import com.skydoves.sandwich.StatusCode
+import com.skydoves.sandwich.message
+import com.skydoves.sandwich.onError
+import com.skydoves.sandwich.onException
+import com.skydoves.sandwich.onSuccess
 import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -88,7 +92,8 @@ class AttendableTypeSelectionViewModel @Inject constructor(
                             "A server error occurred while checking if you have permission to " +
                                     "use this feature. Check your internet connection and try " +
                                     "again, or ask in #it-helpdesk for assistance."
-                        else -> "An error occurred while checking if you have permission to use " +
+                        else ->
+                            "An error occurred while checking if you have permission to use " +
                                 "this feature. Check your internet connection and try again, or " +
                                 "ask in #it-helpdesk for assistance."
                     }
diff --git a/attendance/src/main/java/org/robojackets/apiary/attendance/model/AttendanceViewModel.kt b/attendance/src/main/java/org/robojackets/apiary/attendance/model/AttendanceViewModel.kt
index f180a1a..24a10e7 100644
--- a/attendance/src/main/java/org/robojackets/apiary/attendance/model/AttendanceViewModel.kt
+++ b/attendance/src/main/java/org/robojackets/apiary/attendance/model/AttendanceViewModel.kt
@@ -50,7 +50,8 @@ class AttendanceViewModel @Inject constructor(
 
     init {
         viewModelScope.launch {
-            combine(listOf(
+            combine(
+                listOf(
                 lastAttendee,
                 screenState,
                 totalAttendees,
@@ -61,8 +62,10 @@ class AttendanceViewModel @Inject constructor(
                 selectedAttendable,
                 error,
                 missingHiddenTeams
-            )) {
-                flows -> AttendanceState(
+            )
+            ) {
+                flows ->
+                    AttendanceState(
                     flows[0] as AttendanceStoreResult?,
                     flows[1] as AttendanceScreenState,
                     flows[2] as Int,
@@ -125,7 +128,8 @@ class AttendanceViewModel @Inject constructor(
         loadingAttendables.value = true
         viewModelScope.launch {
             if (attendableType == AttendableType.Team &&
-                (attendableTeams.value.isEmpty() || forceRefresh)) {
+                (attendableTeams.value.isEmpty() || forceRefresh)
+            ) {
                 meetingsRepository.getTeams().onSuccess {
                     attendableTeams.value = this.data.teams
                         .filter { it.attendable }
@@ -141,7 +145,8 @@ class AttendanceViewModel @Inject constructor(
                 }
             }
             if (attendableType == AttendableType.Event &&
-                (attendableEvents.value.isEmpty() || forceRefresh)) {
+                (attendableEvents.value.isEmpty() || forceRefresh)
+            ) {
                 meetingsRepository.getEvents().onSuccess {
                     attendableEvents.value = this.data.events
                 }.onError {
diff --git a/attendance/src/main/java/org/robojackets/apiary/attendance/ui/AttendableTypeSelectionScreen.kt b/attendance/src/main/java/org/robojackets/apiary/attendance/ui/AttendableTypeSelectionScreen.kt
index 5f475cc..0a644e6 100644
--- a/attendance/src/main/java/org/robojackets/apiary/attendance/ui/AttendableTypeSelectionScreen.kt
+++ b/attendance/src/main/java/org/robojackets/apiary/attendance/ui/AttendableTypeSelectionScreen.kt
@@ -26,6 +26,7 @@ import org.robojackets.apiary.base.ui.icons.GroupsIcon
 import org.robojackets.apiary.base.ui.util.ContentPadding
 import org.robojackets.apiary.base.ui.util.LoadingSpinner
 
+@Suppress("LongMethod")
 @Composable
 fun AttendableTypeSelectionScreen(
     viewModel: AttendableTypeSelectionViewModel,
@@ -45,7 +46,8 @@ fun AttendableTypeSelectionScreen(
         if (state.permissionsCheckError?.isNotEmpty() == true) {
             ErrorMessageWithRetry(
                 message = state.permissionsCheckError ?: "An unknown error occurred",
-                onRetry = { viewModel.checkUserAttendanceAccess(forceRefresh = true) })
+                onRetry = { viewModel.checkUserAttendanceAccess(forceRefresh = true) }
+            )
             return@ContentPadding
         }
 
@@ -64,7 +66,8 @@ fun AttendableTypeSelectionScreen(
         Column(
             Modifier
                 .fillMaxWidth()
-                .fillMaxHeight()) {
+                .fillMaxHeight()
+        ) {
             Text("What do you want to take attendance for?", style = MaterialTheme.typography.headlineSmall)
             Spacer(Modifier.defaultMinSize(minHeight = 16.dp))
             Divider()
diff --git a/auth/src/main/java/org/robojackets/apiary/auth/ui/Authentication.kt b/auth/src/main/java/org/robojackets/apiary/auth/ui/Authentication.kt
index f785a01..ed8c943 100644
--- a/auth/src/main/java/org/robojackets/apiary/auth/ui/Authentication.kt
+++ b/auth/src/main/java/org/robojackets/apiary/auth/ui/Authentication.kt
@@ -15,7 +15,6 @@ import androidx.compose.material3.ModalBottomSheet
 import androidx.compose.material3.RadioButton
 import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
-import androidx.compose.material3.rememberBottomSheetScaffoldState
 import androidx.compose.material3.rememberModalBottomSheetState
 import androidx.compose.runtime.*
 import androidx.compose.ui.Alignment
@@ -43,9 +42,6 @@ private fun Authentication(
     onAppEnvChange: (newEnv: AppEnvironment) -> Unit,
     viewModel: AuthenticationViewModel,
 ) {
-    val scaffoldState = rememberBottomSheetScaffoldState()
-    val coroutineScope = rememberCoroutineScope()
-
     val systemUiController = rememberSystemUiController()
     val backgroundColor = MaterialTheme.colorScheme.background
     SideEffect {
@@ -99,7 +95,6 @@ private fun Authentication(
 
     var showChangeEnvBottomSheet by remember { mutableStateOf(false) }
 
-
     if (showChangeEnvBottomSheet) {
         ChangeEnvBottomSheet(
             onDismiss = { showChangeEnvBottomSheet = false },
@@ -211,7 +206,6 @@ private fun ChangeEnvironmentBottomSheetContent(
     viewState: AuthenticationState,
     onAppEnvChange: (newEnv: AppEnvironment) -> Unit,
 ) {
-    val coroutineScope = rememberCoroutineScope()
     var unsavedAppEnvSelection by remember { mutableStateOf(viewState.appEnv) }
     val appEnvChoices = AppEnvironment.values()
     val saveNewAppEnvChoice: (() -> Unit) = {
diff --git a/auth/src/main/java/org/robojackets/apiary/auth/ui/permissions/MissingHiddenTeamsCallout.kt b/auth/src/main/java/org/robojackets/apiary/auth/ui/permissions/MissingHiddenTeamsCallout.kt
index ffbe887..f8f8240 100644
--- a/auth/src/main/java/org/robojackets/apiary/auth/ui/permissions/MissingHiddenTeamsCallout.kt
+++ b/auth/src/main/java/org/robojackets/apiary/auth/ui/permissions/MissingHiddenTeamsCallout.kt
@@ -17,8 +17,10 @@ fun MissingHiddenTeamsCallout(onRefreshTeams: () -> Unit) {
         padding = PaddingValues(start = 12.dp, top = 10.dp, end = 12.dp, bottom = 7.dp)
     ) {
         Column {
-            Text("You don't have permission to view all teams, including " +
-                    "training teams. Ask in #it-helpdesk for access.")
+            Text(
+                "You don't have permission to view all teams, including " +
+                    "training teams. Ask in #it-helpdesk for access."
+            )
             OutlinedButton(onClick = onRefreshTeams, Modifier.padding(top = 0.dp)) {
                 Text("Refresh teams")
             }
diff --git a/base/src/main/java/org/robojackets/apiary/base/ui/ActionPrompt.kt b/base/src/main/java/org/robojackets/apiary/base/ui/ActionPrompt.kt
index 7d48683..ccab524 100644
--- a/base/src/main/java/org/robojackets/apiary/base/ui/ActionPrompt.kt
+++ b/base/src/main/java/org/robojackets/apiary/base/ui/ActionPrompt.kt
@@ -78,7 +78,9 @@ fun ActionPromptCardReadErrorWrongType() {
         title = "Card read error",
         subtitle = "Try tapping again"
     ) {
-        IconWithText(icon = { WarningIcon(tint = danger) },
-            text = "We only support BuzzCards 😉")
+        IconWithText(
+            icon = { WarningIcon(tint = danger) },
+            text = "We only support BuzzCards 😉"
+        )
     }
 }
diff --git a/base/src/main/java/org/robojackets/apiary/base/ui/callout/Callout.kt b/base/src/main/java/org/robojackets/apiary/base/ui/callout/Callout.kt
index 0b3c333..433d3fe 100644
--- a/base/src/main/java/org/robojackets/apiary/base/ui/callout/Callout.kt
+++ b/base/src/main/java/org/robojackets/apiary/base/ui/callout/Callout.kt
@@ -79,7 +79,9 @@ private fun WarningCalloutPreview() {
     WarningCallout(
         titleText = "Some teams are hidden",
     ) {
-        Text("Your account doesn't have permission to view all teams, including training " +
-                "teams. Ask in #it-helpdesk for access.")
+        Text(
+            "Your account doesn't have permission to view all teams, including training " +
+                "teams. Ask in #it-helpdesk for access."
+        )
     }
 }
diff --git a/base/src/main/java/org/robojackets/apiary/base/ui/nfc/NfcRequired.kt b/base/src/main/java/org/robojackets/apiary/base/ui/nfc/NfcRequired.kt
index c1f2a36..1653d76 100644
--- a/base/src/main/java/org/robojackets/apiary/base/ui/nfc/NfcRequired.kt
+++ b/base/src/main/java/org/robojackets/apiary/base/ui/nfc/NfcRequired.kt
@@ -33,7 +33,8 @@ fun NfcRequired(nfcEnabled: Boolean, gatedComposable: @Composable () -> Unit) {
     if (nfcEnabled) {
         gatedComposable()
     } else {
-        Column(Modifier.fillMaxHeight(),
+        Column(
+            Modifier.fillMaxHeight(),
             verticalArrangement = Arrangement.Center,
             horizontalAlignment = Alignment.CenterHorizontally
         ) {
diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt
index 2426b55..251d753 100644
--- a/buildSrc/src/main/java/Dependencies.kt
+++ b/buildSrc/src/main/java/Dependencies.kt
@@ -35,8 +35,6 @@ object ComposeDependencies {
 
     const val lifecycle_viewmodel_compose =
         "androidx.lifecycle:lifecycle-viewmodel-compose:${Versions.lifecycle_viewmodel_compose_version}"
-
-
 }
 
 object MaterialDependencies {

From eb537cdd9bb4f8ff189f5b6a45a3a7611003d538 Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Wed, 19 Jun 2024 23:54:56 -0400
Subject: [PATCH 13/33] Use Java 17

---
 .github/workflows/build.yml                 | 2 +-
 .github/workflows/internal-test-release.yml | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index c8a15b8..cb7f0fc 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -24,7 +24,7 @@ jobs:
     - name: Set up JDK
       uses: actions/setup-java@v4
       with:
-        java-version: '11'
+        java-version: '17'
         distribution: 'corretto'
         cache: gradle
 
diff --git a/.github/workflows/internal-test-release.yml b/.github/workflows/internal-test-release.yml
index 0f45d34..e449cb4 100644
--- a/.github/workflows/internal-test-release.yml
+++ b/.github/workflows/internal-test-release.yml
@@ -23,7 +23,7 @@ jobs:
     - name: Set up JDK
       uses: actions/setup-java@v4
       with:
-        java-version: '11'
+        java-version: '17'
         distribution: 'corretto'
         cache: gradle
 

From 59b9e9636e368a6094a17fecbd82f923a7db4119 Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Wed, 19 Jun 2024 23:55:15 -0400
Subject: [PATCH 14/33] Update dependencies and finish M3 upgrade

---
 Gemfile.lock                                  |  16 +--
 README.md                                     |   2 +-
 app/build.gradle.kts                          |   6 +-
 .../org/robojackets/apiary/MainActivity.kt    |  15 ++-
 attendance/build.gradle.kts                   |   4 +-
 auth/build.gradle.kts                         |   4 +-
 base/build.gradle.kts                         |   4 +-
 .../robojackets/apiary/base/ui/theme/Color.kt |  75 +++++++++++
 .../robojackets/apiary/base/ui/theme/Theme.kt | 118 +++++++++++++++---
 build.gradle.kts                              |  12 +-
 buildSrc/src/main/java/Dependencies.kt        |  49 ++++----
 gradle.properties                             |   1 -
 gradle/wrapper/gradle-wrapper.properties      |   2 +-
 navigation/build.gradle.kts                   |   4 +-
 14 files changed, 239 insertions(+), 73 deletions(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index 89deb36..158cf10 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -207,18 +207,4 @@ GEM
       nanaimo (~> 0.3.0)
       rexml (~> 3.2.4)
     xcpretty (0.3.0)
-      rouge (~> 2.0.7)
-    xcpretty-travis-formatter (1.0.1)
-      xcpretty (~> 0.2, >= 0.0.7)
-
-PLATFORMS
-  x64-mingw32
-  x86_64-linux
-
-DEPENDENCIES
-  dotenv (~> 2.7)
-  fastlane
-  fastlane-plugin-sentry
-
-BUNDLED WITH
-   2.3.1
+      rouge (~> 2
\ No newline at end of file
diff --git a/README.md b/README.md
index e87b90c..6285b40 100644
--- a/README.md
+++ b/README.md
@@ -101,7 +101,7 @@ command does, so you can get around any errors stemming from this by aliasing th
 to `which`.
  - You need to install [GitVersion](https://github.com/GitTools/GitVersion) yourself.
 
-## Release management
+## Release management   
 
 Below are some instructions on the MyRoboJackets Android release process. Note that you will need
 additional permissions on this repo and the MyRoboJackets Android Google Play application to
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index fbf6dad..59ab7e2 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -44,6 +44,7 @@ dependencies {
 
     implementation(HiltDependencies.hilt)
     kapt(HiltDependencies.hilt_android_compiler)
+    implementation(HiltDependencies.dagger_producer)
     implementation(HiltDependencies.hilt_navigation_compose)
 
     implementation(NetworkDependencies.moshi_converter_factory)
@@ -71,11 +72,11 @@ android {
         create("release") {
         }
     }
-    compileSdk = 33
+    compileSdk = 35
     defaultConfig {
         applicationId = "org.robojackets.apiary"
         minSdk = 21
-        targetSdk = 33
+        targetSdk = 35
         versionCode = 12
         versionName = "1.0.0"
         vectorDrawables {
@@ -90,6 +91,7 @@ android {
     }
     buildFeatures {
         compose = true
+        buildConfig = true
     }
     compileOptions {
         sourceCompatibility = JavaVersion.VERSION_17
diff --git a/app/src/main/java/org/robojackets/apiary/MainActivity.kt b/app/src/main/java/org/robojackets/apiary/MainActivity.kt
index 0f926ec..17b05e8 100644
--- a/app/src/main/java/org/robojackets/apiary/MainActivity.kt
+++ b/app/src/main/java/org/robojackets/apiary/MainActivity.kt
@@ -18,19 +18,28 @@ import androidx.compose.material3.NavigationBarItem
 import androidx.compose.material3.Scaffold
 import androidx.compose.material3.Surface
 import androidx.compose.material3.Text
-import androidx.compose.runtime.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.res.stringResource
 import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.navigation.*
 import androidx.navigation.NavDestination.Companion.hierarchy
 import androidx.navigation.NavGraph.Companion.findStartDestination
+import androidx.navigation.NavHostController
+import androidx.navigation.NavType
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.composable
 import androidx.navigation.compose.currentBackStackEntryAsState
 import androidx.navigation.compose.rememberNavController
+import androidx.navigation.navArgument
+import androidx.navigation.navigation
+import androidx.navigation.plusAssign
 import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi
 import com.google.accompanist.navigation.material.ModalBottomSheetLayout
 import com.google.accompanist.navigation.material.bottomSheet
@@ -144,7 +153,7 @@ class MainActivity : ComponentActivity() {
 
         setContent {
             Apiary_MobileTheme {
-                window.statusBarColor = MaterialTheme.colorScheme.secondary.toArgb()
+                window.statusBarColor = MaterialTheme.colorScheme.secondary.toArgb() // FIXME: deprecated
                 val navController = rememberNavController()
                 val bottomSheetNavigator = rememberBottomSheetNavigator()
                 navController.navigatorProvider += bottomSheetNavigator
diff --git a/attendance/build.gradle.kts b/attendance/build.gradle.kts
index 878df10..d01cde1 100644
--- a/attendance/build.gradle.kts
+++ b/attendance/build.gradle.kts
@@ -25,6 +25,7 @@ dependencies {
 
     implementation(HiltDependencies.hilt)
     kapt(HiltDependencies.hilt_android_compiler)
+    implementation(HiltDependencies.dagger_producer)
 
     implementation(NetworkDependencies.moshi)
     ksp(NetworkDependencies.moshi_kotlin_codegen)
@@ -45,7 +46,7 @@ dependencies {
 }
 
 android {
-    compileSdk = 33
+    compileSdk = 35
     defaultConfig {
         minSdk = 21
         vectorDrawables {
@@ -59,6 +60,7 @@ android {
     }
     buildFeatures {
         compose = true
+        buildConfig = true
     }
     compileOptions {
         sourceCompatibility = JavaVersion.VERSION_17
diff --git a/auth/build.gradle.kts b/auth/build.gradle.kts
index 785301f..a222271 100644
--- a/auth/build.gradle.kts
+++ b/auth/build.gradle.kts
@@ -35,6 +35,7 @@ dependencies {
 
     implementation(HiltDependencies.hilt)
     kapt(HiltDependencies.hilt_android_compiler)
+    implementation(HiltDependencies.dagger_producer)
 
     implementation(MaterialDependencies.material_android)
 
@@ -53,7 +54,7 @@ dependencies {
 }
 
 android {
-    compileSdk = 33
+    compileSdk = 35
     defaultConfig {
         minSdk = 21
 
@@ -69,6 +70,7 @@ android {
     }
     buildFeatures {
         compose = true
+        buildConfig = true
     }
     compileOptions {
         sourceCompatibility = JavaVersion.VERSION_17
diff --git a/base/build.gradle.kts b/base/build.gradle.kts
index 1373472..4a268f2 100644
--- a/base/build.gradle.kts
+++ b/base/build.gradle.kts
@@ -27,6 +27,7 @@ dependencies {
 
     implementation(HiltDependencies.hilt)
     kapt(HiltDependencies.hilt_android_compiler)
+    implementation(HiltDependencies.dagger_producer)
 
     implementation(NetworkDependencies.moshi)
     ksp(NetworkDependencies.moshi_kotlin_codegen)
@@ -47,7 +48,7 @@ hilt {
 }
 
 android {
-    compileSdk = 33
+    compileSdk = 35
     defaultConfig {
         minSdk = 21
 
@@ -62,6 +63,7 @@ android {
     }
     buildFeatures {
         compose = true
+        buildConfig = true
     }
     compileOptions {
         sourceCompatibility = JavaVersion.VERSION_17
diff --git a/base/src/main/java/org/robojackets/apiary/base/ui/theme/Color.kt b/base/src/main/java/org/robojackets/apiary/base/ui/theme/Color.kt
index 19bfdb7..83b55a0 100644
--- a/base/src/main/java/org/robojackets/apiary/base/ui/theme/Color.kt
+++ b/base/src/main/java/org/robojackets/apiary/base/ui/theme/Color.kt
@@ -7,6 +7,8 @@ val Gold = Color(0xFFEEB211)
 val GoldLight = Color(0xFFFFE450)
 val GoldDark = Color(0xFFB78300)
 
+val neutral = Color(0xFF949088)
+
 val danger = Color(0xFFB00020)
 
 // Color inspiration from GitHub Primer CSS: https://primer.style/css/
@@ -26,3 +28,76 @@ val warningDarkMuted = Color(0x66BB8009) // outline
 val success = Color(0xFF4CAF50)
 
 val webNavBarBackground = Color(0xFF343A40)
+
+// Imported from Material theme generator
+val primaryLight = Color(0xFFEEB211) // Gold
+val onPrimaryLight = Color(0xFF000000)
+val primaryContainerLight = Color(0xFFF7BA1E)
+val onPrimaryContainerLight = Color(0xFF453100)
+val secondaryLight = Color(0xFFB78300)
+val onSecondaryLight = Color(0xFFFFFFFF)
+val secondaryContainerLight = Color(0xFFFFE0A6)
+val onSecondaryContainerLight = Color(0xFF5C450F)
+val tertiaryLight = Color(0xFF526600)
+val onTertiaryLight = Color(0xFFFFFFFF)
+val tertiaryContainerLight = Color(0xFFB3D04F)
+val onTertiaryContainerLight = Color(0xFF2D3900)
+val errorLight = Color(0xFFBA1A1A)
+val onErrorLight = Color(0xFFFFFFFF)
+val errorContainerLight = Color(0xFFFFDAD6)
+val onErrorContainerLight = Color(0xFF410002)
+val backgroundLight = Color(0xFFFFF8F2)
+val onBackgroundLight = Color(0xFF201B11)
+val surfaceLight = Color(0xFFFFF8F2)
+val onSurfaceLight = Color(0xFF201B11)
+val surfaceVariantLight = Color(0xFFF0E0C7)
+val onSurfaceVariantLight = Color(0xFF504533)
+val outlineLight = Color(0xFF827661)
+val outlineVariantLight = Color(0xFFD4C5AC)
+val scrimLight = Color(0xFF000000)
+val inverseSurfaceLight = Color(0xFF363025)
+val inverseOnSurfaceLight = Color(0xFFFBEFDF)
+val inversePrimaryLight = Color(0xFFFABD22)
+val surfaceDimLight = Color(0xFFE4D8C9)
+val surfaceBrightLight = Color(0xFFFFF8F2)
+val surfaceContainerLowestLight = Color(0xFFFFFFFF)
+val surfaceContainerLowLight = Color(0xFFFEF2E2)
+val surfaceContainerLight = Color(0xFFF8ECDC)
+val surfaceContainerHighLight = Color(0xFFF2E7D7)
+val surfaceContainerHighestLight = Color(0xFFECE1D1)
+
+val primaryDark = Color(0xFFFFDB95)
+val onPrimaryDark = Color(0xFF402D00)
+val primaryContainerDark = Color(0xFFE7AC03)
+val onPrimaryContainerDark = Color(0xFF382700)
+val secondaryDark = Color(0xFFE4C281)
+val onSecondaryDark = Color(0xFF402D00)
+val secondaryContainerDark = Color(0xFF4F3904)
+val onSecondaryContainerDark = Color(0xFFEECC89)
+val tertiaryDark = Color(0xFFCDEC67)
+val onTertiaryDark = Color(0xFF293500)
+val tertiaryContainerDark = Color(0xFFA4C142)
+val onTertiaryContainerDark = Color(0xFF232D00)
+val errorDark = Color(0xFFFFB4AB)
+val onErrorDark = Color(0xFF690005)
+val errorContainerDark = Color(0xFF93000A)
+val onErrorContainerDark = Color(0xFFFFDAD6)
+val backgroundDark = Color(0xFF17130A)
+val onBackgroundDark = Color(0xFFECE1D1)
+val surfaceDark = Color(0xFF17130A)
+val onSurfaceDark = Color(0xFFECE1D1)
+val surfaceVariantDark = Color(0xFF504533)
+val onSurfaceVariantDark = Color(0xFFD4C5AC)
+val outlineDark = Color(0xFF9C8F79)
+val outlineVariantDark = Color(0xFF504533)
+val scrimDark = Color(0xFF000000)
+val inverseSurfaceDark = Color(0xFFECE1D1)
+val inverseOnSurfaceDark = Color(0xFF363025)
+val inversePrimaryDark = Color(0xFF7A5900)
+val surfaceDimDark = Color(0xFF17130A)
+val surfaceBrightDark = Color(0xFF3F382D)
+val surfaceContainerLowestDark = Color(0xFF120E06)
+val surfaceContainerLowDark = Color(0xFF201B11)
+val surfaceContainerDark = Color(0xFF241F15)
+val surfaceContainerHighDark = Color(0xFF2F291F)
+val surfaceContainerHighestDark = Color(0xFF3A3429)
\ No newline at end of file
diff --git a/base/src/main/java/org/robojackets/apiary/base/ui/theme/Theme.kt b/base/src/main/java/org/robojackets/apiary/base/ui/theme/Theme.kt
index deb2e15..ca745e2 100644
--- a/base/src/main/java/org/robojackets/apiary/base/ui/theme/Theme.kt
+++ b/base/src/main/java/org/robojackets/apiary/base/ui/theme/Theme.kt
@@ -1,38 +1,124 @@
 package org.robojackets.apiary.base.ui.theme
 
+import android.app.Activity
+import android.os.Build
 import androidx.compose.foundation.isSystemInDarkTheme
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
 import androidx.compose.material3.lightColorScheme
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.graphics.Color
+import androidx.compose.runtime.SideEffect
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalView
+import androidx.core.view.WindowCompat
 
-val LightColorScheme = lightColorScheme(
-    primary = Gold,
-    secondary = GoldDark,
-    tertiary = Color.Black,
+private val lightScheme = lightColorScheme(
+    primary = primaryLight,
+    onPrimary = onPrimaryLight,
+    primaryContainer = primaryContainerLight,
+    onPrimaryContainer = onPrimaryContainerLight,
+    secondary = secondaryLight,
+    onSecondary = onSecondaryLight,
+    secondaryContainer = secondaryContainerLight,
+    onSecondaryContainer = onSecondaryContainerLight,
+    tertiary = tertiaryLight,
+    onTertiary = onTertiaryLight,
+    tertiaryContainer = tertiaryContainerLight,
+    onTertiaryContainer = onTertiaryContainerLight,
+    error = errorLight,
+    onError = onErrorLight,
+    errorContainer = errorContainerLight,
+    onErrorContainer = onErrorContainerLight,
+    background = backgroundLight,
+    onBackground = onBackgroundLight,
+    surface = surfaceLight,
+    onSurface = onSurfaceLight,
+    surfaceVariant = surfaceVariantLight,
+    onSurfaceVariant = onSurfaceVariantLight,
+    outline = outlineLight,
+    outlineVariant = outlineVariantLight,
+    scrim = scrimLight,
+    inverseSurface = inverseSurfaceLight,
+    inverseOnSurface = inverseOnSurfaceLight,
+    inversePrimary = inversePrimaryLight,
+    surfaceDim = surfaceDimLight,
+    surfaceBright = surfaceBrightLight,
+    surfaceContainerLowest = surfaceContainerLowestLight,
+    surfaceContainerLow = surfaceContainerLowLight,
+    surfaceContainer = surfaceContainerLight,
+    surfaceContainerHigh = surfaceContainerHighLight,
+    surfaceContainerHighest = surfaceContainerHighestLight,
 )
-val DarkColorScheme = darkColorScheme(
-    primary = Gold,
-    secondary = GoldDark,
-    tertiary = Color.White,
+
+private val darkScheme = darkColorScheme(
+    primary = primaryDark,
+    onPrimary = onPrimaryDark,
+    primaryContainer = primaryContainerDark,
+    onPrimaryContainer = onPrimaryContainerDark,
+    secondary = secondaryDark,
+    onSecondary = onSecondaryDark,
+    secondaryContainer = secondaryContainerDark,
+    onSecondaryContainer = onSecondaryContainerDark,
+    tertiary = tertiaryDark,
+    onTertiary = onTertiaryDark,
+    tertiaryContainer = tertiaryContainerDark,
+    onTertiaryContainer = onTertiaryContainerDark,
+    error = errorDark,
+    onError = onErrorDark,
+    errorContainer = errorContainerDark,
+    onErrorContainer = onErrorContainerDark,
+    background = backgroundDark,
+    onBackground = onBackgroundDark,
+    surface = surfaceDark,
+    onSurface = onSurfaceDark,
+    surfaceVariant = surfaceVariantDark,
+    onSurfaceVariant = onSurfaceVariantDark,
+    outline = outlineDark,
+    outlineVariant = outlineVariantDark,
+    scrim = scrimDark,
+    inverseSurface = inverseSurfaceDark,
+    inverseOnSurface = inverseOnSurfaceDark,
+    inversePrimary = inversePrimaryDark,
+    surfaceDim = surfaceDimDark,
+    surfaceBright = surfaceBrightDark,
+    surfaceContainerLowest = surfaceContainerLowestDark,
+    surfaceContainerLow = surfaceContainerLowDark,
+    surfaceContainer = surfaceContainerDark,
+    surfaceContainerHigh = surfaceContainerHighDark,
+    surfaceContainerHighest = surfaceContainerHighestDark,
 )
 
 @Composable
 fun Apiary_MobileTheme(
     darkTheme: Boolean = isSystemInDarkTheme(),
+    // Dynamic color is available on Android 12+
+    dynamicColor: Boolean = false,
     content: @Composable() () -> Unit
 ) {
-    val colors = if (darkTheme) {
-        DarkColorScheme
-    } else {
-        LightColorScheme
+    val colorScheme = when {
+        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
+            val context = LocalContext.current
+            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
+        }
+
+        darkTheme -> darkScheme
+        else -> lightScheme
+    }
+    val view = LocalView.current
+    if (!view.isInEditMode) {
+        SideEffect {
+            val window = (view.context as Activity).window
+            window.statusBarColor = colorScheme.primary.toArgb() // FIXME
+            WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
+        }
     }
 
     MaterialTheme(
-        colorScheme = colors,
+        colorScheme = colorScheme,
         typography = Typography,
-        shapes = Shapes,
         content = content
     )
-}
+}
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 8d61bf1..c7dc063 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,15 +1,15 @@
 buildscript {
     repositories {
-        gradlePluginPortal()
         google()
+        gradlePluginPortal()
         mavenCentral()
     }
     dependencies {
         classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0")
-        classpath("com.android.tools.build:gradle:8.1.0")
+        classpath("com.android.tools.build:gradle:8.5.0")
         classpath("com.google.dagger:hilt-android-gradle-plugin:2.46.1")
         classpath("com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1")
-        classpath("com.google.gms:google-services:4.3.15")
+        classpath("com.google.gms:google-services:4.4.2")
         classpath("com.google.android.gms:oss-licenses-plugin:0.10.6")
     }
 }
@@ -65,9 +65,9 @@ tasks.register("detektAll", io.gitlab.arturbosch.detekt.Detekt::class) {
     include(kotlinFiles)
     exclude(resourceFiles, buildFiles)
     reports {
-        html.enabled = true
-        xml.enabled = false
-        txt.enabled = false
+        html.required.set(true)
+        xml.required.set(false)
+        txt.required.set(false)
     }
 }
 
diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt
index 251d753..c52eaf3 100644
--- a/buildSrc/src/main/java/Dependencies.kt
+++ b/buildSrc/src/main/java/Dependencies.kt
@@ -1,10 +1,10 @@
 object ComposeDependencies {
     object Versions {
-        const val accompanist_version = "0.30.1"
-        const val compose_settings_version = "0.27.0"
-        const val compose_version = "1.4.3"
-        const val lifecycle_viewmodel_compose_version = "2.6.1"
-        const val compose_material3_version = "1.1.1"
+        const val accompanist_version = "0.34.0"
+        const val compose_settings_version = "2.4.0"
+        const val compose_version = "1.6.8"
+        const val lifecycle_viewmodel_compose_version = "2.8.2"
+        const val compose_material3_version = "1.2.1"
     }
 
     const val accompanist_systemuicontroller =
@@ -27,7 +27,7 @@ object ComposeDependencies {
         "androidx.compose.runtime:runtime-livedata:${Versions.compose_version}"
 
     const val compose_settings =
-        "com.github.alorma:compose-settings-ui-m3:${Versions.compose_settings_version}"
+        "com.github.alorma.compose-settings:ui-tiles:${Versions.compose_settings_version}"
 
     const val compose_ui = "androidx.compose.ui:ui:${Versions.compose_version}"
     const val compose_ui_test = "androidx.compose.ui:ui-test-junit4:${Versions.compose_version}"
@@ -39,7 +39,7 @@ object ComposeDependencies {
 
 object MaterialDependencies {
     object Versions {
-        const val material_android_version = "1.9.0"
+        const val material_android_version = "1.12.0"
     }
 
     const val material_android =
@@ -48,11 +48,11 @@ object MaterialDependencies {
 
 object AndroidXDependencies {
     object Versions {
-        const val androidx_activity_compose_version = "1.7.2"
-        const val androidx_appcompat_version = "1.6.1"
-        const val androidx_browser_version = "1.5.0"
-        const val androidx_lifecycle_runtime_ktx_version = "2.6.1"
-        const val androidx_navigation_compose_version = "2.5.3"
+        const val androidx_activity_compose_version = "1.9.0"
+        const val androidx_appcompat_version = "1.7.0"
+        const val androidx_browser_version = "1.8.0"
+        const val androidx_lifecycle_runtime_ktx_version = "2.8.2"
+        const val androidx_navigation_compose_version = "2.7.7"
     }
 
     const val androidx_activity_compose =
@@ -68,7 +68,7 @@ object AndroidXDependencies {
 
 object FirebaseDependencies {
     object Versions {
-        const val firebase_bom_version = "32.0.0"
+        const val firebase_bom_version = "33.1.0"
     }
 
     const val firebase_bom = "com.google.firebase:firebase-bom:${Versions.firebase_bom_version}"
@@ -91,25 +91,26 @@ object AuthDependencies {
 
 object HiltDependencies {
     object Versions {
-        const val hilt_navigation_compose_version = "1.0.0"
+        const val hilt_navigation_compose_version = "1.2.0"
         const val hilt_version = "2.46.1"
     }
 
     const val hilt = "com.google.dagger:hilt-android:${Versions.hilt_version}"
     const val hilt_android_compiler =
         "com.google.dagger:hilt-android-compiler:${Versions.hilt_version}"
+    const val dagger_producer = "com.google.dagger:dagger-producers:${Versions.hilt_version}"
     const val hilt_navigation_compose =
         "androidx.hilt:hilt-navigation-compose:${Versions.hilt_navigation_compose_version}"
 }
 
 object AndroidToolDependencies {
     object Versions {
-        const val android_tools_desugar_version = "1.1.5"
+        const val android_tools_desugar_version = "2.0.4"
         const val krate_version = "2.0.0"
-        const val gson_version = "2.10.1"
-        const val in_app_update_compose_version = "0.0.17"
-        const val open_source_licenses_version = "17.0.1"
-        const val sentry_version = "6.28.0"
+        const val gson_version = "2.11.0"
+        const val in_app_update_compose_version = "0.0.17" // TODO: Update to newer version with breaking changes
+        const val open_source_licenses_version = "17.1.0"
+        const val sentry_version = "7.10.0"
         const val timber_version = "5.0.1"
     }
 
@@ -128,12 +129,12 @@ object AndroidToolDependencies {
 
 object NetworkDependencies {
     object Versions {
-        const val moshi_version = "1.15.0"
-        const val moshi_converter_factory_version = "2.9.0"
-        const val okhttp_bom_version = "4.11.0"
-        const val retrofit_version = "2.9.0"
+        const val moshi_version = "1.15.1"
+        const val moshi_converter_factory_version = "2.11.0"
+        const val okhttp_bom_version = "4.12.0"
+        const val retrofit_version = "2.11.0"
         const val retrofuture_version = "1.7.4"
-        const val sandwich_version = "1.3.8"
+        const val sandwich_version = "1.3.9" // TODO: Upgrade to v2
     }
 
     const val moshi = "com.squareup.moshi:moshi:${Versions.moshi_version}"
diff --git a/gradle.properties b/gradle.properties
index 20edcb1..896ce41 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -6,6 +6,5 @@ kotlin.code.style=official
 
 #Android
 android.useAndroidX=true
-android.defaults.buildfeatures.buildconfig=true
 android.nonTransitiveRClass=true
 android.nonFinalResIds=true
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 142d4cd..e49c70f 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
 #Wed Dec 15 17:03:27 EST 2021
 distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
 distributionPath=wrapper/dists
 zipStorePath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
diff --git a/navigation/build.gradle.kts b/navigation/build.gradle.kts
index 716c8a1..30da45e 100644
--- a/navigation/build.gradle.kts
+++ b/navigation/build.gradle.kts
@@ -24,6 +24,7 @@ dependencies {
 
     implementation(HiltDependencies.hilt)
     kapt(HiltDependencies.hilt_android_compiler)
+    implementation(HiltDependencies.dagger_producer)
 
     implementation(MaterialDependencies.material_android)
 
@@ -32,7 +33,7 @@ dependencies {
 }
 
 android {
-    compileSdk = 33
+    compileSdk = 35
     defaultConfig {
         minSdk = 21
 
@@ -47,6 +48,7 @@ android {
     }
     buildFeatures {
         compose = true
+        buildConfig = true
     }
     compileOptions {
         isCoreLibraryDesugaringEnabled = true

From f65fd28b58749ae5872b2b73ce6f4aa25bb80621 Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Thu, 20 Jun 2024 22:44:57 -0400
Subject: [PATCH 15/33] Fix ruby lock file?

---
 Gemfile.lock | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index 158cf10..89deb36 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -207,4 +207,18 @@ GEM
       nanaimo (~> 0.3.0)
       rexml (~> 3.2.4)
     xcpretty (0.3.0)
-      rouge (~> 2
\ No newline at end of file
+      rouge (~> 2.0.7)
+    xcpretty-travis-formatter (1.0.1)
+      xcpretty (~> 0.2, >= 0.0.7)
+
+PLATFORMS
+  x64-mingw32
+  x86_64-linux
+
+DEPENDENCIES
+  dotenv (~> 2.7)
+  fastlane
+  fastlane-plugin-sentry
+
+BUNDLED WITH
+   2.3.1

From e14cb27a86cb94dd8c9f8b00aa6421577b184dc5 Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Sat, 22 Jun 2024 22:22:46 -0400
Subject: [PATCH 16/33] In-app update cleanup, update Hilt version, move from
 kapt to ksp

---
 README.md                                     |  16 +-
 app/build.gradle.kts                          |   8 +-
 .../apiary/ui/update/UpdateAvailable.kt       | 109 +++++---
 .../apiary/ui/update/UpdateGate.kt            | 237 ++++++++----------
 attendance/build.gradle.kts                   |   6 +-
 auth/build.gradle.kts                         |   6 +-
 base/build.gradle.kts                         |   6 +-
 .../robojackets/apiary/base/ui/theme/Color.kt |   2 +-
 .../robojackets/apiary/base/ui/theme/Theme.kt |   2 +-
 build.gradle.kts                              |   5 +-
 buildSrc/src/main/java/Dependencies.kt        |   7 +-
 navigation/build.gradle.kts                   |   6 +-
 12 files changed, 212 insertions(+), 198 deletions(-)

diff --git a/README.md b/README.md
index 6285b40..5ac4b77 100644
--- a/README.md
+++ b/README.md
@@ -116,17 +116,17 @@ merged in close proximity. Our Concourse pipeline has jobs to automatically hand
 signing, and uploading production releases of the app.
 
 1. After you've merged all PRs to be included in the release, ensure the `.update-priority`
-file is set correctly according to the table below. Use priority 2 as the default. If you want to
-use priority 4 or 5, post in #apiary-mobile first.
+file is set correctly according to the table below. Use priority 1 as the default. If you want to
+use priority 5, post in #apiary-mobile first.
    1. Update priority affects if and how often users receive in-app update prompts to update the
 app to the latest version.
+2. 
 
-| Update priority | Description | Examples | Update timeline for users |
-| --- | --- |--- | --- |
-| 0/1/2 | Very low  or low priority | UI touchups that don't impact functionality, releases with options to opt-in to beta features | No prompt initially. Optional prompt starting 14 days after release. Immediate update 21 days after release. |
-| 3 | Medium priority |  Medium-priority bug fixes, performance improvements, non-time-sensitive feature launches | No prompts for the first 3 days. Optional starting 4 days after release. Immediate update 21 days after release. |
-| 4 | High priority or time-sensitive | High priority bug fixes, time-sensitive feature launches | Optional for the first 24 hours, then immediate. |
-| 5 | Critical bug fixes | Crashes/bugs impacting major features, urgent vulnerabilities | Immediate update required. |
+| Update priority | Description                                                                         | Examples                                                                                      |
+|-----------------|-------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------|
+| 1               | Optional update with prompts when the update is first available, then every 8 days. | UI touchups that don't impact functionality, releases with options to opt-in to beta features |
+| 3               | Optional update with prompts when the update is first available, then every 4 days. | Medium-priority bug fixes, performance improvements, new features                             |
+| 5               | Required update with immediate prompt and no option to decline.                     | Crashes/bugs impacting major features, urgent vulnerabilities                                 |
 
 2. Create a new release on `main` using a tag with a name like `v1.0.0`. Use semantic versioning
 to determine how to increment the version number.
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 59ab7e2..38a2646 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -2,7 +2,7 @@ plugins {
     id("com.android.application")
     kotlin("android")
     id("kotlin-android")
-    kotlin("kapt")
+//    kotlin("kapt") // FIXME: remove if unneeded
     id("com.google.devtools.ksp")
     id("dagger.hilt.android.plugin")
     id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
@@ -43,8 +43,8 @@ dependencies {
     implementation(ComposeDependencies.compose_settings)
 
     implementation(HiltDependencies.hilt)
-    kapt(HiltDependencies.hilt_android_compiler)
-    implementation(HiltDependencies.dagger_producer)
+    ksp(HiltDependencies.hilt_android_compiler)
+//    implementation(HiltDependencies.dagger_producer) // FIXME: remove if unneeded
     implementation(HiltDependencies.hilt_navigation_compose)
 
     implementation(NetworkDependencies.moshi_converter_factory)
@@ -77,7 +77,7 @@ android {
         applicationId = "org.robojackets.apiary"
         minSdk = 21
         targetSdk = 35
-        versionCode = 12
+        versionCode = 21
         versionName = "1.0.0"
         vectorDrawables {
             useSupportLibrary = true
diff --git a/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt b/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt
index 1926b98..fbf2529 100644
--- a/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt
+++ b/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt
@@ -1,39 +1,48 @@
 package org.robojackets.apiary.ui.update
 
-import android.app.Activity
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
 import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.Button
 import androidx.compose.material3.CircularProgressIndicator
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
-import androidx.compose.runtime.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
-import com.google.android.play.core.ktx.AppUpdateResult
+import kotlinx.coroutines.launch
 import org.robojackets.apiary.base.ui.icons.UpdateIcon
-import org.robojackets.apiary.base.ui.util.getActivity
+import se.warting.inappupdate.compose.InAppUpdateState
+import se.warting.inappupdate.compose.Mode
 import se.warting.inappupdate.compose.rememberInAppUpdateState
 import timber.log.Timber
 
-// Just a random number so we can identify our update request later if necessary
-const val UPDATE_REQUEST_CODE = 1999
-
-fun triggerImmediateUpdate(appUpdateResult: AppUpdateResult.Available, activity: Activity) {
-    appUpdateResult.startImmediateUpdate(activity, UPDATE_REQUEST_CODE)
-}
-
+@Suppress("LongMethod")
 @Composable
 fun InstallUpdateButton(onIgnoreUpdate: () -> Unit = {}) {
-    val updateState = rememberInAppUpdateState()
+    val inAppUpdateState = rememberInAppUpdateState(
+        highPrioritizeUpdates = 5,
+        mediumPrioritizeUpdates = 3,
+        promptIntervalHighPrioritizeUpdateInDays = 1,
+        promptIntervalMediumPrioritizeUpdateInDays = 4,
+        promptIntervalLowPrioritizeUpdateInDays = 8,
+    )
     var updateCanceled by remember { mutableStateOf(false) }
 
-    val context = LocalContext.current
+    val scope = rememberCoroutineScope()
     var updateError by remember { mutableStateOf<String?>(null) }
 
     when (updateCanceled) {
@@ -42,19 +51,45 @@ fun InstallUpdateButton(onIgnoreUpdate: () -> Unit = {}) {
         }
         false -> {
             Button(onClick = {
-                val result = updateState.appUpdateResult
-                if (result is AppUpdateResult.Available) {
-                    context.getActivity()?.let { triggerImmediateUpdate(result, it) } ?: run {
-                        Timber.e("Context.getActivity() was null while trying to start immediate update")
-                        updateError = "An update is available, but we were unable to start the update process."
+                when (inAppUpdateState) {
+                    is InAppUpdateState.DownloadedUpdate -> {
+                        scope.launch {
+                            inAppUpdateState.appUpdateResult.completeUpdate()
+                        }
+                    }
+
+                    is InAppUpdateState.RequiredUpdate -> {
+                        inAppUpdateState.onStartUpdate()
+                    }
+
+                    is InAppUpdateState.OptionalUpdate -> {
+                        inAppUpdateState.onStartUpdate(Mode.IMMEDIATE)
                     }
-                } else {
-                    Timber.e("User is in update flow but no update was available")
-                    updateError = "Sorry! It seems there are no updates to install."
+
+                    is InAppUpdateState.InProgressUpdate -> {
+                        Timber.w("UpdateAvailable: inAppUpdateState is InProgressUpdate")
+                        updateError = "The update can't be started because an update is already in" +
+                                " progress"
+                    }
+
+                    is InAppUpdateState.Error -> {
+                        Timber.e(
+                            "UpdateAvailable: inAppUpdateState is Error: ${inAppUpdateState.exception}"
+                        )
+                        updateError =
+                            "An update is available, but an error occurred while trying to start" +
+                                    " it."
+                    }
+                    else -> {
+                        updateError = "The update can't be started right now"
+                    }
+                }
+                if (updateError?.isNotBlank() == true) {
+                    Timber.e("Update error: $updateError")
+                    updateCanceled = true
                 }
-                updateCanceled = true
             }) {
-                Text("Download and install update")
+                Text("Update now")
             }
         }
     }
@@ -95,8 +130,8 @@ fun RequiredUpdatePrompt() {
     ) {
         UpdateIcon(
             Modifier
-            .padding(bottom = 18.dp)
-            .size(96.dp)
+                .padding(bottom = 18.dp)
+                .size(96.dp)
         )
         Text("Update to continue", style = MaterialTheme.typography.headlineMedium)
         Text(
@@ -114,6 +149,13 @@ fun RequiredUpdatePrompt() {
 fun OptionalUpdatePrompt(
     onIgnoreUpdate: () -> Unit
 ) {
+    val inAppUpdateState = rememberInAppUpdateState(
+        highPrioritizeUpdates = 5,
+        mediumPrioritizeUpdates = 3,
+        promptIntervalHighPrioritizeUpdateInDays = 1,
+        promptIntervalMediumPrioritizeUpdateInDays = 4,
+        promptIntervalLowPrioritizeUpdateInDays = 8,
+    )
     Column(
         horizontalAlignment = Alignment.CenterHorizontally,
         verticalArrangement = Arrangement.Center,
@@ -121,8 +163,8 @@ fun OptionalUpdatePrompt(
     ) {
         UpdateIcon(
             Modifier
-            .padding(bottom = 9.dp)
-            .size(72.dp)
+                .padding(bottom = 9.dp)
+                .size(72.dp)
         )
         Text("Update available", style = MaterialTheme.typography.headlineSmall)
         Text(
@@ -132,8 +174,13 @@ fun OptionalUpdatePrompt(
             modifier = Modifier.padding(20.dp)
         )
         InstallUpdateButton(onIgnoreUpdate)
-        TextButton(onClick = onIgnoreUpdate) {
-            Text("Remind me later")
+        TextButton(onClick = {
+            if (inAppUpdateState is InAppUpdateState.OptionalUpdate) {
+                inAppUpdateState.onDeclineUpdate()
+            }
+            onIgnoreUpdate()
+        }) {
+            Text("Not now")
         }
     }
 }
diff --git a/app/src/main/java/org/robojackets/apiary/ui/update/UpdateGate.kt b/app/src/main/java/org/robojackets/apiary/ui/update/UpdateGate.kt
index 168005b..8bc2c2b 100644
--- a/app/src/main/java/org/robojackets/apiary/ui/update/UpdateGate.kt
+++ b/app/src/main/java/org/robojackets/apiary/ui/update/UpdateGate.kt
@@ -3,64 +3,10 @@ package org.robojackets.apiary.ui.update
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.ui.platform.LocalContext
-import androidx.lifecycle.Lifecycle
-import com.google.android.play.core.install.model.AppUpdateType
-import com.google.android.play.core.install.model.UpdateAvailability
-import com.google.android.play.core.ktx.AppUpdateResult
-import com.google.android.play.core.ktx.clientVersionStalenessDays
-import com.google.android.play.core.ktx.updatePriority
-import kotlinx.coroutines.launch
-import org.robojackets.apiary.base.ui.util.OnLifecycleEvent
-import org.robojackets.apiary.base.ui.util.getActivity
+import se.warting.inappupdate.compose.InAppUpdateState
 import se.warting.inappupdate.compose.rememberInAppUpdateState
 import timber.log.Timber
 
-const val UPDATE_PRIORITY_LOWEST = 0
-const val UPDATE_PRIORITY_LOWER = 1
-const val UPDATE_PRIORITY_LOW = 2
-const val UPDATE_PRIORITY_MEDIUM = 3
-const val UPDATE_PRIORITY_HIGH = 4
-const val UPDATE_PRIORITY_HIGHEST = 5
-
-const val OPT_UPDATE_STALENESS_THRESHOLD_LOW_PRIORITY = 14
-const val OPT_UPDATE_STALENESS_THRESHOLD_MEDIUM_PRIORITY = 4
-
-fun isImmediateUpdateOptional(priority: Int, staleness: Int): Boolean {
-    return when (priority) {
-        UPDATE_PRIORITY_LOWEST,
-        UPDATE_PRIORITY_LOWER,
-        UPDATE_PRIORITY_LOW -> staleness >= OPT_UPDATE_STALENESS_THRESHOLD_LOW_PRIORITY
-        UPDATE_PRIORITY_MEDIUM -> staleness >= OPT_UPDATE_STALENESS_THRESHOLD_MEDIUM_PRIORITY
-        UPDATE_PRIORITY_HIGH -> true
-        UPDATE_PRIORITY_HIGHEST -> true
-        else -> {
-            Timber.w("Unknown priority $priority when evaluating for flexible update")
-            false
-        }
-    }
-}
-
-const val REQ_UPDATE_STALENESS_THRESHOLD_LOW_PRIORITY = 21
-const val REQ_UPDATE_STALENESS_THRESHOLD_MEDIUM_PRIORITY = 21
-const val REQ_UPDATE_STALENESS_THRESHOLD_HIGH_PRIORITY = 1
-
-fun isImmediateUpdateRequired(priority: Int, staleness: Int): Boolean {
-    return when (priority) {
-        UPDATE_PRIORITY_LOWEST,
-        UPDATE_PRIORITY_LOWER,
-        UPDATE_PRIORITY_LOW -> staleness >= REQ_UPDATE_STALENESS_THRESHOLD_LOW_PRIORITY
-        UPDATE_PRIORITY_MEDIUM -> staleness >= REQ_UPDATE_STALENESS_THRESHOLD_MEDIUM_PRIORITY
-        UPDATE_PRIORITY_HIGH -> staleness >= REQ_UPDATE_STALENESS_THRESHOLD_HIGH_PRIORITY
-        UPDATE_PRIORITY_HIGHEST -> true
-        else -> {
-            Timber.w("Unknown priority $priority when evaluating for immediate update")
-            false
-        }
-    }
-}
-
 @Suppress("ComplexMethod", "LongMethod")
 @Composable
 fun UpdateGate(
@@ -71,108 +17,127 @@ fun UpdateGate(
     content: @Composable () -> Unit,
 ) {
     content()
-    val updateState = rememberInAppUpdateState()
-    val scope = rememberCoroutineScope()
-    val result = updateState.appUpdateResult
-    val context = LocalContext.current
-    OnLifecycleEvent { owner, event ->
-        when (event) {
-            Lifecycle.Event.ON_RESUME -> {
-                if (result is AppUpdateResult.Available) {
-                    if (result.updateInfo.updateAvailability()
-                        == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS
-                    ) {
-                        context.getActivity()
-                            ?.let { result.startImmediateUpdate(it, UPDATE_REQUEST_CODE) }
+    val inAppUpdateState = rememberInAppUpdateState(
+        highPrioritizeUpdates = 5,
+        mediumPrioritizeUpdates = 3,
+        promptIntervalHighPrioritizeUpdateInDays = 1,
+        promptIntervalMediumPrioritizeUpdateInDays = 4,
+        promptIntervalLowPrioritizeUpdateInDays = 8,
+    )
+
+    when (inAppUpdateState) {
+        is InAppUpdateState.DownloadedUpdate -> {
+            if (inAppUpdateState.isRequiredUpdate) {
+                LaunchedEffect(navReady) {
+                    if (navReady) {
+                        onShowRequiredUpdatePrompt()
+                    }
+                }
+            } else {
+                LaunchedEffect(navReady) {
+                    if (navReady) {
+                        onShowOptionalUpdatePrompt()
                     }
                 }
             }
-            else -> Unit
+            content()
         }
-    }
-
-    when (result) {
-        is AppUpdateResult.NotAvailable -> Unit
-        is AppUpdateResult.Available -> {
-            val priority = result.updateInfo.updatePriority
-            val staleness = result.updateInfo.clientVersionStalenessDays ?: -1
-
-            val immediateAllowed =
-                result.updateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)
 
-            val immediateRequired = immediateAllowed &&
-                    isImmediateUpdateRequired(priority, staleness)
-            val immediateOptional = immediateAllowed &&
-                    !immediateRequired &&
-                    isImmediateUpdateOptional(priority, staleness)
-
-            when {
-                immediateRequired -> {
-                    LaunchedEffect(navReady) {
-                        if (navReady) {
-                            onShowRequiredUpdatePrompt()
-                        }
-                    }
-                }
-                immediateOptional -> {
-                    LaunchedEffect(navReady) {
-                        if (navReady) {
-                            onShowOptionalUpdatePrompt()
-                        }
-                    }
+        is InAppUpdateState.InProgressUpdate -> {
+            Timber.i("Update in progress: ${inAppUpdateState.installState.bytesDownloaded}")
+            LaunchedEffect(navReady) {
+                if (navReady) {
+                    onShowUpdateInProgressScreen()
                 }
-                else -> {
-                    Timber.d("An update is available but no action is required currently")
-                    content()
+            }
+            UpdateInProgress()
+        }
+
+        InAppUpdateState.Loading -> {
+            Timber.i("In-app update - loading state")
+            content()
+        }
+        InAppUpdateState.NotAvailable -> {
+            content()
+        }
+        is InAppUpdateState.OptionalUpdate -> {
+            LaunchedEffect(navReady) {
+                if (navReady && inAppUpdateState.shouldPrompt) {
+                    onShowOptionalUpdatePrompt() // FIXME: test this
                 }
             }
+            content()
         }
-        is AppUpdateResult.InProgress -> Unit
-        is AppUpdateResult.Downloaded -> {
-            LaunchedEffect(result) {
-                if (navReady) {
-                    onShowUpdateInProgressScreen()
-                    scope.launch {
-                        result.completeUpdate()
-                    }
+
+        is InAppUpdateState.RequiredUpdate -> {
+            LaunchedEffect(navReady) {
+                if (navReady && inAppUpdateState.shouldPrompt) {
+                    onShowRequiredUpdatePrompt() // FIXME: test this
                 }
             }
+            content()
+        }
+
+        is InAppUpdateState.Error -> {
+            content()
         }
     }
 }
 
 @Composable
 fun UpdateStatus() {
-    val updateState = rememberInAppUpdateState()
+    val inAppUpdateState = rememberInAppUpdateState(
+        highPrioritizeUpdates = 5,
+        mediumPrioritizeUpdates = 3,
+        promptIntervalHighPrioritizeUpdateInDays = 1,
+        promptIntervalMediumPrioritizeUpdateInDays = 4,
+        promptIntervalLowPrioritizeUpdateInDays = 8,
+    )
+    when (inAppUpdateState) {
+        is InAppUpdateState.DownloadedUpdate -> {
+            if (inAppUpdateState.isRequiredUpdate) {
+                Text("Downloaded > required")
+            } else {
+                Text("Downloaded > optional")
+            }
+        }
 
-    when (val result = updateState.appUpdateResult) {
-        is AppUpdateResult.NotAvailable -> Text("Up to date")
-        is AppUpdateResult.Available -> {
-            val priority = result.updateInfo.updatePriority
-            val staleness = result.updateInfo.clientVersionStalenessDays ?: -1
+        is InAppUpdateState.InProgressUpdate -> {
+            Text(
+                "In progress (${inAppUpdateState.installState.bytesDownloaded}/" +
+                    "${inAppUpdateState.installState.totalBytesToDownload}) bytes) " +
+                    "| Install status: ${inAppUpdateState.installState.installStatus} " +
+                    "| Error code: ${inAppUpdateState.installState.installErrorCode} " +
+                    "| Package name: ${inAppUpdateState.installState.packageName}"
+            )
+        }
 
-            val immediateAllowed =
-                result.updateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)
+        InAppUpdateState.Loading -> {
+            Text("Loading")
+        }
+        InAppUpdateState.NotAvailable -> {
+            Text("Not available")
+        }
+        is InAppUpdateState.OptionalUpdate -> {
+            Text(
+                "Optional update | Should prompt: ${inAppUpdateState.shouldPrompt} " +
+                    "| Priority: ${inAppUpdateState.appUpdateInfo.priority}" +
+                    "| Staleness: ${inAppUpdateState.appUpdateInfo.staleDays}" +
+                    "| Version code: ${inAppUpdateState.appUpdateInfo.versionCode}"
+            )
+        }
 
-            val immediateRequired = immediateAllowed &&
-                    isImmediateUpdateRequired(priority, staleness)
-            val immediateOptional = immediateAllowed &&
-                    !immediateRequired &&
-                    isImmediateUpdateOptional(priority, staleness)
+        is InAppUpdateState.RequiredUpdate -> {
+            Text(
+                "Required update | Should prompt: ${inAppUpdateState.shouldPrompt} " +
+                    "| Priority: ${inAppUpdateState.appUpdateInfo.priority}" +
+                    "| Staleness: ${inAppUpdateState.appUpdateInfo.staleDays}" +
+                    "| Version code: ${inAppUpdateState.appUpdateInfo.versionCode}"
+            )
+        }
 
-            when {
-                immediateRequired -> Text(
-                    "Required update available (priority: " +
-                        "$priority, staleness: $staleness)"
-                )
-                immediateOptional -> Text(
-                    "Optional update available (priority: " +
-                        "$priority, staleness: $staleness)"
-                )
-                else -> Text("Available")
-            }
+        is InAppUpdateState.Error -> {
+            Text("Error: ${inAppUpdateState.exception}")
         }
-        is AppUpdateResult.InProgress -> Text("Update in progress")
-        is AppUpdateResult.Downloaded -> Text("Update downloaded")
     }
 }
diff --git a/attendance/build.gradle.kts b/attendance/build.gradle.kts
index d01cde1..bce3244 100644
--- a/attendance/build.gradle.kts
+++ b/attendance/build.gradle.kts
@@ -2,7 +2,7 @@ plugins {
     id("com.android.library")
     kotlin("android")
     id("kotlin-android")
-    kotlin("kapt")
+//    kotlin("kapt")
     id("com.google.devtools.ksp")
     id("dagger.hilt.android.plugin")
     id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
@@ -24,8 +24,8 @@ dependencies {
     implementation(ComposeDependencies.lifecycle_viewmodel_compose)
 
     implementation(HiltDependencies.hilt)
-    kapt(HiltDependencies.hilt_android_compiler)
-    implementation(HiltDependencies.dagger_producer)
+    ksp(HiltDependencies.hilt_android_compiler)
+//    implementation(HiltDependencies.dagger_producer)
 
     implementation(NetworkDependencies.moshi)
     ksp(NetworkDependencies.moshi_kotlin_codegen)
diff --git a/auth/build.gradle.kts b/auth/build.gradle.kts
index a222271..ce39cb3 100644
--- a/auth/build.gradle.kts
+++ b/auth/build.gradle.kts
@@ -2,7 +2,7 @@ plugins {
     id("com.android.library")
     kotlin("android")
     id("kotlin-android")
-    kotlin("kapt")
+//    kotlin("kapt")
     id("com.google.devtools.ksp")
     id("dagger.hilt.android.plugin")
     id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
@@ -34,8 +34,8 @@ dependencies {
     implementation(ComposeDependencies.compose_ui_tooling)
 
     implementation(HiltDependencies.hilt)
-    kapt(HiltDependencies.hilt_android_compiler)
-    implementation(HiltDependencies.dagger_producer)
+    ksp(HiltDependencies.hilt_android_compiler)
+//    implementation(HiltDependencies.dagger_producer)
 
     implementation(MaterialDependencies.material_android)
 
diff --git a/base/build.gradle.kts b/base/build.gradle.kts
index 4a268f2..9d23205 100644
--- a/base/build.gradle.kts
+++ b/base/build.gradle.kts
@@ -2,7 +2,7 @@ plugins {
     id("com.android.library")
     kotlin("android")
     id("kotlin-android")
-    kotlin("kapt")
+//    kotlin("kapt")
     id("com.google.devtools.ksp")
     id("dagger.hilt.android.plugin")
     id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
@@ -26,8 +26,8 @@ dependencies {
     implementation(MaterialDependencies.material_android)
 
     implementation(HiltDependencies.hilt)
-    kapt(HiltDependencies.hilt_android_compiler)
-    implementation(HiltDependencies.dagger_producer)
+    ksp(HiltDependencies.hilt_android_compiler)
+//    implementation(HiltDependencies.dagger_producer)
 
     implementation(NetworkDependencies.moshi)
     ksp(NetworkDependencies.moshi_kotlin_codegen)
diff --git a/base/src/main/java/org/robojackets/apiary/base/ui/theme/Color.kt b/base/src/main/java/org/robojackets/apiary/base/ui/theme/Color.kt
index 83b55a0..281d309 100644
--- a/base/src/main/java/org/robojackets/apiary/base/ui/theme/Color.kt
+++ b/base/src/main/java/org/robojackets/apiary/base/ui/theme/Color.kt
@@ -100,4 +100,4 @@ val surfaceContainerLowestDark = Color(0xFF120E06)
 val surfaceContainerLowDark = Color(0xFF201B11)
 val surfaceContainerDark = Color(0xFF241F15)
 val surfaceContainerHighDark = Color(0xFF2F291F)
-val surfaceContainerHighestDark = Color(0xFF3A3429)
\ No newline at end of file
+val surfaceContainerHighestDark = Color(0xFF3A3429)
diff --git a/base/src/main/java/org/robojackets/apiary/base/ui/theme/Theme.kt b/base/src/main/java/org/robojackets/apiary/base/ui/theme/Theme.kt
index ca745e2..b4a30f5 100644
--- a/base/src/main/java/org/robojackets/apiary/base/ui/theme/Theme.kt
+++ b/base/src/main/java/org/robojackets/apiary/base/ui/theme/Theme.kt
@@ -121,4 +121,4 @@ fun Apiary_MobileTheme(
         typography = Typography,
         content = content
     )
-}
\ No newline at end of file
+}
diff --git a/build.gradle.kts b/build.gradle.kts
index c7dc063..f7c2d54 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -7,7 +7,8 @@ buildscript {
     dependencies {
         classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0")
         classpath("com.android.tools.build:gradle:8.5.0")
-        classpath("com.google.dagger:hilt-android-gradle-plugin:2.46.1")
+        classpath("com.google.dagger:hilt-android-gradle-plugin:2.51.1") // This version needs to
+        // match the version for other Hilt dependencies defined in Dependencies.kt
         classpath("com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1")
         classpath("com.google.gms:google-services:4.4.2")
         classpath("com.google.android.gms:oss-licenses-plugin:0.10.6")
@@ -42,7 +43,7 @@ tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach
 }
 
 tasks.register("clean", Delete::class) {
-    delete(rootProject.buildDir)
+    delete(rootProject.layout.buildDirectory)
 }
 
 val projectSource = file(projectDir)
diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt
index c52eaf3..8a587f8 100644
--- a/buildSrc/src/main/java/Dependencies.kt
+++ b/buildSrc/src/main/java/Dependencies.kt
@@ -92,7 +92,8 @@ object AuthDependencies {
 object HiltDependencies {
     object Versions {
         const val hilt_navigation_compose_version = "1.2.0"
-        const val hilt_version = "2.46.1"
+        const val hilt_version = "2.51.1" // If you update this version, you also need to update
+        // the com.google.dagger:hilt-android-gradle-plugin version in build.gradle files
     }
 
     const val hilt = "com.google.dagger:hilt-android:${Versions.hilt_version}"
@@ -108,7 +109,7 @@ object AndroidToolDependencies {
         const val android_tools_desugar_version = "2.0.4"
         const val krate_version = "2.0.0"
         const val gson_version = "2.11.0"
-        const val in_app_update_compose_version = "0.0.17" // TODO: Update to newer version with breaking changes
+        const val in_app_update_compose_version = "1.2.0"
         const val open_source_licenses_version = "17.1.0"
         const val sentry_version = "7.10.0"
         const val timber_version = "5.0.1"
@@ -118,7 +119,7 @@ object AndroidToolDependencies {
         "com.android.tools:desugar_jdk_libs:${Versions.android_tools_desugar_version}"
     const val gson = "com.google.code.gson:gson:${Versions.gson_version}"
     const val in_app_update_compose =
-        "se.warting.in-app-update:in-app-update-compose:${Versions.in_app_update_compose_version}"
+        "se.warting.in-app-update:in-app-update-compose-mui:${Versions.in_app_update_compose_version}"
     const val krate = "hu.autsoft:krate:${Versions.krate_version}"
     const val open_source_licenses =
         "com.google.android.gms:play-services-oss-licenses:${Versions.open_source_licenses_version}"
diff --git a/navigation/build.gradle.kts b/navigation/build.gradle.kts
index 30da45e..0c7c10d 100644
--- a/navigation/build.gradle.kts
+++ b/navigation/build.gradle.kts
@@ -2,7 +2,7 @@ plugins {
     id("com.android.library")
     kotlin("android")
     id("kotlin-android")
-    kotlin("kapt")
+//    kotlin("kapt")
     id("com.google.devtools.ksp")
     id("dagger.hilt.android.plugin")
     id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
@@ -23,8 +23,8 @@ dependencies {
     implementation(AndroidXDependencies.androidx_navigation_compose)
 
     implementation(HiltDependencies.hilt)
-    kapt(HiltDependencies.hilt_android_compiler)
-    implementation(HiltDependencies.dagger_producer)
+    ksp(HiltDependencies.hilt_android_compiler)
+//    implementation(HiltDependencies.dagger_producer)
 
     implementation(MaterialDependencies.material_android)
 

From 91ec7bb50989d02bf72954619eb6d93b99146c4e Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Thu, 4 Jul 2024 13:40:14 -0400
Subject: [PATCH 17/33] Final fixes

---
 app/build.gradle.kts                          |  6 ++---
 .../org/robojackets/apiary/MainActivity.kt    | 11 ++++-----
 .../apiary/di/MainActivityModule.kt           |  2 +-
 .../robojackets/apiary/ui/global/AppTopBar.kt |  9 +++++++
 .../apiary/ui/update/UpdateGate.kt            |  4 ++--
 attendance/build.gradle.kts                   |  6 ++---
 .../model/AttendableTypeSelectionViewModel.kt |  6 +++--
 .../attendance/model/AttendanceViewModel.kt   | 12 ++++++----
 auth/build.gradle.kts                         |  8 +++----
 .../apiary/auth/ui/Authentication.kt          | 24 +++++++++++++++----
 .../permissions/MissingHiddenTeamsCallout.kt  |  5 ++--
 base/build.gradle.kts                         |  8 +++----
 .../apiary/base/ui/ActionPrompt.kt            |  4 ++--
 .../apiary/base/ui/callout/Callout.kt         | 12 ++++++----
 .../apiary/base/ui/nfc/NfcRequired.kt         |  3 ---
 .../robojackets/apiary/base/ui/theme/Theme.kt |  9 +++++--
 build.gradle.kts                              |  2 +-
 buildSrc/src/main/java/Dependencies.kt        | 21 ++++++++--------
 navigation/build.gradle.kts                   |  4 ----
 19 files changed, 89 insertions(+), 67 deletions(-)

diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 38a2646..46c0f8c 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -2,7 +2,6 @@ plugins {
     id("com.android.application")
     kotlin("android")
     id("kotlin-android")
-//    kotlin("kapt") // FIXME: remove if unneeded
     id("com.google.devtools.ksp")
     id("dagger.hilt.android.plugin")
     id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
@@ -32,7 +31,7 @@ dependencies {
 
     implementation(AuthDependencies.appauth)
 
-    implementation(ComposeDependencies.accompanist_nav_material)
+    implementation(ComposeDependencies.compose_material_navigation)
     implementation(ComposeDependencies.compose_ui)
     implementation(ComposeDependencies.lifecycle_viewmodel_compose)
     implementation(ComposeDependencies.compose_ui_tooling)
@@ -44,7 +43,6 @@ dependencies {
 
     implementation(HiltDependencies.hilt)
     ksp(HiltDependencies.hilt_android_compiler)
-//    implementation(HiltDependencies.dagger_producer) // FIXME: remove if unneeded
     implementation(HiltDependencies.hilt_navigation_compose)
 
     implementation(NetworkDependencies.moshi_converter_factory)
@@ -53,6 +51,8 @@ dependencies {
     implementation(NetworkDependencies.okhttp_logging_interceptor)
     implementation(NetworkDependencies.retrofit)
     implementation(NetworkDependencies.sandwich) // yum yum
+    implementation(NetworkDependencies.sandwich_retrofit)
+    implementation(NetworkDependencies.sandwich_retrofit_serialization)
 
     implementation(platform(NfcDependencies.nfc_firebase_bom))
     implementation(NfcDependencies.nfc_firebase_analytics) // Firebase BoM and Analytics (f/k/a Core) are required when including TapLinx (line below) manually
diff --git a/app/src/main/java/org/robojackets/apiary/MainActivity.kt b/app/src/main/java/org/robojackets/apiary/MainActivity.kt
index 17b05e8..b638bc0 100644
--- a/app/src/main/java/org/robojackets/apiary/MainActivity.kt
+++ b/app/src/main/java/org/robojackets/apiary/MainActivity.kt
@@ -11,6 +11,9 @@ import androidx.compose.foundation.layout.padding
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Settings
 import androidx.compose.material.icons.outlined.Contactless
+import androidx.compose.material.navigation.ModalBottomSheetLayout
+import androidx.compose.material.navigation.bottomSheet
+import androidx.compose.material.navigation.rememberBottomSheetNavigator
 import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.NavigationBar
@@ -40,10 +43,6 @@ import androidx.navigation.compose.rememberNavController
 import androidx.navigation.navArgument
 import androidx.navigation.navigation
 import androidx.navigation.plusAssign
-import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi
-import com.google.accompanist.navigation.material.ModalBottomSheetLayout
-import com.google.accompanist.navigation.material.bottomSheet
-import com.google.accompanist.navigation.material.rememberBottomSheetNavigator
 import com.nxp.nfclib.NxpNfcLib
 import dagger.hilt.android.AndroidEntryPoint
 import kotlinx.coroutines.flow.launchIn
@@ -132,7 +131,6 @@ class MainActivity : ComponentActivity() {
         nfcLib.registerActivity(this, BuildConfig.taplinxKey, BuildConfig.taplinxOfflineKey)
     }
 
-    @OptIn(ExperimentalMaterialNavigationApi::class)
     @Suppress("LongMethod")
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
@@ -153,7 +151,7 @@ class MainActivity : ComponentActivity() {
 
         setContent {
             Apiary_MobileTheme {
-                window.statusBarColor = MaterialTheme.colorScheme.secondary.toArgb() // FIXME: deprecated
+                window.statusBarColor = MaterialTheme.colorScheme.secondary.toArgb()
                 val navController = rememberNavController()
                 val bottomSheetNavigator = rememberBottomSheetNavigator()
                 navController.navigatorProvider += bottomSheetNavigator
@@ -248,7 +246,6 @@ class MainActivity : ComponentActivity() {
             currentScreen != NavigationDestinations.updateInProgress
 
     @Suppress("LongMethod")
-    @OptIn(ExperimentalMaterialNavigationApi::class)
     @Composable
     private fun AppNavigation(
         navController: NavHostController,
diff --git a/app/src/main/java/org/robojackets/apiary/di/MainActivityModule.kt b/app/src/main/java/org/robojackets/apiary/di/MainActivityModule.kt
index b5aa650..1acf3b5 100644
--- a/app/src/main/java/org/robojackets/apiary/di/MainActivityModule.kt
+++ b/app/src/main/java/org/robojackets/apiary/di/MainActivityModule.kt
@@ -2,7 +2,7 @@ package org.robojackets.apiary.di
 
 import android.content.Context
 import com.nxp.nfclib.NxpNfcLib
-import com.skydoves.sandwich.adapters.ApiResponseCallAdapterFactory
+import com.skydoves.sandwich.retrofit.adapters.ApiResponseCallAdapterFactory
 import com.squareup.moshi.Moshi
 import com.squareup.moshi.Types
 import dagger.Module
diff --git a/app/src/main/java/org/robojackets/apiary/ui/global/AppTopBar.kt b/app/src/main/java/org/robojackets/apiary/ui/global/AppTopBar.kt
index d077626..6d75fdd 100644
--- a/app/src/main/java/org/robojackets/apiary/ui/global/AppTopBar.kt
+++ b/app/src/main/java/org/robojackets/apiary/ui/global/AppTopBar.kt
@@ -9,9 +9,11 @@ import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarColors
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.dp
 import org.robojackets.apiary.base.ui.IconWithText
@@ -22,6 +24,13 @@ import org.robojackets.apiary.base.ui.icons.WarningIcon
 fun AppTopBar(isProdEnv: Boolean) {
     Column {
         TopAppBar(
+            colors = TopAppBarColors(
+                containerColor = MaterialTheme.colorScheme.primary,
+                scrolledContainerColor = Color.Unspecified,
+                navigationIconContentColor = Color.Unspecified,
+                titleContentColor = Color.Unspecified,
+                actionIconContentColor = Color.Unspecified,
+            ),
             title = {
                 Text(
                     text = "MyRoboJackets",
diff --git a/app/src/main/java/org/robojackets/apiary/ui/update/UpdateGate.kt b/app/src/main/java/org/robojackets/apiary/ui/update/UpdateGate.kt
index 8bc2c2b..a8ed0da 100644
--- a/app/src/main/java/org/robojackets/apiary/ui/update/UpdateGate.kt
+++ b/app/src/main/java/org/robojackets/apiary/ui/update/UpdateGate.kt
@@ -63,7 +63,7 @@ fun UpdateGate(
         is InAppUpdateState.OptionalUpdate -> {
             LaunchedEffect(navReady) {
                 if (navReady && inAppUpdateState.shouldPrompt) {
-                    onShowOptionalUpdatePrompt() // FIXME: test this
+                    onShowOptionalUpdatePrompt()
                 }
             }
             content()
@@ -72,7 +72,7 @@ fun UpdateGate(
         is InAppUpdateState.RequiredUpdate -> {
             LaunchedEffect(navReady) {
                 if (navReady && inAppUpdateState.shouldPrompt) {
-                    onShowRequiredUpdatePrompt() // FIXME: test this
+                    onShowRequiredUpdatePrompt()
                 }
             }
             content()
diff --git a/attendance/build.gradle.kts b/attendance/build.gradle.kts
index bce3244..a233cea 100644
--- a/attendance/build.gradle.kts
+++ b/attendance/build.gradle.kts
@@ -2,7 +2,6 @@ plugins {
     id("com.android.library")
     kotlin("android")
     id("kotlin-android")
-//    kotlin("kapt")
     id("com.google.devtools.ksp")
     id("dagger.hilt.android.plugin")
     id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
@@ -13,8 +12,6 @@ dependencies {
     implementation(project(mapOf("path" to ":base")))
     implementation(project(mapOf("path" to ":navigation")))
     implementation(project(mapOf("path" to ":auth")))
-    implementation("androidx.navigation:navigation-common-ktx:2.3.5")
-
     // Dependencies
     coreLibraryDesugaring(AndroidToolDependencies.android_tools_desugar_jdk)
     implementation(AndroidToolDependencies.timber)
@@ -25,7 +22,6 @@ dependencies {
 
     implementation(HiltDependencies.hilt)
     ksp(HiltDependencies.hilt_android_compiler)
-//    implementation(HiltDependencies.dagger_producer)
 
     implementation(NetworkDependencies.moshi)
     ksp(NetworkDependencies.moshi_kotlin_codegen)
@@ -34,6 +30,8 @@ dependencies {
     implementation(NetworkDependencies.retrofit)
     implementation(NetworkDependencies.retrofuture)
     implementation(NetworkDependencies.sandwich)
+    implementation(NetworkDependencies.sandwich_retrofit)
+    implementation(NetworkDependencies.sandwich_retrofit_serialization)
 
     implementation(platform(NfcDependencies.nfc_firebase_bom))
     implementation(NfcDependencies.nfc_firebase_analytics) // Firebase BoM and Analytics (f/k/a Core) are required when including TapLinx (line below) manually
diff --git a/attendance/src/main/java/org/robojackets/apiary/attendance/model/AttendableTypeSelectionViewModel.kt b/attendance/src/main/java/org/robojackets/apiary/attendance/model/AttendableTypeSelectionViewModel.kt
index a87359d..de42a4c 100644
--- a/attendance/src/main/java/org/robojackets/apiary/attendance/model/AttendableTypeSelectionViewModel.kt
+++ b/attendance/src/main/java/org/robojackets/apiary/attendance/model/AttendableTypeSelectionViewModel.kt
@@ -8,6 +8,7 @@ import com.skydoves.sandwich.message
 import com.skydoves.sandwich.onError
 import com.skydoves.sandwich.onException
 import com.skydoves.sandwich.onSuccess
+import com.skydoves.sandwich.retrofit.statusCode
 import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -15,7 +16,8 @@ import kotlinx.coroutines.flow.catch
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 import org.robojackets.apiary.auth.model.Permission
-import org.robojackets.apiary.auth.model.Permission.*
+import org.robojackets.apiary.auth.model.Permission.CREATE_ATTENDANCE
+import org.robojackets.apiary.auth.model.Permission.READ_USERS
 import org.robojackets.apiary.auth.model.UserInfo
 import org.robojackets.apiary.auth.network.UserRepository
 import org.robojackets.apiary.auth.util.getMissingPermissions
@@ -99,7 +101,7 @@ class AttendableTypeSelectionViewModel @Inject constructor(
                     }
                 }
                 .onException {
-                    Timber.e(this.exception)
+                    Timber.e(this.throwable)
                     permissionsCheckError.value = "An error occurred while checking if you have " +
                             "permission to use this feature. Check your internet connection and " +
                             "try again, or ask in #it-helpdesk for assistance."
diff --git a/attendance/src/main/java/org/robojackets/apiary/attendance/model/AttendanceViewModel.kt b/attendance/src/main/java/org/robojackets/apiary/attendance/model/AttendanceViewModel.kt
index 24a10e7..7e88964 100644
--- a/attendance/src/main/java/org/robojackets/apiary/attendance/model/AttendanceViewModel.kt
+++ b/attendance/src/main/java/org/robojackets/apiary/attendance/model/AttendanceViewModel.kt
@@ -9,9 +9,13 @@ import com.skydoves.sandwich.onError
 import com.skydoves.sandwich.onException
 import com.skydoves.sandwich.onSuccess
 import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
-import org.robojackets.apiary.attendance.model.AttendanceScreenState.*
+import org.robojackets.apiary.attendance.model.AttendanceScreenState.Loading
+import org.robojackets.apiary.attendance.model.AttendanceScreenState.ReadyForTap
 import org.robojackets.apiary.attendance.network.AttendanceRepository
 import org.robojackets.apiary.base.model.Attendable
 import org.robojackets.apiary.base.model.AttendableType
@@ -191,7 +195,7 @@ class AttendanceViewModel @Inject constructor(
                         error.value = "Unable to fetch team info"
                     }.onException {
                         Timber.e(
-                            this.exception,
+                            this.throwable,
                             "Exception occurred while fetching attendable teams"
                         )
                         error.value = "Unable to fetch team info"
@@ -209,7 +213,7 @@ class AttendanceViewModel @Inject constructor(
                         error.value = "Unable to fetch event info"
                     }.onException {
                         Timber.e(
-                            this.exception,
+                            this.throwable,
                             "Exception occurred while fetching attendable events"
                         )
                         error.value = "Unable to fetch event info"
diff --git a/auth/build.gradle.kts b/auth/build.gradle.kts
index ce39cb3..0ffc5e3 100644
--- a/auth/build.gradle.kts
+++ b/auth/build.gradle.kts
@@ -2,7 +2,6 @@ plugins {
     id("com.android.library")
     kotlin("android")
     id("kotlin-android")
-//    kotlin("kapt")
     id("com.google.devtools.ksp")
     id("dagger.hilt.android.plugin")
     id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
@@ -22,7 +21,7 @@ dependencies {
     implementation(AndroidToolDependencies.timber)
 
     implementation(AndroidXDependencies.androidx_activity_compose)
-    implementation(AndroidXDependencies.androidx_lifecycle_runtime_ktx)
+    implementation(AndroidXDependencies.androidx_lifecycle_runtime)
     implementation(AndroidXDependencies.androidx_navigation_compose)
 
     implementation(AuthDependencies.appauth)
@@ -35,9 +34,6 @@ dependencies {
 
     implementation(HiltDependencies.hilt)
     ksp(HiltDependencies.hilt_android_compiler)
-//    implementation(HiltDependencies.dagger_producer)
-
-    implementation(MaterialDependencies.material_android)
 
     implementation(NetworkDependencies.moshi)
     ksp(NetworkDependencies.moshi_kotlin_codegen)
@@ -46,6 +42,8 @@ dependencies {
     implementation(NetworkDependencies.retrofit)
     implementation(NetworkDependencies.retrofuture)
     implementation(NetworkDependencies.sandwich)
+    implementation(NetworkDependencies.sandwich_retrofit)
+    implementation(NetworkDependencies.sandwich_retrofit_serialization)
 
     // Test dependencies
     androidTestImplementation(ComposeDependencies.compose_ui_test)
diff --git a/auth/src/main/java/org/robojackets/apiary/auth/ui/Authentication.kt b/auth/src/main/java/org/robojackets/apiary/auth/ui/Authentication.kt
index ed8c943..1903a52 100644
--- a/auth/src/main/java/org/robojackets/apiary/auth/ui/Authentication.kt
+++ b/auth/src/main/java/org/robojackets/apiary/auth/ui/Authentication.kt
@@ -1,10 +1,17 @@
 package org.robojackets.apiary.auth.ui
 
-import android.app.Activity.*
+import android.app.Activity.RESULT_OK
 import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.result.contract.ActivityResultContracts
 import androidx.compose.foundation.Image
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.selection.selectable
 import androidx.compose.foundation.selection.selectableGroup
 import androidx.compose.material3.AlertDialog
@@ -16,7 +23,13 @@ import androidx.compose.material3.RadioButton
 import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.material3.rememberModalBottomSheetState
-import androidx.compose.runtime.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.painterResource
@@ -28,12 +41,13 @@ import net.openid.appauth.AuthorizationResponse
 import org.robojackets.apiary.auth.R
 import org.robojackets.apiary.auth.model.AuthenticationState
 import org.robojackets.apiary.auth.model.AuthenticationViewModel
-import org.robojackets.apiary.auth.model.LoginStatus.*
+import org.robojackets.apiary.auth.model.LoginStatus.COMPLETE
+import org.robojackets.apiary.auth.model.LoginStatus.ERROR
+import org.robojackets.apiary.auth.model.LoginStatus.NOT_STARTED
 import org.robojackets.apiary.auth.oauth2.AuthManager
 import org.robojackets.apiary.base.AppEnvironment
 import org.robojackets.apiary.base.ui.util.MadeWithLove
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Suppress("LongMethod", "MagicNumber")
 @Composable
 private fun Authentication(
diff --git a/auth/src/main/java/org/robojackets/apiary/auth/ui/permissions/MissingHiddenTeamsCallout.kt b/auth/src/main/java/org/robojackets/apiary/auth/ui/permissions/MissingHiddenTeamsCallout.kt
index f8f8240..0869601 100644
--- a/auth/src/main/java/org/robojackets/apiary/auth/ui/permissions/MissingHiddenTeamsCallout.kt
+++ b/auth/src/main/java/org/robojackets/apiary/auth/ui/permissions/MissingHiddenTeamsCallout.kt
@@ -19,9 +19,10 @@ fun MissingHiddenTeamsCallout(onRefreshTeams: () -> Unit) {
         Column {
             Text(
                 "You don't have permission to view all teams, including " +
-                    "training teams. Ask in #it-helpdesk for access."
+                    "training teams. Ask in #it-helpdesk for access.",
+                Modifier.padding(top = 4.dp)
             )
-            OutlinedButton(onClick = onRefreshTeams, Modifier.padding(top = 0.dp)) {
+            OutlinedButton(onClick = onRefreshTeams, Modifier.padding(top = 4.dp)) {
                 Text("Refresh teams")
             }
         }
diff --git a/base/build.gradle.kts b/base/build.gradle.kts
index 9d23205..05a1ab6 100644
--- a/base/build.gradle.kts
+++ b/base/build.gradle.kts
@@ -2,7 +2,6 @@ plugins {
     id("com.android.library")
     kotlin("android")
     id("kotlin-android")
-//    kotlin("kapt")
     id("com.google.devtools.ksp")
     id("dagger.hilt.android.plugin")
     id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
@@ -21,18 +20,17 @@ dependencies {
     implementation(ComposeDependencies.compose_material3)
     implementation(ComposeDependencies.compose_ui)
     implementation(ComposeDependencies.compose_ui_tooling)
-    implementation(ComposeDependencies.accompanist_nav_material)
-
-    implementation(MaterialDependencies.material_android)
+    implementation(ComposeDependencies.compose_material_navigation)
 
     implementation(HiltDependencies.hilt)
     ksp(HiltDependencies.hilt_android_compiler)
-//    implementation(HiltDependencies.dagger_producer)
 
     implementation(NetworkDependencies.moshi)
     ksp(NetworkDependencies.moshi_kotlin_codegen)
     implementation(NetworkDependencies.retrofit)
     implementation(NetworkDependencies.sandwich)
+    implementation(NetworkDependencies.sandwich_retrofit)
+    implementation(NetworkDependencies.sandwich_retrofit_serialization)
 
     implementation(platform(NfcDependencies.nfc_firebase_bom))
     implementation(NfcDependencies.nfc_firebase_analytics) // Firebase BoM and Analytics (f/k/a Core) are required when including TapLinx (line below) manually
diff --git a/base/src/main/java/org/robojackets/apiary/base/ui/ActionPrompt.kt b/base/src/main/java/org/robojackets/apiary/base/ui/ActionPrompt.kt
index ccab524..4ef5d93 100644
--- a/base/src/main/java/org/robojackets/apiary/base/ui/ActionPrompt.kt
+++ b/base/src/main/java/org/robojackets/apiary/base/ui/ActionPrompt.kt
@@ -27,7 +27,7 @@ fun ActionPrompt(
     Column(
         verticalArrangement = Arrangement.SpaceBetween,
         horizontalAlignment = Alignment.CenterHorizontally,
-        modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp)
+        modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp, start = 4.dp, end = 4.dp)
     ) {
         icon()
         Text(
@@ -44,7 +44,7 @@ fun ActionPrompt(
         subtitle?.let {
             Text(
                 text = subtitle,
-                style = MaterialTheme.typography.titleMedium,
+                style = MaterialTheme.typography.bodyLarge,
                 textAlign = TextAlign.Center,
             )
         }
diff --git a/base/src/main/java/org/robojackets/apiary/base/ui/callout/Callout.kt b/base/src/main/java/org/robojackets/apiary/base/ui/callout/Callout.kt
index 433d3fe..04a7101 100644
--- a/base/src/main/java/org/robojackets/apiary/base/ui/callout/Callout.kt
+++ b/base/src/main/java/org/robojackets/apiary/base/ui/callout/Callout.kt
@@ -2,7 +2,11 @@ package org.robojackets.apiary.base.ui.callout
 
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
@@ -15,13 +19,11 @@ import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 import org.robojackets.apiary.base.ui.IconWithText
 import org.robojackets.apiary.base.ui.icons.ErrorIcon
+import org.robojackets.apiary.base.ui.theme.isLight
 import org.robojackets.apiary.base.ui.theme.warningDarkSubtle
 import org.robojackets.apiary.base.ui.theme.warningLightMuted
 import org.robojackets.apiary.base.ui.theme.warningLightSubtle
 
-// TODO: M3 upgrade
-// - Implement replacement for isLight
-
 @Composable
 fun Callout(
     title: @Composable () -> Unit,
@@ -52,7 +54,7 @@ fun WarningCallout(
     padding: PaddingValues? = null,
     body: @Composable () -> Unit,
 ) {
-    val isLightTheme = true // TODO: M3 upgrade, previously: MaterialTheme.colors.isLight
+    val isLightTheme = MaterialTheme.colorScheme.isLight()
 
     Callout(
         title = {
diff --git a/base/src/main/java/org/robojackets/apiary/base/ui/nfc/NfcRequired.kt b/base/src/main/java/org/robojackets/apiary/base/ui/nfc/NfcRequired.kt
index 1653d76..370b76c 100644
--- a/base/src/main/java/org/robojackets/apiary/base/ui/nfc/NfcRequired.kt
+++ b/base/src/main/java/org/robojackets/apiary/base/ui/nfc/NfcRequired.kt
@@ -22,9 +22,6 @@ import org.robojackets.apiary.base.ui.icons.ErrorIcon
 import org.robojackets.apiary.base.ui.theme.danger
 import org.robojackets.apiary.base.ui.theme.warningLightEmphasis
 
-// TODO: M3 upgrade
-// - Opening the app with NFC disabled causes a crash
-
 @Composable
 fun NfcRequired(nfcEnabled: Boolean, gatedComposable: @Composable () -> Unit) {
     val context = LocalContext.current
diff --git a/base/src/main/java/org/robojackets/apiary/base/ui/theme/Theme.kt b/base/src/main/java/org/robojackets/apiary/base/ui/theme/Theme.kt
index b4a30f5..4d2aae7 100644
--- a/base/src/main/java/org/robojackets/apiary/base/ui/theme/Theme.kt
+++ b/base/src/main/java/org/robojackets/apiary/base/ui/theme/Theme.kt
@@ -3,6 +3,7 @@ package org.robojackets.apiary.base.ui.theme
 import android.app.Activity
 import android.os.Build
 import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.ColorScheme
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.darkColorScheme
 import androidx.compose.material3.dynamicDarkColorScheme
@@ -10,6 +11,7 @@ import androidx.compose.material3.dynamicLightColorScheme
 import androidx.compose.material3.lightColorScheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.SideEffect
+import androidx.compose.ui.graphics.luminance
 import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalView
@@ -91,10 +93,13 @@ private val darkScheme = darkColorScheme(
     surfaceContainerHighest = surfaceContainerHighestDark,
 )
 
+// From https://stackoverflow.com/a/71594753
+@Suppress("MagicNumber")
+fun ColorScheme.isLight() = this.background.luminance() > 0.5
+
 @Composable
 fun Apiary_MobileTheme(
     darkTheme: Boolean = isSystemInDarkTheme(),
-    // Dynamic color is available on Android 12+
     dynamicColor: Boolean = false,
     content: @Composable() () -> Unit
 ) {
@@ -111,7 +116,7 @@ fun Apiary_MobileTheme(
     if (!view.isInEditMode) {
         SideEffect {
             val window = (view.context as Activity).window
-            window.statusBarColor = colorScheme.primary.toArgb() // FIXME
+            window.statusBarColor = colorScheme.primary.toArgb()
             WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
         }
     }
diff --git a/build.gradle.kts b/build.gradle.kts
index f7c2d54..cddc6b1 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,8 +1,8 @@
 buildscript {
     repositories {
         google()
-        gradlePluginPortal()
         mavenCentral()
+        gradlePluginPortal()
     }
     dependencies {
         classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0")
diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt
index 8a587f8..f366345 100644
--- a/buildSrc/src/main/java/Dependencies.kt
+++ b/buildSrc/src/main/java/Dependencies.kt
@@ -5,12 +5,11 @@ object ComposeDependencies {
         const val compose_version = "1.6.8"
         const val lifecycle_viewmodel_compose_version = "2.8.2"
         const val compose_material3_version = "1.2.1"
+        const val compose_material_navigation_version = "1.7.0-beta01"
     }
 
     const val accompanist_systemuicontroller =
         "com.google.accompanist:accompanist-systemuicontroller:${Versions.accompanist_version}"
-    const val accompanist_nav_material =
-        "com.google.accompanist:accompanist-navigation-material:${Versions.accompanist_version}"
 
     const val compose_foundation =
         "androidx.compose.foundation:foundation:${Versions.compose_version}"
@@ -21,10 +20,8 @@ object ComposeDependencies {
         "androidx.compose.material:material-icons-core:${Versions.compose_version}"
     const val compose_material_icons_extended =
         "androidx.compose.material:material-icons-extended:${Versions.compose_version}"
-
-    const val compose_runtime = "androidx.compose.runtime:runtime:${Versions.compose_version}"
-    const val compose_runtime_livedata =
-        "androidx.compose.runtime:runtime-livedata:${Versions.compose_version}"
+    const val compose_material_navigation =
+        "androidx.compose.material:material-navigation:${Versions.compose_material_navigation_version}"
 
     const val compose_settings =
         "com.github.alorma.compose-settings:ui-tiles:${Versions.compose_settings_version}"
@@ -51,7 +48,7 @@ object AndroidXDependencies {
         const val androidx_activity_compose_version = "1.9.0"
         const val androidx_appcompat_version = "1.7.0"
         const val androidx_browser_version = "1.8.0"
-        const val androidx_lifecycle_runtime_ktx_version = "2.8.2"
+        const val androidx_lifecycle_runtime_version = "2.8.2"
         const val androidx_navigation_compose_version = "2.7.7"
     }
 
@@ -60,8 +57,8 @@ object AndroidXDependencies {
     const val androidx_appcompat =
         "androidx.appcompat:appcompat:${Versions.androidx_appcompat_version}"
     const val androidx_browser = "androidx.browser:browser:${Versions.androidx_browser_version}"
-    const val androidx_lifecycle_runtime_ktx =
-        "androidx.lifecycle:lifecycle-runtime-ktx:${Versions.androidx_lifecycle_runtime_ktx_version}"
+    const val androidx_lifecycle_runtime =
+        "androidx.lifecycle:lifecycle-runtime:${Versions.androidx_lifecycle_runtime_version}"
     const val androidx_navigation_compose =
         "androidx.navigation:navigation-compose:${Versions.androidx_navigation_compose_version}"
 }
@@ -135,7 +132,7 @@ object NetworkDependencies {
         const val okhttp_bom_version = "4.12.0"
         const val retrofit_version = "2.11.0"
         const val retrofuture_version = "1.7.4"
-        const val sandwich_version = "1.3.9" // TODO: Upgrade to v2
+        const val sandwich_version = "2.0.8"
     }
 
     const val moshi = "com.squareup.moshi:moshi:${Versions.moshi_version}"
@@ -148,6 +145,10 @@ object NetworkDependencies {
     const val retrofit = "com.squareup.retrofit2:retrofit:${Versions.retrofit_version}"
     const val retrofuture = "net.sourceforge.streamsupport:android-retrofuture:${Versions.retrofuture_version}"
     const val sandwich = "com.github.skydoves:sandwich:${Versions.sandwich_version}"
+    const val sandwich_retrofit =
+        "com.github.skydoves:sandwich-retrofit:${Versions.sandwich_version}"
+    const val sandwich_retrofit_serialization =
+        "com.github.skydoves:sandwich-retrofit-serialization:${Versions.sandwich_version}"
 }
 
 object TestDependencies {
diff --git a/navigation/build.gradle.kts b/navigation/build.gradle.kts
index 0c7c10d..e466d60 100644
--- a/navigation/build.gradle.kts
+++ b/navigation/build.gradle.kts
@@ -2,7 +2,6 @@ plugins {
     id("com.android.library")
     kotlin("android")
     id("kotlin-android")
-//    kotlin("kapt")
     id("com.google.devtools.ksp")
     id("dagger.hilt.android.plugin")
     id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
@@ -24,9 +23,6 @@ dependencies {
 
     implementation(HiltDependencies.hilt)
     ksp(HiltDependencies.hilt_android_compiler)
-//    implementation(HiltDependencies.dagger_producer)
-
-    implementation(MaterialDependencies.material_android)
 
     // Test dependencies
     androidTestImplementation(TestDependencies.junit)

From b59ae30a7dda8bb63e03d59cb08ede79ebe4e001 Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Thu, 4 Jul 2024 13:54:55 -0400
Subject: [PATCH 18/33] Remove unnecessary themes.xml files

---
 attendance/src/main/res/values-night/themes.xml | 16 ----------------
 attendance/src/main/res/values/themes.xml       | 16 ----------------
 auth/src/main/res/values-night/themes.xml       | 16 ----------------
 auth/src/main/res/values/themes.xml             | 16 ----------------
 4 files changed, 64 deletions(-)
 delete mode 100644 attendance/src/main/res/values-night/themes.xml
 delete mode 100644 attendance/src/main/res/values/themes.xml
 delete mode 100644 auth/src/main/res/values-night/themes.xml
 delete mode 100644 auth/src/main/res/values/themes.xml

diff --git a/attendance/src/main/res/values-night/themes.xml b/attendance/src/main/res/values-night/themes.xml
deleted file mode 100644
index f864a73..0000000
--- a/attendance/src/main/res/values-night/themes.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<resources xmlns:tools="http://schemas.android.com/tools">
-    <!-- Base application theme. -->
-    <style name="Theme.Apiary_Mobile" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
-        <!-- Primary brand color. -->
-        <item name="colorPrimary">@color/purple_200</item>
-        <item name="colorPrimaryVariant">@color/purple_700</item>
-        <item name="colorOnPrimary">@color/black</item>
-        <!-- Secondary brand color. -->
-        <item name="colorSecondary">@color/teal_200</item>
-        <item name="colorSecondaryVariant">@color/teal_200</item>
-        <item name="colorOnSecondary">@color/black</item>
-        <!-- Status bar color. -->
-        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
-        <!-- Customize your theme here. -->
-    </style>
-</resources>
\ No newline at end of file
diff --git a/attendance/src/main/res/values/themes.xml b/attendance/src/main/res/values/themes.xml
deleted file mode 100644
index a17b26e..0000000
--- a/attendance/src/main/res/values/themes.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<resources xmlns:tools="http://schemas.android.com/tools">
-    <!-- Base application theme. -->
-    <style name="Theme.Apiary_Mobile" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
-        <!-- Primary brand color. -->
-        <item name="colorPrimary">@color/purple_500</item>
-        <item name="colorPrimaryVariant">@color/purple_700</item>
-        <item name="colorOnPrimary">@color/white</item>
-        <!-- Secondary brand color. -->
-        <item name="colorSecondary">@color/teal_200</item>
-        <item name="colorSecondaryVariant">@color/teal_700</item>
-        <item name="colorOnSecondary">@color/black</item>
-        <!-- Status bar color. -->
-        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
-        <!-- Customize your theme here. -->
-    </style>
-</resources>
\ No newline at end of file
diff --git a/auth/src/main/res/values-night/themes.xml b/auth/src/main/res/values-night/themes.xml
deleted file mode 100644
index f864a73..0000000
--- a/auth/src/main/res/values-night/themes.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<resources xmlns:tools="http://schemas.android.com/tools">
-    <!-- Base application theme. -->
-    <style name="Theme.Apiary_Mobile" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
-        <!-- Primary brand color. -->
-        <item name="colorPrimary">@color/purple_200</item>
-        <item name="colorPrimaryVariant">@color/purple_700</item>
-        <item name="colorOnPrimary">@color/black</item>
-        <!-- Secondary brand color. -->
-        <item name="colorSecondary">@color/teal_200</item>
-        <item name="colorSecondaryVariant">@color/teal_200</item>
-        <item name="colorOnSecondary">@color/black</item>
-        <!-- Status bar color. -->
-        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
-        <!-- Customize your theme here. -->
-    </style>
-</resources>
\ No newline at end of file
diff --git a/auth/src/main/res/values/themes.xml b/auth/src/main/res/values/themes.xml
deleted file mode 100644
index a17b26e..0000000
--- a/auth/src/main/res/values/themes.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<resources xmlns:tools="http://schemas.android.com/tools">
-    <!-- Base application theme. -->
-    <style name="Theme.Apiary_Mobile" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
-        <!-- Primary brand color. -->
-        <item name="colorPrimary">@color/purple_500</item>
-        <item name="colorPrimaryVariant">@color/purple_700</item>
-        <item name="colorOnPrimary">@color/white</item>
-        <!-- Secondary brand color. -->
-        <item name="colorSecondary">@color/teal_200</item>
-        <item name="colorSecondaryVariant">@color/teal_700</item>
-        <item name="colorOnSecondary">@color/black</item>
-        <!-- Status bar color. -->
-        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
-        <!-- Customize your theme here. -->
-    </style>
-</resources>
\ No newline at end of file

From 3541cbd153017faca252a0b9513e2b8d9d30a437 Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Thu, 4 Jul 2024 14:16:05 -0400
Subject: [PATCH 19/33] Remove unused dependencies

---
 buildSrc/src/main/java/Dependencies.kt | 9 ---------
 1 file changed, 9 deletions(-)

diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt
index f366345..d91b0bf 100644
--- a/buildSrc/src/main/java/Dependencies.kt
+++ b/buildSrc/src/main/java/Dependencies.kt
@@ -34,15 +34,6 @@ object ComposeDependencies {
         "androidx.lifecycle:lifecycle-viewmodel-compose:${Versions.lifecycle_viewmodel_compose_version}"
 }
 
-object MaterialDependencies {
-    object Versions {
-        const val material_android_version = "1.12.0"
-    }
-
-    const val material_android =
-        "com.google.android.material:material:${Versions.material_android_version}"
-}
-
 object AndroidXDependencies {
     object Versions {
         const val androidx_activity_compose_version = "1.9.0"

From bb3bf1fad537ddce4d58bdb4b2320f6f6682bb1c Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Thu, 4 Jul 2024 14:18:49 -0400
Subject: [PATCH 20/33] change plugin repo order to optimize builds

---
 settings.gradle.kts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/settings.gradle.kts b/settings.gradle.kts
index 826960a..e8742c2 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,8 +1,8 @@
 pluginManagement {
     repositories {
         google()
-        gradlePluginPortal()
         mavenCentral()
+        gradlePluginPortal()
     }
 }
 

From afe5c612816ea13483860dceabe171d5320caae3 Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Sun, 14 Jul 2024 14:38:15 -0400
Subject: [PATCH 21/33] Add back material dependency

---
 app/build.gradle.kts                   | 2 ++
 attendance/build.gradle.kts            | 2 ++
 auth/build.gradle.kts                  | 2 ++
 base/build.gradle.kts                  | 2 ++
 build.gradle.kts                       | 2 +-
 buildSrc/src/main/java/Dependencies.kt | 9 +++++++++
 6 files changed, 18 insertions(+), 1 deletion(-)

diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 46c0f8c..26e8d4c 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -45,6 +45,8 @@ dependencies {
     ksp(HiltDependencies.hilt_android_compiler)
     implementation(HiltDependencies.hilt_navigation_compose)
 
+    implementation(MaterialDependencies.material_android)
+
     implementation(NetworkDependencies.moshi_converter_factory)
     implementation(NetworkDependencies.okhttp)
     implementation(platform(NetworkDependencies.okhttp_bom))
diff --git a/attendance/build.gradle.kts b/attendance/build.gradle.kts
index a233cea..2a6a0c5 100644
--- a/attendance/build.gradle.kts
+++ b/attendance/build.gradle.kts
@@ -23,6 +23,8 @@ dependencies {
     implementation(HiltDependencies.hilt)
     ksp(HiltDependencies.hilt_android_compiler)
 
+    implementation(MaterialDependencies.material_android)
+
     implementation(NetworkDependencies.moshi)
     ksp(NetworkDependencies.moshi_kotlin_codegen)
     implementation(NetworkDependencies.okhttp)
diff --git a/auth/build.gradle.kts b/auth/build.gradle.kts
index 0ffc5e3..ab545e9 100644
--- a/auth/build.gradle.kts
+++ b/auth/build.gradle.kts
@@ -35,6 +35,8 @@ dependencies {
     implementation(HiltDependencies.hilt)
     ksp(HiltDependencies.hilt_android_compiler)
 
+    implementation(MaterialDependencies.material_android)
+
     implementation(NetworkDependencies.moshi)
     ksp(NetworkDependencies.moshi_kotlin_codegen)
     implementation(NetworkDependencies.okhttp)
diff --git a/base/build.gradle.kts b/base/build.gradle.kts
index 05a1ab6..81c6e58 100644
--- a/base/build.gradle.kts
+++ b/base/build.gradle.kts
@@ -25,6 +25,8 @@ dependencies {
     implementation(HiltDependencies.hilt)
     ksp(HiltDependencies.hilt_android_compiler)
 
+    implementation(MaterialDependencies.material_android)
+
     implementation(NetworkDependencies.moshi)
     ksp(NetworkDependencies.moshi_kotlin_codegen)
     implementation(NetworkDependencies.retrofit)
diff --git a/build.gradle.kts b/build.gradle.kts
index cddc6b1..4e4923e 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -6,7 +6,7 @@ buildscript {
     }
     dependencies {
         classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0")
-        classpath("com.android.tools.build:gradle:8.5.0")
+        classpath("com.android.tools.build:gradle:8.5.1")
         classpath("com.google.dagger:hilt-android-gradle-plugin:2.51.1") // This version needs to
         // match the version for other Hilt dependencies defined in Dependencies.kt
         classpath("com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1")
diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt
index d91b0bf..6ddc2fd 100644
--- a/buildSrc/src/main/java/Dependencies.kt
+++ b/buildSrc/src/main/java/Dependencies.kt
@@ -34,6 +34,15 @@ object ComposeDependencies {
         "androidx.lifecycle:lifecycle-viewmodel-compose:${Versions.lifecycle_viewmodel_compose_version}"
 }
 
+object MaterialDependencies {
+    object Versions {
+        const val material_android_version = "1.6.1"
+    }
+
+    const val material_android =
+        "com.google.android.material:material:${Versions.material_android_version}"
+}
+
 object AndroidXDependencies {
     object Versions {
         const val androidx_activity_compose_version = "1.9.0"

From 7dbde523e80c65bb3073ba57aec965fa1ad2adcd Mon Sep 17 00:00:00 2001
From: Kristaps Berzinch <kristapsberzinch@gmail.com>
Date: Sun, 14 Jul 2024 15:12:45 -0400
Subject: [PATCH 22/33] Add CodeQL analysis for Ruby

---
 .github/workflows/build.yml | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index cb7f0fc..0031e77 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -46,7 +46,7 @@ jobs:
     - name: Initialize CodeQL
       uses: github/codeql-action/init@v3
       with:
-        languages: java-kotlin
+        languages: java-kotlin,ruby
         queries: security-extended,security-and-quality
 
     - name: Grant execute permission for gradlew
@@ -85,11 +85,16 @@ jobs:
         GOOGLE_PLAY_PRIVATE_KEY: ${{ secrets.GOOGLE_PLAY_PRIVATE_KEY }}
         IS_CI: true
 
-    - name: Perform CodeQL Analysis
+    - name: Perform CodeQL Analysis for Java/Kotlin
       uses: github/codeql-action/analyze@v3
       with:
         category: "/language:java-kotlin"
 
+    - name: Perform CodeQL Analysis for Ruby
+      uses: github/codeql-action/analyze@v3
+      with:
+        category: "/language:ruby"
+
     - name: Generate and submit dependency graph
       uses: gradle/actions/dependency-submission@v3
 

From b772ac618f6705768acfa71788549f697da3c0e0 Mon Sep 17 00:00:00 2001
From: Kristaps Berzinch <kristapsberzinch@gmail.com>
Date: Sun, 14 Jul 2024 15:42:03 -0400
Subject: [PATCH 23/33] Move Ruby analysis to separate workflow

---
 .github/workflows/build.yml       |  7 +------
 .github/workflows/ruby-codeql.yml | 31 +++++++++++++++++++++++++++++++
 2 files changed, 32 insertions(+), 6 deletions(-)
 create mode 100644 .github/workflows/ruby-codeql.yml

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 0031e77..c44948e 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -46,7 +46,7 @@ jobs:
     - name: Initialize CodeQL
       uses: github/codeql-action/init@v3
       with:
-        languages: java-kotlin,ruby
+        languages: java-kotlin
         queries: security-extended,security-and-quality
 
     - name: Grant execute permission for gradlew
@@ -90,11 +90,6 @@ jobs:
       with:
         category: "/language:java-kotlin"
 
-    - name: Perform CodeQL Analysis for Ruby
-      uses: github/codeql-action/analyze@v3
-      with:
-        category: "/language:ruby"
-
     - name: Generate and submit dependency graph
       uses: gradle/actions/dependency-submission@v3
 
diff --git a/.github/workflows/ruby-codeql.yml b/.github/workflows/ruby-codeql.yml
new file mode 100644
index 0000000..d23c9b2
--- /dev/null
+++ b/.github/workflows/ruby-codeql.yml
@@ -0,0 +1,31 @@
+name: CodeQL Analysis
+
+on:
+  push:
+    branches: [ "main" ]
+  pull_request:
+    branches: [ "main" ]
+
+jobs:
+  analyze:
+
+    runs-on: ubuntu-latest
+
+    permissions:
+      security-events: write
+
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v4
+
+    # Initializes the CodeQL tools for scanning.
+    - name: Initialize CodeQL
+      uses: github/codeql-action/init@v3
+      with:
+        languages: ruby
+        build-mode: none
+
+    - name: Perform CodeQL Analysis
+      uses: github/codeql-action/analyze@v3
+      with:
+        category: "/language:ruby"

From 9638bc4c4604927d2eb0769dc78b00d1b068f896 Mon Sep 17 00:00:00 2001
From: Kristaps Berzinch <kristapsberzinch@gmail.com>
Date: Sun, 14 Jul 2024 15:59:19 -0400
Subject: [PATCH 24/33] Rename job for clarity

---
 .github/workflows/ruby-codeql.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/ruby-codeql.yml b/.github/workflows/ruby-codeql.yml
index d23c9b2..1ada5ca 100644
--- a/.github/workflows/ruby-codeql.yml
+++ b/.github/workflows/ruby-codeql.yml
@@ -7,7 +7,7 @@ on:
     branches: [ "main" ]
 
 jobs:
-  analyze:
+  ruby:
 
     runs-on: ubuntu-latest
 

From 3e5a347aace7ab8da0317e03074eb71c850118b3 Mon Sep 17 00:00:00 2001
From: Kristaps Berzinch <kristapsberzinch@gmail.com>
Date: Sun, 14 Jul 2024 16:06:06 -0400
Subject: [PATCH 25/33] Remove redundant comment from Ruby CodeQL workflow

---
 .github/workflows/ruby-codeql.yml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/.github/workflows/ruby-codeql.yml b/.github/workflows/ruby-codeql.yml
index 1ada5ca..29291cc 100644
--- a/.github/workflows/ruby-codeql.yml
+++ b/.github/workflows/ruby-codeql.yml
@@ -18,7 +18,6 @@ jobs:
     - name: Checkout repository
       uses: actions/checkout@v4
 
-    # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL
       uses: github/codeql-action/init@v3
       with:

From f732a951d989ae362401d42805276143f7b782c8 Mon Sep 17 00:00:00 2001
From: Kristaps Berzinch <kristapsberzinch@gmail.com>
Date: Sun, 14 Jul 2024 16:07:15 -0400
Subject: [PATCH 26/33] Trim trailing whitespace in README

---
 README.md | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/README.md b/README.md
index 5ac4b77..ca337f7 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ NFC functionality uses the NXP MIFARE TapLinx Android SDK.  You must provide a l
 license key from the TapLinx Developer Center on https://www.mifare.net/en/products/tools/taplinx/.
 
 Additionally, important licensing information about the TapLinx library is included in the [`libs`](libs)
-directory, including the [license](libs/LA_OPT_NXP_Software_License.txt) 
+directory, including the [license](libs/LA_OPT_NXP_Software_License.txt)
 and [Software Content Register](libs/Taplinx_Android_SDK_SCR.txt).
 
 **Note:** For RoboJackets developers, reach out in #apiary-mobile in Slack to obtain our keys.
@@ -31,7 +31,7 @@ sentryDsn=SENTRY_DSN_HERE
 There are 5 modules encompassing features and utilities.
 
 - **app** - The main application module
-  
+
 Note: To avoid circular dependencies, the below modules **must not** have the **app** module as a
 dependency.  Place such code in the `base` (or a new) module.
 
@@ -42,12 +42,12 @@ dependency.  Place such code in the `base` (or a new) module.
 
 #### Dependency management
 
-Dependency versions are managed centrally in 
+Dependency versions are managed centrally in
 [Dependencies.kt](buildSrc/src/main/java/Dependencies.kt) in the `buildSrc` module.  If you change
 a version in `Dependencies.kt`, make sure to manually sync Gradle because Android Studio might not
 recognize that the change requires a Gradle sync.
 
-After adding a dependency in `Dependencies.kt`, you must also add it to the appropriate Gradle 
+After adding a dependency in `Dependencies.kt`, you must also add it to the appropriate Gradle
 Script (take a look at a `build.gradle.kts` file for one of the modules for examples).
 
 ### Environment configuration
@@ -101,7 +101,7 @@ command does, so you can get around any errors stemming from this by aliasing th
 to `which`.
  - You need to install [GitVersion](https://github.com/GitTools/GitVersion) yourself.
 
-## Release management   
+## Release management
 
 Below are some instructions on the MyRoboJackets Android release process. Note that you will need
 additional permissions on this repo and the MyRoboJackets Android Google Play application to
@@ -120,7 +120,7 @@ file is set correctly according to the table below. Use priority 1 as the defaul
 use priority 5, post in #apiary-mobile first.
    1. Update priority affects if and how often users receive in-app update prompts to update the
 app to the latest version.
-2. 
+2.
 
 | Update priority | Description                                                                         | Examples                                                                                      |
 |-----------------|-------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------|
@@ -132,22 +132,22 @@ app to the latest version.
 to determine how to increment the version number.
    1. Go to https://github.com/RoboJackets/apiary-mobile/releases
    2. Press the **Draft a new release** button.
-   3. Decide on the new version tag; it **must** start with `v`. In general, you should increment 
-the previous release's version using [semantic versioning](https://semver.org/) guidelines (most 
+   3. Decide on the new version tag; it **must** start with `v`. In general, you should increment
+the previous release's version using [semantic versioning](https://semver.org/) guidelines (most
 releases will be a 0.1.0 (minor) or 0.0.1 (patch) increment). Press **Choose a tag**, then enter the
 new tag name to create it on publish.
    4. Leave **Release title** blank. Instead, press **Generate release notes**. The release title
 and description should automatically fill in with the changes since the last release.
       1. In general, you shouldn't need to manually set the value of the `Previous tag` field,
       unless the release notes seem incorrect.
-3. When you publish the release (which creates a new tag), a Concourse job to create a draft Google 
+3. When you publish the release (which creates a new tag), a Concourse job to create a draft Google
 Play internal test release will begin shortly.
    1. If it doesn't start, a common reason is that it wasn't alphabetically the latest tag, so the
    [`tagged-release`](https://concourse.sandbox.aws.robojackets.net/teams/information-technology/pipelines/apiary-mobile/resources/tagged-release)
    resource didn't trigger a new build. We can disable old versions of the resource to trigger a new
    build.
 4. If the Concourse [build-release job](https://concourse.sandbox.aws.robojackets.net/teams/information-technology/pipelines/apiary-mobile/jobs/build-release)
-finishes successfully, you'll find a new draft release on the Internal Test track in Google Play. 
+finishes successfully, you'll find a new draft release on the Internal Test track in Google Play.
 At this point, you should do some small QA efforts to verify the new build. Post in #apiary-mobile
 to have some people help you test. Note that access to the internal test track must be granted via
 the Google Play Console.

From 1b00a68070fee7f60c70513a5830553fcee055ff Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Wed, 17 Jul 2024 22:14:25 -0400
Subject: [PATCH 27/33] Remove fade transitions from app navigations

---
 .../java/org/robojackets/apiary/MainActivity.kt  | 16 +++++++++++++---
 .../apiary/ui/settings/SettingsViewModel.kt      |  9 +++++++--
 .../apiary/navigation/NavigationDirections.kt    |  6 ++++--
 3 files changed, 24 insertions(+), 7 deletions(-)

diff --git a/app/src/main/java/org/robojackets/apiary/MainActivity.kt b/app/src/main/java/org/robojackets/apiary/MainActivity.kt
index b638bc0..4abb8a3 100644
--- a/app/src/main/java/org/robojackets/apiary/MainActivity.kt
+++ b/app/src/main/java/org/robojackets/apiary/MainActivity.kt
@@ -6,6 +6,8 @@ import android.os.Bundle
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
 import androidx.annotation.StringRes
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material.icons.Icons
@@ -87,7 +89,7 @@ sealed class Screen(
 
     object Settings :
         Screen(
-            NavigationDestinations.settings,
+            NavigationDestinations.settingsSubgraph,
             R.string.settings,
             Icons.Filled.Settings,
             "settings"
@@ -262,6 +264,8 @@ class MainActivity : ComponentActivity() {
             navController = navController,
             startDestination = startDestination,
             modifier = modifier,
+            enterTransition = { EnterTransition.None },
+            exitTransition = { ExitTransition.None },
         ) {
             composable(NavigationDestinations.authentication) {
                 AuthenticationScreen(hiltViewModel(), authManager)
@@ -307,9 +311,15 @@ class MainActivity : ComponentActivity() {
                     )
                 }
             }
-            composable(NavigationDestinations.settings) {
-                SettingsScreen(hiltViewModel())
+            navigation(
+                startDestination = NavigationDestinations.settings,
+                route = NavigationDestinations.settingsSubgraph,
+            ) {
+                composable(NavigationDestinations.settings) {
+                    SettingsScreen(hiltViewModel())
+                }
             }
+
             composable(NavigationDestinations.requiredUpdatePrompt) {
                 RequiredUpdatePrompt()
             }
diff --git a/app/src/main/java/org/robojackets/apiary/ui/settings/SettingsViewModel.kt b/app/src/main/java/org/robojackets/apiary/ui/settings/SettingsViewModel.kt
index e6c543e..0ce8ff0 100644
--- a/app/src/main/java/org/robojackets/apiary/ui/settings/SettingsViewModel.kt
+++ b/app/src/main/java/org/robojackets/apiary/ui/settings/SettingsViewModel.kt
@@ -7,12 +7,16 @@ import androidx.browser.customtabs.CustomTabsClient
 import androidx.browser.customtabs.CustomTabsIntent
 import androidx.browser.customtabs.CustomTabsServiceConnection
 import androidx.compose.ui.graphics.toArgb
+import androidx.lifecycle.SavedStateHandle
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
 import com.skydoves.sandwich.getOrThrow
 import dagger.hilt.android.lifecycle.HiltViewModel
 import io.sentry.Sentry
-import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 import org.robojackets.apiary.auth.AuthStateManager
 import org.robojackets.apiary.auth.model.UserInfo
@@ -28,6 +32,7 @@ import io.sentry.protocol.User as SentryUser
 
 @HiltViewModel
 class SettingsViewModel @Inject constructor(
+    @Suppress("UnusedPrivateMember") private val savedStateHandle: SavedStateHandle,
     val globalSettings: GlobalSettings,
     val navigationManager: NavigationManager,
     val serverInfoRepository: ServerInfoRepository,
@@ -76,7 +81,7 @@ class SettingsViewModel @Inject constructor(
             ) {
                 flows ->
                     SettingsState(
-                    flows[0] as UserInfo?
+                    flows[0]
                 )
             }.catch { throwable -> throw throwable }
                 .collect { _state.value = it }
diff --git a/navigation/src/main/java/org/robojackets/apiary/navigation/NavigationDirections.kt b/navigation/src/main/java/org/robojackets/apiary/navigation/NavigationDirections.kt
index 700a105..dd43110 100644
--- a/navigation/src/main/java/org/robojackets/apiary/navigation/NavigationDirections.kt
+++ b/navigation/src/main/java/org/robojackets/apiary/navigation/NavigationDirections.kt
@@ -5,7 +5,8 @@ import androidx.navigation.NavOptions
 // Based on https://proandroiddev.com/how-to-make-jetpack-compose-navigation-easier-and-testable-b4b19fd5f2e4
 object NavigationDestinations {
     const val authentication = "authentication"
-    const val settings = "settings"
+    const val settingsSubgraph = "settings"
+    const val settings = "settingsMain"
     const val attendanceSubgraph = "attendance"
     const val attendableTypeSelection = "attendableTypeSelection"
     const val attendableSelection = "attendableSelection"
@@ -20,7 +21,8 @@ object NavigationActions {
         fun anyScreenToAuthentication() = object : NavigationAction {
             override val destination = NavigationDestinations.authentication
             override val navOptions = NavOptions.Builder()
-                .setPopUpTo(0, true)
+//                .setPopUpTo(NavigationDestinations.authentication, true)
+                .setLaunchSingleTop(true)
                 .build()
         }
     }

From 8427876d2277fbaa244525bc08ec7170460030e5 Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Wed, 17 Jul 2024 22:14:30 -0400
Subject: [PATCH 28/33] Update release instructions

---
 README.md | 44 +++++++++-----------------------------------
 1 file changed, 9 insertions(+), 35 deletions(-)

diff --git a/README.md b/README.md
index ca337f7..8073594 100644
--- a/README.md
+++ b/README.md
@@ -73,12 +73,6 @@ is a helpful resource.
 
 Detekt is used for linting Kotlin code.  The recommended command to run it is
 
-_(Windows)_
-```bash
-./gradlew detektAll -PdetektAutoFix=true
-```
-
-_(*nix)_
 ```bash
 ./gradlew detektAll -PdetektAutoFix=true
 ```
@@ -87,7 +81,7 @@ The detektAutoFix parameter will automatically fix simple issues.
 
 ### Fastlane
 
-We use Fastlane to automate steps of the Android release process, in combination with Concourse CI.
+We use Fastlane to automate steps of the Android release process, in combination with Github Actions.
 
 To install Fastlane, you'll need Ruby with the development kit installed.  On Windows, install
 the latest 64-bit version of Ruby+Devkit from https://rubyinstaller.org/downloads/.
@@ -115,12 +109,11 @@ App releases don't have to perfectly coincide with PRs being merged, especially
 merged in close proximity. Our Concourse pipeline has jobs to automatically handle building,
 signing, and uploading production releases of the app.
 
-1. After you've merged all PRs to be included in the release, ensure the `.update-priority`
-file is set correctly according to the table below. Use priority 1 as the default. If you want to
-use priority 5, post in #apiary-mobile first.
-   1. Update priority affects if and how often users receive in-app update prompts to update the
-app to the latest version.
-2.
+1. After you've merged all PRs to be included in the release, open the [Release to Internal Test](https://github.com/RoboJackets/apiary-mobile/actions/workflows/internal-test-release.yml)
+   Github Actions pipeline.
+2. Find the `Run workflow` button. Use the table below to enter a value for the `update_priority`. 
+   Update priority is an integer passed to Google Play, and it determines update nag behavior
+   (frequency/intensity) in the app.
 
 | Update priority | Description                                                                         | Examples                                                                                      |
 |-----------------|-------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------|
@@ -128,31 +121,12 @@ app to the latest version.
 | 3               | Optional update with prompts when the update is first available, then every 4 days. | Medium-priority bug fixes, performance improvements, new features                             |
 | 5               | Required update with immediate prompt and no option to decline.                     | Crashes/bugs impacting major features, urgent vulnerabilities                                 |
 
-2. Create a new release on `main` using a tag with a name like `v1.0.0`. Use semantic versioning
-to determine how to increment the version number.
-   1. Go to https://github.com/RoboJackets/apiary-mobile/releases
-   2. Press the **Draft a new release** button.
-   3. Decide on the new version tag; it **must** start with `v`. In general, you should increment
-the previous release's version using [semantic versioning](https://semver.org/) guidelines (most
-releases will be a 0.1.0 (minor) or 0.0.1 (patch) increment). Press **Choose a tag**, then enter the
-new tag name to create it on publish.
-   4. Leave **Release title** blank. Instead, press **Generate release notes**. The release title
-and description should automatically fill in with the changes since the last release.
-      1. In general, you shouldn't need to manually set the value of the `Previous tag` field,
-      unless the release notes seem incorrect.
-3. When you publish the release (which creates a new tag), a Concourse job to create a draft Google
-Play internal test release will begin shortly.
-   1. If it doesn't start, a common reason is that it wasn't alphabetically the latest tag, so the
-   [`tagged-release`](https://concourse.sandbox.aws.robojackets.net/teams/information-technology/pipelines/apiary-mobile/resources/tagged-release)
-   resource didn't trigger a new build. We can disable old versions of the resource to trigger a new
-   build.
-4. If the Concourse [build-release job](https://concourse.sandbox.aws.robojackets.net/teams/information-technology/pipelines/apiary-mobile/jobs/build-release)
-finishes successfully, you'll find a new draft release on the Internal Test track in Google Play.
+3. If the build runs successfully, you'll find a new draft release on the Internal Test track in Google Play.
 At this point, you should do some small QA efforts to verify the new build. Post in #apiary-mobile
 to have some people help you test. Note that access to the internal test track must be granted via
 the Google Play Console.
    1. Internal testers may need to uninstall the app to see the update if it was recently published.
-6. If no issues are found, it's time to release! Promote the build to the Production track in
+4. If no issues are found, it's time to release! Promote the build to the Production track in
 Google Play, add release notes, and save the release.
-7. Google Play typically spends a day or two reviewing the release, then makes it available. In
+5. Google Play typically spends a day or two reviewing the release, then makes it available. In
 general, expect it to take at least ~24 hours for a production release to be available to users.
\ No newline at end of file

From 6acceb052cde19570b1a22c9d0c7b9b3633fe894 Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Wed, 17 Jul 2024 22:19:34 -0400
Subject: [PATCH 29/33] Refactor repeated update state code

---
 .../apiary/ui/update/UpdateAvailable.kt       | 17 ++---------
 .../apiary/ui/update/UpdateGate.kt            | 30 ++++++++-----------
 2 files changed, 15 insertions(+), 32 deletions(-)

diff --git a/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt b/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt
index fbf2529..a1f2c0c 100644
--- a/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt
+++ b/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt
@@ -27,19 +27,12 @@ import kotlinx.coroutines.launch
 import org.robojackets.apiary.base.ui.icons.UpdateIcon
 import se.warting.inappupdate.compose.InAppUpdateState
 import se.warting.inappupdate.compose.Mode
-import se.warting.inappupdate.compose.rememberInAppUpdateState
 import timber.log.Timber
 
 @Suppress("LongMethod")
 @Composable
 fun InstallUpdateButton(onIgnoreUpdate: () -> Unit = {}) {
-    val inAppUpdateState = rememberInAppUpdateState(
-        highPrioritizeUpdates = 5,
-        mediumPrioritizeUpdates = 3,
-        promptIntervalHighPrioritizeUpdateInDays = 1,
-        promptIntervalMediumPrioritizeUpdateInDays = 4,
-        promptIntervalLowPrioritizeUpdateInDays = 8,
-    )
+    val inAppUpdateState = rememberInAppUpdateStateWithDefaults()
     var updateCanceled by remember { mutableStateOf(false) }
 
     val scope = rememberCoroutineScope()
@@ -149,13 +142,7 @@ fun RequiredUpdatePrompt() {
 fun OptionalUpdatePrompt(
     onIgnoreUpdate: () -> Unit
 ) {
-    val inAppUpdateState = rememberInAppUpdateState(
-        highPrioritizeUpdates = 5,
-        mediumPrioritizeUpdates = 3,
-        promptIntervalHighPrioritizeUpdateInDays = 1,
-        promptIntervalMediumPrioritizeUpdateInDays = 4,
-        promptIntervalLowPrioritizeUpdateInDays = 8,
-    )
+    val inAppUpdateState = rememberInAppUpdateStateWithDefaults()
     Column(
         horizontalAlignment = Alignment.CenterHorizontally,
         verticalArrangement = Arrangement.Center,
diff --git a/app/src/main/java/org/robojackets/apiary/ui/update/UpdateGate.kt b/app/src/main/java/org/robojackets/apiary/ui/update/UpdateGate.kt
index a8ed0da..9315fa9 100644
--- a/app/src/main/java/org/robojackets/apiary/ui/update/UpdateGate.kt
+++ b/app/src/main/java/org/robojackets/apiary/ui/update/UpdateGate.kt
@@ -7,6 +7,17 @@ import se.warting.inappupdate.compose.InAppUpdateState
 import se.warting.inappupdate.compose.rememberInAppUpdateState
 import timber.log.Timber
 
+@Composable
+fun rememberInAppUpdateStateWithDefaults(): InAppUpdateState {
+    return rememberInAppUpdateState(
+        highPrioritizeUpdates = 5,
+        mediumPrioritizeUpdates = 3,
+        promptIntervalHighPrioritizeUpdateInDays = 1,
+        promptIntervalMediumPrioritizeUpdateInDays = 4,
+        promptIntervalLowPrioritizeUpdateInDays = 8,
+    )
+}
+
 @Suppress("ComplexMethod", "LongMethod")
 @Composable
 fun UpdateGate(
@@ -17,15 +28,7 @@ fun UpdateGate(
     content: @Composable () -> Unit,
 ) {
     content()
-    val inAppUpdateState = rememberInAppUpdateState(
-        highPrioritizeUpdates = 5,
-        mediumPrioritizeUpdates = 3,
-        promptIntervalHighPrioritizeUpdateInDays = 1,
-        promptIntervalMediumPrioritizeUpdateInDays = 4,
-        promptIntervalLowPrioritizeUpdateInDays = 8,
-    )
-
-    when (inAppUpdateState) {
+    when (val inAppUpdateState = rememberInAppUpdateStateWithDefaults()) {
         is InAppUpdateState.DownloadedUpdate -> {
             if (inAppUpdateState.isRequiredUpdate) {
                 LaunchedEffect(navReady) {
@@ -86,14 +89,7 @@ fun UpdateGate(
 
 @Composable
 fun UpdateStatus() {
-    val inAppUpdateState = rememberInAppUpdateState(
-        highPrioritizeUpdates = 5,
-        mediumPrioritizeUpdates = 3,
-        promptIntervalHighPrioritizeUpdateInDays = 1,
-        promptIntervalMediumPrioritizeUpdateInDays = 4,
-        promptIntervalLowPrioritizeUpdateInDays = 8,
-    )
-    when (inAppUpdateState) {
+    when (val inAppUpdateState = rememberInAppUpdateStateWithDefaults()) {
         is InAppUpdateState.DownloadedUpdate -> {
             if (inAppUpdateState.isRequiredUpdate) {
                 Text("Downloaded > required")

From 04a5f9cd69d875f26b1ebf3b4d01b3bc69e98e5b Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Wed, 17 Jul 2024 23:09:01 -0400
Subject: [PATCH 30/33] Fix bug where optional update bottom sheet background
 didn't match dark theme

---
 .../apiary/ui/update/UpdateAvailable.kt       | 52 ++++++++++---------
 1 file changed, 28 insertions(+), 24 deletions(-)

diff --git a/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt b/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt
index a1f2c0c..e8afabd 100644
--- a/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt
+++ b/app/src/main/java/org/robojackets/apiary/ui/update/UpdateAvailable.kt
@@ -10,6 +10,7 @@ import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.Button
 import androidx.compose.material3.CircularProgressIndicator
 import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
 import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
@@ -143,31 +144,34 @@ fun OptionalUpdatePrompt(
     onIgnoreUpdate: () -> Unit
 ) {
     val inAppUpdateState = rememberInAppUpdateStateWithDefaults()
-    Column(
-        horizontalAlignment = Alignment.CenterHorizontally,
-        verticalArrangement = Arrangement.Center,
-        modifier = Modifier.fillMaxHeight(.5F),
-    ) {
-        UpdateIcon(
-            Modifier
-                .padding(bottom = 9.dp)
-                .size(72.dp)
-        )
-        Text("Update available", style = MaterialTheme.typography.headlineSmall)
-        Text(
-            "Install the latest version of MyRoboJackets for the latest features and " +
-                "bug fixes. It'll only take a minute.",
-            textAlign = TextAlign.Center,
-            modifier = Modifier.padding(20.dp)
-        )
-        InstallUpdateButton(onIgnoreUpdate)
-        TextButton(onClick = {
-            if (inAppUpdateState is InAppUpdateState.OptionalUpdate) {
-                inAppUpdateState.onDeclineUpdate()
+    Surface {
+        Column(
+            horizontalAlignment = Alignment.CenterHorizontally,
+            verticalArrangement = Arrangement.Center,
+            modifier = Modifier.fillMaxHeight(.5F),
+        ) {
+            UpdateIcon(
+                Modifier
+                    .padding(bottom = 9.dp)
+                    .size(72.dp)
+            )
+            Text("Update available", style = MaterialTheme.typography.headlineSmall)
+            Text(
+                "Install the latest version of MyRoboJackets for the latest features and " +
+                        "bug fixes. It'll only take a minute.",
+                textAlign = TextAlign.Center,
+                modifier = Modifier.padding(20.dp)
+            )
+            InstallUpdateButton(onIgnoreUpdate)
+            TextButton(onClick = {
+                Timber.w("DEBUG: Optional update declined. $inAppUpdateState")
+                if (inAppUpdateState is InAppUpdateState.OptionalUpdate) {
+                    inAppUpdateState.onDeclineUpdate()
+                }
+                onIgnoreUpdate()
+            }) {
+                Text("Not now")
             }
-            onIgnoreUpdate()
-        }) {
-            Text("Not now")
         }
     }
 }

From 54709679497a198177d47b9ce2a7f1b1d606e5ff Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Wed, 17 Jul 2024 23:09:14 -0400
Subject: [PATCH 31/33] Fix copy spacing

---
 .../org/robojackets/apiary/ui/update/UpdateGate.kt   | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/app/src/main/java/org/robojackets/apiary/ui/update/UpdateGate.kt b/app/src/main/java/org/robojackets/apiary/ui/update/UpdateGate.kt
index 9315fa9..aa4978e 100644
--- a/app/src/main/java/org/robojackets/apiary/ui/update/UpdateGate.kt
+++ b/app/src/main/java/org/robojackets/apiary/ui/update/UpdateGate.kt
@@ -117,18 +117,18 @@ fun UpdateStatus() {
         is InAppUpdateState.OptionalUpdate -> {
             Text(
                 "Optional update | Should prompt: ${inAppUpdateState.shouldPrompt} " +
-                    "| Priority: ${inAppUpdateState.appUpdateInfo.priority}" +
-                    "| Staleness: ${inAppUpdateState.appUpdateInfo.staleDays}" +
-                    "| Version code: ${inAppUpdateState.appUpdateInfo.versionCode}"
+                    " | Priority: ${inAppUpdateState.appUpdateInfo.priority}" +
+                    " | Staleness: ${inAppUpdateState.appUpdateInfo.staleDays}" +
+                    " | Version code: ${inAppUpdateState.appUpdateInfo.versionCode}"
             )
         }
 
         is InAppUpdateState.RequiredUpdate -> {
             Text(
                 "Required update | Should prompt: ${inAppUpdateState.shouldPrompt} " +
-                    "| Priority: ${inAppUpdateState.appUpdateInfo.priority}" +
-                    "| Staleness: ${inAppUpdateState.appUpdateInfo.staleDays}" +
-                    "| Version code: ${inAppUpdateState.appUpdateInfo.versionCode}"
+                    " | Priority: ${inAppUpdateState.appUpdateInfo.priority}" +
+                    " | Staleness: ${inAppUpdateState.appUpdateInfo.staleDays}" +
+                    " | Version code: ${inAppUpdateState.appUpdateInfo.versionCode}"
             )
         }
 

From 08496901f33984b03ace281edc6c234701a85190 Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Wed, 17 Jul 2024 23:09:28 -0400
Subject: [PATCH 32/33] Add debug buttons in settings to test update screens

---
 .../apiary/ui/settings/Settings.kt            | 40 ++++++++++++++++++-
 .../apiary/ui/settings/SettingsViewModel.kt   | 12 ++++++
 2 files changed, 51 insertions(+), 1 deletion(-)

diff --git a/app/src/main/java/org/robojackets/apiary/ui/settings/Settings.kt b/app/src/main/java/org/robojackets/apiary/ui/settings/Settings.kt
index 9e8b3ec..7bda8ca 100644
--- a/app/src/main/java/org/robojackets/apiary/ui/settings/Settings.kt
+++ b/app/src/main/java/org/robojackets/apiary/ui/settings/Settings.kt
@@ -9,7 +9,15 @@ import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.*
+import androidx.compose.material.icons.outlined.Build
+import androidx.compose.material.icons.outlined.Feedback
+import androidx.compose.material.icons.outlined.Home
+import androidx.compose.material.icons.outlined.Info
+import androidx.compose.material.icons.outlined.Logout
+import androidx.compose.material.icons.outlined.Person
+import androidx.compose.material.icons.outlined.PrivacyTip
+import androidx.compose.material.icons.outlined.Update
+import androidx.compose.material.icons.outlined.VerifiedUser
 import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
@@ -41,6 +49,9 @@ private fun Settings(
      onOpenPrivacyPolicy: () -> Unit,
      onOpenMakeAWish: () -> Unit,
      onRefreshUser: () -> Unit,
+     onNavigateToOptionalUpdateBottomSheet: () -> Unit,
+     onNavigateToRequiredUpdatePrompt: () -> Unit,
+     onNavigateToUpdateInProgress: () -> Unit,
  ) {
     val context = LocalContext.current
 
@@ -66,6 +77,21 @@ private fun Settings(
                     subtitle = { Text(text = user?.allPermissions?.joinToString(separator = ", ") ?: "None") },
                     onClick = { onRefreshUser() }
                 )
+                SettingsMenuLink(
+                    icon = { Icon(Icons.Outlined.Update, contentDescription = "update") },
+                    title = { Text(text = "DEBUG: Open optional update bottom sheet") },
+                    onClick = { onNavigateToOptionalUpdateBottomSheet() }
+                )
+                SettingsMenuLink(
+                    icon = { Icon(Icons.Outlined.Update, contentDescription = "update") },
+                    title = { Text(text = "DEBUG: Open required update prompt") },
+                    onClick = { onNavigateToRequiredUpdatePrompt() }
+                )
+                SettingsMenuLink(
+                    icon = { Icon(Icons.Outlined.Update, contentDescription = "update") },
+                    title = { Text(text = "DEBUG: Open update in progress screen") },
+                    onClick = { onNavigateToUpdateInProgress() }
+                )
             }
             SettingsMenuLink(
                 icon = { Icon(Icons.Outlined.Logout, contentDescription = "logout") },
@@ -165,6 +191,15 @@ fun SettingsScreen(
            },
            onRefreshUser = {
                viewModel.getUser(forceRefresh = true)
+           },
+           onNavigateToOptionalUpdateBottomSheet = {
+               viewModel.navigateToOptionalUpdateBottomSheet()
+           },
+           onNavigateToRequiredUpdatePrompt = {
+               viewModel.navigateToRequiredUpdatePrompt()
+           },
+           onNavigateToUpdateInProgress = {
+               viewModel.navigateToUpdateInProgress()
            }
        )
     }
@@ -181,5 +216,8 @@ private fun SettingsPreview() {
         onOpenPrivacyPolicy = {},
         onOpenMakeAWish = {},
         onRefreshUser = {},
+        onNavigateToOptionalUpdateBottomSheet = {},
+        onNavigateToRequiredUpdatePrompt = {},
+        onNavigateToUpdateInProgress = {},
     )
 }
diff --git a/app/src/main/java/org/robojackets/apiary/ui/settings/SettingsViewModel.kt b/app/src/main/java/org/robojackets/apiary/ui/settings/SettingsViewModel.kt
index 0ce8ff0..d3202ea 100644
--- a/app/src/main/java/org/robojackets/apiary/ui/settings/SettingsViewModel.kt
+++ b/app/src/main/java/org/robojackets/apiary/ui/settings/SettingsViewModel.kt
@@ -98,6 +98,18 @@ class SettingsViewModel @Inject constructor(
         navigateToLogin()
     }
 
+    fun navigateToOptionalUpdateBottomSheet() {
+        navigationManager.navigate(NavigationActions.UpdatePrompts.anyScreenToOptionalUpdatePrompt())
+    }
+
+    fun navigateToRequiredUpdatePrompt() {
+        navigationManager.navigate(NavigationActions.UpdatePrompts.anyScreenToRequiredUpdatePrompt())
+    }
+
+    fun navigateToUpdateInProgress() {
+        navigationManager.navigate(NavigationActions.UpdatePrompts.anyScreenToUpdateInProgress())
+    }
+
     fun getCustomTabsIntent(toolbarColor: Int = webNavBarBackground.toArgb()): CustomTabsIntent {
         val customTabsBuilder = CustomTabsIntent.Builder()
 

From ed920eb423bbd5a8db1057c52723e065f725aa27 Mon Sep 17 00:00:00 2001
From: Evan Strat <evan10s@users.noreply.github.com>
Date: Wed, 17 Jul 2024 23:09:49 -0400
Subject: [PATCH 33/33] revert version code change

---
 app/build.gradle.kts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 26e8d4c..a3dcadf 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -79,7 +79,7 @@ android {
         applicationId = "org.robojackets.apiary"
         minSdk = 21
         targetSdk = 35
-        versionCode = 21
+        versionCode = 12
         versionName = "1.0.0"
         vectorDrawables {
             useSupportLibrary = true