This commit is contained in:
Valere
2019-06-19 10:46:59 +02:00
committed by Benoit Marty
parent 79735c6338
commit 0e46fc4c0a
112 changed files with 3957 additions and 249 deletions

View File

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/pushGatewayKind"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:textStyle="bold"
android:textSize="20sp"
android:textAllCaps="true"
tools:text="Http Pusher" />
<TextView
android:id="@+id/pushGatewayAppId"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:text="app_id"
android:textStyle="bold" />
<TextView
android:id="@+id/pushGatewayAppIdValue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:textStyle=""
tools:text="im.vector.app.android" />
<TextView
android:id="@+id/pushGatewayKey"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:text="push_key:"
android:textStyle="bold" />
<TextView
android:id="@+id/pushGatewayKeyValue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:textStyle=""
tools:text="fBbCDxVa-n8:APA91bE0wGY4ijpj-LQkkmjJYhNp2vA_9Xvabh02xaTKua9WA9wpNZwxfHdsbIDWthVXKPFTNcCl75ek1kqMGOggnUwnSCj-8ReF4G69pZVUhz-" />
<TextView
android:id="@+id/pushGatewayAppName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:text="app_display_name"
android:textStyle="bold" />
<TextView
android:id="@+id/pushGatewayAppNameValue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:textStyle=""
tools:text="EBMDOLFJD" />
<TextView
android:id="@+id/pushGatewayDeviceName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:text="device_name:"
android:textStyle="bold" />
<TextView
android:id="@+id/pushGatewayDeviceNameValue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:textStyle=""
tools:text="EBMDOLFJD" />
<TextView
android:id="@+id/pushGatewayURL"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:text="Url:"
android:textStyle="bold" />
<TextView
android:id="@+id/pushGatewayURLValue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:textStyle=""
tools:text="EBMDOLFJD" />
<TextView
android:id="@+id/pushGatewayFormat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:text="Format:"
android:textStyle="bold" />
<TextView
android:id="@+id/pushGatewayFormatValue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:textStyle=""
tools:text="event_id_only" />
</LinearLayout>

View File

