diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
index b61de7b..f275581 100644
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android.yml
@@ -58,8 +58,16 @@ jobs:
:app:checkDebugAarMetadata :app:assembleDebug :app:assembleDebugAndroidTest
:app:lintDebug :app:testDebugUnitTest
- api27:
- name: API 27 package smoke
+ devices:
+ name: API ${{ matrix.api-level }} legacy compatibility
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - api-level: 27
+ target: default
+ - api-level: 36
+ target: google_apis
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
@@ -99,13 +107,22 @@ jobs:
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- - name: Package instrumentation on clean API 27
+ - name: Legacy instrumentation on clean emulator
uses: ReactiveCircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # v2
with:
- api-level: 27
- target: default
+ api-level: ${{ matrix.api-level }}
+ target: ${{ matrix.target }}
arch: x86_64
- avd-name: foundation-api27
+ avd-name: foundation-api${{ matrix.api-level }}
force-avd-creation: true
emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -wipe-data
- script: ./gradlew --no-daemon --stacktrace :app:connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.example.acetimetracker.ExampleInstrumentedTest
+ script: ./gradlew --no-daemon --stacktrace :app:connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.example.acetimetracker.ExampleInstrumentedTest,com.example.acetimetracker.LegacyWindowInsetsTest,com.example.acetimetracker.MainActivityBackTest,com.example.acetimetracker.LegacyWindowRuntimeTest
+
+ - name: Retain device reports
+ if: always()
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
+ with:
+ name: legacy-api${{ matrix.api-level }}
+ path: |
+ app/build/reports/androidTests/connected/
+ app/build/outputs/androidTest-results/connected/
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 18dc479..d8fa3a9 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -13,8 +13,7 @@ android {
defaultConfig {
applicationId = "com.example.acetimetracker"
minSdk = 27
- // Task 2 owns the target-36 system-bar and Back compatibility change.
- targetSdk = 34
+ targetSdk = 36
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
diff --git a/app/src/androidTest/java/com/example/acetimetracker/LegacyWindowInsetsTest.kt b/app/src/androidTest/java/com/example/acetimetracker/LegacyWindowInsetsTest.kt
new file mode 100644
index 0000000..16989e0
--- /dev/null
+++ b/app/src/androidTest/java/com/example/acetimetracker/LegacyWindowInsetsTest.kt
@@ -0,0 +1,111 @@
+package com.example.acetimetracker
+
+import android.graphics.Rect
+import android.os.Build
+import android.widget.FrameLayout
+import androidx.core.graphics.Insets
+import androidx.core.view.DisplayCutoutCompat
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import com.example.acetimetracker.core.ui.installLegacyWindowInsets
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class LegacyWindowInsetsTest {
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+
+ // Removing the listener, adding to current padding, summing IME and bars,
+ // or consuming unrelated inset types must break these real View assertions.
+ @Test
+ @SdkSuppress(minSdkVersion = 30)
+ fun appliesSystemBarsAndCutoutOnce() = withContent { view ->
+ ViewCompat.dispatchApplyWindowInsets(view, fixture())
+ assertPadding(view, 33, 29, 7, 27)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 30)
+ fun imeUsesMaximumBottomInsetAndResets() = withContent { view ->
+ ViewCompat.dispatchApplyWindowInsets(view, fixture(imeBottom = 250))
+ assertEquals(261, view.paddingBottom)
+ ViewCompat.dispatchApplyWindowInsets(view, fixture())
+ assertEquals(27, view.paddingBottom)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 30)
+ fun redispatchAndReinstallDoNotAccumulate() = withContent { view ->
+ ViewCompat.dispatchApplyWindowInsets(view, fixture())
+ ViewCompat.dispatchApplyWindowInsets(view, fixture())
+ installLegacyWindowInsets(view)
+ ViewCompat.dispatchApplyWindowInsets(view, fixture())
+ assertPadding(view, 33, 29, 7, 27)
+ }
+
+ @Test
+ fun hiddenBarsRestoreInitialPadding() = withContent { view ->
+ ViewCompat.dispatchApplyWindowInsets(view, if (Build.VERSION.SDK_INT >= 30) fixture(250) else bars())
+ ViewCompat.dispatchApplyWindowInsets(view, WindowInsetsCompat.Builder().build())
+ assertPadding(view, 3, 5, 7, 11)
+ }
+
+ @Test
+ fun systemBarsRedispatchAndReinstallDoNotAccumulate() = withContent { view ->
+ ViewCompat.dispatchApplyWindowInsets(view, bars())
+ assertPadding(view, 3, 29, 7, 27)
+ installLegacyWindowInsets(view)
+ ViewCompat.dispatchApplyWindowInsets(view, bars())
+ ViewCompat.dispatchApplyWindowInsets(view, bars())
+ assertPadding(view, 3, 29, 7, 27)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 30)
+ fun forwardsUnhandledInsetsToChildren() = withContent { view ->
+ val child = FrameLayout(view.context)
+ view.addView(child)
+ var delivered: WindowInsetsCompat? = null
+ ViewCompat.setOnApplyWindowInsetsListener(child) { _, insets ->
+ delivered = insets
+ insets
+ }
+ val input = WindowInsetsCompat.Builder(fixture(imeBottom = 250))
+ .setInsets(WindowInsetsCompat.Type.systemGestures(), Insets.of(12, 0, 13, 16))
+ .build()
+ ViewCompat.dispatchApplyWindowInsets(view, input)
+ assertEquals(Insets.NONE, delivered!!.getInsets(WindowInsetsCompat.Type.systemBars()))
+ assertEquals(Insets.NONE, delivered!!.getInsets(WindowInsetsCompat.Type.displayCutout()))
+ assertEquals(Insets.NONE, delivered!!.getInsets(WindowInsetsCompat.Type.ime()))
+ assertEquals(Insets.of(12, 0, 13, 16), delivered!!.getInsets(WindowInsetsCompat.Type.systemGestures()))
+ }
+
+ private fun withContent(block: (FrameLayout) -> Unit) {
+ instrumentation.runOnMainSync {
+ val view = FrameLayout(instrumentation.targetContext)
+ view.setPadding(3, 5, 7, 11)
+ installLegacyWindowInsets(view)
+ block(view)
+ }
+ }
+
+ private fun bars() = WindowInsetsCompat.Builder()
+ .setInsets(WindowInsetsCompat.Type.systemBars(), Insets.of(0, 24, 0, 16))
+ .build()
+
+ private fun fixture(imeBottom: Int = 0) = WindowInsetsCompat.Builder(bars())
+ .setDisplayCutout(DisplayCutoutCompat(Rect(30, 0, 0, 0), emptyList()))
+ .setInsets(WindowInsetsCompat.Type.ime(), Insets.of(0, 0, 0, imeBottom))
+ .build()
+
+ private fun assertPadding(view: FrameLayout, left: Int, top: Int, right: Int, bottom: Int) {
+ assertEquals(left, view.paddingLeft)
+ assertEquals(top, view.paddingTop)
+ assertEquals(right, view.paddingRight)
+ assertEquals(bottom, view.paddingBottom)
+ }
+}
diff --git a/app/src/androidTest/java/com/example/acetimetracker/LegacyWindowRuntimeTest.kt b/app/src/androidTest/java/com/example/acetimetracker/LegacyWindowRuntimeTest.kt
new file mode 100644
index 0000000..e5be823
--- /dev/null
+++ b/app/src/androidTest/java/com/example/acetimetracker/LegacyWindowRuntimeTest.kt
@@ -0,0 +1,172 @@
+package com.example.acetimetracker
+
+import android.os.SystemClock
+import android.util.Log
+import android.view.ViewGroup
+import android.widget.EditText
+import android.view.inputmethod.InputMethodManager
+import androidx.core.view.GravityCompat
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.appcompat.app.AppCompatActivity
+import androidx.drawerlayout.widget.DrawerLayout
+import androidx.lifecycle.Lifecycle
+import androidx.test.core.app.ActivityScenario
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.Espresso.closeSoftKeyboard
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Device coverage supplements synthetic fixtures, including pre-30 IME delivery. */
+@RunWith(AndroidJUnit4::class)
+class LegacyWindowRuntimeTest {
+ @Test
+ fun focusedPasswordRemainsAboveKeyboard() = verifyFocusedField(Login::class.java, R.id.etPassword)
+
+ @Test
+ fun focusedConfirmationRemainsAboveKeyboard() = verifyFocusedField(Registration::class.java, R.id.etConfirmPassword)
+
+ private fun verifyFocusedField(activityClass: Class, fieldId: Int) {
+ ActivityScenario.launch(activityClass).use { scenario ->
+ closeSoftKeyboard()
+ onView(withId(fieldId)).check(matches(isDisplayed()))
+ var windowBottom = 0
+ scenario.onActivity {
+ windowBottom = bottomOnScreen(it.window.decorView)
+ val password = it.findViewById(fieldId)
+ password.requestFocus()
+ (it.getSystemService(android.content.Context.INPUT_METHOD_SERVICE) as InputMethodManager)
+ .showSoftInput(password, InputMethodManager.SHOW_IMPLICIT)
+ }
+ await("focused password fully visible above keyboard") {
+ var ready = false
+ scenario.onActivity {
+ val password = it.findViewById(fieldId)
+ val insets = ViewCompat.getRootWindowInsets(it.window.decorView)!!
+ val ime = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
+ val bottom = bottomOnScreen(password)
+ ready = insets.isVisible(WindowInsetsCompat.Type.ime()) && password.hasFocus() &&
+ bottom <= windowBottom - ime
+ Log.i("P1Runtime", "Focused password: bottom=$bottom, keyboardTop=${windowBottom - ime}, focus=${password.hasFocus()}")
+ }
+ ready
+ }
+ }
+ }
+
+ @Test
+ fun realKeyboardShowHideRestoresContentPadding() {
+ ActivityScenario.launch(Login::class.java).use { scenario ->
+ var initialBottom = 0
+ var windowBottom = 0
+ var initialUsableBottom = 0
+ closeSoftKeyboard()
+ await("initial layout and bar insets settled") {
+ var ready = false
+ scenario.onActivity {
+ val decor = it.window.decorView
+ val content = it.findViewById(android.R.id.content)
+ val insets = ViewCompat.getRootWindowInsets(decor)
+ ready = decor.hasWindowFocus() && content.isLaidOut && insets != null &&
+ !insets.isVisible(WindowInsetsCompat.Type.ime()) &&
+ content.paddingBottom == insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom
+ if (ready) {
+ initialBottom = content.paddingBottom
+ windowBottom = bottomOnScreen(decor)
+ initialUsableBottom = bottomOnScreen(content.getChildAt(0))
+ }
+ }
+ ready
+ }
+ onView(withId(R.id.etEmail)).perform(click())
+ await("keyboard visible and content above it") {
+ var ready = false
+ scenario.onActivity {
+ val content = it.findViewById(android.R.id.content)
+ val insets = ViewCompat.getRootWindowInsets(it.window.decorView)!!
+ val ime = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
+ val usableBottom = bottomOnScreen(content.getChildAt(0))
+ ready = insets.isVisible(WindowInsetsCompat.Type.ime()) && ime > initialBottom &&
+ content.paddingBottom == ime && usableBottom == windowBottom - ime
+ Log.i("P1Runtime", "IME check: bottom=$ime, contentHeight=${content.height}, usableBottom=$usableBottom, expected=${windowBottom - ime}")
+ }
+ ready
+ }
+ systemBack()
+ await("keyboard hidden and original bar padding restored") {
+ var ready = false
+ scenario.onActivity {
+ val content = it.findViewById(android.R.id.content)
+ val insets = ViewCompat.getRootWindowInsets(it.window.decorView)!!
+ ready = !insets.isVisible(WindowInsetsCompat.Type.ime()) && content.paddingBottom == initialBottom &&
+ bottomOnScreen(content.getChildAt(0)) == initialUsableBottom
+ if (ready) Log.i("P1Runtime", "IME hidden: bottom=${content.paddingBottom}, contentHeight=${content.height}")
+ }
+ ready
+ }
+ assertEquals(Lifecycle.State.RESUMED, scenario.state)
+ }
+ }
+
+ @Test
+ fun registrationLoginLinkAndSystemBackPreserveFormNavigation() {
+ ActivityScenario.launch(Registration::class.java).use { scenario ->
+ onView(withId(R.id.etUsername)).check(matches(isDisplayed()))
+ onView(withId(R.id.tvLoginLink)).perform(click())
+ closeSoftKeyboard()
+ onView(withId(R.id.btnLogin)).check(matches(isDisplayed()))
+ systemBack()
+ await("Registration resumed after system Back transition") {
+ var ready = false
+ if (scenario.state == Lifecycle.State.RESUMED) {
+ scenario.onActivity { ready = it.window.decorView.hasWindowFocus() }
+ }
+ ready
+ }
+ onView(withId(R.id.etUsername)).check(matches(isDisplayed()))
+ }
+ }
+
+ @Test
+ fun visibleBackReturnsFromAboutToDrawerHost() {
+ ActivityScenario.launch(MainActivity::class.java).use { scenario ->
+ scenario.onActivity {
+ it.findViewById(R.id.drawer_layout).openDrawer(GravityCompat.START, false)
+ }
+ onView(withId(R.id.nav_about)).perform(click())
+ onView(withId(R.id.btnBack)).check(matches(isDisplayed())).perform(click())
+ await("drawer host resumed") { scenario.state == Lifecycle.State.RESUMED }
+ scenario.onActivity { assertTrue(it.findViewById(R.id.drawer_layout).isShown) }
+ }
+ }
+
+ private fun systemBack() {
+ InstrumentationRegistry.getInstrumentation().uiAutomation
+ .executeShellCommand("input keyevent KEYCODE_BACK").use { descriptor ->
+ java.io.FileInputStream(descriptor.fileDescriptor).use { it.readBytes() }
+ }
+ }
+
+ private fun bottomOnScreen(view: android.view.View): Int {
+ val location = IntArray(2)
+ view.getLocationOnScreen(location)
+ return location[1] + view.height
+ }
+
+ private fun await(description: String, condition: () -> Boolean) {
+ val deadline = SystemClock.uptimeMillis() + 10000
+ while (SystemClock.uptimeMillis() < deadline) {
+ if (condition()) return
+ SystemClock.sleep(50)
+ }
+ assertTrue("Timed out waiting for $description", condition())
+ }
+}
diff --git a/app/src/androidTest/java/com/example/acetimetracker/MainActivityBackTest.kt b/app/src/androidTest/java/com/example/acetimetracker/MainActivityBackTest.kt
new file mode 100644
index 0000000..9c22e85
--- /dev/null
+++ b/app/src/androidTest/java/com/example/acetimetracker/MainActivityBackTest.kt
@@ -0,0 +1,56 @@
+package com.example.acetimetracker
+
+import android.os.SystemClock
+import androidx.core.view.GravityCompat
+import androidx.drawerlayout.widget.DrawerLayout
+import androidx.lifecycle.Lifecycle
+import androidx.test.core.app.ActivityScenario
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class MainActivityBackTest {
+ @Test
+ fun backClosesDrawerBeforeFinishing() = verifyBack { scenario ->
+ scenario.onActivity { it.onBackPressedDispatcher.onBackPressed() }
+ }
+
+ @Test
+ fun systemBackClosesDrawerBeforeFinishing() = verifyBack {
+ InstrumentationRegistry.getInstrumentation().uiAutomation
+ .executeShellCommand("input keyevent KEYCODE_BACK").use { descriptor ->
+ java.io.FileInputStream(descriptor.fileDescriptor).use { it.readBytes() }
+ }
+ }
+
+ private fun verifyBack(back: (ActivityScenario) -> Unit) {
+ ActivityScenario.launch(MainActivity::class.java).use { scenario ->
+ lateinit var activity: MainActivity
+ scenario.onActivity {
+ activity = it
+ it.findViewById(R.id.drawer_layout).openDrawer(GravityCompat.START, false)
+ }
+ back(scenario)
+ val deadline = SystemClock.uptimeMillis() + 5000
+ var drawerOpen = true
+ while (drawerOpen && SystemClock.uptimeMillis() < deadline) {
+ scenario.onActivity {
+ drawerOpen = it.findViewById(R.id.drawer_layout).isDrawerOpen(GravityCompat.START)
+ assertFalse(it.isFinishing)
+ }
+ SystemClock.sleep(20)
+ }
+ assertFalse(drawerOpen)
+ back(scenario)
+ while (scenario.state != Lifecycle.State.DESTROYED && SystemClock.uptimeMillis() < deadline) {
+ SystemClock.sleep(20)
+ }
+ assertEquals(Lifecycle.State.DESTROYED, scenario.state)
+ assertFalse(activity.isChangingConfigurations)
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/acetimetracker/About.kt b/app/src/main/java/com/example/acetimetracker/About.kt
index a7896ae..bb579fb 100644
--- a/app/src/main/java/com/example/acetimetracker/About.kt
+++ b/app/src/main/java/com/example/acetimetracker/About.kt
@@ -2,16 +2,15 @@ package com.example.acetimetracker
import android.os.Bundle
import android.widget.ImageButton
-import androidx.appcompat.app.AppCompatActivity
-class About : AppCompatActivity() {
+class About : LegacyActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_about)
val btnBack = findViewById(R.id.btnBack)
btnBack.setOnClickListener {
- onBackPressed() // This will simulate the back button press
+ onBackPressedDispatcher.onBackPressed()
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/acetimetracker/Categories.kt b/app/src/main/java/com/example/acetimetracker/Categories.kt
index 9f920ae..7dd1439 100644
--- a/app/src/main/java/com/example/acetimetracker/Categories.kt
+++ b/app/src/main/java/com/example/acetimetracker/Categories.kt
@@ -6,13 +6,12 @@ import android.widget.Button
import android.widget.EditText
import android.widget.ImageButton
import android.widget.Toast
-import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FirebaseFirestore
-class Categories : AppCompatActivity() {
+class Categories : LegacyActivity() {
private lateinit var recyclerView: RecyclerView
private lateinit var categoryAdapter: CategoryAdapter
private lateinit var firestore: FirebaseFirestore
@@ -45,7 +44,7 @@ class Categories : AppCompatActivity() {
// Back button listener
btnBack.setOnClickListener {
- onBackPressed()
+ onBackPressedDispatcher.onBackPressed()
}
// Add Category button listener
diff --git a/app/src/main/java/com/example/acetimetracker/EditProfileActivity.kt b/app/src/main/java/com/example/acetimetracker/EditProfileActivity.kt
index 42831dc..78d82f2 100644
--- a/app/src/main/java/com/example/acetimetracker/EditProfileActivity.kt
+++ b/app/src/main/java/com/example/acetimetracker/EditProfileActivity.kt
@@ -12,7 +12,6 @@ import android.widget.EditText
import android.widget.ImageView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
-import androidx.appcompat.app.AppCompatActivity
import com.airbnb.lottie.LottieAnimationView
import com.airbnb.lottie.LottieDrawable
import com.bumptech.glide.Glide
@@ -23,7 +22,7 @@ import com.google.firebase.firestore.SetOptions
import com.google.firebase.storage.FirebaseStorage
import java.util.UUID
-class EditProfileActivity : AppCompatActivity() {
+class EditProfileActivity : LegacyActivity() {
private lateinit var auth: FirebaseAuth
private lateinit var firestore: FirebaseFirestore
diff --git a/app/src/main/java/com/example/acetimetracker/GraphActivity.kt b/app/src/main/java/com/example/acetimetracker/GraphActivity.kt
index faad6b7..7e2c8af 100644
--- a/app/src/main/java/com/example/acetimetracker/GraphActivity.kt
+++ b/app/src/main/java/com/example/acetimetracker/GraphActivity.kt
@@ -3,7 +3,6 @@ package com.example.acetimetracker
import android.graphics.Color
import android.os.Bundle
import android.util.Log
-import androidx.appcompat.app.AppCompatActivity
import com.github.mikephil.charting.charts.PieChart
import com.github.mikephil.charting.data.PieData
import com.github.mikephil.charting.data.PieDataSet
@@ -13,7 +12,7 @@ import com.github.mikephil.charting.utils.ColorTemplate
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FirebaseFirestore
-class GraphActivity : AppCompatActivity() {
+class GraphActivity : LegacyActivity() {
private lateinit var chart: PieChart
private lateinit var firestore: FirebaseFirestore
diff --git a/app/src/main/java/com/example/acetimetracker/LegacyActivity.kt b/app/src/main/java/com/example/acetimetracker/LegacyActivity.kt
new file mode 100644
index 0000000..c5d8f6b
--- /dev/null
+++ b/app/src/main/java/com/example/acetimetracker/LegacyActivity.kt
@@ -0,0 +1,23 @@
+package com.example.acetimetracker
+
+import android.os.Bundle
+import android.view.ViewGroup
+import android.view.WindowManager
+import androidx.activity.enableEdgeToEdge
+import androidx.appcompat.app.AppCompatActivity
+import com.example.acetimetracker.core.ui.installLegacyWindowInsets
+
+/** Temporary inset owner for XML screens. The future Compose shell owns its own insets. */
+abstract class LegacyActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+ // Required for IME inset delivery on pre-Android 11 windows.
+ window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
+ }
+
+ override fun setContentView(layoutResID: Int) {
+ super.setContentView(layoutResID)
+ installLegacyWindowInsets(findViewById(android.R.id.content))
+ }
+}
diff --git a/app/src/main/java/com/example/acetimetracker/Login.kt b/app/src/main/java/com/example/acetimetracker/Login.kt
index bf53d62..ec946f9 100644
--- a/app/src/main/java/com/example/acetimetracker/Login.kt
+++ b/app/src/main/java/com/example/acetimetracker/Login.kt
@@ -9,12 +9,11 @@ import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
-import androidx.appcompat.app.AppCompatActivity
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.auth
import com.google.firebase.Firebase
-class Login : AppCompatActivity() {
+class Login : LegacyActivity() {
private lateinit var auth: FirebaseAuth
private lateinit var etEmail: EditText
diff --git a/app/src/main/java/com/example/acetimetracker/MainActivity.kt b/app/src/main/java/com/example/acetimetracker/MainActivity.kt
index 1ab0935..d37bd6d 100644
--- a/app/src/main/java/com/example/acetimetracker/MainActivity.kt
+++ b/app/src/main/java/com/example/acetimetracker/MainActivity.kt
@@ -7,8 +7,8 @@ import android.view.MenuItem
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
+import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.ActionBarDrawerToggle
-import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.core.view.GravityCompat
import androidx.drawerlayout.widget.DrawerLayout
@@ -21,7 +21,7 @@ import com.google.android.material.navigation.NavigationView
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FirebaseFirestore
-class MainActivity : AppCompatActivity() {
+class MainActivity : LegacyActivity() {
private lateinit var drawerLayout: DrawerLayout
private lateinit var navigationView: NavigationView
@@ -40,6 +40,20 @@ class MainActivity : AppCompatActivity() {
drawerLayout = findViewById(R.id.drawer_layout)
navigationView = findViewById(R.id.nav_view)
+ onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
+ drawerLayout.closeDrawer(GravityCompat.START)
+ } else {
+ isEnabled = false
+ try {
+ onBackPressedDispatcher.onBackPressed()
+ } finally {
+ isEnabled = true
+ }
+ }
+ }
+ })
auth = FirebaseAuth.getInstance()
firestore = FirebaseFirestore.getInstance()
@@ -172,11 +186,4 @@ class MainActivity : AppCompatActivity() {
return super.onOptionsItemSelected(item)
}
- override fun onBackPressed() {
- if (drawerLayout.isDrawerOpen(navigationView)) {
- drawerLayout.closeDrawer(navigationView)
- } else {
- super.onBackPressed()
- }
- }
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/example/acetimetracker/Myprogress.kt b/app/src/main/java/com/example/acetimetracker/Myprogress.kt
index 60f121e..1dff359 100644
--- a/app/src/main/java/com/example/acetimetracker/Myprogress.kt
+++ b/app/src/main/java/com/example/acetimetracker/Myprogress.kt
@@ -10,7 +10,6 @@ import android.widget.ArrayAdapter
import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast
-import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@@ -23,7 +22,7 @@ import com.github.mikephil.charting.formatter.IndexAxisValueFormatter
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FirebaseFirestore
-class Myprogress : AppCompatActivity() {
+class Myprogress : LegacyActivity() {
private lateinit var categorySpinner: Spinner
private lateinit var recyclerView: RecyclerView
private lateinit var timesheetAdapter: TimesheetAdapter
diff --git a/app/src/main/java/com/example/acetimetracker/Registration.kt b/app/src/main/java/com/example/acetimetracker/Registration.kt
index 486e5a8..2949910 100644
--- a/app/src/main/java/com/example/acetimetracker/Registration.kt
+++ b/app/src/main/java/com/example/acetimetracker/Registration.kt
@@ -1,6 +1,5 @@
package com.example.acetimetracker
-import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
@@ -14,7 +13,7 @@ import android.text.style.UnderlineSpan
import android.widget.TextView
import com.google.firebase.auth.FirebaseAuth
-class Registration : AppCompatActivity() {
+class Registration : LegacyActivity() {
private lateinit var auth: FirebaseAuth // Declare Firebase Authentication instance
diff --git a/app/src/main/java/com/example/acetimetracker/Splash.kt b/app/src/main/java/com/example/acetimetracker/Splash.kt
index a27d491..11fed51 100644
--- a/app/src/main/java/com/example/acetimetracker/Splash.kt
+++ b/app/src/main/java/com/example/acetimetracker/Splash.kt
@@ -5,9 +5,8 @@ import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.os.Looper
-import androidx.appcompat.app.AppCompatActivity
-class Splash : AppCompatActivity() {
+class Splash : LegacyActivity() {
private val SPLASH_DELAY: Long = 4000 // 4 seconds
private lateinit var handler: Handler
diff --git a/app/src/main/java/com/example/acetimetracker/Timer.kt b/app/src/main/java/com/example/acetimetracker/Timer.kt
index 0df805f..b75acc8 100644
--- a/app/src/main/java/com/example/acetimetracker/Timer.kt
+++ b/app/src/main/java/com/example/acetimetracker/Timer.kt
@@ -14,12 +14,11 @@ import android.widget.ImageButton
import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast
-import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FirebaseFirestore
-class Timer : AppCompatActivity() {
+class Timer : LegacyActivity() {
private lateinit var timerTextView: TextView
private lateinit var startButton: Button
@@ -121,7 +120,7 @@ class Timer : AppCompatActivity() {
feedbackTextView = findViewById(R.id.feedbackTextView)
btnBack.setOnClickListener {
- onBackPressed()
+ onBackPressedDispatcher.onBackPressed()
}
fetchCategories()
diff --git a/app/src/main/java/com/example/acetimetracker/Timesheet.kt b/app/src/main/java/com/example/acetimetracker/Timesheet.kt
index 37eab2e..0376908 100644
--- a/app/src/main/java/com/example/acetimetracker/Timesheet.kt
+++ b/app/src/main/java/com/example/acetimetracker/Timesheet.kt
@@ -15,7 +15,6 @@ import android.widget.ImageView
import android.widget.Spinner
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
-import androidx.appcompat.app.AppCompatActivity
import com.bumptech.glide.Glide
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FieldValue
@@ -26,7 +25,7 @@ import java.util.Calendar
import java.util.Locale
import java.util.UUID
-class Timesheet : AppCompatActivity() {
+class Timesheet : LegacyActivity() {
private lateinit var firestore: FirebaseFirestore
private lateinit var auth: FirebaseAuth
diff --git a/app/src/main/java/com/example/acetimetracker/core/ui/LegacyWindowInsets.kt b/app/src/main/java/com/example/acetimetracker/core/ui/LegacyWindowInsets.kt
new file mode 100644
index 0000000..4c7e3ff
--- /dev/null
+++ b/app/src/main/java/com/example/acetimetracker/core/ui/LegacyWindowInsets.kt
@@ -0,0 +1,34 @@
+package com.example.acetimetracker.core.ui
+
+import android.view.ViewGroup
+import androidx.core.graphics.Insets
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import com.example.acetimetracker.R
+
+private data class InitialPadding(val left: Int, val top: Int, val right: Int, val bottom: Int)
+
+internal fun installLegacyWindowInsets(content: ViewGroup) {
+ val initial = (content.getTag(R.id.legacy_initial_padding) as? InitialPadding)
+ ?: InitialPadding(content.paddingLeft, content.paddingTop, content.paddingRight, content.paddingBottom)
+ .also { content.setTag(R.id.legacy_initial_padding, it) }
+ val barsAndCutout = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()
+ val handled = barsAndCutout or WindowInsetsCompat.Type.ime()
+ ViewCompat.setOnApplyWindowInsetsListener(content) { view, insets ->
+ val bars = insets.getInsets(barsAndCutout)
+ val ime = insets.getInsets(WindowInsetsCompat.Type.ime())
+ view.setPadding(
+ initial.left + bars.left,
+ initial.top + bars.top,
+ initial.right + bars.right,
+ initial.bottom + maxOf(bars.bottom, ime.bottom)
+ )
+ // Children retain gesture/tappable-element insets, but must not apply these twice.
+ WindowInsetsCompat.Builder(insets)
+ .setInsets(handled, Insets.NONE)
+ .setInsetsIgnoringVisibility(barsAndCutout, Insets.NONE)
+ .setDisplayCutout(null)
+ .build()
+ }
+ ViewCompat.requestApplyInsets(content)
+}
diff --git a/app/src/main/java/com/example/acetimetracker/profile.kt b/app/src/main/java/com/example/acetimetracker/profile.kt
index 73edbfa..69ca3e9 100644
--- a/app/src/main/java/com/example/acetimetracker/profile.kt
+++ b/app/src/main/java/com/example/acetimetracker/profile.kt
@@ -7,13 +7,12 @@ import android.widget.Button
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
-import androidx.appcompat.app.AppCompatActivity
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.CircleCrop
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FirebaseFirestore
-class profile : AppCompatActivity() {
+class profile : LegacyActivity() {
private lateinit var auth: FirebaseAuth
private lateinit var firestore: FirebaseFirestore
@@ -27,7 +26,7 @@ class profile : AppCompatActivity() {
val btnBack = findViewById(R.id.btnBack)
btnBack.setOnClickListener {
- onBackPressed()
+ onBackPressedDispatcher.onBackPressed()
}
val editProfileButton = findViewById