diff --git a/CHANGES.md b/CHANGES.md index 82009c1ddc..16b8f0e410 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -21,6 +21,7 @@ Bugfix 🐛: - Fix timeline items not loading when there are only filtered events - Fix "Voice & Video" grayed out in Settings (#1733) - Fix Allow VOIP call in all rooms with 2 participants (even if not DM) + - Migration from old client does not enable notifications (#1723) Translations 🗣: - diff --git a/vector/src/main/java/im/vector/riotx/core/preference/VectorSwitchPreference.kt b/vector/src/main/java/im/vector/riotx/core/preference/VectorSwitchPreference.kt index 9a2064d741..4728856e32 100644 --- a/vector/src/main/java/im/vector/riotx/core/preference/VectorSwitchPreference.kt +++ b/vector/src/main/java/im/vector/riotx/core/preference/VectorSwitchPreference.kt @@ -16,11 +16,18 @@ package im.vector.riotx.core.preference +import android.animation.Animator +import android.animation.ArgbEvaluator +import android.animation.ValueAnimator import android.content.Context +import android.graphics.Color import android.util.AttributeSet import android.widget.TextView +import androidx.core.animation.doOnEnd import androidx.preference.PreferenceViewHolder import androidx.preference.SwitchPreference +import im.vector.riotx.R +import im.vector.riotx.features.themes.ThemeUtils /** * Switch preference with title on multiline (only used in XML) @@ -41,10 +48,49 @@ class VectorSwitchPreference : SwitchPreference { isIconSpaceReserved = true } + var isHighlighted = false + set(value) { + field = value + notifyChanged() + } + + var currentHighlightAnimator: Animator? = null + override fun onBindViewHolder(holder: PreferenceViewHolder) { // display the title in multi-line to avoid ellipsis. holder.itemView.findViewById(android.R.id.title)?.isSingleLine = false + // cancel existing animation (find a way to resume if happens during anim?) + currentHighlightAnimator?.cancel() + + val itemView = holder.itemView + if (isHighlighted) { + val colorFrom = Color.TRANSPARENT + val colorTo = ThemeUtils.getColor(itemView.context, R.attr.colorControlHighlight) + currentHighlightAnimator = ValueAnimator.ofObject(ArgbEvaluator(), colorFrom, colorTo).apply { + duration = 250 // milliseconds + addUpdateListener { animator -> + itemView.setBackgroundColor(animator.animatedValue as Int) + } + doOnEnd { + currentHighlightAnimator = ValueAnimator.ofObject(ArgbEvaluator(), colorTo, colorFrom).apply { + duration = 250 // milliseconds + addUpdateListener { animator -> + itemView.setBackgroundColor(animator.animatedValue as Int) + } + doOnEnd { + isHighlighted = false + } + start() + } + } + startDelay = 200 + start() + } + } else { + itemView.setBackgroundColor(Color.TRANSPARENT) + } + super.onBindViewHolder(holder) } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt index 5bed5b1f78..ef9bece7e6 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt @@ -42,10 +42,13 @@ import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.pushers.PushersManager import im.vector.riotx.features.disclaimer.showDisclaimerDialog import im.vector.riotx.features.notifications.NotificationDrawerManager +import im.vector.riotx.features.popup.DefaultVectorAlert import im.vector.riotx.features.popup.PopupAlertManager import im.vector.riotx.features.popup.VerificationVectorAlert import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler import im.vector.riotx.features.settings.VectorPreferences +import im.vector.riotx.features.settings.VectorSettingsActivity +import im.vector.riotx.features.themes.ThemeUtils import im.vector.riotx.features.workers.signout.ServerBackupStatusViewModel import im.vector.riotx.features.workers.signout.ServerBackupStatusViewState import im.vector.riotx.push.fcm.FcmHelper @@ -69,7 +72,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet @Inject lateinit var viewModelFactory: HomeActivityViewModel.Factory private val serverBackupStatusViewModel: ServerBackupStatusViewModel by viewModel() - @Inject lateinit var serverBackupviewModelFactory: ServerBackupStatusViewModel.Factory + @Inject lateinit var serverBackupviewModelFactory: ServerBackupStatusViewModel.Factory @Inject lateinit var activeSessionHolder: ActiveSessionHolder @Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler @@ -134,6 +137,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet when (it) { is HomeActivityViewEvents.AskPasswordToInitCrossSigning -> handleAskPasswordToInitCrossSigning(it) is HomeActivityViewEvents.OnNewSession -> handleOnNewSession(it) + HomeActivityViewEvents.PromptToEnableSessionPush -> handlePromptToEnablePush() }.exhaustive } homeActivityViewModel.subscribe(this) { renderState(it) } @@ -193,6 +197,42 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet } } + private fun handlePromptToEnablePush() { + popupAlertManager.postVectorAlert( + DefaultVectorAlert( + uid = "enablePush", + title = getString(R.string.alert_push_are_disabled_title), + description = getString(R.string.alert_push_are_disabled_description), + iconId = R.drawable.ic_room_actions_notifications_mutes, + shouldBeDisplayedIn = { + it is HomeActivity + } + ).apply { + colorInt = ThemeUtils.getColor(this@HomeActivity, R.attr.vctr_notice_secondary) + contentAction = Runnable { + (weakCurrentActivity?.get() as? VectorBaseActivity)?.let { + // action(it) + homeActivityViewModel.handle(HomeActivityViewActions.PushPromptHasBeenReviewed) + it.navigator.openSettings(it, VectorSettingsActivity.EXTRA_DIRECT_ACCESS_NOTIFICATIONS) + } + } + dismissedAction = Runnable { + homeActivityViewModel.handle(HomeActivityViewActions.PushPromptHasBeenReviewed) + } + addButton(getString(R.string.dismiss), Runnable { + homeActivityViewModel.handle(HomeActivityViewActions.PushPromptHasBeenReviewed) + }, true) + addButton(getString(R.string.settings), Runnable { + (weakCurrentActivity?.get() as? VectorBaseActivity)?.let { + // action(it) + homeActivityViewModel.handle(HomeActivityViewActions.PushPromptHasBeenReviewed) + it.navigator.openSettings(it, VectorSettingsActivity.EXTRA_DIRECT_ACCESS_NOTIFICATIONS) + } + }, true) + } + ) + } + private fun promptSecurityEvent(userItem: MatrixItem.UserItem?, titleRes: Int, descRes: Int, action: ((VectorBaseActivity) -> Unit)) { popupAlertManager.postVectorAlert( VerificationVectorAlert( diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewActions.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewActions.kt new file mode 100644 index 0000000000..605e0916c7 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewActions.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.home + +import im.vector.riotx.core.platform.VectorViewModelAction + +sealed class HomeActivityViewActions : VectorViewModelAction { + object PushPromptHasBeenReviewed : HomeActivityViewActions() +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewEvents.kt index 1cdabe824c..03004d0b73 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewEvents.kt @@ -22,4 +22,5 @@ import im.vector.riotx.core.platform.VectorViewEvents sealed class HomeActivityViewEvents : VectorViewEvents { data class AskPasswordToInitCrossSigning(val userItem: MatrixItem.UserItem?) : HomeActivityViewEvents() data class OnNewSession(val userItem: MatrixItem.UserItem?, val waitForIncomingRequest: Boolean = true) : HomeActivityViewEvents() + object PromptToEnableSessionPush : HomeActivityViewEvents() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt index f89bb5a547..4e64508df5 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt @@ -16,6 +16,7 @@ package im.vector.riotx.features.home +import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -23,24 +24,32 @@ import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.NoOpMatrixCallback +import im.vector.matrix.android.api.pushrules.RuleIds import im.vector.matrix.android.api.session.InitialSyncProgressService +import im.vector.matrix.android.api.session.room.model.Membership +import im.vector.matrix.android.api.session.room.roomSummaryQueryParams import im.vector.matrix.android.api.util.toMatrixItem import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth import im.vector.matrix.rx.asObservable import im.vector.riotx.core.di.ActiveSessionHolder -import im.vector.riotx.core.platform.EmptyAction +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.features.login.ReAuthHelper +import im.vector.riotx.features.settings.VectorPreferences +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import timber.log.Timber class HomeActivityViewModel @AssistedInject constructor( @Assisted initialState: HomeActivityViewState, @Assisted private val args: HomeActivityArgs, private val activeSessionHolder: ActiveSessionHolder, - private val reAuthHelper: ReAuthHelper -) : VectorViewModel(initialState) { + private val reAuthHelper: ReAuthHelper, + private val vectorPreferences: VectorPreferences +) : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { @@ -62,6 +71,7 @@ class HomeActivityViewModel @AssistedInject constructor( init { observeInitialSync() mayBeInitializeCrossSigning() + checkSessionPushIsOn() } private fun observeInitialSync() { @@ -115,6 +125,41 @@ class HomeActivityViewModel @AssistedInject constructor( } } + /* + * After migration from riot to element some users reported that their + * push setting for the session was set to off + * In order to mitigate this, we want to display a popup once to the user + * giving him the option to review this setting + */ + private fun checkSessionPushIsOn() { + viewModelScope.launch(Dispatchers.IO) { + // Don't do that if it's a login or a register (pass in memory) + if (reAuthHelper.data != null) return@launch + // Check if disabled for this device + if (!vectorPreferences.areNotificationEnabledForDevice()) { + // Check if set at account level + val mRuleMaster = activeSessionHolder.getSafeActiveSession() + ?.getPushRules() + ?.getAllRules() + ?.find { it.ruleId == RuleIds.RULE_ID_DISABLE_ALL } + if (mRuleMaster?.enabled == false) { + // So push are enabled at account level but not for this session + // Let's check that there are some rooms? + val knownRooms = activeSessionHolder.getSafeActiveSession()?.getRoomSummaries(roomSummaryQueryParams { + memberships = Membership.activeMemberships() + })?.size ?: 0 + + // Prompt once to the user + if (knownRooms > 1 && !vectorPreferences.didAskUserToEnableSessionPush()) { + // delay a bit + delay(1500) + _viewEvents.post(HomeActivityViewEvents.PromptToEnableSessionPush) + } + } + } + } + } + private fun maybeBootstrapCrossSigning() { // In case of account creation, it is already done before if (args.accountCreation) return @@ -167,7 +212,11 @@ class HomeActivityViewModel @AssistedInject constructor( }) } - override fun handle(action: EmptyAction) { - // NA + override fun handle(action: HomeActivityViewActions) { + when (action) { + HomeActivityViewActions.PushPromptHasBeenReviewed -> { + vectorPreferences.setDidAskUserToEnableSessionPush() + } + }.exhaustive } } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt index 50f4d516bf..c9284cf132 100755 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt @@ -167,6 +167,8 @@ class VectorPreferences @Inject constructor(private val context: Context) { private const val DID_ASK_TO_USE_ANALYTICS_TRACKING_KEY = "DID_ASK_TO_USE_ANALYTICS_TRACKING_KEY" private const val SETTINGS_DISPLAY_ALL_EVENTS_KEY = "SETTINGS_DISPLAY_ALL_EVENTS_KEY" + private const val DID_ASK_TO_ENABLE_SESSION_PUSH = "DID_ASK_TO_ENABLE_SESSION_PUSH" + private const val MEDIA_SAVING_3_DAYS = 0 private const val MEDIA_SAVING_1_WEEK = 1 private const val MEDIA_SAVING_1_MONTH = 2 @@ -285,6 +287,16 @@ class VectorPreferences @Inject constructor(private val context: Context) { return BuildConfig.DEBUG || (developerMode() && defaultPrefs.getBoolean(SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY, false)) } + fun didAskUserToEnableSessionPush(): Boolean { + return defaultPrefs.getBoolean(DID_ASK_TO_ENABLE_SESSION_PUSH, false) + } + + fun setDidAskUserToEnableSessionPush() { + defaultPrefs.edit { + putBoolean(DID_ASK_TO_ENABLE_SESSION_PUSH, true) + } + } + /** * Tells if we have already asked the user to disable battery optimisations on android >= M devices. * diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsActivity.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsActivity.kt index bbe6358bd9..b92ee316ee 100755 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsActivity.kt @@ -70,6 +70,11 @@ class VectorSettingsActivity : VectorBaseActivity(), VectorSettingsDevicesFragment::class.java, null, FRAGMENT_TAG) + EXTRA_DIRECT_ACCESS_NOTIFICATIONS -> { + requestHighlightPreferenceKeyOnResume(VectorPreferences.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY) + replaceFragment(R.id.vector_settings_page, VectorSettingsNotificationPreferenceFragment::class.java, null, FRAGMENT_TAG) + } + else -> replaceFragment(R.id.vector_settings_page, VectorSettingsRootFragment::class.java, null, FRAGMENT_TAG) } @@ -140,6 +145,7 @@ class VectorSettingsActivity : VectorBaseActivity(), const val EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY = 2 const val EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS = 3 const val EXTRA_DIRECT_ACCESS_GENERAL = 4 + const val EXTRA_DIRECT_ACCESS_NOTIFICATIONS = 5 private const val FRAGMENT_TAG = "VectorSettingsPreferencesFragment" } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsNotificationPreferenceFragment.kt index f191b43724..6f731711db 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsNotificationPreferenceFragment.kt @@ -17,6 +17,7 @@ package im.vector.riotx.features.settings import android.app.Activity +import android.content.Context import android.content.Intent import android.media.RingtoneManager import android.net.Uri @@ -46,6 +47,8 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( override var titleRes: Int = R.string.settings_notifications override val preferenceXmlRes = R.xml.vector_settings_notifications + private var interactionListener: VectorSettingsFragmentInteractionListener? = null + override fun bindPref() { findPreference(VectorPreferences.SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY)!!.let { pref -> val pushRuleService = session @@ -139,6 +142,24 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( override fun onResume() { super.onResume() activeSessionHolder.getSafeActiveSession()?.refreshPushers() + + interactionListener?.requestedKeyToHighlight()?.let { key -> + interactionListener?.requestHighlightPreferenceKeyOnResume(null) + val preference = findPreference(key) + preference?.isHighlighted = true + } + } + + override fun onAttach(context: Context) { + super.onAttach(context) + if (context is VectorSettingsFragmentInteractionListener) { + interactionListener = context + } + } + + override fun onDetach() { + interactionListener = null + super.onDetach() } override fun onPreferenceTreeClick(preference: Preference?): Boolean { diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 70a39ee926..1eb4b312cb 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -78,6 +78,7 @@ Active call Play Pause + Dismiss @@ -2568,4 +2569,7 @@ Not all features in Riot are implemented in Element yet. Main missing (and comin Banned by %1$s Failed to UnBan user + + Push notifications are disabled + Review your settings to enable push notifications