WIP
122
vector/src/debug/res/layout/item_pushgateway.xml
Normal 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>
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.");
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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) ?: ""
|
||||
|
@ -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>
|
@ -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()
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
@ -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}")
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
@ -25,6 +25,4 @@ class LocaleProvider(private val resources: Resources) {
|
||||
fun current(): Locale {
|
||||
return ConfigurationCompat.getLocales(resources.configuration)[0]
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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() {
|
||||
|
||||
|
@ -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() {
|
||||
|
||||
|
@ -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) {
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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)*/)
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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";
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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>() {
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
11
vector/src/main/res/anim/fade_in.xml
Normal 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>
|
11
vector/src/main/res/anim/fade_out.xml
Normal 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>
|
10
vector/src/main/res/anim/right_in.xml
Normal 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>
|
16
vector/src/main/res/anim/right_out.xml
Normal 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>
|
Before Width: | Height: | Size: 726 B |
BIN
vector/src/main/res/drawable-hdpi/sync.png
Normal file
After Width: | Height: | Size: 466 B |
Before Width: | Height: | Size: 538 B |
BIN
vector/src/main/res/drawable-mdpi/sync.png
Normal file
After Width: | Height: | Size: 345 B |
Before Width: | Height: | Size: 1018 B |
BIN
vector/src/main/res/drawable-xhdpi/sync.png
Normal file
After Width: | Height: | Size: 609 B |
Before Width: | Height: | Size: 1.5 KiB |
BIN
vector/src/main/res/drawable-xxhdpi/sync.png
Normal file
After Width: | Height: | Size: 882 B |
Before Width: | Height: | Size: 1.9 KiB |
BIN
vector/src/main/res/drawable-xxxhdpi/sync.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
14
vector/src/main/res/drawable/ic_bell.xml
Normal 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>
|
22
vector/src/main/res/drawable/ic_flair.xml
Normal 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>
|
22
vector/src/main/res/drawable/ic_lock.xml
Normal 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>
|
22
vector/src/main/res/drawable/ic_settings_general.xml
Normal 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>
|
14
vector/src/main/res/drawable/ic_settings_lab.xml
Normal 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>
|
@ -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>
|
||||
|
14
vector/src/main/res/drawable/ic_sliders.xml
Normal 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>
|
@ -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" />
|
||||
|
||||
|
@ -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>
|
@ -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>
|
||||
|
@ -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 & Privacy</string>
|
||||
<string name="settings_expert">Expert</string>
|
||||
<string name="settings_push_gateways">Push Gateways</string>
|
||||
|
||||
</resources>
|
@ -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>
|
||||
|
@ -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-->
|
||||
|
45
vector/src/main/res/xml/vector_settings_notifications.xml
Normal 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>
|
54
vector/src/main/res/xml/vector_settings_preferences_root.xml
Normal 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>
|