@@ -11,6 +11,7 @@ package com.nextcloud.talk.activities
1111
1212import android.app.KeyguardManager
1313import android.content.Intent
14+ import android.net.Uri
1415import android.os.Bundle
1516import android.provider.ContactsContract
1617import android.text.TextUtils
@@ -33,7 +34,6 @@ import com.nextcloud.talk.data.user.model.User
3334import com.nextcloud.talk.databinding.ActivityMainBinding
3435import com.nextcloud.talk.invitation.InvitationsActivity
3536import com.nextcloud.talk.lock.LockedActivity
36- import com.nextcloud.talk.models.json.conversations.RoomOverall
3737import com.nextcloud.talk.users.UserManager
3838import com.nextcloud.talk.utils.ApiUtils
3939import com.nextcloud.talk.utils.ClosedInterfaceImpl
@@ -42,10 +42,8 @@ import com.nextcloud.talk.utils.SecurityUtils
4242import com.nextcloud.talk.utils.ShortcutManagerHelper
4343import com.nextcloud.talk.utils.bundle.BundleKeys
4444import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
45- import io.reactivex.Observer
46- import io.reactivex.SingleObserver
4745import io.reactivex.android.schedulers.AndroidSchedulers
48- import io.reactivex.disposables.Disposable
46+ import io.reactivex.disposables.CompositeDisposable
4947import io.reactivex.schedulers.Schedulers
5048import javax.inject.Inject
5149
@@ -62,6 +60,8 @@ class MainActivity :
6260 @Inject
6361 lateinit var userManager: UserManager
6462
63+ private val disposables = CompositeDisposable ()
64+
6565 private val onBackPressedCallback = object : OnBackPressedCallback (true ) {
6666 override fun handleOnBackPressed () {
6767 finish()
@@ -93,6 +93,11 @@ class MainActivity :
9393 onBackPressedDispatcher.addCallback(this , onBackPressedCallback)
9494 }
9595
96+ override fun onDestroy () {
97+ super .onDestroy()
98+ disposables.dispose()
99+ }
100+
96101 fun lockScreenIfConditionsApply () {
97102 val keyguardManager = getSystemService(KEYGUARD_SERVICE ) as KeyguardManager
98103 if (keyguardManager.isKeyguardSecure && appPreferences.isScreenLocked) {
@@ -168,7 +173,8 @@ class MainActivity :
168173 val user = userId.substringBeforeLast(" @" )
169174 val baseUrl = userId.substringAfterLast(" @" )
170175
171- if (currentUserProviderOld.currentUser.blockingGet()?.baseUrl!! .endsWith(baseUrl) == true ) {
176+ val currentUser = currentUserProviderOld.currentUser.blockingGet()
177+ if (currentUser?.baseUrl?.endsWith(baseUrl) == true ) {
172178 startConversation(user)
173179 } else {
174180 Snackbar .make(
@@ -196,35 +202,28 @@ class MainActivity :
196202 invite = userId
197203 )
198204
199- ncApi.createRoom(
205+ val disposable = ncApi.createRoom(
200206 credentials,
201207 retrofitBucket.url,
202208 retrofitBucket.queryMap
203209 )
204210 .subscribeOn(Schedulers .io())
205211 .observeOn(AndroidSchedulers .mainThread())
206- .subscribe(object : Observer <RoomOverall > {
207- override fun onSubscribe (d : Disposable ) {
208- // unused atm
209- }
210-
211- override fun onNext (roomOverall : RoomOverall ) {
212+ .subscribe(
213+ { roomOverall ->
214+ if (isFinishing || isDestroyed) return @subscribe
212215 val bundle = Bundle ()
213216 bundle.putString(KEY_ROOM_TOKEN , roomOverall.ocs!! .data!! .token)
214217
215218 val chatIntent = Intent (context, ChatActivity ::class .java)
216219 chatIntent.putExtras(bundle)
217220 startActivity(chatIntent)
221+ },
222+ { e ->
223+ Log .e(TAG , " Error creating room" , e)
218224 }
219-
220- override fun onError (e : Throwable ) {
221- // unused atm
222- }
223-
224- override fun onComplete () {
225- // unused atm
226- }
227- })
225+ )
226+ disposables.add(disposable)
228227 }
229228
230229 override fun onNewIntent (intent : Intent ) {
@@ -234,7 +233,7 @@ class MainActivity :
234233 }
235234
236235 private fun handleIntent (intent : Intent ) {
237- // Handle deep links first (nctalk :// scheme and https:// web links )
236+ // Handle deep links first (nextcloudtalk :// scheme)
238237 if (handleDeepLink(intent)) {
239238 return
240239 }
@@ -244,7 +243,7 @@ class MainActivity :
244243 val internalUserId = intent.extras?.getLong(BundleKeys .KEY_INTERNAL_USER_ID )
245244
246245 var user: User ? = null
247- if (internalUserId != null ) {
246+ if (internalUserId != null && internalUserId != 0L ) {
248247 user = userManager.getUserWithId(internalUserId).blockingGet()
249248 }
250249
@@ -260,108 +259,106 @@ class MainActivity :
260259 startActivity(chatIntent)
261260 }
262261 } else {
263- userManager.users.subscribe(object : SingleObserver <List <User >> {
264- override fun onSubscribe (d : Disposable ) {
265- // unused atm
266- }
267-
268- override fun onSuccess (users : List <User >) {
269- if (users.isNotEmpty()) {
270- ClosedInterfaceImpl ().setUpPushTokenRegistration()
271- runOnUiThread {
262+ val disposable = userManager.users
263+ .subscribeOn(Schedulers .io())
264+ .observeOn(AndroidSchedulers .mainThread())
265+ .subscribe(
266+ { users ->
267+ if (isFinishing || isDestroyed) return @subscribe
268+ if (users.isNotEmpty()) {
269+ ClosedInterfaceImpl ().setUpPushTokenRegistration()
272270 openConversationList()
273- }
274- } else {
275- runOnUiThread {
271+ } else {
276272 launchServerSelection()
277273 }
274+ },
275+ { e ->
276+ Log .e(TAG , " Error loading existing users" , e)
277+ if (isFinishing || isDestroyed) return @subscribe
278+ Toast .makeText(
279+ context,
280+ context.resources.getString(R .string.nc_common_error_sorry),
281+ Toast .LENGTH_SHORT
282+ ).show()
278283 }
279- }
280-
281- override fun onError (e : Throwable ) {
282- Log .e(TAG , " Error loading existing users" , e)
283- Toast .makeText(
284- context,
285- context.resources.getString(R .string.nc_common_error_sorry),
286- Toast .LENGTH_SHORT
287- ).show()
288- }
289- })
284+ )
285+ disposables.add(disposable)
290286 }
291287 }
292288
293289 /* *
294290 * Handles deep link URIs for opening conversations.
295291 *
296292 * Supports:
297- * - nctalk://conversation/{token}?user={userId}
298- * - https://{server}/call/{token}
299- * - https://{server}/index.php/call/{token}
293+ * - nextcloudtalk://[user@]server/call/token
300294 *
301295 * @param intent The intent to process
302296 * @return true if the intent was handled as a deep link, false otherwise
303297 */
304298 private fun handleDeepLink (intent : Intent ): Boolean {
305- val uri = intent.data ? : return false
306- val deepLinkResult = DeepLinkHandler .parseDeepLink(uri) ? : return false
299+ val deepLinkResult = intent.data?.let { DeepLinkHandler .parseDeepLink(it) } ? : return false
307300
308- Log .d(TAG , " Handling deep link: $uri -> token=${deepLinkResult.roomToken} " )
301+ Log .d(TAG , " Handling deep link: token=${deepLinkResult.roomToken} , server= ${deepLinkResult.serverUrl }" )
309302
310- userManager.users.subscribe(object : SingleObserver <List <User >> {
311- override fun onSubscribe (d : Disposable ) {
312- // unused atm
313- }
303+ val disposable = userManager.users
304+ .subscribeOn(Schedulers .io())
305+ .observeOn(AndroidSchedulers .mainThread())
306+ .subscribe(
307+ { users ->
308+ if (isFinishing || isDestroyed) return @subscribe
314309
315- override fun onSuccess (users : List <User >) {
316- if (users.isEmpty()) {
317- runOnUiThread {
310+ if (users.isEmpty()) {
318311 launchServerSelection()
312+ return @subscribe
319313 }
320- return
321- }
322314
323- val targetUser = resolveTargetUser(users, deepLinkResult)
315+ val targetUser = resolveTargetUser(users, deepLinkResult)
324316
325- if (targetUser == null ) {
326- runOnUiThread {
317+ if (targetUser == null ) {
327318 Toast .makeText(
328319 context,
329320 context.resources.getString(R .string.nc_no_account_for_server),
330321 Toast .LENGTH_LONG
331322 ).show()
332323 openConversationList()
324+ return @subscribe
333325 }
334- return
335- }
336326
337- if (userManager.setUserAsActive(targetUser).blockingGet()) {
338- // Report shortcut usage for ranking
339- ShortcutManagerHelper .reportShortcutUsed(
340- context,
341- deepLinkResult.roomToken,
342- targetUser.id!!
343- )
327+ if (userManager.setUserAsActive(targetUser).blockingGet()) {
328+ // Report shortcut usage for ranking
329+ targetUser.id?.let { userId ->
330+ ShortcutManagerHelper .reportShortcutUsed(
331+ context,
332+ deepLinkResult.roomToken,
333+ userId
334+ )
335+ }
336+
337+ if (isFinishing || isDestroyed) return @subscribe
344338
345- runOnUiThread {
346339 val chatIntent = Intent (context, ChatActivity ::class .java)
347340 chatIntent.putExtra(KEY_ROOM_TOKEN , deepLinkResult.roomToken)
348341 chatIntent.putExtra(BundleKeys .KEY_INTERNAL_USER_ID , targetUser.id)
349342 startActivity(chatIntent)
343+ } else {
344+ Toast .makeText(
345+ context,
346+ context.resources.getString(R .string.nc_common_error_sorry),
347+ Toast .LENGTH_SHORT
348+ ).show()
350349 }
351- }
352- }
353-
354- override fun onError (e : Throwable ) {
355- Log .e(TAG , " Error loading users for deep link" , e)
356- runOnUiThread {
350+ },
351+ { e ->
352+ Log .e(TAG , " Error loading users for deep link" , e)
353+ if (isFinishing || isDestroyed) return @subscribe
357354 Toast .makeText(
358355 context,
359356 context.resources.getString(R .string.nc_common_error_sorry),
360357 Toast .LENGTH_SHORT
361358 ).show()
362359 }
363- }
364- } )
360+ )
361+ disposables.add(disposable )
365362
366363 return true
367364 }
@@ -370,31 +367,38 @@ class MainActivity :
370367 * Resolves which user account to use for a deep link.
371368 *
372369 * Priority:
373- * 1. User ID specified in deep link (for nctalk:// URIs)
374- * 2. User matching the server URL (for https:// web links)
375- * 3. Current active user as fallback
370+ * 1. User matching both username and server URL
371+ * 2. User matching the server URL only
372+ * 3. Current active user as fallback (if server matches)
376373 */
377- private fun resolveTargetUser (
378- users : List <User >,
379- deepLinkResult : DeepLinkHandler .DeepLinkResult
380- ): User ? {
381- // If user ID is specified, use that user
382- deepLinkResult.internalUserId?.let { userId ->
383- return userManager.getUserWithId(userId).blockingGet()
374+ private fun resolveTargetUser (users : List <User >, deepLinkResult : DeepLinkHandler .DeepLinkResult ): User ? {
375+ val deepLinkHost = Uri .parse(deepLinkResult.serverUrl).host?.lowercase()
376+ if (deepLinkHost.isNullOrBlank()) {
377+ return currentUserProviderOld.currentUser.blockingGet()
384378 }
385379
386- // If server URL is specified, find matching account
387- deepLinkResult.serverUrl?.let { serverUrl ->
388- val matchingUser = users.find { user ->
389- user.baseUrl?.lowercase()?.contains(serverUrl.lowercase()) == true
390- }
391- if (matchingUser != null ) {
392- return matchingUser
380+ // Priority: exact match (username + server) > server match > current user fallback
381+ val username = deepLinkResult.username
382+ val exactMatch = if (username != null ) {
383+ users.find { user ->
384+ val userHost = user.baseUrl?.let { Uri .parse(it).host?.lowercase() }
385+ userHost == deepLinkHost && user.username?.lowercase() == username.lowercase()
393386 }
387+ } else {
388+ null
389+ }
390+
391+ val serverMatch = users.find { user ->
392+ val userHost = user.baseUrl?.let { Uri .parse(it).host?.lowercase() }
393+ userHost == deepLinkHost
394+ }
395+
396+ val currentUser = currentUserProviderOld.currentUser.blockingGet()
397+ val currentUserMatch = currentUser?.takeIf {
398+ it.baseUrl?.let { url -> Uri .parse(url).host?.lowercase() } == deepLinkHost
394399 }
395400
396- // Fall back to current user
397- return currentUserProviderOld.currentUser.blockingGet()
401+ return exactMatch ? : serverMatch ? : currentUserMatch
398402 }
399403
400404 companion object {
0 commit comments