Skip to content

Commit b33937a

Browse files
committed
bubbles off by default and lead to android bubble settings if needed when enabled
1 parent c72622e commit b33937a

File tree

3 files changed

+171
-14
lines changed

3 files changed

+171
-14
lines changed

app/src/main/java/com/nextcloud/talk/settings/SettingsActivity.kt

Lines changed: 167 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import android.animation.Animator
1515
import android.animation.AnimatorListenerAdapter
1616
import android.annotation.SuppressLint
1717
import android.app.KeyguardManager
18+
import android.app.NotificationManager
19+
import android.content.ActivityNotFoundException
1820
import android.content.Context
1921
import android.content.DialogInterface
2022
import android.content.Intent
@@ -144,12 +146,16 @@ class SettingsActivity :
144146
private var dbQueryDisposable: Disposable? = null
145147
private var openedByNotificationWarning: Boolean = false
146148
private var focusBubbleSettings: Boolean = false
149+
private var isUpdatingBubbleSwitchState: Boolean = false
150+
private var pendingBubbleEnableAfterSystemChange: Boolean = false
147151
private var isOnline: MutableState<Boolean> = mutableStateOf(false)
148152

149153
@SuppressLint("StringFormatInvalid")
150154
override fun onCreate(savedInstanceState: Bundle?) {
151155
super.onCreate(savedInstanceState)
152156
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
157+
pendingBubbleEnableAfterSystemChange =
158+
savedInstanceState?.getBoolean(STATE_PENDING_ENABLE_BUBBLES) ?: false
153159
networkMonitor.isOnlineLiveData.observe(this) { online ->
154160
isOnline.value = online
155161
handleNetworkChange(isOnline.value)
@@ -201,6 +207,11 @@ class SettingsActivity :
201207
focusBubbleSettings = extras?.getBoolean(KEY_FOCUS_BUBBLE_SETTINGS) ?: false
202208
}
203209

210+
override fun onSaveInstanceState(outState: Bundle) {
211+
super.onSaveInstanceState(outState)
212+
outState.putBoolean(STATE_PENDING_ENABLE_BUBBLES, pendingBubbleEnableAfterSystemChange)
213+
}
214+
204215
override fun onResume() {
205216
super.onResume()
206217
supportActionBar?.show()
@@ -431,24 +442,29 @@ class SettingsActivity :
431442
binding.settingsBubbles.visibility = View.VISIBLE
432443
binding.settingsBubblesForce.visibility = View.VISIBLE
433444

434-
val bubblesEnabled = appPreferences.areBubblesEnabled()
435-
binding.settingsBubblesSwitch.isChecked = bubblesEnabled
445+
val systemAllowsAllConversations = isSystemBubblePreferenceAll()
446+
updateBubbleSummary(systemAllowsAllConversations)
447+
448+
var bubblesEnabled = appPreferences.areBubblesEnabled()
449+
if (bubblesEnabled && !systemAllowsAllConversations) {
450+
appPreferences.setBubblesEnabled(false)
451+
bubblesEnabled = false
452+
}
453+
454+
setGlobalBubbleSwitchState(bubblesEnabled)
436455
binding.settingsBubblesForceSwitch.isChecked = appPreferences.areBubblesForced()
437456

438457
updateBubbleForceRowState(bubblesEnabled)
439458

440-
binding.settingsBubbles.setOnClickListener {
441-
val newValue = !binding.settingsBubblesSwitch.isChecked
442-
binding.settingsBubblesSwitch.isChecked = newValue
443-
appPreferences.setBubblesEnabled(newValue)
444-
updateBubbleForceRowState(newValue)
445-
446-
// Dismiss all active bubbles when disabling globally
447-
if (!newValue) {
448-
currentUser?.let { user ->
449-
NotificationUtils.dismissAllBubbles(this, user)
450-
}
459+
binding.settingsBubblesSwitch.setOnCheckedChangeListener { _, isChecked ->
460+
if (isUpdatingBubbleSwitchState) {
461+
return@setOnCheckedChangeListener
451462
}
463+
handleGlobalBubblePreferenceChange(isChecked)
464+
}
465+
466+
binding.settingsBubbles.setOnClickListener {
467+
binding.settingsBubblesSwitch.performClick()
452468
}
453469

454470
binding.settingsBubblesForce.setOnClickListener {
@@ -467,6 +483,8 @@ class SettingsActivity :
467483
)
468484
}
469485
}
486+
487+
maybeEnableBubblesAfterSystemChange(systemAllowsAllConversations)
470488
}
471489

472490
private fun updateBubbleForceRowState(globalEnabled: Boolean) {
@@ -475,6 +493,141 @@ class SettingsActivity :
475493
binding.settingsBubblesForceSwitch.isEnabled = globalEnabled
476494
}
477495

496+
private fun handleGlobalBubblePreferenceChange(enabled: Boolean) {
497+
val systemAllowsAllConversations = isSystemBubblePreferenceAll()
498+
499+
if (enabled) {
500+
if (!systemAllowsAllConversations) {
501+
pendingBubbleEnableAfterSystemChange = true
502+
showSystemBubblesDisabledFeedback()
503+
updateBubbleSummary(systemAllowsAllConversations)
504+
setGlobalBubbleSwitchState(false)
505+
navigateToSystemBubbleSettings()
506+
return
507+
}
508+
509+
pendingBubbleEnableAfterSystemChange = false
510+
appPreferences.setBubblesEnabled(true)
511+
updateBubbleForceRowState(true)
512+
updateBubbleSummary(true)
513+
} else {
514+
pendingBubbleEnableAfterSystemChange = false
515+
appPreferences.setBubblesEnabled(false)
516+
updateBubbleForceRowState(false)
517+
updateBubbleSummary(systemAllowsAllConversations)
518+
currentUser?.let { user ->
519+
NotificationUtils.dismissAllBubbles(this, user)
520+
}
521+
}
522+
}
523+
524+
private fun setGlobalBubbleSwitchState(checked: Boolean) {
525+
isUpdatingBubbleSwitchState = true
526+
binding.settingsBubblesSwitch.isChecked = checked
527+
isUpdatingBubbleSwitchState = false
528+
}
529+
530+
private fun updateBubbleSummary(systemAllowsAllConversations: Boolean) {
531+
val summaryText = if (systemAllowsAllConversations) {
532+
R.string.nc_notification_settings_bubbles_desc
533+
} else {
534+
R.string.nc_notification_settings_bubbles_system_disabled
535+
}
536+
binding.settingsBubblesSummary.setText(summaryText)
537+
}
538+
539+
private fun showSystemBubblesDisabledFeedback() {
540+
Toast.makeText(
541+
this,
542+
R.string.nc_notification_settings_bubbles_system_disabled_toast,
543+
Toast.LENGTH_LONG
544+
).show()
545+
}
546+
547+
private fun maybeEnableBubblesAfterSystemChange(systemAllowsAllConversations: Boolean) {
548+
if (!pendingBubbleEnableAfterSystemChange || !systemAllowsAllConversations) {
549+
return
550+
}
551+
552+
pendingBubbleEnableAfterSystemChange = false
553+
appPreferences.setBubblesEnabled(true)
554+
setGlobalBubbleSwitchState(true)
555+
updateBubbleForceRowState(true)
556+
updateBubbleSummary(true)
557+
}
558+
559+
private fun isSystemBubblePreferenceAll(): Boolean {
560+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
561+
return false
562+
}
563+
564+
val notificationManager = getSystemService(NotificationManager::class.java)
565+
return notificationManager?.bubblePreference == NotificationManager.BUBBLE_PREFERENCE_ALL
566+
}
567+
568+
private fun navigateToSystemBubbleSettings() {
569+
val targetPackage = packageName
570+
val targetUid = applicationInfo?.uid ?: -1
571+
val candidateIntents = mutableListOf<Intent>()
572+
573+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
574+
candidateIntents += Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS)
575+
.withNotificationExtras(targetPackage, targetUid)
576+
.apply {
577+
putExtra(
578+
Settings.EXTRA_CHANNEL_ID,
579+
NotificationUtils.NotificationChannels.NOTIFICATION_CHANNEL_MESSAGES_V4.name
580+
)
581+
}
582+
583+
candidateIntents += Intent("android.settings.APP_NOTIFICATION_BUBBLE_SETTINGS")
584+
.withNotificationExtras(targetPackage, targetUid)
585+
586+
val explicitBubbleComponents = listOf(
587+
"com.android.settings.Settings\$AppBubbleNotificationSettingsActivity",
588+
"com.android.settings.Settings\$BubbleNotificationSettingsActivity"
589+
)
590+
591+
explicitBubbleComponents.forEach { componentName ->
592+
candidateIntents += Intent(Intent.ACTION_MAIN)
593+
.withNotificationExtras(targetPackage, targetUid)
594+
.setClassName("com.android.settings", componentName)
595+
}
596+
}
597+
598+
candidateIntents += Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
599+
.withNotificationExtras(targetPackage, targetUid)
600+
601+
candidateIntents += Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
602+
.withNotificationExtras(targetPackage, targetUid)
603+
604+
candidateIntents.firstOrNull { launchIntentSafely(it) } ?: Toast.makeText(
605+
this,
606+
R.string.nc_notification_settings_bubbles_open_failed,
607+
Toast.LENGTH_LONG
608+
).show()
609+
}
610+
611+
private fun Intent.withNotificationExtras(targetPackage: String, targetUid: Int): Intent {
612+
data = Uri.fromParts("package", targetPackage, null)
613+
putExtra(Settings.EXTRA_APP_PACKAGE, targetPackage)
614+
putExtra("app_uid", targetUid)
615+
return this
616+
}
617+
618+
private fun launchIntentSafely(intent: Intent): Boolean {
619+
return try {
620+
if (intent.resolveActivity(packageManager) != null) {
621+
startActivity(intent)
622+
true
623+
} else {
624+
false
625+
}
626+
} catch (activityNotFoundException: ActivityNotFoundException) {
627+
false
628+
}
629+
}
630+
478631
private fun setupNotificationSoundsSettings() {
479632
if (NotificationUtils.isCallsNotificationChannelEnabled(this)) {
480633
val callRingtoneUri = getCallRingtoneUri(context, (appPreferences))
@@ -1547,6 +1700,7 @@ class SettingsActivity :
15471700
private const val DISABLED_ALPHA: Float = 0.38f
15481701
private const val ENABLED_ALPHA: Float = 1.0f
15491702
private const val LINEBREAK = "\n"
1703+
private const val STATE_PENDING_ENABLE_BUBBLES = "statePendingEnableBubbles"
15501704
const val HTTP_CODE_OK: Int = 200
15511705
const val HTTP_ERROR_CODE_BAD_REQUEST: Int = 400
15521706
const val NO_NOTIFICATION_REMINDER_WANTED = 0L

app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferencesImpl.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ class AppPreferencesImpl(val context: Context) : AppPreferences {
271271

272272
override fun areBubblesEnabled(): Boolean =
273273
runBlocking {
274-
async { readBoolean(BUBBLES_ENABLED, true).first() }
274+
async { readBoolean(BUBBLES_ENABLED, false).first() }
275275
}.getCompleted()
276276

277277
override fun setBubblesEnabled(value: Boolean) =

app/src/main/res/values/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,8 +362,11 @@ How to translate with transifex:
362362
<string name="nc_conversation_notification_bubble_enable_conversation">Enable bubbles for this conversation in the conversation info.</string>
363363
<string name="nc_notification_settings_bubbles">Bubbles</string>
364364
<string name="nc_notification_settings_bubbles_desc">Allow Talk notifications to appear as floating bubbles.</string>
365+
<string name="nc_notification_settings_bubbles_system_disabled">Enable “All conversations can bubble” in Android notification settings to allow bubbles.</string>
365366
<string name="nc_notification_settings_bubbles_force">All conversations can bubble</string>
366367
<string name="nc_notification_settings_bubbles_force_desc">Override individual conversation bubble settings.</string>
368+
<string name="nc_notification_settings_bubbles_open_failed">Unable to open Android bubble settings. Please enable bubbles for Talk from system notification settings.</string>
369+
<string name="nc_notification_settings_bubbles_system_disabled_toast">Turn on “All conversations can bubble” in Android notification settings first.</string>
367370
<string name="nc_sensitive_conversation">Sensitive conversation</string>
368371
<string name="nc_sensitive_conversation_hint">Message preview will be disabled in conversation list and notifications</string>
369372
<string name="nc_important_conversation">Important conversation</string>

0 commit comments

Comments
 (0)