@ -50,7 +50,7 @@ public class FcmHelper {
*
* @param activity the first launch Activity
*/
public static void ensureFcmTokenIsRetrieved(final Activity activity) {
public static void ensureFcmTokenIsRetrieved(final Activity activity, PushersManager pushersManager) {
// No op
}
}

View File

@ -10,14 +10,10 @@
"client_info": {
"mobilesdk_app_id": "1:912726360885:android:448c9b63161abc9c",
"android_client_info": {
"package_name": "im.vector.riotredesign"
"package_name": "im.vector.alpha"
}
},
"oauth_client": [
{
"client_id": "912726360885-rsae0i66rgqt6ivnudu1pv4tksg9i8b2.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "912726360885-e87n3jva9uoj4vbidvijq78ebg02asv2.apps.googleusercontent.com",
"client_type": 3
@ -29,15 +25,100 @@
}
],
"services": {
"analytics_service": {
"status": 1
},
"appinvite_service": {
"status": 1,
"other_platform_oauth_client": []
},
"ads_service": {
"status": 2
"other_platform_oauth_client": [
{
"client_id": "912726360885-rsae0i66rgqt6ivnudu1pv4tksg9i8b2.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:912726360885:android:3120c24f6ef22f2b",
"android_client_info": {
"package_name": "im.vector.app"
}
},
"oauth_client": [
{
"client_id": "912726360885-e87n3jva9uoj4vbidvijq78ebg02asv2.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyAFZX8IhIfgzdOZvxDP_ISO5WYoU7jmQ5c"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "912726360885-rsae0i66rgqt6ivnudu1pv4tksg9i8b2.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:912726360885:android:25ef253beaff462e",
"android_client_info": {
"package_name": "im.vector.riotredesign"
}
},
"oauth_client": [
{
"client_id": "912726360885-e87n3jva9uoj4vbidvijq78ebg02asv2.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyAFZX8IhIfgzdOZvxDP_ISO5WYoU7jmQ5c"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "912726360885-rsae0i66rgqt6ivnudu1pv4tksg9i8b2.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:912726360885:android:bb204b7a7b08a10b",
"android_client_info": {
"package_name": "im.veon"
}
},
"oauth_client": [
{
"client_id": "912726360885-e87n3jva9uoj4vbidvijq78ebg02asv2.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyAFZX8IhIfgzdOZvxDP_ISO5WYoU7jmQ5c"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "912726360885-rsae0i66rgqt6ivnudu1pv4tksg9i8b2.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
}

View File

@ -23,16 +23,15 @@ import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.widget.Toast;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.InstanceIdResult;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.firebase.iid.FirebaseInstanceId;
import im.vector.riotredesign.R;
import im.vector.riotredesign.core.pushers.PushersManager;
import timber.log.Timber;
/**
@ -66,6 +65,7 @@ public class FcmHelper {
.edit()
.putString(PREFS_KEY_FCM_TOKEN, token)
.apply();
}
/**
@ -73,8 +73,8 @@ public class FcmHelper {
*
* @param activity the first launch Activity
*/
public static void ensureFcmTokenIsRetrieved(final Activity activity) {
if (TextUtils.isEmpty(getFcmToken(activity))) {
public static void ensureFcmTokenIsRetrieved(final Activity activity, PushersManager pushersManager) {
// if (TextUtils.isEmpty(getFcmToken(activity))) {
//vfe: according to firebase doc
@ -82,18 +82,11 @@ public class FcmHelper {
if (checkPlayServices(activity)) {
try {
FirebaseInstanceId.getInstance().getInstanceId()
.addOnSuccessListener(activity, new OnSuccessListener<InstanceIdResult>() {
@Override
public void onSuccess(InstanceIdResult instanceIdResult) {
storeFcmToken(activity, instanceIdResult.getToken());
}
.addOnSuccessListener(activity, instanceIdResult -> {
storeFcmToken(activity, instanceIdResult.getToken());
pushersManager.registerPusherWithFcmKey(instanceIdResult.getToken());
})
.addOnFailureListener(activity, new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed " + e.getMessage());
}
});
.addOnFailureListener(activity, e -> Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed " + e.getMessage()));
} catch (Throwable e) {
Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed " + e.getMessage());
}
@ -101,7 +94,7 @@ public class FcmHelper {
Toast.makeText(activity, R.string.no_valid_google_play_services_apk, Toast.LENGTH_SHORT).show();
Timber.e("No valid Google Play Services found. Cannot use FCM.");
}
}
// }
}
/**

View File

@ -22,13 +22,19 @@ package im.vector.riotredesign.push.fcm
import android.os.Handler
import android.os.Looper
import android.text.TextUtils
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.work.*
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
import im.vector.riotredesign.BuildConfig
import im.vector.riotredesign.R
import im.vector.riotredesign.core.preference.BingRule
import im.vector.riotredesign.core.pushers.PushersManager
import im.vector.riotredesign.features.badge.BadgeProxy
import im.vector.riotredesign.features.notifications.NotifiableEventResolver
import im.vector.riotredesign.features.notifications.NotifiableMessageEvent
@ -36,13 +42,15 @@ import im.vector.riotredesign.features.notifications.NotificationDrawerManager
import im.vector.riotredesign.features.notifications.SimpleNotifiableEvent
import org.koin.android.ext.android.inject
import timber.log.Timber
import java.util.concurrent.TimeUnit
/**
* Class extending FirebaseMessagingService.
*/
class VectorFirebaseMessagingService : FirebaseMessagingService() {
val notificationDrawerManager by inject<NotificationDrawerManager>()
private val notificationDrawerManager by inject<NotificationDrawerManager>()
private val pusherManager by inject<PushersManager>()
private val notifiableEventResolver by lazy {
NotifiableEventResolver(this)
@ -67,18 +75,14 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
Timber.i("## onMessageReceived()" + message.data.toString())
Timber.i("## onMessageReceived() from FCM with priority " + message.priority)
}
//safe guard
/* TODO
val pushManager = Matrix.getInstance(applicationContext).pushManager
if (!pushManager.areDeviceNotificationsAllowed()) {
Timber.i("## onMessageReceived() : the notifications are disabled")
return
mUIHandler.post {
if (ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
//we are in foreground, let the sync do the things?
Timber.v("PUSH received in a foreground state, ignore")
} else {
onMessageReceivedInternal(message.data)
}
}
*/
//TODO if the app is in foreground, we could just ignore this. The sync loop is already going?
// TODO mUIHandler.post { onMessageReceivedInternal(message.data, pushManager) }
}
/**
@ -90,9 +94,22 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
override fun onNewToken(refreshedToken: String?) {
Timber.i("onNewToken: FCM Token has been updated")
FcmHelper.storeFcmToken(this, refreshedToken)
// TODO Matrix.getInstance(this)?.pushManager?.resetFCMRegistration(refreshedToken)
if (refreshedToken == null) {
Timber.w("onNewToken:received null token")
} else {
pusherManager.registerPusherWithFcmKey(refreshedToken)
}
}
/**
* Called when the FCM server deletes pending messages. This may be due to:
* - Too many messages stored on the FCM server.
* This can occur when an app's servers send a bunch of non-collapsible messages to FCM servers while the device is offline.
* - The device hasn't connected in a long time and the app server has recently (within the last 4 weeks)
* sent a message to the app on that device.
*
* It is recommended that the app do a full sync with the app server after receiving this call.
*/
override fun onDeletedMessages() {
Timber.v("## onDeletedMessages()")
}
@ -103,53 +120,63 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
* @param data Data map containing message data as key/value pairs.
* For Set of keys use data.keySet().
*/
private fun onMessageReceivedInternal(data: Map<String, String> /*, pushManager: PushManager*/) {
private fun onMessageReceivedInternal(data: Map<String, String>) {
try {
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
Timber.i("## onMessageReceivedInternal() : $data")
}
val eventId = data["event_id"]
val roomId = data["room_id"]
if (eventId == null || roomId == null) {
Timber.e("## onMessageReceivedInternal() missing eventId and/or roomId")
return
}
// update the badge counter
val unreadCount = data.get("unread")?.let { Integer.parseInt(it) } ?: 0
BadgeProxy.updateBadgeCount(applicationContext, unreadCount)
/* TODO
val session = Matrix.getInstance(applicationContext)?.defaultSession
val session = safeGetCurrentSession()
if (VectorApp.isAppInBackground() && !pushManager.isBackgroundSyncAllowed) {
//Notification contains metadata and maybe data information
handleNotificationWithoutSyncingMode(data, session)
if (session == null) {
Timber.w("## Can't sync from push, no current session")
} else {
// Safe guard... (race?)
if (isEventAlreadyKnown(data["event_id"], data["room_id"])) return
//Catch up!!
EventStreamServiceX.onPushReceived(this)
if (isEventAlreadyKnown(eventId, roomId)) {
Timber.i("Ignoring push, event already knwown")
} else {
Timber.v("Requesting background sync")
val workRequest = OneTimeWorkRequestBuilder<SyncWorker>()
.setInputData(Data.Builder().put("timeout", 0L).build())
.setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build())
.setBackoffCriteria(BackoffPolicy.LINEAR, 10_000, TimeUnit.MILLISECONDS)
.build()
WorkManager.getInstance().enqueueUniqueWork("BG_SYNCP", ExistingWorkPolicy.REPLACE, workRequest)
}
}
*/
} catch (e: Exception) {
Timber.e(e, "## onMessageReceivedInternal() failed : " + e.message)
}
}
fun safeGetCurrentSession(): Session? {
try {
return Matrix.getInstance().currentSession
} catch (e: Throwable) {
Timber.e(e, "## Failed to get current session")
return null
}
}
// check if the event was not yet received
// a previous catchup might have already retrieved the notified event
private fun isEventAlreadyKnown(eventId: String?, roomId: String?): Boolean {
if (null != eventId && null != roomId) {
try {
/* TODO
val sessions = Matrix.getInstance(applicationContext).sessions
if (null != sessions && !sessions.isEmpty()) {
for (session in sessions) {
if (session.dataHandler?.store?.isReady == true) {
session.dataHandler.store?.getEvent(eventId, roomId)?.let {
Timber.e("## isEventAlreadyKnown() : ignore the event " + eventId
+ " in room " + roomId + " because it is already known")
return true
}
}
}
}
*/
val session = safeGetCurrentSession() ?: return false
val room = session.getRoom(roomId) ?: return false
return room.getTimeLineEvent(eventId) != null
} catch (e: Exception) {
Timber.e(e, "## isEventAlreadyKnown() : failed to check if the event was already defined " + e.message)
}
@ -199,7 +226,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
return
} else {
var notifiableEvent = notifiableEventResolver.resolveEvent(event, null, null /* TODO session.fulfillRule(event) */, session)
var notifiableEvent = notifiableEventResolver.resolveEvent(event, null /* TODO session.fulfillRule(event) */, session)
if (notifiableEvent == null) {
Timber.e("Unsupported notifiable event ${eventId}")
@ -211,7 +238,8 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
if (notifiableEvent is NotifiableMessageEvent) {
if (TextUtils.isEmpty(notifiableEvent.senderName)) {
notifiableEvent.senderName = data["sender_display_name"] ?: data["sender"] ?: ""
notifiableEvent.senderName = data["sender_display_name"]
?: data["sender"] ?: ""
}
if (TextUtils.isEmpty(notifiableEvent.roomName)) {
notifiableEvent.roomName = findRoomNameBestEffort(data, session) ?: ""

View File

@ -3,7 +3,6 @@
xmlns:tools="http://schemas.android.com/tools"
package="im.vector.riotredesign">
<uses-permission android:name="android.permission.INTERNET" />
<application
@ -16,6 +15,12 @@
android:supportsRtl="true"
android:theme="@style/AppTheme.Light"
tools:replace="android:allowBackup">
<receiver
android:name=".core.services.RestartBroadcastReceiver"
android:enabled="true"
android:exported="false">
</receiver>
<activity
android:name=".features.MainActivity"
@ -32,7 +37,6 @@
</intent-filter>
</activity-alias>
<activity android:name=".features.home.HomeActivity" />
<activity android:name=".features.login.LoginActivity" />
<activity android:name=".features.media.ImageMediaViewerActivity" />
@ -58,9 +62,8 @@
android:label="@string/encryption_message_recovery" />
<activity
android:name="im.vector.riotredesign.features.reactions.EmojiReactionPickerActivity"
android:name=".features.reactions.EmojiReactionPickerActivity"
android:label="@string/title_activity_emoji_reaction_picker" />
<activity android:name=".features.roomdirectory.RoomDirectoryActivity" />
<activity android:name=".features.roomdirectory.roompreview.RoomPreviewActivity" />
<activity android:name=".features.home.room.detail.RoomDetailActivity" />
@ -69,6 +72,15 @@
<service
android:name=".core.services.CallService"
android:exported="false" />
<!--<service-->
<!--android:name="im.vector.matrix.android.internal.session.sync.job.SyncService"-->
<!--android:exported="false" />-->
<service
android:name=".core.services.VectorSyncService"
android:exported="false">
</service>
<provider
android:name="androidx.core.content.FileProvider"
@ -79,7 +91,6 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/riotx_provider_paths" />
</provider>
</application>
</manifest>

View File

@ -16,14 +16,25 @@
package im.vector.riotredesign
import android.app.AlarmManager
import android.app.Application
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.content.res.Configuration
import android.os.Handler
import android.os.HandlerThread
import android.os.IBinder
import androidx.core.provider.FontRequest
import androidx.core.provider.FontsContractCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.multidex.MultiDex
import androidx.work.*
import com.airbnb.epoxy.EpoxyAsyncUtil
import com.airbnb.epoxy.EpoxyController
import com.facebook.stetho.Stetho
@ -31,24 +42,32 @@ import com.github.piasy.biv.BigImageViewer
import com.github.piasy.biv.loader.glide.GlideImageLoader
import com.jakewharton.threetenabp.AndroidThreeTen
import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.internal.session.sync.job.SyncService
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
import im.vector.riotredesign.core.di.AppModule
import im.vector.riotredesign.core.services.RestartBroadcastReceiver
import im.vector.riotredesign.core.services.VectorSyncService
import im.vector.riotredesign.features.configuration.VectorConfiguration
import im.vector.riotredesign.features.crypto.keysbackup.KeysBackupModule
import im.vector.riotredesign.features.home.HomeModule
import im.vector.riotredesign.features.lifecycle.VectorActivityLifecycleCallbacks
import im.vector.riotredesign.features.notifications.NotificationUtils
import im.vector.riotredesign.features.notifications.PushRuleTriggerListener
import im.vector.riotredesign.features.rageshake.VectorFileLogger
import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.riotredesign.features.roomdirectory.RoomDirectoryModule
import im.vector.riotredesign.features.version.getVersion
import org.koin.android.ext.android.get
import org.koin.android.ext.android.inject
import org.koin.log.EmptyLogger
import org.koin.standalone.StandAloneContext.startKoin
import timber.log.Timber
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.TimeUnit
class VectorApplication : Application(), SyncService.SyncListener {
class VectorApplication : Application() {
lateinit var appContext: Context
//font thread handler
@ -56,6 +75,29 @@ class VectorApplication : Application() {
val vectorConfiguration: VectorConfiguration by inject()
private var mBinder: SyncService.LocalBinder? = null
private val connection = object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName?) {
Timber.v("Service unbounded")
mBinder?.removeListener(this@VectorApplication)
mBinder = null
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Timber.v("Service bounded")
mBinder = service as SyncService.LocalBinder
mBinder?.addListener(this@VectorApplication)
mBinder?.getService()?.nextBatchDelay = 0
mBinder?.getService()?.timeout = 10_000L
mBinder?.getService()?.doSync()
}
}
// var slowMode = false
override fun onCreate() {
super.onCreate()
appContext = this
@ -91,10 +133,111 @@ class VectorApplication : Application() {
R.array.com_google_android_gms_fonts_certs
)
// val efp = koin.koinContext.get<EmojiCompatFontProvider>()
FontsContractCompat.requestFont(this, fontRequest, koin.koinContext.get<EmojiCompatFontProvider>(), getFontThreadHandler())
vectorConfiguration.initConfiguration()
NotificationUtils.createNotificationChannels(applicationContext)
ProcessLifecycleOwner.get().lifecycle.addObserver(object : LifecycleObserver {
fun cancelAlarm() {
val intent = Intent(applicationContext, RestartBroadcastReceiver::class.java)
val pIntent = PendingIntent.getBroadcast(applicationContext, RestartBroadcastReceiver.REQUEST_CODE,
intent, PendingIntent.FLAG_UPDATE_CURRENT)
val alarm = getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarm.cancel(pIntent)
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun entersForeground() {
// HttpLongPoolingSyncService.startService(applicationContext)
// cancelAlarm()
if (Matrix.getInstance().currentSession == null) return
WorkManager.getInstance().cancelAllWorkByTag("BG_SYNC")
Intent(applicationContext, VectorSyncService::class.java).also { intent ->
// intent.action = "NORMAL"
// try {
// startService(intent)
// } catch (e: Throwable) {
// Timber.e("Failed to launch sync service")
// }
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
}
var isPushAvailable = true
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun entersBackground() {
Timber.i("App entered background")
//we have here 3 modes
if (isPushAvailable) {
// PUSH IS AVAILABLE:
// Just stop the service, we will sync when a notification is received
try {
unbindService(connection)
mBinder?.getService()?.stopMe()
mBinder = null
} catch (t: Throwable) {
Timber.e(t)
}
} else {
// NO PUSH, and don't care about battery
// unbindService(connection)
// mBinder?.getService()?.stopMe()// kill also
// mBinder = null
//In this case we will keep a permanent
//TODO if no push schedule reccuring alarm
// val workRequest = PeriodicWorkRequestBuilder<SyncWorker>(1, TimeUnit.MINUTES)
// .setConstraints(Constraints.Builder()
// .setRequiredNetworkType(NetworkType.CONNECTED)
// .build())
// .setBackoffCriteria(BackoffPolicy.LINEAR, 10_000, TimeUnit.MILLISECONDS)
// .build()
// WorkManager.getInstance().enqueueUniquePeriodicWork(
// "BG_SYNC",
// ExistingPeriodicWorkPolicy.KEEP,
// workRequest)
val workRequest = OneTimeWorkRequestBuilder<SyncWorker>()
// .setInitialDelay(30_000, TimeUnit.MILLISECONDS)
.setInputData(Data.Builder().put("timeout", 0L).build())
.setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build())
.setBackoffCriteria(BackoffPolicy.LINEAR, 10_000, TimeUnit.MILLISECONDS)
.build()
WorkManager.getInstance().enqueueUniqueWork("BG_SYNCP", ExistingWorkPolicy.REPLACE, workRequest)
// val intent = Intent(applicationContext, RestartBroadcastReceiver::class.java)
// // Create a PendingIntent to be triggered when the alarm goes off
// val pIntent = PendingIntent.getBroadcast(applicationContext, RestartBroadcastReceiver.REQUEST_CODE,
// intent, PendingIntent.FLAG_UPDATE_CURRENT);
// // Setup periodic alarm every every half hour from this point onwards
// val firstMillis = System.currentTimeMillis(); // alarm is set right away
// val alarmMgr = getSystemService(Context.ALARM_SERVICE) as AlarmManager
// // First parameter is the type: ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP, RTC_WAKEUP
// // Interval can be INTERVAL_FIFTEEN_MINUTES, INTERVAL_HALF_HOUR, INTERVAL_HOUR, INTERVAL_DAY
//// alarm.setInexactRepeating(AlarmManager.RTC_WAKEUP, firstMillis,
//// 30_000L, pIntent)
// alarmMgr.set(AlarmManager.RTC_WAKEUP, firstMillis, pIntent);
Timber.i("Alarm scheduled to restart service")
}
}
})
Matrix.getInstance().currentSession?.let {
it.refreshPushers()
//bind to the sync service
get<PushRuleTriggerListener>().startWithSession(it)
}
}
private fun logInfo() {
@ -130,4 +273,36 @@ class VectorApplication : Application() {
return mFontThreadHandler!!
}
override fun onSyncFinsh() {
//in foreground sync right now!!
Timber.v("Sync just finished")
// mBinder?.getService()?.doSync()
}
override fun networkNotAvailable() {
//we then want to retry in 10s?
}
override fun onFailed(failure: Throwable) {
//stop it also?
// if (failure is Failure.NetworkConnection
// && failure.cause is SocketTimeoutException) {
// // Timeout are not critical just retry?
// //TODO
// }
//
// if (failure !is Failure.NetworkConnection
// || failure.cause is JsonEncodingException) {
// //TODO Retry in 10S?
// }
//
// if (failure is Failure.ServerError
// && (failure.error.code == MatrixError.UNKNOWN_TOKEN || failure.error.code == MatrixError.MISSING_TOKEN)) {
// // No token or invalid token, stop the thread
// mBinder?.getService()?.unbindService(connection)
// mBinder?.getService()?.stopMe()
// }
}
}

View File

@ -21,6 +21,8 @@ import android.content.Context.MODE_PRIVATE
import im.vector.matrix.android.api.Matrix
import im.vector.riotredesign.EmojiCompatFontProvider
import im.vector.riotredesign.core.error.ErrorFormatter
import im.vector.riotredesign.core.pushers.PushersManager
import im.vector.riotredesign.core.resources.AppNameProvider
import im.vector.riotredesign.core.resources.LocaleProvider
import im.vector.riotredesign.core.resources.StringArrayProvider
import im.vector.riotredesign.core.resources.StringProvider
@ -33,7 +35,9 @@ import im.vector.riotredesign.features.home.room.list.AlphabeticalRoomComparator
import im.vector.riotredesign.features.home.room.list.ChronologicalRoomComparator
import im.vector.riotredesign.features.navigation.DefaultNavigator
import im.vector.riotredesign.features.navigation.Navigator
import im.vector.riotredesign.features.notifications.NotifiableEventResolver
import im.vector.riotredesign.features.notifications.NotificationDrawerManager
import im.vector.riotredesign.features.notifications.PushRuleTriggerListener
import org.koin.dsl.module.module
class AppModule(private val context: Context) {
@ -80,10 +84,18 @@ class AppModule(private val context: Context) {
ErrorFormatter(get())
}
single {
PushRuleTriggerListener(get(),get())
}
single {
NotificationDrawerManager(context)
}
single {
NotifiableEventResolver(context)
}
factory {
Matrix.getInstance().currentSession!!
}
@ -103,5 +115,13 @@ class AppModule(private val context: Context) {
single {
EmojiCompatFontProvider()
}
single {
AppNameProvider(context)
}
single {
PushersManager(get(), get(), get(), get())
}
}
}

View File

@ -28,6 +28,8 @@ abstract class VectorPreferenceFragment : PreferenceFragmentCompat() {
activity as VectorBaseActivity
}
abstract var titleRes: Int
/* ==========================================================================================
* Life cycle
* ========================================================================================== */
@ -36,6 +38,7 @@ abstract class VectorPreferenceFragment : PreferenceFragmentCompat() {
override fun onResume() {
super.onResume()
(activity as? VectorBaseActivity)?.supportActionBar?.setTitle(titleRes)
Timber.v("onResume Fragment ${this.javaClass.simpleName}")
}

View File

@ -0,0 +1,34 @@
package im.vector.riotredesign.core.pushers
import im.vector.matrix.android.api.session.Session
import im.vector.riotredesign.R
import im.vector.riotredesign.core.resources.AppNameProvider
import im.vector.riotredesign.core.resources.LocaleProvider
import im.vector.riotredesign.core.resources.StringProvider
private const val DEFAULT_PUSHER_FILE_TAG = "mobile"
class PushersManager(
private val currentSession: Session,
private val localeProvider: LocaleProvider,
private val stringProvider: StringProvider,
private val appNameProvider: AppNameProvider
) {
fun registerPusherWithFcmKey(pushKey: String) {
var profileTag = DEFAULT_PUSHER_FILE_TAG + "_" + Math.abs(currentSession.sessionParams.credentials.userId.hashCode())
currentSession.addHttpPusher(
pushKey,
stringProvider.getString(R.string.pusher_app_id),
profileTag,
localeProvider.current().language,
appNameProvider.getAppName(),
currentSession.sessionParams.credentials.deviceId ?: "MOBILE",
stringProvider.getString(R.string.pusher_http_url),
false,
true
)
}
}

View File

@ -0,0 +1,26 @@
package im.vector.riotredesign.core.resources
import android.content.Context
import timber.log.Timber
class AppNameProvider(private val context: Context) {
fun getAppName(): String {
try {
val appPackageName = context.applicationContext.packageName
val pm = context.packageManager
val appInfo = pm.getApplicationInfo(appPackageName, 0)
var appName = pm.getApplicationLabel(appInfo).toString()
// Use appPackageName instead of appName if appName contains any non-ASCII character
if (!appName.matches("\\A\\p{ASCII}*\\z".toRegex())) {
appName = appPackageName
}
return appName
} catch (e: Exception) {
Timber.e(e, "## AppNameProvider() : failed " + e.message)
return "RiotXAndroid"
}
}
}

View File

@ -25,6 +25,4 @@ class LocaleProvider(private val resources: Resources) {
fun current(): Locale {
return ConfigurationCompat.getLocales(resources.configuration)[0]
}
}

View File

@ -0,0 +1,102 @@
//package im.vector.riotredesign.core.services
//
//import android.app.NotificationManager
//import android.content.Context
//import android.content.Intent
//import android.os.Build.VERSION.SDK_INT
//import android.os.Build.VERSION_CODES
//import android.os.Handler
//import android.os.HandlerThread
//import android.os.Looper
//import androidx.core.content.ContextCompat.startForegroundService
//import im.vector.matrix.android.api.Matrix
//import im.vector.matrix.android.api.session.Session
//import im.vector.riotredesign.R
//import im.vector.riotredesign.features.notifications.NotificationUtils
//import timber.log.Timber
//import java.net.HttpURLConnection
//import java.net.URL
//
//
///**
// *
// * This is used to display message notifications to the user when Push is not enabled (or not configured)
// *
// * This service is used to implement a long pooling mechanism in order to get messages from
// * the home server when the user is not interacting with the app.
// *
// * It is intended to be started when the app enters background, and stopped when app is in foreground.
// *
// * When in foreground, the app uses another mechanism to get messages (doing sync wia a thread).
// *
// */
//class HttpLongPoolingSyncService : VectorService() {
//
// private var mServiceLooper: Looper? = null
// private var mHandler: Handler? = null
// private val currentSessions = ArrayList<Session>()
// private var mCount = 0
// private var lastTimeMs = System.currentTimeMillis()
//
// lateinit var myRun: () -> Unit
// override fun onCreate() {
// //Add the permanent listening notification
// super.onCreate()
//
// if (SDK_INT >= VERSION_CODES.O) {
// val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// val notification = NotificationUtils.buildForegroundServiceNotification(applicationContext, R.string.notification_listening_for_events, false)
// startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification)
// }
// val thread = HandlerThread("My service Handler")
// thread.start()
//
// mServiceLooper = thread.looper
// mHandler = Handler(mServiceLooper)
// myRun = {
// val diff = System.currentTimeMillis() - lastTimeMs
// lastTimeMs = System.currentTimeMillis()
// val isAlive = Matrix.getInstance().currentSession?.isSyncThreadAlice()
// val state = Matrix.getInstance().currentSession?.syncThreadState()
// Timber.w(" timeDiff[${diff/1000}] Yo me here $mCount, sync thread is Alive? $isAlive, state:$state")
// mCount++
// mHandler?.postDelayed(Runnable { myRun() }, 10_000L)
// }
// }
//
// override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// //START_STICKY mode makes sense for things that will be explicitly started
// //and stopped to run for arbitrary periods of time
//
// mHandler?.post {
// myRun()
// }
// return START_STICKY
// }
//
//
// override fun onDestroy() {
// //TODO test if this service should be relaunched (preference)
// Timber.i("Service is destroyed, relaunch asap")
// Intent(applicationContext, RestartBroadcastReceiver::class.java).also { sendBroadcast(it) }
// super.onDestroy()
// }
//
// companion object {
//
// fun startService(context: Context) {
// Timber.i("Start sync service")
// val intent = Intent(context, HttpLongPoolingSyncService::class.java)
// try {
// if (SDK_INT >= VERSION_CODES.O) {
// startForegroundService(context, intent)
// } else {
// context.startService(intent)
// }
// } catch (ex: Throwable) {
// //TODO
// Timber.e(ex)
// }
// }
// }
//}

View File

@ -0,0 +1,37 @@
package im.vector.riotredesign.core.services
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import androidx.core.content.ContextCompat
import androidx.legacy.content.WakefulBroadcastReceiver
import im.vector.matrix.android.internal.session.sync.job.SyncService
import timber.log.Timber
class RestartBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// This method is called when the BroadcastReceiver is receiving an Intent broadcast.
Timber.d("RestartBroadcastReceiver received intent")
Intent(context,VectorSyncService::class.java).also {
it.action = "SLOW"
context.startService(it)
try {
if (SDK_INT >= Build.VERSION_CODES.O) {
ContextCompat.startForegroundService(context, intent)
} else {
context.startService(intent)
}
} catch (ex: Throwable) {
//TODO
Timber.e(ex)
}
}
}
companion object {
const val REQUEST_CODE = 0
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright 2019 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.riotredesign.core.services
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.os.IBinder
import im.vector.matrix.android.internal.session.sync.job.SyncService
import im.vector.riotredesign.R
import im.vector.riotredesign.features.notifications.NotificationUtils
import timber.log.Timber
import java.util.*
class VectorSyncService : SyncService() {
override fun onCreate() {
Timber.v("VectorSyncService - onCreate ")
super.onCreate()
}
override fun onDestroy() {
Timber.v("VectorSyncService - onDestroy ")
removeForegroundNotif()
super.onDestroy()
}
private fun removeForegroundNotif() {
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancel(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE)
}
/**
* Service is started only in fdroid mode when no FCM is available
* Otherwise it is bounded
*/
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Timber.v("VectorSyncService - onStartCommand ")
if (SDK_INT >= Build.VERSION_CODES.O) {
val notification = NotificationUtils.buildForegroundServiceNotification(applicationContext, R.string.notification_listening_for_events, false)
startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification)
}
return super.onStartCommand(intent, flags, startId)
}
/**
* If the service is bounded and the service was previously started we can remove foreground notif
*/
override fun onBind(intent: Intent?): IBinder {
Timber.v("VectorSyncService - onBind ")
stopForeground(true)
return super.onBind(intent)
}
override fun onUnbind(intent: Intent?): Boolean {
Timber.v("VectorSyncService - onUnbind ")
return super.onUnbind(intent)
}
}

View File

@ -59,17 +59,24 @@ class MainActivity : VectorBaseActivity() {
} else {
// Handle some wanted cleanup
when {
clearCredentials -> session.signOut(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
Timber.w("SIGN_OUT: success, start app")
start()
}
})
clearCache -> session.clearCache(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
start()
}
})
clearCredentials -> {
session.signOut(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
Timber.w("SIGN_OUT: success, start app")
//TODO stop sync service
start()
}
})
}
clearCache -> {
//TODO stop sync service
session.clearCache(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
//TODO start sync service
start()
}
})
}
else -> start()
}
}

View File

@ -39,9 +39,11 @@ import im.vector.riotredesign.core.platform.ToolbarConfigurable
import im.vector.riotredesign.core.platform.VectorBaseActivity
import im.vector.riotredesign.features.crypto.keysrequest.KeyRequestHandler
import im.vector.riotredesign.features.crypto.verification.IncomingVerificationRequestHandler
import im.vector.riotredesign.core.pushers.PushersManager
import im.vector.riotredesign.features.rageshake.BugReporter
import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.riotredesign.features.workers.signout.SignOutUiWorker
import im.vector.riotredesign.push.fcm.FcmHelper
import kotlinx.android.synthetic.main.activity_home.*
import org.koin.android.ext.android.inject
import org.koin.android.scope.ext.android.bindScope
@ -52,7 +54,7 @@ import im.vector.riotredesign.features.workers.signout.SignOutViewModel
class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
// Supported navigation actions for this Activity
// Supported navigation domainActions for this Activity
sealed class Navigation {
object OpenDrawer : Navigation()
}
@ -60,6 +62,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
private val homeActivityViewModel: HomeActivityViewModel by viewModel()
private lateinit var navigationViewModel: HomeNavigationViewModel
private val homeNavigator by inject<HomeNavigator>()
private val pushManager by inject<PushersManager>()
// TODO Move this elsewhere
private val incomingVerificationRequestHandler by inject<IncomingVerificationRequestHandler>()
@ -80,6 +83,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
super.onCreate(savedInstanceState)
bindScope(getOrCreateScope(HomeModule.HOME_SCOPE))
homeNavigator.activity = this
FcmHelper.ensureFcmTokenIsRetrieved(this, pushManager)
navigationViewModel = ViewModelProviders.of(this).get(HomeNavigationViewModel::class.java)

View File

@ -30,7 +30,7 @@ import im.vector.matrix.android.api.session.user.model.User
* QUOTE: User is currently quoting a message
* EDIT: User is currently editing an existing message
*
* Depending on the state the bottom toolbar will change (icons/preview/actions...)
* Depending on the state the bottom toolbar will change (icons/preview/domainActions...)
*/
enum class SendMode {
REGULAR,

View File

@ -20,7 +20,7 @@ import androidx.lifecycle.ViewModel
import im.vector.riotredesign.core.utils.LiveEvent
/**
* Activity shared view model to handle message actions
* Activity shared view model to handle message domainActions
*/
class ActionsHandler : ViewModel() {

View File

@ -38,8 +38,8 @@ import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInf
import kotlinx.android.synthetic.main.bottom_sheet_message_actions.*
/**
* Bottom sheet fragment that shows a message preview with list of contextual actions
* (Includes fragments for quick reactions and list of actions)
* Bottom sheet fragment that shows a message preview with list of contextual domainActions
* (Includes fragments for quick reactions and list of domainActions)
*/
class MessageActionsBottomSheet : BaseMvRxBottomSheetDialog() {

View File

@ -37,7 +37,7 @@ data class SimpleAction(val uid: String, val titleRes: Int, val iconResId: Int?,
data class MessageMenuState(val actions: List<SimpleAction> = emptyList()) : MvRxState
/**
* Manages list actions for a given message (copy / paste / forward...)
* Manages list domainActions for a given message (copy / paste / forward...)
*/
class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<MessageMenuState>(initialState) {

View File

@ -32,10 +32,12 @@ import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.showPassword
import im.vector.riotredesign.core.platform.VectorBaseActivity
import im.vector.riotredesign.features.home.HomeActivity
import im.vector.riotredesign.features.notifications.PushRuleTriggerListener
import io.reactivex.Observable
import io.reactivex.functions.Function3
import io.reactivex.rxkotlin.subscribeBy
import kotlinx.android.synthetic.main.activity_login.*
import org.koin.android.ext.android.get
private const val DEFAULT_HOME_SERVER_URI = "https://matrix.org"
private const val DEFAULT_IDENTITY_SERVER_URI = "https://vector.im"
@ -74,8 +76,10 @@ class LoginActivity : VectorBaseActivity() {
Matrix.getInstance().currentSession = data
data.open()
data.setFilter(FilterService.FilterPreset.RiotFilter)
data.startSync()
//TODO sync
// data.shoudPauseOnBackground(false)
// data.startSync()
get<PushRuleTriggerListener>().startWithSession(data)
goToHome()
}

View File

@ -16,9 +16,16 @@
package im.vector.riotredesign.features.notifications
import android.content.Context
import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.riotredesign.R
import im.vector.riotredesign.core.preference.BingRule
import timber.log.Timber
// TODO Remove
class RoomState {
@ -36,128 +43,128 @@ class NotifiableEventResolver(val context: Context) {
//private val eventDisplay = RiotEventDisplay(context)
fun resolveEvent(event: Event, roomState: RoomState?, bingRule: BingRule?, session: Session): NotifiableEvent? {
// TODO
return null
/*
val store = session.dataHandler.store
if (store == null) {
Log.e("## NotifiableEventResolver, unable to get store")
//TODO notify somehow that something did fail?
return null
}
fun resolveEvent(event: Event/*, roomState: RoomState?*/, bingRule: PushRule?, session: Session): NotifiableEvent? {
// val store = session.dataHandler.store
// if (store == null) {
// Log.e("## NotifiableEventResolver, unable to get store")
// //TODO notify somehow that something did fail?
// return null
// }
when (event.getClearType()) {
EventType.MESSAGE -> {
return resolveMessageEvent(event, bingRule, session, store)
}
EventType.ENCRYPTED -> {
val messageEvent = resolveMessageEvent(event, bingRule, session, store)
messageEvent?.lockScreenVisibility = NotificationCompat.VISIBILITY_PRIVATE
return messageEvent
}
EventType.STATE_ROOM_MEMBER -> {
return resolveStateRoomEvent(event, bingRule, session, store)
EventType.MESSAGE -> {
return resolveMessageEvent(event, bingRule, session)
}
// EventType.ENCRYPTED -> {
// val messageEvent = resolveMessageEvent(event, bingRule, session, store)
// messageEvent?.lockScreenVisibility = NotificationCompat.VISIBILITY_PRIVATE
// return messageEvent
// }
// EventType.STATE_ROOM_MEMBER -> {
// return resolveStateRoomEvent(event, bingRule, session, store)
// }
else -> {
//If the event can be displayed, display it as is
eventDisplay.getTextualDisplay(event, roomState)?.toString()?.let { body ->
return SimpleNotifiableEvent(
session.myUserId,
eventId = event.eventId,
noisy = bingRule?.notificationSound != null,
timestamp = event.originServerTs,
description = body,
soundName = bingRule?.notificationSound,
title = context.getString(R.string.notification_unknown_new_event),
type = event.getClearType())
}
// eventDisplay.getTextualDisplay(event, roomState)?.toString()?.let { body ->
// return SimpleNotifiableEvent(
// session.myUserId,
// eventId = event.eventId,
// noisy = bingRule?.notificationSound != null,
// timestamp = event.originServerTs,
// description = body,
// soundName = bingRule?.notificationSound,
// title = context.getString(R.string.notification_unknown_new_event),
// type = event.type)
// }
//Unsupported event
Timber.w("NotifiableEventResolver Received an unsupported event matching a bing rule")
return null
}
}
*/
}
/*
private fun resolveMessageEvent(event: Event, bingRule: BingRule?, session: MXSession, store: IMXStore): NotifiableEvent? {
private fun resolveMessageEvent(event: Event, pushRule: PushRule?, session: Session): NotifiableEvent? {
//If we are here, that means that the event should be notified to the user, we check now how it should be presented (sound)
val soundName = bingRule?.notificationSound
val noisy = bingRule?.notificationSound != null
// val soundName = pushRule?.notificationSound
val noisy = true//pushRule?.notificationSound != null
//The event only contains an eventId, and roomId (type is m.room.*) , we need to get the displayable content (names, avatar, text, etc...)
val room = store.getRoom(event.roomId /*roomID cannot be null (see Matrix SDK code)*/)
val room = session.getRoom(event.roomId!! /*roomID cannot be null (see Matrix SDK code)*/)
if (room == null) {
Timber.e("## Unable to resolve room for eventId [${event.eventId}] and roomID [${event.roomId}]")
// Ok room is not known in store, but we can still display something
val body = eventDisplay.getTextualDisplay(event, null)?.toString()
val body = event.content?.toModel<MessageContent>()?.body
?: context.getString(R.string.notification_unknown_new_event)
val roomName = context.getString(R.string.notification_unknown_room_name)
val senderDisplayName = event.sender ?: ""
val senderDisplayName = event.senderId ?: ""
val notifiableEvent = NotifiableMessageEvent(
eventId = event.eventId,
timestamp = event.originServerTs,
eventId = event.eventId ?: "",
timestamp = event.originServerTs ?: 0,
noisy = noisy,
senderName = senderDisplayName,
senderId = event.sender,
senderId = event.senderId,
body = body,
roomId = event.roomId,
roomId = event.roomId ?: "",
roomName = roomName)
notifiableEvent.matrixID = session.myUserId
notifiableEvent.soundName = soundName
notifiableEvent.matrixID = session.sessionParams.credentials.userId
// notifiableEvent.soundName = soundName
return notifiableEvent
} else {
val body = eventDisplay.getTextualDisplay(event, room.state)?.toString()
val tEvent = room.getTimeLineEvent(event.eventId!!)
val body = event.content?.toModel<MessageContent>()?.body
?: context.getString(R.string.notification_unknown_new_event)
val roomName = room.getRoomDisplayName(context)
val senderDisplayName = room.state.getMemberName(event.sender) ?: event.sender ?: ""
val roomName = event.roomId ?: "room"
val senderDisplayName = tEvent?.senderName ?: "?"
val notifiableEvent = NotifiableMessageEvent(
eventId = event.eventId,
timestamp = event.originServerTs,
eventId = event.eventId!!,
timestamp = event.originServerTs ?: 0,
noisy = noisy,
senderName = senderDisplayName,
senderId = event.sender,
senderId = event.senderId,
body = body,
roomId = event.roomId,
roomId = event.roomId ?: "00",
roomName = roomName,
roomIsDirect = room.isDirect)
roomIsDirect = true)
notifiableEvent.matrixID = session.myUserId
notifiableEvent.soundName = soundName
notifiableEvent.matrixID = session.sessionParams.credentials.userId
notifiableEvent.soundName = null
val roomAvatarPath = session.mediaCache?.thumbnailCacheFile(room.avatarUrl, 50)
if (roomAvatarPath != null) {
notifiableEvent.roomAvatarPath = roomAvatarPath.path
} else {
// prepare for the next time
session.mediaCache?.loadAvatarThumbnail(session.homeServerConfig, ImageView(context), room.avatarUrl, 50)
}
room.state.getMember(event.sender)?.avatarUrl?.let {
val size = context.resources.getDimensionPixelSize(R.dimen.profile_avatar_size)
val userAvatarUrlPath = session.mediaCache?.thumbnailCacheFile(it, size)
if (userAvatarUrlPath != null) {
notifiableEvent.senderAvatarPath = userAvatarUrlPath.path
} else {
// prepare for the next time
session.mediaCache?.loadAvatarThumbnail(session.homeServerConfig, ImageView(context), it, size)
}
}
// val roomAvatarPath = session.mediaCache?.thumbnailCacheFile(room.avatarUrl, 50)
// if (roomAvatarPath != null) {
// notifiableEvent.roomAvatarPath = roomAvatarPath.path
// } else {
// // prepare for the next time
// session.mediaCache?.loadAvatarThumbnail(session.homeServerConfig, ImageView(context), room.avatarUrl, 50)
// }
//
// room.state.getMember(event.sender)?.avatarUrl?.let {
// val size = context.resources.getDimensionPixelSize(R.dimen.profile_avatar_size)
// val userAvatarUrlPath = session.mediaCache?.thumbnailCacheFile(it, size)
// if (userAvatarUrlPath != null) {
// notifiableEvent.senderAvatarPath = userAvatarUrlPath.path
// } else {
// // prepare for the next time
// session.mediaCache?.loadAvatarThumbnail(session.homeServerConfig, ImageView(context), it, size)
// }
// }
return notifiableEvent
}
}
/*
private fun resolveStateRoomEvent(event: Event, bingRule: BingRule?, session: MXSession, store: IMXStore): NotifiableEvent? {
if (RoomMember.MEMBERSHIP_INVITE == event.contentAsJsonObject?.getAsJsonPrimitive("membership")?.asString) {
val room = store.getRoom(event.roomId /*roomID cannot be null (see Matrix SDK code)*/)

View File

@ -27,7 +27,7 @@ import org.koin.standalone.inject
import timber.log.Timber
/**
* Receives actions broadcast by notification (on click, on dismiss, inline replies, etc.)
* Receives domainActions broadcast by notification (on click, on dismiss, inline replies, etc.)
*/
class NotificationBroadcastReceiver : BroadcastReceiver(), KoinComponent {

View File

@ -42,7 +42,7 @@ class NotificationDrawerManager(val context: Context) {
private var firstTime = true
private var eventList = loadEventInfo()
private var myUserDisplayName: String = ""
private var myUserDisplayName: String = "Todo"
private var myUserAvatarUrl: String = ""
private val avatarSize = context.resources.getDimensionPixelSize(R.dimen.profile_avatar_size)
@ -185,7 +185,8 @@ class NotificationDrawerManager(val context: Context) {
if (myUserDisplayName.isBlank()) {
// Should not happen, but myUserDisplayName cannot be blank if used to create a Person
return
//TODO
// return
}
synchronized(eventList) {

View File

@ -64,7 +64,7 @@ object NotificationUtils {
const val NOTIFICATION_ID_FOREGROUND_SERVICE = 61
/* ==========================================================================================
* IDs for actions
* IDs for domainActions
* ========================================================================================== */
private const val JOIN_ACTION = "${BuildConfig.APPLICATION_ID}.NotificationActions.JOIN_ACTION"
@ -180,7 +180,7 @@ object NotificationUtils {
* @return the polling thread listener notification
*/
@SuppressLint("NewApi")
fun buildForegroundServiceNotification(context: Context, @StringRes subTitleResId: Int): Notification {
fun buildForegroundServiceNotification(context: Context, @StringRes subTitleResId: Int, withProgress: Boolean = true): Notification {
// build the pending intent go to the home screen if this is clicked.
val i = Intent(context, HomeActivity::class.java)
i.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
@ -190,16 +190,21 @@ object NotificationUtils {
val builder = NotificationCompat.Builder(context, LISTENING_FOR_EVENTS_NOTIFICATION_CHANNEL_ID)
.setContentTitle(context.getString(subTitleResId))
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
.setSmallIcon(R.drawable.logo_transparent)
.setProgress(0, 0, true)
.setSmallIcon(R.drawable.sync)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setColor(accentColor)
.setContentIntent(pi)
.apply {
if (withProgress) {
setProgress(0, 0, true)
}
}
// hide the notification from the status bar
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
builder.priority = NotificationCompat.PRIORITY_MIN
}
// PRIORITY_MIN should not be used with Service#startForeground(int, Notification)
builder.priority = NotificationCompat.PRIORITY_LOW
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
// builder.priority = NotificationCompat.PRIORITY_MIN
// }
val notification = builder.build()
@ -220,7 +225,7 @@ object NotificationUtils {
PendingIntent::class.java)
deprecatedMethod.invoke(notification, context, context.getString(R.string.app_name), context.getString(subTitleResId), pi)
} catch (ex: Exception) {
Timber.e(ex, "## buildNotification(): Exception - setLatestEventInfo() Msg=" + ex.message)
Timber.e(ex, "## buildNotification(): Exception - setLatestEventInfo() Msg=")
}
}
@ -421,7 +426,7 @@ object NotificationUtils {
priority = NotificationCompat.PRIORITY_LOW
}
//Add actions and notification intents
//Add domainActions and notification intents
// Mark room as read
val markRoomReadIntent = Intent(context, NotificationBroadcastReceiver::class.java)
markRoomReadIntent.action = MARK_ROOM_READ_ACTION

View File

@ -0,0 +1,46 @@
package im.vector.riotredesign.features.notifications
import im.vector.matrix.android.api.pushrules.Action
import im.vector.matrix.android.api.pushrules.PushRuleService
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.Event
import timber.log.Timber
class PushRuleTriggerListener(
private val resolver: NotifiableEventResolver,
private val drawerManager: NotificationDrawerManager
) : PushRuleService.PushRuleListener {
var session: Session? = null
override fun onMatchRule(event: Event, actions: List<Action>) {
if (session == null) {
Timber.e("Called without active session")
return
}
resolver.resolveEvent(event,null,session!!)?.let {
drawerManager.onNotifiableEventReceived(it)
}
}
override fun batchFinish() {
drawerManager.refreshNotificationDrawer(null)
}
fun startWithSession(session: Session) {
if (this.session != null) {
stop()
}
this.session = session
session.addPushRuleListener(this)
}
fun stop() {
session?.removePushRuleListener(this)
session = null
drawerManager.clearAllEvents()
drawerManager.refreshNotificationDrawer(null)
}
}

View File

@ -50,6 +50,8 @@ public class PreferencesManager {
public static final String SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY = "SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY";
public static final String SETTINGS_APP_TERM_CONDITIONS_PREFERENCE_KEY = "SETTINGS_APP_TERM_CONDITIONS_PREFERENCE_KEY";
public static final String SETTINGS_PRIVACY_POLICY_PREFERENCE_KEY = "SETTINGS_PRIVACY_POLICY_PREFERENCE_KEY";
//TODO delete
public static final String SETTINGS_NOTIFICATION_PRIVACY_PREFERENCE_KEY = "SETTINGS_NOTIFICATION_PRIVACY_PREFERENCE_KEY";
public static final String SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY = "SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY";
public static final String SETTINGS_NOTIFICATION_ADVANCED_PREFERENCE_KEY = "SETTINGS_NOTIFICATION_ADVANCED_PREFERENCE_KEY";

View File

@ -26,6 +26,7 @@ import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.VectorBaseActivity
import kotlinx.android.synthetic.main.activity_vector_settings.*
import org.koin.android.ext.android.inject
import timber.log.Timber
/**
* Displays the client settings.
@ -35,7 +36,6 @@ class VectorSettingsActivity : VectorBaseActivity(),
FragmentManager.OnBackStackChangedListener,
VectorSettingsFragmentInteractionListener {
private lateinit var vectorSettingsPreferencesFragment: VectorSettingsPreferencesFragment
override fun getLayoutRes() = R.layout.activity_vector_settings
@ -48,14 +48,15 @@ class VectorSettingsActivity : VectorBaseActivity(),
override fun initUiAndData() {
configureToolbar(settingsToolbar)
var vectorSettingsPreferencesFragment: Fragment? = null
if (isFirstCreation()) {
vectorSettingsPreferencesFragment = VectorSettingsPreferencesFragment.newInstance(session.sessionParams.credentials.userId)
vectorSettingsPreferencesFragment = VectorSettingsPreferencesFragmentV2.newInstance()
// display the fragment
supportFragmentManager.beginTransaction()
.replace(R.id.vector_settings_page, vectorSettingsPreferencesFragment, FRAGMENT_TAG)
.commit()
} else {
vectorSettingsPreferencesFragment = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as VectorSettingsPreferencesFragment
vectorSettingsPreferencesFragment = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG)
}
@ -77,19 +78,33 @@ class VectorSettingsActivity : VectorBaseActivity(),
override fun onPreferenceStartFragment(caller: PreferenceFragmentCompat?, pref: Preference?): Boolean {
var oFragment: Fragment? = null
if (PreferencesManager.SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY == pref?.key) {
if ("Legacy" == pref?.title) {
oFragment = VectorSettingsPreferencesFragment.newInstance(session.sessionParams.credentials.userId)
} else if (PreferencesManager.SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY == pref?.key) {
oFragment = VectorSettingsNotificationsTroubleshootFragment.newInstance(session.sessionParams.credentials.userId)
} else if (PreferencesManager.SETTINGS_NOTIFICATION_ADVANCED_PREFERENCE_KEY == pref?.key) {
oFragment = VectorSettingsAdvancedNotificationPreferenceFragment.newInstance(session.sessionParams.credentials.userId)
} else {
try {
pref?.fragment?.let {
oFragment = supportFragmentManager.fragmentFactory
.instantiate(
classLoader,
it, pref.extras)
}
} catch (e: Throwable) {
showSnackbar(getString(R.string.not_implemented))
Timber.e(e)
}
}
if (oFragment != null) {
oFragment.setTargetFragment(caller, 0)
oFragment!!.setTargetFragment(caller, 0)
// Replace the existing Fragment with the new Fragment
supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.anim_slide_in_bottom, R.anim.anim_slide_out_bottom,
R.anim.anim_slide_in_bottom, R.anim.anim_slide_out_bottom)
.replace(R.id.vector_settings_page, oFragment, pref?.title.toString())
.setCustomAnimations(R.anim.right_in, R.anim.fade_out,
R.anim.fade_in, R.anim.right_out)
.replace(R.id.vector_settings_page, oFragment!!, pref?.title.toString())
.addToBackStack(null)
.commit()
return true

View File

@ -51,6 +51,8 @@ class VectorSettingsAdvancedNotificationPreferenceFragment : VectorPreferenceFra
}
} */
override var titleRes: Int = R.string.settings_notification_advanced
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
// define the layout
addPreferencesFromResource(R.xml.vector_settings_notification_advanced_preferences)
@ -177,7 +179,6 @@ class VectorSettingsAdvancedNotificationPreferenceFragment : VectorPreferenceFra
override fun onResume() {
super.onResume()
(activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_notification_advanced)
// find the view from parent activity
mLoadingView = activity!!.findViewById(R.id.vector_settings_spinner_views)
@ -222,14 +223,14 @@ class VectorSettingsAdvancedNotificationPreferenceFragment : VectorPreferenceFra
if (TextUtils.equals(ruleId, BingRule.RULE_ID_DISABLE_ALL) || TextUtils.equals(ruleId, BingRule.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS)) {
isEnabled = !isEnabled
} else if (isEnabled) {
val actions = rule!!.actions
val domainActions = rule!!.domainActions
// no action -> noting will be done
if (null == actions || actions.isEmpty()) {
if (null == domainActions || domainActions.isEmpty()) {
isEnabled = false
} else if (1 == actions.size) {
} else if (1 == domainActions.size) {
try {
isEnabled = !TextUtils.equals(actions[0] as String, BingRule.ACTION_DONT_NOTIFY)
isEnabled = !TextUtils.equals(domainActions[0] as String, BingRule.ACTION_DONT_NOTIFY)
} catch (e: Exception) {
Timber.e(e, "## refreshPreferences failed")
}

View File

@ -0,0 +1,17 @@
package im.vector.riotredesign.features.settings
import android.os.Bundle
import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.VectorBaseActivity
import im.vector.riotredesign.core.platform.VectorPreferenceFragment
class VectorSettingsNotificationPreferenceFragment : VectorPreferenceFragment() {
override var titleRes: Int = R.string.settings_notifications
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.vector_settings_notifications)
}
}

View File

@ -84,6 +84,7 @@ import java.util.*
class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPreferences.OnSharedPreferenceChangeListener {
override var titleRes: Int = R.string.title_activity_settings
// members
private val mSession by inject<Session>()
@ -1493,14 +1494,14 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
if (TextUtils.equals(ruleId, BingRule.RULE_ID_DISABLE_ALL) || TextUtils.equals(ruleId, BingRule.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS)) {
isEnabled = !isEnabled
} else if (isEnabled) {
val actions = rule?.actions
val domainActions = rule?.domainActions
// no action -> noting will be done
if (null == actions || actions.isEmpty()) {
if (null == domainActions || domainActions.isEmpty()) {
isEnabled = false
} else if (1 == actions.size) {
} else if (1 == domainActions.size) {
try {
isEnabled = !TextUtils.equals(actions[0] as String, BingRule.ACTION_DONT_NOTIFY)
isEnabled = !TextUtils.equals(domainActions[0] as String, BingRule.ACTION_DONT_NOTIFY)
} catch (e: Exception) {
Timber.e(e, "## refreshPreferences failed " + e.message)
}

View File

@ -0,0 +1,24 @@
package im.vector.riotredesign.features.settings
import android.os.Bundle
import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.withArgs
import im.vector.riotredesign.core.platform.VectorPreferenceFragment
class VectorSettingsPreferencesFragmentV2 : VectorPreferenceFragment() {
override var titleRes: Int = R.string.title_activity_settings
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.vector_settings_preferences_root)
}
companion object {
fun newInstance() = VectorSettingsPreferencesFragmentV2()
.withArgs {
//putString(ARG_MATRIX_ID, matrixId)
}
}
}

View File

@ -0,0 +1,46 @@
package im.vector.riotredesign.features.settings.push
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import com.airbnb.epoxy.EpoxyModelWithHolder
import im.vector.matrix.android.api.session.pushers.Pusher
import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder
@EpoxyModelClass(layout = R.layout.item_pushgateway)
abstract class PushGatewayItem : EpoxyModelWithHolder<PushGatewayItem.Holder>() {
@EpoxyAttribute
lateinit var pusher: Pusher
override fun bind(holder: Holder) {
holder.kind.text = when (pusher.kind) {
"http" -> "Http Pusher"
"mail" -> "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.deviceName.text = pusher.deviceDisplayName
}
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 format by bind<TextView>(R.id.pushGatewayFormatValue)
val url by bind<TextView>(R.id.pushGatewayURLValue)
val appName by bind<TextView>(R.id.pushGatewayAppNameValue)
val appId by bind<TextView>(R.id.pushGatewayAppIdValue)
}
}
//
//abstract class ReactionInfoSimpleItem : EpoxyModelWithHolder<ReactionInfoSimpleItem.Holder>() {

View File

@ -0,0 +1,71 @@
/*
* Copyright 2019 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.riotredesign.features.settings.push
import android.os.Bundle
import android.widget.LinearLayout
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.VectorBaseActivity
import im.vector.riotredesign.core.platform.VectorBaseFragment
import kotlinx.android.synthetic.main.fragment_settings_pushgateways.*
class PushGatewaysFragment : VectorBaseFragment() {
override fun getLayoutResId(): Int = R.layout.fragment_settings_pushgateways
private val viewModel: PushGatewaysViewModel by fragmentViewModel(PushGatewaysViewModel::class)
private val epoxyController by lazy { PushGateWayController() }
override fun onResume() {
super.onResume()
(activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_push_gateways)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val lmgr = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
epoxyRecyclerView.layoutManager = lmgr
val dividerItemDecoration = DividerItemDecoration(epoxyRecyclerView.context,
lmgr.orientation)
epoxyRecyclerView.addItemDecoration(dividerItemDecoration)
epoxyRecyclerView.adapter = epoxyController.adapter
}
override fun invalidate() = withState(viewModel) {
epoxyController.setData(it)
}
class PushGateWayController : TypedEpoxyController<PushGatewayViewState>() {
override fun buildModels(data: PushGatewayViewState?) {
val pushers = data?.pushgateways?.invoke() ?: return
pushers.forEach {
pushGatewayItem {
id("${it.pushKey}_${it.appId}")
pusher(it)
}
}
}
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2019 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.riotredesign.features.settings.push
import androidx.lifecycle.Observer
import com.airbnb.mvrx.*
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.pushers.Pusher
import im.vector.riotredesign.core.platform.VectorViewModel
import org.koin.android.ext.android.get
data class PushGatewayViewState(
val pushgateways: Async<List<Pusher>> = Uninitialized)
: MvRxState
class PushGatewaysViewModel(initialState: PushGatewayViewState) : VectorViewModel<PushGatewayViewState>(initialState) {
companion object : MvRxViewModelFactory<PushGatewaysViewModel, PushGatewayViewState> {
override fun create(viewModelContext: ViewModelContext, state: PushGatewayViewState): PushGatewaysViewModel? {
val session = viewModelContext.activity.get<Session>()
val fragment = (viewModelContext as FragmentViewModelContext).fragment
val livePushers = session.livePushers()
val viewModel = PushGatewaysViewModel(state)
livePushers.observe(fragment, Observer {
viewModel.setState {
PushGatewayViewState(Success(it))
}
})
return viewModel
}
}
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<alpha
android:duration="300"
android:fromAlpha="0.0"
android:toAlpha="1.0" />
</set>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<alpha
android:duration="300"
android:fromAlpha="1.0"
android:toAlpha="0.0" />
</set>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<translate
android:duration="300"
android:fromXDelta="100%"
android:interpolator="@android:anim/decelerate_interpolator"
android:toXDelta="0%" />
</set>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<translate
android:duration="300"
android:fromXDelta="0%"
android:interpolator="@android:anim/decelerate_interpolator"
android:toXDelta="100%" />
<alpha
android:duration="300"
android:fromAlpha="1.0"
android:toAlpha="0.0" />
</set>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 726 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 538 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1018 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 882 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="12dp"
android:height="14dp"
android:viewportWidth="12"
android:viewportHeight="14">
<path
android:strokeWidth="1"
android:pathData="M12,10.002L0,10.002a1.8,1.8 0,0 0,1.8 -1.8L1.8,5.2a4.2,4.2 0,1 1,8.4 0v3a1.8,1.8 0,0 0,1.8 1.8zM7.038,12.402a1.2,1.2 0,0 1,-2.076 0h2.076z"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#454545"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,22 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="10dp"
android:height="14dp"
android:viewportWidth="10"
android:viewportHeight="14">
<path
android:strokeWidth="1"
android:pathData="M1,4.818a4,3.818 0,1 0,8 0a4,3.818 0,1 0,-8 0z"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#454545"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M2.834,8.03L2.143,13 5,11.364 7.857,13l-0.691,-4.975"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#454545"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,22 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="13dp"
android:height="14dp"
android:viewportWidth="13"
android:viewportHeight="14">
<path
android:strokeWidth="1"
android:pathData="M2.2,6.4L10.8,6.4A1.2,1.2 0,0 1,12 7.6L12,11.8A1.2,1.2 0,0 1,10.8 13L2.2,13A1.2,1.2 0,0 1,1 11.8L1,7.6A1.2,1.2 0,0 1,2.2 6.4z"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#454545"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M3.444,6.4L3.444,4c0,-1.657 1.368,-3 3.056,-3s3.056,1.343 3.056,3v2.4"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#454545"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,22 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="14dp"
android:height="14dp"
android:viewportWidth="14"
android:viewportHeight="14">
<path
android:strokeWidth="1"
android:pathData="M7,7m-1.636,0a1.636,1.636 0,1 1,3.272 0a1.636,1.636 0,1 1,-3.272 0"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#454545"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M11.036,8.636a0.9,0.9 0,0 0,0.18 0.993l0.033,0.033a1.09,1.09 0,1 1,-1.544 1.543l-0.032,-0.032a0.9,0.9 0,0 0,-0.993 -0.18,0.9 0.9,0 0,0 -0.545,0.823v0.093a1.09,1.09 0,1 1,-2.182 0v-0.049a0.9,0.9 0,0 0,-0.59 -0.824,0.9 0.9,0 0,0 -0.992,0.18l-0.033,0.033a1.09,1.09 0,1 1,-1.543 -1.544l0.032,-0.032a0.9,0.9 0,0 0,0.18 -0.993,0.9 0.9,0 0,0 -0.823,-0.545L2.09,8.135a1.09,1.09 0,1 1,0 -2.182h0.049a0.9,0.9 0,0 0,0.824 -0.59,0.9 0.9,0 0,0 -0.18,-0.992l-0.033,-0.033a1.09,1.09 0,1 1,1.544 -1.543l0.032,0.032a0.9,0.9 0,0 0,0.993 0.18h0.044a0.9,0.9 0,0 0,0.545 -0.823L5.908,2.09a1.09,1.09 0,0 1,2.182 0v0.049a0.9,0.9 0,0 0,0.545 0.824,0.9 0.9,0 0,0 0.993,-0.18l0.033,-0.033a1.09,1.09 0,1 1,1.543 1.544l-0.032,0.032a0.9,0.9 0,0 0,-0.18 0.993v0.044a0.9,0.9 0,0 0,0.823 0.545h0.093a1.09,1.09 0,1 1,0 2.182h-0.049a0.9,0.9 0,0 0,-0.824 0.545z"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#454545"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="11dp"
android:height="14dp"
android:viewportWidth="11"
android:viewportHeight="14">
<path
android:strokeWidth="1"
android:pathData="M0.588,8.8s0.588,-0.6 2.353,-0.6c1.765,0 2.941,1.2 4.706,1.2C9.412,9.4 10,8.8 10,8.8V1.6s-0.588,0.6 -2.353,0.6C5.882,2.2 4.706,1 2.941,1c-1.765,0 -2.353,0.6 -2.353,0.6v7.2zM0.588,13V8.8"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#454545"
android:strokeLineCap="round"/>
</vector>

View File

@ -9,7 +9,7 @@
android:strokeWidth="1.2"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#FFF"
android:strokeColor="#454545"
android:strokeLineCap="round"/>
<path
android:pathData="M16.055,12.455a1.35,1.35 0,0 0,0.27 1.489l0.049,0.049a1.636,1.636 0,1 1,-2.316 2.315l-0.049,-0.049a1.35,1.35 0,0 0,-1.489 -0.27,1.35 1.35,0 0,0 -0.818,1.236v0.139a1.636,1.636 0,0 1,-3.273 0v-0.074a1.35,1.35 0,0 0,-0.884 -1.235,1.35 1.35,0 0,0 -1.489,0.27l-0.049,0.049a1.636,1.636 0,1 1,-2.315 -2.316l0.049,-0.049a1.35,1.35 0,0 0,0.27 -1.489,1.35 1.35,0 0,0 -1.236,-0.818h-0.139a1.636,1.636 0,0 1,0 -3.273h0.074a1.35,1.35 0,0 0,1.235 -0.884,1.35 1.35,0 0,0 -0.27,-1.489l-0.049,-0.049a1.636,1.636 0,1 1,2.316 -2.315l0.049,0.049a1.35,1.35 0,0 0,1.489 0.27h0.065a1.35,1.35 0,0 0,0.819 -1.236v-0.139a1.636,1.636 0,0 1,3.272 0v0.074a1.35,1.35 0,0 0,0.819 1.235,1.35 1.35,0 0,0 1.489,-0.27l0.049,-0.049a1.636,1.636 0,1 1,2.315 2.316l-0.049,0.049a1.35,1.35 0,0 0,-0.27 1.489v0.065a1.35,1.35 0,0 0,1.236 0.819h0.139a1.636,1.636 0,0 1,0 3.272h-0.074a1.35,1.35 0,0 0,-1.235 0.819z"
@ -17,6 +17,6 @@
android:strokeWidth="1.2"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#FFF"
android:strokeColor="#454545"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="14dp"
android:height="12dp"
android:viewportWidth="14"
android:viewportHeight="12">
<path
android:strokeWidth="1"
android:pathData="M2.636,11V7.111M2.636,4.889V1M7,11V6M7,3.778V1M11.364,11V8.222M11.364,6V1M1,7.111h3.273M5.364,3.778h3.272M9.727,8.222H13"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#454545"
android:strokeLineCap="round"/>
</vector>

View File

@ -74,6 +74,7 @@
android:background="?attr/selectableItemBackground"
android:padding="16dp"
android:src="@drawable/ic_settings_x"
android:tint="@android:color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/epoxyRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:itemSpacing="1dp"
tools:listitem="@layout/item_pushgateway" />
</FrameLayout>

View File

@ -20,9 +20,12 @@
<item>https://scalar.vector.im/api</item>
</string-array>
<!-- empty means to use the active HS url -->
<!-- set a valid URL like "https://matrix.org" to use a custom one -->
<string name="push_server_url" translatable="false" />
<!--
Pusher config for the application
https://matrix.org/docs/spec/client_server/r0.4.0#id128
-->
<string name="pusher_http_url" translatable="false">https://matrix.org/_matrix/push/v1/notify</string>
<string name="pusher_app_id" translatable="false">im.vector.app.android</string>
<string-array name="room_directory_servers" translatable="false">
<item>matrix.org</item>

View File

@ -11,4 +11,12 @@
<string name="navigate_to_room_when_already_in_the_room">You are already viewing this room!</string>
<string name="quick_reactions">Quick Reactions</string>
<!-- Settings -->
<string name="settings_general_title">General</string>
<string name="settings_preferences">Preferences</string>
<string name="settings_security_and_privacy">Security &amp; Privacy</string>
<string name="settings_expert">Expert</string>
<string name="settings_push_gateways">Push Gateways</string>
</resources>

View File

@ -28,11 +28,14 @@
<item name="riotx_fab_label_bg">@color/riotx_fab_label_bg_dark</item>
<item name="riotx_fab_label_color">@color/riotx_fab_label_color_dark</item>
<item name="riotx_touch_guard_bg">@color/riotx_touch_guard_bg_dark</item>
<item name="riotx_keys_backup_banner_accent_color">@color/riotx_keys_backup_banner_accent_color_dark</item>
<!-- Drawables -->
<item name="riotx_highlighted_message_background">@drawable/highlighted_message_background_dark</item>
<item name="android:listDivider">@color/riotx_header_panel_border_mobile_dark</item>
<!-- Material color: Note: this block should be the same in all theme because it references only common colors and ?riotx attributes -->
<item name="colorPrimary">@color/riotx_accent</item>
<item name="colorPrimaryVariant">@color/primary_color_dark_light</item>

View File

@ -33,6 +33,8 @@
<!-- Drawables -->
<item name="riotx_highlighted_message_background">@drawable/highlighted_message_background_light</item>
<item name="android:listDivider">@color/riotx_header_panel_border_mobile_light</item>
<!-- Material color: Note: this block should be the same in all theme because it references only common colors and ?riotx attributes -->
<item name="colorPrimary">@color/riotx_accent</item>
<!--item name="colorPrimaryVariant">@color/primary_color_dark_light</item-->

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreference
android:key="SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY"
android:title="@string/settings_enable_all_notif" />
<SwitchPreference
android:dependency="SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY"
android:key="SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY"
android:title="@string/settings_enable_this_device" />
<!--<im.vector.riotredesign.core.preference.VectorSwitchPreference-->
<!--android:dependency="SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY"-->
<!--android:key="SETTINGS_TURN_SCREEN_ON_PREFERENCE_KEY"-->
<!--android:title="@string/settings_turn_screen_on" />-->
<!--<Preference-->
<!--android:dialogTitle="@string/settings_notification_privacy"-->
<!--android:key="SETTINGS_NOTIFICATION_PRIVACY_PREFERENCE_KEY"-->
<!--android:title="@string/settings_notification_privacy" />-->
<Preference
android:dependency="SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY"
android:key="SETTINGS_NOTIFICATION_ADVANCED_PREFERENCE_KEY"
android:persistent="false"
android:summary="@string/settings_notification_advanced_summary"
android:title="@string/settings_notification_advanced"
app:fragment="im.vector.fragments.VectorSettingsNotificationsAdvancedFragment" />
<Preference
android:key="SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY"
android:title="@string/settings_notification_troubleshoot"
app:fragment="im.vector.riotredesign.features.settings.VectorSettingsNotificationsTroubleshootFragment" />
<PreferenceCategory android:title="@string/settings_expert">
<Preference
android:layout_width="match_parent"
android:title="@string/settings_push_gateways"
app:fragment="im.vector.riotredesign.features.settings.push.PushGatewaysFragment" />
</PreferenceCategory>
</PreferenceScreen>

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Preference
android:layout_width="match_parent"
android:icon="@drawable/ic_settings_general"
android:title="@string/settings_general_title"
app:fragment="com.example.SyncFragment" />
<Preference
android:layout_width="match_parent"
android:icon="@drawable/ic_flair"
android:title="@string/settings_flair"
app:fragment="com.example.SyncFragment" />
<Preference
android:layout_width="match_parent"
android:icon="@drawable/ic_bell"
android:key="SETTINGS_NOTIFICATIONS_KEY"
android:title="@string/settings_notifications"
app:fragment="im.vector.riotredesign.features.settings.VectorSettingsNotificationPreferenceFragment" />
<Preference
android:layout_width="match_parent"
android:icon="@drawable/ic_sliders"
android:title="@string/settings_preferences"
app:fragment="com.example.SyncFragment" />
<Preference
android:layout_width="match_parent"
android:icon="@drawable/ic_lock"
android:title="@string/settings_security_and_privacy"
app:fragment="com.example.SyncFragment" />
<Preference
android:layout_width="match_parent"
android:icon="@drawable/ic_settings_lab"
android:title="@string/room_settings_labs_pref_title"
app:fragment="com.example.SyncFragment" />
<Preference
android:layout_width="match_parent"
android:icon="@drawable/ic_devices_info"
android:title="Legacy"
app:fragment="com.example.SyncFragment" />
</PreferenceScreen>