Skip to content

Commit c9de592

Browse files
committed
Migration to Nav3
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
1 parent 9169430 commit c9de592

12 files changed

Lines changed: 215 additions & 162 deletions

app/build.gradle.kts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,19 @@ plugins {
77
alias(libs.plugins.hilt)
88
alias(libs.plugins.kapt)
99
alias(libs.plugins.kotlin)
10+
alias(libs.plugins.kotlinx.serialization)
1011
alias(libs.plugins.ksp)
1112
}
1213

1314
android {
14-
compileSdk = 35
15+
compileSdk = 36
1516

1617
namespace = "at.bitfire.icsdroid"
1718

1819
defaultConfig {
1920
applicationId = "at.bitfire.icsdroid"
2021
minSdk = 23
21-
targetSdk = 35
22+
targetSdk = 36
2223

2324
versionCode = 88
2425
versionName = "2.3.1"
@@ -136,7 +137,10 @@ dependencies {
136137
implementation(libs.androidx.datastore)
137138
implementation(libs.androidx.lifecycle.viewmodel)
138139
implementation(libs.androidx.lifecycle.viewmodel.compose)
140+
implementation(libs.androidx.lifecycle.viewmodel.navigation3)
139141
implementation(libs.androidx.lifecycle.runtime.compose)
142+
implementation(libs.androidx.navigation3.runtime)
143+
implementation(libs.androidx.navigation3.ui)
140144
implementation(libs.androidx.work.runtime)
141145

142146
// Jetpack Compose
@@ -157,6 +161,8 @@ dependencies {
157161
implementation(libs.room.base)
158162
ksp(libs.room.compiler)
159163

164+
implementation(libs.kotlinx.serialization.core)
165+
160166
// for tests
161167
androidTestImplementation(libs.androidx.test.junit)
162168
androidTestImplementation(libs.androidx.test.rules)

app/src/main/AndroidManifest.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
</service>
8181

8282
<activity
83-
android:name=".ui.views.SubscriptionListActivity"
83+
android:name=".MainActivity"
8484
android:exported="true">
8585
<intent-filter>
8686
<action android:name="android.intent.action.MAIN" />
@@ -89,7 +89,7 @@
8989
</activity>
9090
<activity
9191
android:name=".ui.views.AddSubscriptionActivity"
92-
android:parentActivityName=".ui.views.SubscriptionListActivity"
92+
android:parentActivityName=".MainActivity"
9393
android:exported="true"
9494
android:windowSoftInputMode="adjustResize"
9595
tools:ignore="UnusedAttribute">
@@ -122,12 +122,12 @@
122122
<activity
123123
android:name=".ui.views.EditSubscriptionActivity"
124124
android:label="@string/activity_edit_calendar"
125-
android:parentActivityName=".ui.views.SubscriptionListActivity"
125+
android:parentActivityName=".MainActivity"
126126
android:windowSoftInputMode="stateAlwaysHidden" />
127127
<activity
128128
android:name=".ui.InfoActivity"
129129
android:label="@string/activity_app_info"
130-
android:parentActivityName=".ui.views.SubscriptionListActivity" />
130+
android:parentActivityName=".MainActivity" />
131131

132132
</application>
133133

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package at.bitfire.icsdroid
2+
3+
import android.os.Bundle
4+
import androidx.appcompat.app.AppCompatActivity
5+
import at.bitfire.icsdroid.MainActivity.Companion.EXTRA_ERROR_MESSAGE
6+
import at.bitfire.icsdroid.MainActivity.Companion.EXTRA_THROWABLE
7+
import at.bitfire.icsdroid.ui.nav.MainApp
8+
import at.bitfire.icsdroid.ui.theme.setContentThemed
9+
import dagger.hilt.android.AndroidEntryPoint
10+
11+
@AndroidEntryPoint
12+
class MainActivity : AppCompatActivity() {
13+
companion object {
14+
/**
15+
* Set this extra to request calendar permission when the activity starts.
16+
*/
17+
const val EXTRA_REQUEST_CALENDAR_PERMISSION = "permission"
18+
19+
const val PRIVACY_POLICY_URL = "https://icsx5.bitfire.at/privacy/"
20+
21+
/**
22+
* If set, an alert dialog will be displayed with the message of this error.
23+
* May be set together with [EXTRA_THROWABLE] to display a stack trace.
24+
*/
25+
const val EXTRA_ERROR_MESSAGE = "errorMessage"
26+
27+
/**
28+
* If set, an alert dialog will be displayed with the stack trace of this error.
29+
* If set, [EXTRA_ERROR_MESSAGE] must also be set, otherwise does nothing.
30+
*/
31+
const val EXTRA_THROWABLE = "errorThrowable"
32+
}
33+
34+
override fun onCreate(savedInstanceState: Bundle?) {
35+
super.onCreate(savedInstanceState)
36+
37+
setContentThemed {
38+
MainApp(savedInstanceState)
39+
}
40+
}
41+
}

app/src/main/java/at/bitfire/icsdroid/PermissionUtils.kt

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ import android.content.pm.PackageManager
1010
import android.os.Build
1111
import android.util.Log
1212
import android.widget.Toast
13+
import androidx.activity.compose.rememberLauncherForActivityResult
1314
import androidx.activity.result.contract.ActivityResultContracts
1415
import androidx.annotation.StringRes
15-
import androidx.appcompat.app.AppCompatActivity
16+
import androidx.compose.runtime.Composable
17+
import androidx.compose.ui.platform.LocalContext
1618
import androidx.core.content.ContextCompat
19+
import at.bitfire.icsdroid.PermissionUtils.CALENDAR_PERMISSIONS
1720

1821
object PermissionUtils {
1922

@@ -52,45 +55,44 @@ object PermissionUtils {
5255
* When all requested permissions are granted, [onGranted] is called.
5356
* When not all requested permissions are granted, a toast is shown.
5457
*
55-
* @param activity The activity where to register the request launcher.
5658
* @param permissions The permissions to be requested.
5759
* @param toastMessage The message to show in a toast if at least one permissions was not granted.
5860
* @param onGranted What to call when all permissions were granted.
5961
*
6062
* @return The request launcher for launching the request.
6163
*/
62-
private fun registerPermissionRequest(
63-
activity: AppCompatActivity,
64+
@Composable
65+
private fun rememberPermissionRequest(
6466
permissions: Array<String>,
6567
@StringRes toastMessage: Int,
6668
onGranted: () -> Unit = {},
6769
): (() -> Unit) {
68-
val request = activity.registerForActivityResult(
70+
val context = LocalContext.current
71+
val launcher = rememberLauncherForActivityResult(
6972
ActivityResultContracts.RequestMultiplePermissions()
7073
) { permissionsResult ->
7174
Log.i(Constants.TAG, "Requested permissions: ${permissions.asList()}, got permissions: $permissionsResult")
7275
if (permissions.all { requestedPermission -> permissionsResult.getOrDefault(requestedPermission, null) == true })
73-
// all permissions granted
76+
// all permissions granted
7477
onGranted()
7578
else {
7679
// some permissions missing
77-
Toast.makeText(activity, toastMessage, Toast.LENGTH_LONG).show()
80+
Toast.makeText(context, toastMessage, Toast.LENGTH_LONG).show()
7881
}
7982
}
80-
return { request.launch(permissions) }
83+
return { launcher.launch(permissions) }
8184
}
8285

8386
/**
8487
* Registers a calendar permission request launcher.
8588
*
86-
* @param activity activity to register permission request launcher
8789
* @param onGranted called when calendar permissions have been granted
8890
*
8991
* @return Call the returning function to launch the request
9092
*/
91-
fun registerCalendarPermissionRequest(activity: AppCompatActivity, onGranted: () -> Unit = {}) =
92-
registerPermissionRequest(
93-
activity,
93+
@Composable
94+
fun rememberCalendarPermissionRequest(onGranted: () -> Unit = {}) =
95+
rememberPermissionRequest(
9496
CALENDAR_PERMISSIONS,
9597
R.string.calendar_permissions_required,
9698
onGranted
@@ -99,15 +101,14 @@ object PermissionUtils {
99101
/**
100102
* Registers a notification permission request launcher.
101103
*
102-
* @param activity activity to register permission request launcher
103104
* @param onGranted called when calendar permissions have been granted
104105
*
105106
* @return Call the returning function to launch the request
106107
*/
107-
fun registerNotificationPermissionRequest(activity: AppCompatActivity, onGranted: () -> Unit = {}) =
108+
@Composable
109+
fun rememberNotificationPermissionRequest(onGranted: () -> Unit = {}) =
108110
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
109-
registerPermissionRequest(
110-
activity,
111+
rememberPermissionRequest(
111112
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
112113
R.string.notification_permissions_required,
113114
onGranted

app/src/main/java/at/bitfire/icsdroid/ProcessEventsTask.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import at.bitfire.icsdroid.calendar.LocalEvent
1717
import at.bitfire.icsdroid.db.AppDatabase
1818
import at.bitfire.icsdroid.db.entity.Subscription
1919
import at.bitfire.icsdroid.ui.NotificationUtils
20-
import at.bitfire.icsdroid.ui.views.SubscriptionListActivity
2120
import dagger.hilt.EntryPoint
2221
import dagger.hilt.InstallIn
2322
import dagger.hilt.android.EntryPointAccessors
@@ -268,9 +267,9 @@ class ProcessEventsTask(
268267
private fun notifyError() {
269268
val exception = exception ?: return
270269
val message = exception.localizedMessage ?: exception.message ?: exception.toString()
271-
val errorIntent = Intent(context, SubscriptionListActivity::class.java).apply {
272-
putExtra(SubscriptionListActivity.EXTRA_ERROR_MESSAGE, message)
273-
putExtra(SubscriptionListActivity.EXTRA_THROWABLE, exception)
270+
val errorIntent = Intent(context, MainActivity::class.java).apply {
271+
putExtra(MainActivity.EXTRA_ERROR_MESSAGE, message)
272+
putExtra(MainActivity.EXTRA_THROWABLE, exception)
274273
}
275274

276275
val notificationManager = NotificationUtils.createChannels(context)

app/src/main/java/at/bitfire/icsdroid/ui/NotificationUtils.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import android.content.Context
1111
import android.content.Intent
1212
import android.os.Build
1313
import androidx.core.app.NotificationCompat
14+
import at.bitfire.icsdroid.MainActivity
1415
import at.bitfire.icsdroid.R
15-
import at.bitfire.icsdroid.ui.views.SubscriptionListActivity
1616

1717
object NotificationUtils {
1818

@@ -37,8 +37,8 @@ object NotificationUtils {
3737
*/
3838
fun showCalendarPermissionNotification(context: Context) {
3939
val nm = createChannels(context)
40-
val askPermissionsIntent = Intent(context, SubscriptionListActivity::class.java).apply {
41-
putExtra(SubscriptionListActivity.EXTRA_REQUEST_CALENDAR_PERMISSION, true)
40+
val askPermissionsIntent = Intent(context, MainActivity::class.java).apply {
41+
putExtra(MainActivity.EXTRA_REQUEST_CALENDAR_PERMISSION, true)
4242
}
4343
val notification = NotificationCompat.Builder(context, CHANNEL_SYNC)
4444
.setSmallIcon(R.drawable.ic_sync_problem_white)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package at.bitfire.icsdroid.ui.nav
2+
3+
import androidx.navigation3.runtime.NavKey
4+
import kotlinx.serialization.Serializable
5+
6+
@Serializable
7+
sealed interface Destination : NavKey {
8+
object SubscriptionList : Destination
9+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package at.bitfire.icsdroid.ui.nav
2+
3+
import android.os.Bundle
4+
import androidx.compose.runtime.Composable
5+
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
6+
import androidx.navigation3.runtime.entry
7+
import androidx.navigation3.runtime.entryProvider
8+
import androidx.navigation3.runtime.rememberNavBackStack
9+
import androidx.navigation3.runtime.rememberSavedStateNavEntryDecorator
10+
import androidx.navigation3.ui.NavDisplay
11+
import androidx.navigation3.ui.rememberSceneSetupNavEntryDecorator
12+
import at.bitfire.icsdroid.ui.screen.SubscriptionsScreen
13+
14+
@Composable
15+
fun MainApp(savedInstanceState: Bundle?) {
16+
val backStack = rememberNavBackStack<Destination>(Destination.SubscriptionList)
17+
18+
NavDisplay(
19+
entryDecorators = listOf(
20+
// Add the default decorators for managing scenes and saving state
21+
rememberSceneSetupNavEntryDecorator(),
22+
rememberSavedStateNavEntryDecorator(),
23+
// Then add the view model store decorator
24+
rememberViewModelStoreNavEntryDecorator()
25+
),
26+
backStack = backStack,
27+
onBack = { backStack.removeLastOrNull() },
28+
entryProvider = entryProvider {
29+
entry(Destination.SubscriptionList) {
30+
SubscriptionsScreen(savedInstanceState)
31+
}
32+
}
33+
)
34+
}

0 commit comments

Comments
 (0)