Skip to content

Commit 7bdf9c8

Browse files
authored
Merge branch 'develop' into renovate/org.maplibre.gl-android-sdk-10.x
2 parents 94d3d33 + e9c262e commit 7bdf9c8

File tree

316 files changed

+1881
-506
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

316 files changed

+1881
-506
lines changed

.github/workflows/build.yml

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ on:
99

1010
# Enrich gradle.properties for CI/CD
1111
env:
12-
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx8g -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.incremental=false -XX:+UseParallelGC
13-
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 8 --no-daemon
12+
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx7g -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.incremental=false -XX:+UseParallelGC
13+
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 8
1414

1515
jobs:
1616
debug:
@@ -41,21 +41,28 @@ jobs:
4141
uses: gradle/actions/setup-gradle@v3
4242
with:
4343
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
44-
- name: Assemble debug APK
44+
- name: Assemble debug Gplay APK
4545
if: ${{ matrix.variant == 'debug' }}
4646
env:
4747
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
4848
ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }}
4949
ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }}
50-
run: ./gradlew :app:assembleGplayDebug :app:assembleFDroidDebug -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES
51-
- name: Upload APK APKs
50+
run: ./gradlew :app:assembleGplayDebug -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES
51+
- name: Assemble debug Fdroid APK
52+
if: ${{ matrix.variant == 'debug' }}
53+
env:
54+
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
55+
ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }}
56+
ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }}
57+
run: ./gradlew app:assembleFDroidDebug -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES
58+
- name: Upload debug APKs
5259
if: ${{ matrix.variant == 'debug' }}
5360
uses: actions/upload-artifact@v4
5461
with:
5562
name: elementx-debug
5663
path: |
57-
app/build/outputs/apk/gplay/debug/*.apk
58-
app/build/outputs/apk/fdroid/debug/*.apk
64+
app/build/outputs/apk/gplay/debug/*-universal-debug.apk
65+
app/build/outputs/apk/fdroid/debug/*-universal-debug.apk
5966
- uses: rnkdsh/[email protected]
6067
id: diawi
6168
# Do not fail the whole build if Diawi upload fails

app/src/main/AndroidManifest.xml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,45 @@
7474

7575
<data android:scheme="io.element" />
7676
</intent-filter>
77+
<!--
78+
Element web links
79+
-->
80+
<intent-filter android:autoVerify="true">
81+
<action android:name="android.intent.action.VIEW" />
82+
83+
<category android:name="android.intent.category.DEFAULT" />
84+
<category android:name="android.intent.category.BROWSABLE" />
85+
86+
<data android:scheme="https" />
87+
<data android:host="*.element.io" />
88+
</intent-filter>
89+
<!--
90+
matrix.to links
91+
Note: On Android 12 and higher clicking a web link (that is not an Android App Link) always shows content in a web browser
92+
https://developer.android.com/training/app-links#web-links
93+
-->
94+
<intent-filter>
95+
<action android:name="android.intent.action.VIEW" />
96+
97+
<category android:name="android.intent.category.DEFAULT" />
98+
<category android:name="android.intent.category.BROWSABLE" />
99+
100+
<data android:scheme="https" />
101+
<data android:host="matrix.to" />
102+
</intent-filter>
103+
<!--
104+
links from matrix.to website
105+
-->
106+
<intent-filter>
107+
<action android:name="android.intent.action.VIEW" />
108+
109+
<category android:name="android.intent.category.DEFAULT" />
110+
<category android:name="android.intent.category.BROWSABLE" />
111+
112+
<data android:scheme="element" />
113+
<data android:host="user" />
114+
<data android:host="room" />
115+
</intent-filter>
77116
</activity>
78117

79118
<provider

app/src/main/kotlin/io/element/android/x/MainActivity.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import io.element.android.compound.theme.ElementTheme
3939
import io.element.android.compound.theme.Theme
4040
import io.element.android.compound.theme.isDark
4141
import io.element.android.compound.theme.mapToTheme
42+
import io.element.android.features.call.ui.ElementCallActivity
4243
import io.element.android.features.lockscreen.api.handleSecureFlag
4344
import io.element.android.features.lockscreen.api.isLocked
4445
import io.element.android.libraries.architecture.bindings
@@ -58,6 +59,13 @@ class MainActivity : NodeActivity() {
5859
Timber.tag(loggerTag.value).w("onCreate, with savedInstanceState: ${savedInstanceState != null}")
5960
installSplashScreen()
6061
super.onCreate(savedInstanceState)
62+
if (ElementCallActivity.maybeStart(this, intent)) {
63+
Timber.tag(loggerTag.value).w("Starting Element Call Activity")
64+
if (savedInstanceState == null) {
65+
finish()
66+
return
67+
}
68+
}
6169
appBindings = bindings()
6270
appBindings.lockScreenService().handleSecureFlag(this)
6371
enableEdgeToEdge()
@@ -135,11 +143,18 @@ class MainActivity : NodeActivity() {
135143
* Called when:
136144
* - the launcher icon is clicked (if the app is already running);
137145
* - a notification is clicked.
146+
* - a deep link have been clicked
138147
* - the app is going to background (<- this is strange)
139148
*/
140149
override fun onNewIntent(intent: Intent) {
141150
super.onNewIntent(intent)
142151
Timber.tag(loggerTag.value).w("onNewIntent")
152+
153+
if (ElementCallActivity.maybeStart(this, intent)) {
154+
Timber.tag(loggerTag.value).w("Starting Element Call Activity")
155+
return
156+
}
157+
143158
// If the mainNode is not init yet, keep the intent for later.
144159
// It can happen when the activity is killed by the system. The methods are called in this order :
145160
// onCreate(savedInstanceState=true) -> onNewIntent -> onResume -> onMainNodeInit

appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ import com.bumble.appyx.core.plugin.plugins
3535
import com.bumble.appyx.navmodel.backstack.BackStack
3636
import com.bumble.appyx.navmodel.backstack.operation.push
3737
import com.bumble.appyx.navmodel.backstack.operation.replace
38-
import com.bumble.appyx.navmodel.backstack.operation.singleTop
3938
import dagger.assisted.Assisted
4039
import dagger.assisted.AssistedInject
4140
import io.element.android.anvilannotations.ContributesNode
@@ -57,16 +56,20 @@ import io.element.android.features.roomdirectory.api.RoomDescription
5756
import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint
5857
import io.element.android.features.roomlist.api.RoomListEntryPoint
5958
import io.element.android.features.securebackup.api.SecureBackupEntryPoint
59+
import io.element.android.features.userprofile.api.UserProfileEntryPoint
6060
import io.element.android.libraries.architecture.BackstackView
6161
import io.element.android.libraries.architecture.BaseFlowNode
6262
import io.element.android.libraries.architecture.createNode
63+
import io.element.android.libraries.architecture.waitForNavTargetAttached
6364
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
6465
import io.element.android.libraries.di.AppScope
6566
import io.element.android.libraries.di.SessionScope
6667
import io.element.android.libraries.matrix.api.MatrixClient
68+
import io.element.android.libraries.matrix.api.core.EventId
6769
import io.element.android.libraries.matrix.api.core.MAIN_SPACE
6870
import io.element.android.libraries.matrix.api.core.RoomId
6971
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
72+
import io.element.android.libraries.matrix.api.core.UserId
7073
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
7174
import io.element.android.libraries.matrix.api.permalink.PermalinkData
7275
import io.element.android.libraries.matrix.api.sync.SyncState
@@ -91,6 +94,7 @@ class LoggedInFlowNode @AssistedInject constructor(
9194
private val createRoomEntryPoint: CreateRoomEntryPoint,
9295
private val appNavigationStateService: AppNavigationStateService,
9396
private val secureBackupEntryPoint: SecureBackupEntryPoint,
97+
private val userProfileEntryPoint: UserProfileEntryPoint,
9498
private val ftueEntryPoint: FtueEntryPoint,
9599
private val coroutineScope: CoroutineScope,
96100
private val networkMonitor: NetworkMonitor,
@@ -197,6 +201,11 @@ class LoggedInFlowNode @AssistedInject constructor(
197201
val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages()
198202
) : NavTarget
199203

204+
@Parcelize
205+
data class UserProfile(
206+
val userId: UserId,
207+
) : NavTarget
208+
200209
@Parcelize
201210
data class Settings(
202211
val initialElement: PreferencesEntryPoint.InitialTarget = PreferencesEntryPoint.InitialTarget.Root
@@ -270,14 +279,14 @@ class LoggedInFlowNode @AssistedInject constructor(
270279
}
271280

272281
override fun onForwardedToSingleRoom(roomId: RoomId) {
273-
coroutineScope.launch { attachRoom(roomId) }
282+
coroutineScope.launch { attachRoom(roomId.toRoomIdOrAlias()) }
274283
}
275284

276285
override fun onPermalinkClicked(data: PermalinkData) {
277286
when (data) {
278287
is PermalinkData.UserLink -> {
279-
// FIXME Add a user profile screen.
280-
Timber.e("User link clicked: ${data.userId}. TODO Add a user profile screen")
288+
// Should not happen (handled by MessagesNode)
289+
Timber.e("User link clicked: ${data.userId}.")
281290
}
282291
is PermalinkData.RoomLink -> {
283292
backstack.push(
@@ -306,6 +315,17 @@ class LoggedInFlowNode @AssistedInject constructor(
306315
)
307316
createNode<RoomFlowNode>(buildContext, plugins = listOf(inputs, callback))
308317
}
318+
is NavTarget.UserProfile -> {
319+
val callback = object : UserProfileEntryPoint.Callback {
320+
override fun onOpenRoom(roomId: RoomId) {
321+
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias()))
322+
}
323+
}
324+
userProfileEntryPoint.nodeBuilder(this, buildContext)
325+
.params(UserProfileEntryPoint.Params(userId = navTarget.userId))
326+
.callback(callback)
327+
.build()
328+
}
309329
is NavTarget.Settings -> {
310330
val callback = object : PreferencesEntryPoint.Callback {
311331
override fun onOpenBugReport() {
@@ -321,7 +341,7 @@ class LoggedInFlowNode @AssistedInject constructor(
321341
}
322342
}
323343
val inputs = PreferencesEntryPoint.Params(navTarget.initialElement)
324-
return preferencesEntryPoint.nodeBuilder(this, buildContext)
344+
preferencesEntryPoint.nodeBuilder(this, buildContext)
325345
.params(inputs)
326346
.callback(callback)
327347
.build()
@@ -345,11 +365,6 @@ class LoggedInFlowNode @AssistedInject constructor(
345365
}
346366
NavTarget.Ftue -> {
347367
ftueEntryPoint.nodeBuilder(this, buildContext)
348-
.callback(object : FtueEntryPoint.Callback {
349-
override fun onFtueFlowFinished() {
350-
lifecycleScope.launch { attachRoomList() }
351-
}
352-
})
353368
.build()
354369
}
355370
NavTarget.RoomDirectorySearch -> {
@@ -368,32 +383,42 @@ class LoggedInFlowNode @AssistedInject constructor(
368383
}
369384
}
370385

371-
suspend fun attachRoomList() {
372-
if (!canShowRoomList()) return
373-
attachChild<Node> {
374-
backstack.singleTop(NavTarget.RoomList)
386+
suspend fun attachRoom(roomIdOrAlias: RoomIdOrAlias, eventId: EventId? = null) {
387+
waitForNavTargetAttached { navTarget ->
388+
navTarget is NavTarget.RoomList
375389
}
376-
}
377-
378-
suspend fun attachRoom(roomId: RoomId) {
379-
if (!canShowRoomList()) return
380390
attachChild<RoomFlowNode> {
381-
backstack.singleTop(NavTarget.RoomList)
382-
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias()))
391+
backstack.push(
392+
NavTarget.Room(
393+
roomIdOrAlias = roomIdOrAlias,
394+
initialElement = RoomNavigationTarget.Messages(
395+
focusedEventId = eventId
396+
)
397+
)
398+
)
383399
}
384400
}
385401

386-
private fun canShowRoomList(): Boolean {
387-
return ftueService.state.value is FtueState.Complete
402+
suspend fun attachUser(userId: UserId) {
403+
waitForNavTargetAttached { navTarget ->
404+
navTarget is NavTarget.RoomList
405+
}
406+
attachChild<Node> {
407+
backstack.push(
408+
NavTarget.UserProfile(
409+
userId = userId,
410+
)
411+
)
412+
}
388413
}
389414

390415
@Composable
391416
override fun View(modifier: Modifier) {
392417
Box(modifier = modifier) {
393418
val lockScreenState by lockScreenStateService.lockState.collectAsState()
394-
val isFtueDisplayed by ftueService.state.collectAsState()
419+
val ftueState by ftueService.state.collectAsState()
395420
BackstackView()
396-
if (isFtueDisplayed is FtueState.Complete) {
421+
if (ftueState is FtueState.Complete) {
397422
PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LoggedInPermanent)
398423
}
399424
if (lockScreenState == LockScreenLockState.Locked) {

appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ import io.element.android.libraries.designsystem.theme.components.CircularProgre
5555
import io.element.android.libraries.di.AppScope
5656
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
5757
import io.element.android.libraries.matrix.api.core.SessionId
58+
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
59+
import io.element.android.libraries.matrix.api.permalink.PermalinkData
5860
import io.element.android.libraries.sessionstorage.api.LoggedInState
5961
import kotlinx.coroutines.flow.distinctUntilChanged
6062
import kotlinx.coroutines.flow.launchIn
@@ -279,17 +281,37 @@ class RootFlowNode @AssistedInject constructor(
279281
when (resolvedIntent) {
280282
is ResolvedIntent.Navigation -> navigateTo(resolvedIntent.deeplinkData)
281283
is ResolvedIntent.Oidc -> onOidcAction(resolvedIntent.oidcAction)
284+
is ResolvedIntent.Permalink -> navigateTo(resolvedIntent.permalinkData)
282285
}
283286
}
284287

288+
private suspend fun navigateTo(permalinkData: PermalinkData) {
289+
Timber.d("Navigating to $permalinkData")
290+
attachSession(null)
291+
.apply {
292+
when (permalinkData) {
293+
is PermalinkData.FallbackLink -> Unit
294+
is PermalinkData.RoomEmailInviteLink -> Unit
295+
is PermalinkData.RoomLink -> {
296+
attachRoom(
297+
roomIdOrAlias = permalinkData.roomIdOrAlias,
298+
eventId = permalinkData.eventId,
299+
)
300+
}
301+
is PermalinkData.UserLink -> {
302+
attachUser(permalinkData.userId)
303+
}
304+
}
305+
}
306+
}
307+
285308
private suspend fun navigateTo(deeplinkData: DeeplinkData) {
286309
Timber.d("Navigating to $deeplinkData")
287310
attachSession(deeplinkData.sessionId)
288-
.attachSession()
289311
.apply {
290312
when (deeplinkData) {
291-
is DeeplinkData.Root -> attachRoomList()
292-
is DeeplinkData.Room -> attachRoom(deeplinkData.roomId)
313+
is DeeplinkData.Root -> Unit // The room list will always be shown, observing FtueState
314+
is DeeplinkData.Room -> attachRoom(deeplinkData.roomId.toRoomIdOrAlias())
293315
}
294316
}
295317
}
@@ -298,10 +320,12 @@ class RootFlowNode @AssistedInject constructor(
298320
oidcActionFlow.post(oidcAction)
299321
}
300322

301-
private suspend fun attachSession(sessionId: SessionId): LoggedInAppScopeFlowNode {
323+
// [sessionId] will be null for permalink.
324+
private suspend fun attachSession(sessionId: SessionId?): LoggedInFlowNode {
302325
// TODO handle multi-session
303-
return waitForChildAttached { navTarget ->
304-
navTarget is NavTarget.LoggedInFlow && navTarget.sessionId == sessionId
326+
return waitForChildAttached<LoggedInAppScopeFlowNode, NavTarget> { navTarget ->
327+
navTarget is NavTarget.LoggedInFlow && (sessionId == null || navTarget.sessionId == sessionId)
305328
}
329+
.attachSession()
306330
}
307331
}

0 commit comments

Comments
 (0)