11/*
22 * Nextcloud Talk - Android Client
33 *
4+ * SPDX-FileCopyrightText: 2025 Alexandre Wery <nextcloud-talk-android@alwy.be>
45 * SPDX-FileCopyrightText: 2024 Christian Reiner <foss@christian-reiner.info>
56 * SPDX-FileCopyrightText: 2024 Parneet Singh <gurayaparneet@gmail.com>
67 * SPDX-FileCopyrightText: 2024 Giacomo Pacini <giacomo@paciosoft.com>
@@ -127,6 +128,7 @@ import com.nextcloud.talk.events.UserMentionClickEvent
127128import com.nextcloud.talk.events.WebSocketCommunicationEvent
128129import com.nextcloud.talk.jobs.DeleteConversationWorker
129130import com.nextcloud.talk.jobs.DownloadFileToCacheWorker
131+ import com.nextcloud.talk.jobs.NotificationWorker.Companion.BUBBLE_SWITCH_KEY
130132import com.nextcloud.talk.jobs.ShareOperationWorker
131133import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
132134import com.nextcloud.talk.location.LocationPickerActivity
@@ -142,6 +144,7 @@ import com.nextcloud.talk.models.json.threads.ThreadInfo
142144import com.nextcloud.talk.polls.ui.PollCreateDialogFragment
143145import com.nextcloud.talk.polls.ui.PollMainDialogFragment
144146import com.nextcloud.talk.remotefilebrowser.activities.RemoteFileBrowserActivity
147+ import com.nextcloud.talk.settings.SettingsActivity
145148import com.nextcloud.talk.shareditems.activities.SharedItemsActivity
146149import com.nextcloud.talk.signaling.SignalingMessageReceiver
147150import com.nextcloud.talk.signaling.SignalingMessageSender
@@ -198,6 +201,7 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_START_CALL_AFTER_ROOM_SWIT
198201import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SWITCH_TO_ROOM
199202import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_THREAD_ID
200203import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
204+ import com.nextcloud.talk.utils.preferences.preferencestorage.DatabaseStorageModule
201205import com.nextcloud.talk.utils.rx.DisposableSet
202206import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder
203207import com.nextcloud.talk.webrtc.Globals
@@ -241,7 +245,7 @@ import kotlin.math.abs
241245
242246@Suppress(" TooManyFunctions" , " LargeClass" , " LongMethod" )
243247@AutoInjector(NextcloudTalkApplication ::class )
244- class ChatActivity :
248+ open class ChatActivity :
245249 BaseActivity (),
246250 CallStartedMessageInterface {
247251
@@ -1196,8 +1200,8 @@ class ChatActivity :
11961200
11971201 pendingTargetMessageId = extras?.getString(BundleKeys .KEY_MESSAGE_ID )?.toLongOrNull()?.takeIf { it > 0L }
11981202 ? : extras?.getLong(BundleKeys .KEY_MESSAGE_ID )?.takeIf { it > 0L }
1199- pendingTargetThreadId = extras?.getString(BundleKeys . KEY_THREAD_ID )?.toLongOrNull()?.takeIf { it > 0L }
1200- ? : extras?.getLong(BundleKeys . KEY_THREAD_ID )?.takeIf { it > 0L }
1203+ pendingTargetThreadId = extras?.getString(KEY_THREAD_ID )?.toLongOrNull()?.takeIf { it > 0L }
1204+ ? : extras?.getLong(KEY_THREAD_ID )?.takeIf { it > 0L }
12011205 pendingTargetSearchQuery = extras?.getString(BundleKeys .KEY_SEARCH_QUERY )
12021206 }
12031207
@@ -2033,6 +2037,12 @@ class ChatActivity :
20332037 onClick = { openScheduledMessages() }
20342038 )
20352039 }
2040+ if (NotificationUtils .deviceSupportsBubbles) {
2041+ items + = MenuItemData (
2042+ title = getString(R .string.nc_create_bubble),
2043+ onClick = { createConversationBubble() }
2044+ )
2045+ }
20362046 }
20372047 if (currentConversation?.objectType == ConversationEnums .ObjectType .FILE ) {
20382048 items + = MenuItemData (
@@ -2275,8 +2285,10 @@ class ChatActivity :
22752285
22762286 private fun setupChatEmptyStateView () {
22772287 binding.chatEmptyStateComposeView.setContent {
2278- val type by chatEmptyStateType
2279- type?.let { ChatEmptyState (it) }
2288+ MaterialTheme (colorScheme = viewThemeUtils.getColorScheme(this @ChatActivity)) {
2289+ val type by chatEmptyStateType
2290+ type?.let { ChatEmptyState (it) }
2291+ }
22802292 }
22812293 }
22822294
@@ -2308,6 +2320,79 @@ class ChatActivity :
23082320 chatEmptyStateType.value = ChatEmptyStateType .Lobby (sb.toString())
23092321 }
23102322
2323+ private fun openBubbleSettings () {
2324+ val intent = Intent (this , SettingsActivity ::class .java)
2325+ intent.putExtra(BundleKeys .KEY_FOCUS_BUBBLE_SETTINGS , true )
2326+ startActivity(intent)
2327+ }
2328+
2329+ @Suppress(" ReturnCount" )
2330+ private fun createConversationBubble () {
2331+ if (! NotificationUtils .deviceSupportsBubbles) {
2332+ Log .e(
2333+ TAG ,
2334+ " createConversationBubble was called but device doesn't support it. It should not be possible " +
2335+ " to get here via UI!"
2336+ )
2337+ return
2338+ }
2339+
2340+ if (! appPreferences.areBubblesEnabled() || ! NotificationUtils .areSystemBubblesEnabled(context)) {
2341+ // Do not replace with snackbar as it needs to survive screen change
2342+ Toast .makeText(
2343+ context,
2344+ getString(R .string.nc_conversation_notification_bubble_disabled),
2345+ Toast .LENGTH_SHORT
2346+ ).show()
2347+ openBubbleSettings()
2348+ return
2349+ }
2350+
2351+ if (! appPreferences.areBubblesForced() && ! isConversationBubbleEnabled()) {
2352+ // Do not replace with snackbar as it needs to survive screen change
2353+ Toast .makeText(
2354+ context,
2355+ getString(R .string.nc_conversation_notification_bubble_enable_conversation),
2356+ Toast .LENGTH_SHORT
2357+ ).show()
2358+ showConversationInfoScreen(focusBubbleSwitch = true )
2359+ return
2360+ }
2361+
2362+ val conversationName = currentConversation?.displayName ? : getString(R .string.nc_app_name)
2363+ currentConversation?.let {
2364+ val bubbleInfo = NotificationUtils .BubbleInfo (
2365+ roomToken = roomToken,
2366+ conversationRemoteId = it.name,
2367+ conversationName = conversationName,
2368+ conversationUser = conversationUser,
2369+ isOneToOneConversation = isOneToOneConversation(),
2370+ credentials = credentials
2371+ )
2372+
2373+ NotificationUtils .createConversationBubble(
2374+ context = context,
2375+ bubbleInfo = bubbleInfo,
2376+ appPreferences = appPreferences,
2377+ lifecycleScope
2378+ )
2379+ }
2380+ }
2381+
2382+ private fun isConversationBubbleEnabled (): Boolean =
2383+ runCatching {
2384+ DatabaseStorageModule (conversationUser, roomToken).getBoolean(BUBBLE_SWITCH_KEY , false )
2385+ }.onFailure { e ->
2386+ when (e) {
2387+ is IOException -> Log .e(TAG , " Failed to read conversation bubble preference: IO error" , e)
2388+ is IllegalStateException -> Log .e(
2389+ TAG ,
2390+ " Failed to read conversation bubble preference: Invalid state" ,
2391+ e
2392+ )
2393+ }
2394+ }.getOrDefault(false )
2395+
23112396 private fun onRemoteFileBrowsingResult (intent : Intent ? ) {
23122397 val pathList = intent?.getStringArrayListExtra(RemoteFileBrowserActivity .EXTRA_SELECTED_PATHS )
23132398 if (pathList?.size!! >= 1 ) {
@@ -2700,11 +2785,14 @@ class ChatActivity :
27002785 )
27012786 }
27022787
2703- private fun showConversationInfoScreen () {
2788+ private fun showConversationInfoScreen (focusBubbleSwitch : Boolean = false ) {
27042789 val bundle = Bundle ()
27052790
27062791 bundle.putString(KEY_ROOM_TOKEN , roomToken)
27072792 bundle.putBoolean(BundleKeys .KEY_ROOM_ONE_TO_ONE , isOneToOneConversation())
2793+ if (focusBubbleSwitch) {
2794+ bundle.putBoolean(BundleKeys .KEY_FOCUS_CONVERSATION_BUBBLE , true )
2795+ }
27082796
27092797 val upcomingEvent =
27102798 (chatViewModel.upcomingEventViewState.value as ? ChatViewModel .UpcomingEventUIState .Success )?.event
@@ -2723,9 +2811,10 @@ class ChatActivity :
27232811 sessionIdAfterRoomJoined != " 0"
27242812
27252813 @Suppress(" Detekt.TooGenericExceptionCaught" )
2726- private fun cancelNotificationsForCurrentConversation () {
2814+ protected open fun cancelNotificationsForCurrentConversation () {
2815+ val isBubbleMode = Build .VERSION .SDK_INT >= Build .VERSION_CODES .S && isLaunchedFromBubble
27272816 if (conversationUser != null ) {
2728- if (! TextUtils .isEmpty(roomToken)) {
2817+ if (! TextUtils .isEmpty(roomToken) && ! isBubbleMode ) {
27292818 try {
27302819 NotificationUtils .cancelExistingNotificationsForRoom(
27312820 applicationContext,
@@ -3632,7 +3721,7 @@ class ChatActivity :
36323721 val lon = message.geoLocationParameters.longitude
36333722 metaData =
36343723 " {\" type\" :\" geo-location\" ,\" id\" :\" geo:$lat ,$lon \" ,\" latitude\" :\" $lat \" ," +
3635- " \" longitude\" :\" $lon \" ,\" name\" :\" $name \" }"
3724+ " \" longitude\" :\" $lon \" ,\" name\" :\" $name \" }"
36363725 }
36373726
36383727 shareToNotes(shareUri, noteToSelfConversation.token, message, objectId, metaData)
0 commit comments