Merge pull request #4052 from vector-im/feature/adm/email_notification_toggle

Add email notification toggle
This commit is contained in:
Benoit Marty 2021-09-24 20:42:44 +02:00 committed by GitHub
commit 045e4bbf76
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 325 additions and 41 deletions

1
changelog.d/2243.feature Normal file
View file

@ -0,0 +1 @@
Adds email notification registration to Settings

View file

@ -26,7 +26,14 @@ data class Pusher(
val data: PusherData,
val state: PusherState
)
) {
companion object {
const val KIND_EMAIL = "email"
const val KIND_HTTP = "http"
const val APP_ID_EMAIL = "m.email"
}
}
enum class PusherState {
UNREGISTERED,

View file

@ -27,14 +27,12 @@ interface PushersService {
/**
* Add a new HTTP pusher.
* Note that only `http` kind is supported by the SDK for now.
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-pushers-set
*
* @param pushkey This is a unique identifier for this pusher. The value you should use for
* this is the routing or destination address information for the notification,
* for example, the APNS token for APNS or the Registration ID for GCM. If your
* notification client has no such concept, use any unique identifier. Max length, 512 chars.
* If the kind is "email", this is the email address to send notifications to.
* @param appId the application id
* This is a reverse-DNS style identifier for the application. It is recommended
* that this end with the platform, such that different platform versions get
@ -64,6 +62,30 @@ interface PushersService {
append: Boolean,
withEventIdOnly: Boolean): UUID
/**
* Add a new Email pusher.
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-pushers-set
*
* @param email The email address to send notifications to.
* @param lang The preferred language for receiving notifications (e.g. "en" or "en-US").
* @param emailBranding The branding placeholder to include in the email communications.
* @param appDisplayName A human readable string that will allow the user to identify what application owns this pusher.
* @param deviceDisplayName A human readable string that will allow the user to identify what device owns this pusher.
* @param append If true, the homeserver should add another pusher with the given pushkey and App ID in addition
* to any others with different user IDs. Otherwise, the homeserver must remove any other pushers
* with the same App ID and pushkey for different users. Typically We always want to append for
* email pushers since we don't want to stop other accounts notifying to the same email address.
* @return A work request uuid. Can be used to listen to the status
* (LiveData<WorkInfo> status = workManager.getWorkInfoByIdLiveData(<UUID>))
* @throws [InvalidParameterException] if a parameter is not correct
*/
fun addEmailPusher(email: String,
lang: String,
emailBranding: String,
appDisplayName: String,
deviceDisplayName: String,
append: Boolean = true): UUID
/**
* Directly ask the push gateway to send a push to this device
* If successful, the push gateway has accepted the request. In this case, the app should receive a Push with the provided eventId.
@ -80,10 +102,23 @@ interface PushersService {
eventId: String)
/**
* Remove the http pusher
* Remove a registered pusher
* @param pusher the pusher to remove, can be http or email
*/
suspend fun removePusher(pusher: Pusher)
/**
* Remove a Http pusher by its pushkey and appId
* @see addHttpPusher
*/
suspend fun removeHttpPusher(pushkey: String, appId: String)
/**
* Remove an Email pusher
* @see addEmailPusher
*/
suspend fun removeEmailPusher(email: String)
/**
* Get the current pushers, as a LiveData
*/

View file

@ -43,7 +43,7 @@ import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationMan
import org.matrix.android.sdk.internal.session.media.MediaModule
import org.matrix.android.sdk.internal.session.openid.OpenIdModule
import org.matrix.android.sdk.internal.session.profile.ProfileModule
import org.matrix.android.sdk.internal.session.pushers.AddHttpPusherWorker
import org.matrix.android.sdk.internal.session.pushers.AddPusherWorker
import org.matrix.android.sdk.internal.session.pushers.PushersModule
import org.matrix.android.sdk.internal.session.room.RoomModule
import org.matrix.android.sdk.internal.session.room.relation.SendRelationWorker
@ -127,7 +127,7 @@ internal interface SessionComponent {
fun inject(worker: SyncWorker)
fun inject(worker: AddHttpPusherWorker)
fun inject(worker: AddPusherWorker)
fun inject(worker: SendVerificationMessageWorker)

View file

@ -33,8 +33,8 @@ import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import javax.inject.Inject
internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
: SessionSafeCoroutineWorker<AddHttpPusherWorker.Params>(context, params, Params::class.java) {
internal class AddPusherWorker(context: Context, params: WorkerParameters)
: SessionSafeCoroutineWorker<AddPusherWorker.Params>(context, params, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(

View file

@ -66,27 +66,45 @@ internal class DefaultPushersService @Inject constructor(
deviceDisplayName: String,
url: String,
append: Boolean,
withEventIdOnly: Boolean)
: UUID {
// Do some parameter checks. It's ok to throw Exception, to inform developer of the problem
if (pushkey.length > 512) throw InvalidParameterException("pushkey should not exceed 512 chars")
if (appId.length > 64) throw InvalidParameterException("appId should not exceed 64 chars")
if ("/_matrix/push/v1/notify" !in url) throw InvalidParameterException("url should contain '/_matrix/push/v1/notify'")
withEventIdOnly: Boolean
) = addPusher(
JsonPusher(
pushKey = pushkey,
kind = Pusher.KIND_HTTP,
appId = appId,
profileTag = profileTag,
lang = lang,
appDisplayName = appDisplayName,
deviceDisplayName = deviceDisplayName,
data = JsonPusherData(url, EVENT_ID_ONLY.takeIf { withEventIdOnly }),
append = append
)
)
val pusher = JsonPusher(
pushKey = pushkey,
kind = "http",
appId = appId,
appDisplayName = appDisplayName,
deviceDisplayName = deviceDisplayName,
profileTag = profileTag,
lang = lang,
data = JsonPusherData(url, EVENT_ID_ONLY.takeIf { withEventIdOnly }),
append = append)
override fun addEmailPusher(email: String,
lang: String,
emailBranding: String,
appDisplayName: String,
deviceDisplayName: String,
append: Boolean
) = addPusher(
JsonPusher(
pushKey = email,
kind = Pusher.KIND_EMAIL,
appId = Pusher.APP_ID_EMAIL,
profileTag = "",
lang = lang,
appDisplayName = appDisplayName,
deviceDisplayName = deviceDisplayName,
data = JsonPusherData(brand = emailBranding),
append = append
)
)
val params = AddHttpPusherWorker.Params(sessionId, pusher)
val request = workManagerProvider.matrixOneTimeWorkRequestBuilder<AddHttpPusherWorker>()
private fun addPusher(pusher: JsonPusher): UUID {
pusher.validateParameters()
val params = AddPusherWorker.Params(sessionId, pusher)
val request = workManagerProvider.matrixOneTimeWorkRequestBuilder<AddPusherWorker>()
.setConstraints(WorkManagerProvider.workConstraints)
.setInputData(WorkerParamsFactory.toData(params))
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
@ -95,8 +113,27 @@ internal class DefaultPushersService @Inject constructor(
return request.id
}
private fun JsonPusher.validateParameters() {
// Do some parameter checks. It's ok to throw Exception, to inform developer of the problem
if (pushKey.length > 512) throw InvalidParameterException("pushkey should not exceed 512 chars")
if (appId.length > 64) throw InvalidParameterException("appId should not exceed 64 chars")
data?.url?.let { url -> if ("/_matrix/push/v1/notify" !in url) throw InvalidParameterException("url should contain '/_matrix/push/v1/notify'") }
}
override suspend fun removePusher(pusher: Pusher) {
removePusher(pusher.pushKey, pusher.appId)
}
override suspend fun removeHttpPusher(pushkey: String, appId: String) {
val params = RemovePusherTask.Params(pushkey, appId)
removePusher(pushkey, appId)
}
override suspend fun removeEmailPusher(email: String) {
removePusher(pushKey = email, Pusher.APP_ID_EMAIL)
}
private suspend fun removePusher(pushKey: String, pushAppId: String) {
val params = RemovePusherTask.Params(pushKey, pushAppId)
removePusherTask.execute(params)
}

View file

@ -32,5 +32,8 @@ internal data class JsonPusherData(
* Currently the only format available is 'event_id_only'.
*/
@Json(name = "format")
val format: String? = null
val format: String? = null,
@Json(name = "brand")
val brand: String? = null
)

View file

@ -39,13 +39,15 @@ import im.vector.app.features.themes.ThemeUtils
/**
* Set a text in the TextView, or set visibility to GONE if the text is null
*/
fun TextView.setTextOrHide(newText: CharSequence?, hideWhenBlank: Boolean = true) {
fun TextView.setTextOrHide(newText: CharSequence?, hideWhenBlank: Boolean = true, vararg relatedViews: View = emptyArray()) {
if (newText == null
|| (newText.isBlank() && hideWhenBlank)) {
isVisible = false
relatedViews.forEach { it.isVisible = false }
} else {
this.text = newText
isVisible = true
relatedViews.forEach { it.isVisible = true }
}
}

View file

@ -61,6 +61,23 @@ class PushersManager @Inject constructor(
)
}
fun registerEmailForPush(email: String) {
val currentSession = activeSessionHolder.getActiveSession()
val appName = appNameProvider.getAppName()
currentSession.addEmailPusher(
email = email,
lang = localeProvider.current().language,
emailBranding = appName,
appDisplayName = appName,
deviceDisplayName = currentSession.sessionParams.deviceId ?: "MOBILE"
)
}
suspend fun unregisterEmailPusher(email: String) {
val currentSession = activeSessionHolder.getSafeActiveSession() ?: return
currentSession.removeEmailPusher(email)
}
suspend fun unregisterPusher(pushKey: String) {
val currentSession = activeSessionHolder.getSafeActiveSession() ?: return
currentSession.removeHttpPusher(pushKey, stringProvider.getString(R.string.pusher_app_id))

View file

@ -117,6 +117,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
// notifications
const val SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY = "SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY"
const val SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY = "SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY"
const val SETTINGS_EMAIL_NOTIFICATION_CATEGORY_PREFERENCE_KEY = "SETTINGS_EMAIL_NOTIFICATION_CATEGORY_PREFERENCE_KEY"
// public static final String SETTINGS_TURN_SCREEN_ON_PREFERENCE_KEY = "SETTINGS_TURN_SCREEN_ON_PREFERENCE_KEY";
const val SETTINGS_SYSTEM_CALL_NOTIFICATION_PREFERENCE_KEY = "SETTINGS_SYSTEM_CALL_NOTIFICATION_PREFERENCE_KEY"

View file

@ -29,6 +29,7 @@ import im.vector.app.databinding.ActivityVectorSettingsBinding
import im.vector.app.features.discovery.DiscoverySettingsFragment
import im.vector.app.features.settings.devices.VectorSettingsDevicesFragment
import im.vector.app.features.settings.notifications.VectorSettingsNotificationPreferenceFragment
import im.vector.app.features.settings.threepids.ThreePidsSettingsFragment
import org.matrix.android.sdk.api.failure.GlobalError
import org.matrix.android.sdk.api.session.Session
@ -136,6 +137,10 @@ class VectorSettingsActivity : VectorBaseActivity<ActivityVectorSettingsBinding>
return keyToHighlight
}
override fun navigateToEmailAndPhoneNumbers() {
navigateTo(ThreePidsSettingsFragment::class.java)
}
override fun handleInvalidToken(globalError: GlobalError.InvalidToken) {
if (ignoreInvalidTokenError) {
Timber.w("Ignoring invalid token global error")

View file

@ -20,4 +20,6 @@ interface VectorSettingsFragmentInteractionListener {
fun requestHighlightPreferenceKeyOnResume(key: String?)
fun requestedKeyToHighlight(): String?
fun navigateToEmailAndPhoneNumbers()
}

View file

@ -23,7 +23,10 @@ import android.media.RingtoneManager
import android.net.Uri
import android.os.Parcelable
import android.widget.Toast
import androidx.lifecycle.LiveData
import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.map
import androidx.preference.Preference
import androidx.preference.SwitchPreference
import im.vector.app.R
@ -43,10 +46,14 @@ import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.VectorSettingsBaseFragment
import im.vector.app.features.settings.VectorSettingsFragmentInteractionListener
import im.vector.app.push.fcm.FcmHelper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.pushrules.RuleIds
import org.matrix.android.sdk.api.pushrules.RuleKind
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.api.session.pushers.Pusher
import javax.inject.Inject
// Referenced in vector_settings_preferences_root.xml
@ -116,11 +123,51 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
}
}
bindEmailNotifications()
refreshBackgroundSyncPrefs()
handleSystemPreference()
}
private fun bindEmailNotifications() {
val initialEmails = session.getEmailsWithPushInformation()
bindEmailNotificationCategory(initialEmails)
session.getEmailsWithPushInformationLive().observe(this) { emails ->
if (initialEmails != emails) {
bindEmailNotificationCategory(emails)
}
}
}
private fun bindEmailNotificationCategory(emails: List<Pair<ThreePid.Email, Boolean>>) {
findPreference<VectorPreferenceCategory>(VectorPreferences.SETTINGS_EMAIL_NOTIFICATION_CATEGORY_PREFERENCE_KEY)?.let { category ->
category.removeAll()
if (emails.isEmpty()) {
val vectorPreference = VectorPreference(requireContext())
vectorPreference.title = resources.getString(R.string.settings_notification_emails_no_emails)
category.addPreference(vectorPreference)
vectorPreference.setOnPreferenceClickListener {
interactionListener?.navigateToEmailAndPhoneNumbers()
true
}
} else {
emails.forEach { (emailPid, isEnabled) ->
val pref = VectorSwitchPreference(requireContext())
pref.title = resources.getString(R.string.settings_notification_emails_enable_for_email, emailPid.email)
pref.isChecked = isEnabled
pref.setTransactionalSwitchChangeListener(lifecycleScope) { isChecked ->
if (isChecked) {
pushManager.registerEmailForPush(emailPid.email)
} else {
pushManager.unregisterEmailPusher(emailPid.email)
}
}
category.addPreference(pref)
}
}
}
}
private val batteryStartForActivityResult = registerStartForActivityResult {
// Noop
}
@ -343,3 +390,43 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
}
}
}
private fun SwitchPreference.setTransactionalSwitchChangeListener(scope: CoroutineScope, transaction: suspend (Boolean) -> Unit) {
setOnPreferenceChangeListener { switchPreference, isChecked ->
require(switchPreference is SwitchPreference)
val originalState = switchPreference.isChecked
scope.launch {
try {
transaction(isChecked as Boolean)
} catch (failure: Throwable) {
switchPreference.isChecked = originalState
Toast.makeText(switchPreference.context, R.string.unknown_error, Toast.LENGTH_SHORT).show()
}
}
true
}
}
/**
* Fetches the current users 3pid emails and pairs them with their enabled state.
* If no pusher is available for a given email we can infer that push is not registered for the email.
* @return a list of ThreePid emails paired with the email notification enabled state. true if email notifications are enabled, false if not.
* @see ThreePid.Email
*/
private fun Session.getEmailsWithPushInformation(): List<Pair<ThreePid.Email, Boolean>> {
val emailPushers = getPushers().filter { it.kind == Pusher.KIND_EMAIL }
return getThreePids()
.filterIsInstance<ThreePid.Email>()
.map { it to emailPushers.any { pusher -> pusher.pushKey == it.email } }
}
private fun Session.getEmailsWithPushInformationLive(): LiveData<List<Pair<ThreePid.Email, Boolean>>> {
return getThreePidsLive(refreshData = false)
.distinctUntilChanged()
.map { threePids ->
val emailPushers = getPushers().filter { it.kind == Pusher.KIND_EMAIL }
threePids
.filterIsInstance<ThreePid.Email>()
.map { it to emailPushers.any { pusher -> pusher.pushKey == it.email } }
}
}

View file

@ -26,6 +26,8 @@ class PushGateWayController @Inject constructor(
private val stringProvider: StringProvider
) : TypedEpoxyController<PushGatewayViewState>() {
var interactionListener: PushGatewayItemInteractions? = null
override fun buildModels(data: PushGatewayViewState?) {
val host = this
data?.pushGateways?.invoke()?.let { pushers ->
@ -39,6 +41,9 @@ class PushGateWayController @Inject constructor(
pushGatewayItem {
id("${it.pushKey}_${it.appId}")
pusher(it)
host.interactionListener?.let {
interactions(it)
}
}
}
}

View file

@ -17,7 +17,9 @@
package im.vector.app.features.settings.push
import im.vector.app.core.platform.VectorViewModelAction
import org.matrix.android.sdk.api.session.pushers.Pusher
sealed class PushGatewayAction : VectorViewModelAction {
object Refresh : PushGatewayAction()
data class RemovePusher(val pusher: Pusher) : PushGatewayAction()
}

View file

@ -16,12 +16,14 @@
package im.vector.app.features.settings.push
import android.view.View
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import com.airbnb.epoxy.EpoxyModelWithHolder
import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.extensions.setTextOrHide
import org.matrix.android.sdk.api.session.pushers.Pusher
@EpoxyModelClass(layout = R.layout.item_pushgateway)
@ -30,33 +32,45 @@ abstract class PushGatewayItem : EpoxyModelWithHolder<PushGatewayItem.Holder>()
@EpoxyAttribute
lateinit var pusher: Pusher
@EpoxyAttribute
lateinit var interactions: PushGatewayItemInteractions
override fun bind(holder: Holder) {
super.bind(holder)
holder.kind.text = when (pusher.kind) {
// TODO Create const
"http" -> "Http Pusher"
"mail" -> "Email Pusher"
else -> pusher.kind
Pusher.KIND_HTTP -> "Http Pusher"
Pusher.KIND_EMAIL -> "Email Pusher"
else -> pusher.kind
}
holder.appId.text = pusher.appId
holder.pushKey.text = pusher.pushKey
holder.appName.text = pusher.appDisplayName
holder.url.text = pusher.data.url
holder.format.text = pusher.data.format
holder.url.setTextOrHide(pusher.data.url, hideWhenBlank = true, holder.urlTitle)
holder.format.setTextOrHide(pusher.data.format, hideWhenBlank = true, holder.formatTitle)
holder.deviceName.text = pusher.deviceDisplayName
holder.removeButton.setOnClickListener {
interactions.onRemovePushTapped(pusher)
}
}
class Holder : VectorEpoxyHolder() {
val kind by bind<TextView>(R.id.pushGatewayKind)
val pushKey by bind<TextView>(R.id.pushGatewayKeyValue)
val deviceName by bind<TextView>(R.id.pushGatewayDeviceNameValue)
val formatTitle by bind<View>(R.id.pushGatewayFormat)
val format by bind<TextView>(R.id.pushGatewayFormatValue)
val urlTitle by bind<View>(R.id.pushGatewayURL)
val url by bind<TextView>(R.id.pushGatewayURLValue)
val appName by bind<TextView>(R.id.pushGatewayAppNameValue)
val appId by bind<TextView>(R.id.pushGatewayAppIdValue)
val removeButton by bind<View>(R.id.pushGatewayDeleteButton)
}
}
interface PushGatewayItemInteractions {
fun onRemovePushTapped(pusher: Pusher)
}
//
// abstract class ReactionInfoSimpleItem : EpoxyModelWithHolder<ReactionInfoSimpleItem.Holder>() {

View file

@ -0,0 +1,23 @@
/*
* Copyright (c) 2021 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.app.features.settings.push
import im.vector.app.core.platform.VectorViewEvents
sealed class PushGatewayViewEvents : VectorViewEvents {
data class RemovePusherFailed(val cause: Throwable): PushGatewayViewEvents()
}

View file

@ -24,11 +24,14 @@ import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentGenericRecyclerBinding
import org.matrix.android.sdk.api.session.pushers.Pusher
import javax.inject.Inject
@ -64,7 +67,21 @@ class PushGatewaysFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
epoxyController.interactionListener = object : PushGatewayItemInteractions {
override fun onRemovePushTapped(pusher: Pusher) = viewModel.handle(PushGatewayAction.RemovePusher(pusher))
}
views.genericRecyclerView.configureWith(epoxyController, dividerDrawable = R.drawable.divider_horizontal)
viewModel.observeViewEvents {
when (it) {
is PushGatewayViewEvents.RemovePusherFailed -> {
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.dialog_title_error)
.setMessage(errorFormatter.toHumanReadable(it.cause))
.setPositiveButton(android.R.string.ok, null)
.show()
}
}.exhaustive
}
}
override fun onDestroyView() {

View file

@ -16,6 +16,7 @@
package im.vector.app.features.settings.push
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxState
@ -26,8 +27,8 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.pushers.Pusher
import org.matrix.android.sdk.rx.RxSession
@ -38,7 +39,7 @@ data class PushGatewayViewState(
class PushGatewaysViewModel @AssistedInject constructor(@Assisted initialState: PushGatewayViewState,
private val session: Session)
: VectorViewModel<PushGatewayViewState, PushGatewayAction, EmptyViewEvents>(initialState) {
: VectorViewModel<PushGatewayViewState, PushGatewayAction, PushGatewayViewEvents>(initialState) {
@AssistedFactory
interface Factory {
@ -70,10 +71,21 @@ class PushGatewaysViewModel @AssistedInject constructor(@Assisted initialState:
override fun handle(action: PushGatewayAction) {
when (action) {
is PushGatewayAction.Refresh -> handleRefresh()
is PushGatewayAction.Refresh -> handleRefresh()
is PushGatewayAction.RemovePusher -> removePusher(action.pusher)
}.exhaustive
}
private fun removePusher(pusher: Pusher) {
viewModelScope.launch {
kotlin.runCatching {
session.removePusher(pusher)
}.onFailure {
_viewEvents.post(PushGatewayViewEvents.RemovePusherFailed(it))
}
}
}
private fun handleRefresh() {
session.refreshPushers()
}

View file

@ -120,7 +120,6 @@
android:text="@string/push_gateway_item_format"
android:textStyle="bold" />
<TextView
android:id="@+id/pushGatewayFormatValue"
style="@style/Widget.Vector.TextView.Body"
@ -130,5 +129,11 @@
android:textStyle=""
tools:text="event_id_only" />
<Button
android:id="@+id/pushGatewayDeleteButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/remove" />
</LinearLayout>

View file

@ -1112,6 +1112,12 @@
<string name="settings_notification_advanced">Advanced Notification Settings</string>
<string name="settings_notification_by_event">Notification importance by event</string>
<string name="settings_notification_emails_category">Email notification</string>
<string name="settings_notification_emails_no_emails">To receive email with notification, please associate an email to your Matrix account</string>
<!-- The variable is a single email address, eg Enable email notifications for example@matrix.org -->
<string name="settings_notification_emails_enable_for_email">Enable email notifications for %s</string>
<string name="settings_notification_default">Default Notifications</string>
<string name="settings_notification_mentions_and_keywords">Mentions and Keywords</string>
<string name="settings_notification_other">Other</string>

View file

@ -55,6 +55,10 @@
</im.vector.app.core.preference.VectorPreferenceCategory>
<im.vector.app.core.preference.VectorPreferenceCategory
android:key="SETTINGS_EMAIL_NOTIFICATION_CATEGORY_PREFERENCE_KEY"
android:title="@string/settings_notification_emails_category"/>
<im.vector.app.core.preference.VectorPreferenceCategory
android:persistent="false"
android:title="@string/settings_notification_configuration">
@ -114,7 +118,6 @@
android:key="SETTINGS_START_ON_BOOT_PREFERENCE_KEY"
android:title="@string/settings_start_on_boot" />
</im.vector.app.core.preference.VectorPreferenceCategory>
<im.vector.app.core.preference.VectorPreferenceCategory android:title="@string/settings_troubleshoot_title">