Skip to content

Commit 22a99d2

Browse files
ArnyminerZsunkup
andauthored
Nav3 migration add subscription activity (#642)
* Migration to Nav3 Signed-off-by: Arnau Mora <arnyminerz@proton.me> * Increase SDK to 36 Signed-off-by: Arnau Mora <arnyminerz@proton.me> * Upgrade bitfire versions Signed-off-by: Arnau Mora <arnyminerz@proton.me> * Change Java Version 21 Signed-off-by: Arnau Mora <arnyminerz@proton.me> * Remove legacy view and data binding Signed-off-by: Arnau Mora <arnyminerz@proton.me> * Downgrade test rules/runner? Signed-off-by: Arnau Mora <arnyminerz@proton.me> * Force Conscrypt version Signed-off-by: Arnau Mora <arnyminerz@proton.me> * Upgrade KotlinX serialization Signed-off-by: Arnau Mora <arnyminerz@proton.me> * Fixed issues Signed-off-by: Arnau Mora <arnyminerz@proton.me> * Migrate AddSubscriptionActivity to Nav3 Signed-off-by: Arnau Mora <arnyminerz@proton.me> * Update manifest Signed-off-by: Arnau Mora <arnyminerz@proton.me> * Join `LaunchedEffect` blocks Signed-off-by: Arnau Mora <arnyminerz@proton.me> * `configureEdgeToEdge` Signed-off-by: Arnau Mora <arnyminerz@proton.me> * Reformat Signed-off-by: Arnau Mora <arnyminerz@proton.me> * Copyright headers Signed-off-by: Arnau Mora <arnyminerz@proton.me> * Cleanup * Avoid clearing backstack --------- Signed-off-by: Arnau Mora <arnyminerz@proton.me> Co-authored-by: Sunik Kupfer <kupfer@bitfire.at>
1 parent 403bec8 commit 22a99d2

8 files changed

Lines changed: 137 additions & 150 deletions

File tree

app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,18 +81,13 @@
8181

8282
<activity
8383
android:name=".MainActivity"
84-
android:exported="true">
84+
android:exported="true"
85+
android:windowSoftInputMode="adjustResize">
8586
<intent-filter>
8687
<action android:name="android.intent.action.MAIN" />
8788
<category android:name="android.intent.category.LAUNCHER" />
8889
</intent-filter>
89-
</activity>
90-
<activity
91-
android:name=".ui.views.AddSubscriptionActivity"
92-
android:parentActivityName=".MainActivity"
93-
android:exported="true"
94-
android:windowSoftInputMode="adjustResize"
95-
tools:ignore="UnusedAttribute">
90+
9691
<intent-filter>
9792
<!-- intent filter for resources with text/calendar MIME type -->
9893
<action android:name="android.intent.action.VIEW" />

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import android.content.Context
1313
import android.content.Intent
1414
import android.os.Bundle
1515
import android.os.IBinder
16-
import at.bitfire.icsdroid.ui.views.AddSubscriptionActivity
1716

1817
class AccountAuthenticatorService: Service() {
1918

@@ -31,7 +30,7 @@ class AccountAuthenticatorService: Service() {
3130
): AbstractAccountAuthenticator(context) {
3231

3332
override fun addAccount(response: AccountAuthenticatorResponse?, accountType: String, authTokenType: String?, requiredFeatures: Array<String>?, options: Bundle?): Bundle {
34-
val intent = Intent(context, AddSubscriptionActivity::class.java)
33+
val intent = Intent(context, MainActivity::class.java)
3534
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response)
3635
val bundle = Bundle(1)
3736
bundle.putParcelable(AccountManager.KEY_INTENT, intent)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class MainActivity : AppCompatActivity() {
2121
super.onCreate(savedInstanceState)
2222

2323
setContentThemed {
24-
MainApp(savedInstanceState, intent.extras)
24+
MainApp(savedInstanceState, intent.extras, ::finish)
2525
}
2626
}
2727

app/src/main/java/at/bitfire/icsdroid/ui/nav/Destination.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,18 @@
44

55
package at.bitfire.icsdroid.ui.nav
66

7+
import androidx.annotation.ColorInt
78
import androidx.navigation3.runtime.NavKey
89
import kotlinx.serialization.Serializable
910

1011
@Serializable
1112
sealed interface Destination : NavKey {
1213
@Serializable
1314
object SubscriptionList : Destination
15+
16+
@Serializable
17+
data class AddSubscription(
18+
val title: String? = null,
19+
@param:ColorInt val color: Int? = null,
20+
): Destination
1421
}

app/src/main/java/at/bitfire/icsdroid/ui/nav/MainApp.kt

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44

55
package at.bitfire.icsdroid.ui.nav
66

7+
import android.accounts.AccountManager
8+
import android.content.Intent
9+
import android.net.Uri
710
import android.os.Bundle
811
import androidx.compose.runtime.Composable
12+
import androidx.compose.runtime.LaunchedEffect
913
import androidx.compose.runtime.getValue
1014
import androidx.compose.runtime.mutableStateOf
1115
import androidx.compose.runtime.remember
@@ -23,13 +27,31 @@ import at.bitfire.icsdroid.MainActivity.Companion.EXTRA_REQUEST_CALENDAR_PERMISS
2327
import at.bitfire.icsdroid.MainActivity.Companion.EXTRA_THROWABLE
2428
import at.bitfire.icsdroid.service.ComposableStartupService
2529
import at.bitfire.icsdroid.ui.partials.AlertDialog
30+
import at.bitfire.icsdroid.ui.screen.AddSubscriptionScreen
2631
import at.bitfire.icsdroid.ui.screen.SubscriptionsScreen
2732
import java.util.ServiceLoader
2833

34+
/**
35+
* Computes the correct initial destination from some intent extras:
36+
* - If [AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE] is present -> [Destination.AddSubscription]
37+
* - Otherwise: [Destination.SubscriptionList]
38+
*/
39+
private fun calculateInitialDestination(intentExtras: Bundle?): Destination {
40+
return if (intentExtras?.containsKey(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE) == true) {
41+
// If KEY_ACCOUNT_AUTHENTICATOR_RESPONSE was given, intent was launched from authenticator,
42+
// open the add subscription screen
43+
Destination.AddSubscription()
44+
} else {
45+
// If no condition matches, show the subscriptions list
46+
Destination.SubscriptionList
47+
}
48+
}
49+
2950
@Composable
3051
fun MainApp(
3152
savedInstanceState: Bundle?,
3253
intentExtras: Bundle?,
54+
onFinish: () -> Unit,
3355
) {
3456
// If EXTRA_PERMISSION is true, request the calendar permissions
3557
val requestPermissions = intentExtras?.getBoolean(EXTRA_REQUEST_CALENDAR_PERMISSION, false) == true
@@ -53,7 +75,12 @@ fun MainApp(
5375
if (show) service.Content()
5476
}
5577

56-
val backStack = rememberNavBackStack<Destination>(Destination.SubscriptionList)
78+
val backStack = rememberNavBackStack(calculateInitialDestination(intentExtras))
79+
80+
fun goBack(depth: Int = 1) {
81+
if (backStack.size <= 1) onFinish()
82+
else repeat(depth) { backStack.removeAt(backStack.lastIndex) }
83+
}
5784

5885
NavDisplay(
5986
entryDecorators = listOf(
@@ -62,9 +89,33 @@ fun MainApp(
6289
rememberViewModelStoreNavEntryDecorator()
6390
),
6491
backStack = backStack,
92+
onBack = ::goBack,
6593
entryProvider = entryProvider {
6694
entry(Destination.SubscriptionList) {
67-
SubscriptionsScreen(requestPermissions)
95+
SubscriptionsScreen(
96+
requestPermissions,
97+
onAddRequested = { backStack.add(Destination.AddSubscription()) }
98+
)
99+
}
100+
entry<Destination.AddSubscription> { destination ->
101+
var url: String? = null
102+
LaunchedEffect(intentExtras) {
103+
if (intentExtras != null) {
104+
intentExtras.getString(Intent.EXTRA_TEXT)
105+
?.trim()
106+
?.let { url = it }
107+
BundleCompat.getParcelable(intentExtras, Intent.EXTRA_STREAM, Uri::class.java)
108+
?.toString()
109+
?.let { url = it }
110+
}
111+
}
112+
113+
AddSubscriptionScreen(
114+
title = destination.title,
115+
color = destination.color,
116+
url = url,
117+
onBackRequested = { goBack() }
118+
)
68119
}
69120
}
70121
)

app/src/main/java/at/bitfire/icsdroid/ui/screen/AddSubscriptionScreen.kt

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,22 @@
44

55
package at.bitfire.icsdroid.ui.screen
66

7+
import android.content.Intent
78
import android.net.Uri
9+
import android.provider.OpenableColumns
810
import android.util.Log
11+
import android.widget.Toast
12+
import androidx.activity.compose.rememberLauncherForActivityResult
13+
import androidx.activity.result.contract.ActivityResultContracts
914
import androidx.compose.animation.AnimatedVisibility
1015
import androidx.compose.animation.expandVertically
1116
import androidx.compose.foundation.ExperimentalFoundationApi
1217
import androidx.compose.foundation.layout.Arrangement
18+
import androidx.compose.foundation.layout.Box
1319
import androidx.compose.foundation.layout.Row
1420
import androidx.compose.foundation.layout.fillMaxSize
1521
import androidx.compose.foundation.layout.fillMaxWidth
22+
import androidx.compose.foundation.layout.imePadding
1623
import androidx.compose.foundation.layout.padding
1724
import androidx.compose.foundation.pager.HorizontalPager
1825
import androidx.compose.foundation.pager.PagerState
@@ -35,6 +42,7 @@ import androidx.compose.runtime.rememberCoroutineScope
3542
import androidx.compose.ui.Alignment
3643
import androidx.compose.ui.Modifier
3744
import androidx.compose.ui.graphics.toArgb
45+
import androidx.compose.ui.platform.LocalContext
3846
import androidx.compose.ui.res.stringResource
3947
import androidx.compose.ui.unit.dp
4048
import androidx.hilt.navigation.compose.hiltViewModel
@@ -47,6 +55,68 @@ import at.bitfire.icsdroid.ui.views.EnterUrlComposable
4755
import at.bitfire.icsdroid.ui.views.SubscriptionSettingsComposable
4856
import kotlinx.coroutines.launch
4957

58+
@Composable
59+
fun AddSubscriptionScreen(
60+
title: String?,
61+
color: Int?,
62+
url: String?,
63+
model: AddSubscriptionModel = hiltViewModel(),
64+
onBackRequested: () -> Unit
65+
) {
66+
val context = LocalContext.current
67+
val uiState = model.uiState
68+
69+
LaunchedEffect(uiState) {
70+
if (uiState.success) {
71+
// on success, show notification and close activity
72+
Toast.makeText(context, context.getString(R.string.add_calendar_created), Toast.LENGTH_LONG).show()
73+
onBackRequested()
74+
}
75+
uiState.errorMessage?.let {
76+
// on error, show error message
77+
Toast.makeText(context, it, Toast.LENGTH_LONG).show()
78+
}
79+
}
80+
81+
LaunchedEffect(Unit) {
82+
title?.let(model.subscriptionSettingsUseCase::setTitle)
83+
color?.let(model.subscriptionSettingsUseCase::setColor)
84+
url?.let {
85+
model.subscriptionSettingsUseCase.setUrl(it)
86+
model.checkUrlIntroductionPage()
87+
}
88+
}
89+
90+
val pickFile = rememberLauncherForActivityResult(
91+
ActivityResultContracts.OpenDocument()
92+
) { uri: Uri? ->
93+
if (uri != null) {
94+
// keep the picked file accessible after the first sync and reboots
95+
context.contentResolver.takePersistableUriPermission(
96+
uri,
97+
Intent.FLAG_GRANT_READ_URI_PERMISSION
98+
)
99+
model.subscriptionSettingsUseCase.setUrl(uri.toString())
100+
101+
// Get file name
102+
val displayName = context.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
103+
if (!cursor.moveToFirst()) return@use null
104+
val name = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
105+
cursor.getString(name)
106+
}
107+
model.subscriptionSettingsUseCase.setFileName(displayName)
108+
}
109+
}
110+
111+
Box(modifier = Modifier.imePadding()) {
112+
AddSubscriptionScreen(
113+
onPickFileRequested = { pickFile.launch(arrayOf("text/calendar")) },
114+
finish = onBackRequested,
115+
checkUrlIntroductionPage = model::checkUrlIntroductionPage
116+
)
117+
}
118+
}
119+
50120
@Composable
51121
fun AddSubscriptionScreen(
52122
onPickFileRequested: () -> Unit,

app/src/main/java/at/bitfire/icsdroid/ui/screen/SubscriptionsScreen.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,12 @@ import at.bitfire.icsdroid.ui.partials.CalendarListItem
6363
import at.bitfire.icsdroid.ui.partials.ExtendedTopAppBar
6464
import at.bitfire.icsdroid.ui.partials.GenericAlertDialog
6565
import at.bitfire.icsdroid.ui.partials.SyncIntervalDialog
66-
import at.bitfire.icsdroid.ui.views.AddSubscriptionActivity
6766
import at.bitfire.icsdroid.ui.views.EditSubscriptionActivity
6867

6968
@Composable
7069
fun SubscriptionsScreen(
7170
requestPermissions: Boolean,
71+
onAddRequested: () -> Unit,
7272
model: SubscriptionsModel = hiltViewModel()
7373
) {
7474
val activity = LocalActivity.current
@@ -99,9 +99,7 @@ fun SubscriptionsScreen(
9999
onAboutRequested = {
100100
activity?.startActivity(Intent(context, InfoActivity::class.java))
101101
},
102-
onAddRequested = {
103-
activity?.startActivity(Intent(context, AddSubscriptionActivity::class.java))
104-
},
102+
onAddRequested = onAddRequested,
105103
onRequestCalendarPermissions = requestCalendarPermissions,
106104
onRequestNotificationPermission = requestNotificationPermission,
107105
onItemSelected = { subscription ->

0 commit comments

Comments
 (0)