Merge branch 'develop' into feature/dagger

This commit is contained in:
ganfra 2019-06-27 18:56:23 +02:00
commit 2063a3e535
120 changed files with 3914 additions and 3977 deletions

View File

@ -61,4 +61,6 @@ interface PushersService {
}

fun livePushers(): LiveData<List<Pusher>>

fun pushers() : List<Pusher>
}

View File

@ -191,5 +191,4 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
}
}


}

View File

@ -37,12 +37,15 @@ internal class BingRuleWatcher @Inject constructor(monarchy: Monarchy,
override val query = Monarchy.Query<EventEntity> {

EventEntity.types(it, listOf(
EventType.REDACTION, EventType.MESSAGE, EventType.REDACTION, EventType.ENCRYPTED)
EventType.MESSAGE,
EventType.REDACTION,
EventType.ENCRYPTED)
)

}

override fun processChanges(inserted: List<EventEntity>, updated: List<EventEntity>, deleted: List<EventEntity>) {
// TODO Use const for "global"
val rules = defaultPushRuleService.getPushRules("global")
inserted.map { it.asDomain() }
.filter { it.senderId != sessionParams.credentials.userId }

View File

@ -112,4 +112,8 @@ internal class DefaultPusherService @Inject constructor(
{ it.asDomain() }
)
}

override fun pushers(): List<Pusher> {
return monarchy.fetchAllCopiedSync { PusherEntity.where(it, sessionParam.credentials.userId) }.map { it.asDomain() }
}
}

View File

@ -238,8 +238,11 @@ dependencies {
kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.4.0'

// gplay flavor only
gplayImplementation 'com.google.firebase:firebase-core:16.0.8'
gplayImplementation 'com.google.firebase:firebase-messaging:17.5.0'
gplayImplementation('com.google.firebase:firebase-messaging:19.0.1') {
exclude group: 'com.google.firebase', module: 'firebase-core'
exclude group: 'com.google.firebase', module: 'firebase-analytics'
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
}

// TESTS
testImplementation 'junit:junit:4.12'

View File

@ -8,7 +8,7 @@

<application>

<receiver android:name=".receiver.OnApplicationUpgradeOrRebootReceiver">
<receiver android:name="im.vector.riotredesign.fdroid.receiver.OnApplicationUpgradeOrRebootReceiver">
<intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
<action android:name="android.intent.action.BOOT_COMPLETED" />
@ -16,10 +16,14 @@
</receiver>

<receiver
android:name=".core.services.AlarmSyncBroadcastReceiver"
android:name="im.vector.riotredesign.fdroid.receiver.AlarmSyncBroadcastReceiver"
android:enabled="true"
android:exported="false" />

<service
android:name=".fdroid.receiver.service.VectorSyncService"
android:exported="false" />

</application>

</manifest>

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotredesign.push.fcm.troubleshoot
package im.vector.riotredesign.fdroid.features.settings.troubleshoot

import androidx.fragment.app.Fragment
import im.vector.riotredesign.R

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotredesign.push.fcm.troubleshoot
package im.vector.riotredesign.fdroid.features.settings.troubleshoot

import android.content.Context
import android.net.ConnectivityManager

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotredesign.push.fcm.troubleshoot
package im.vector.riotredesign.fdroid.features.settings.troubleshoot

import androidx.fragment.app.Fragment
import im.vector.riotredesign.R

View File

@ -0,0 +1,20 @@
/*
* 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.
*/

/**
* Code exclusively used by the FDroid build and not referenced on the main source code
*/
package im.vector.riotredesign.fdroid

View File

@ -1,4 +1,20 @@
package im.vector.riotredesign.core.services
/*
* 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.fdroid.receiver

import android.app.AlarmManager
import android.app.PendingIntent
@ -9,6 +25,7 @@ import android.os.Build
import android.os.PowerManager
import androidx.core.content.ContextCompat
import im.vector.matrix.android.internal.session.sync.job.SyncService
import im.vector.riotredesign.fdroid.service.VectorSyncService
import timber.log.Timber

class AlarmSyncBroadcastReceiver : BroadcastReceiver() {
@ -48,15 +65,14 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() {
}

companion object {
const val REQUEST_CODE = 0
private const val REQUEST_CODE = 0

fun scheduleAlarm(context: Context, userId: String, delay: Long) {
//Reschedule
val intent = Intent(context, AlarmSyncBroadcastReceiver::class.java).apply {
putExtra(SyncService.EXTRA_USER_ID, userId)
}
val pIntent = PendingIntent.getBroadcast(context, AlarmSyncBroadcastReceiver.REQUEST_CODE,
intent, PendingIntent.FLAG_UPDATE_CURRENT)
val pIntent = PendingIntent.getBroadcast(context, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val firstMillis = System.currentTimeMillis() + delay
val alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@ -68,8 +84,7 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() {

fun cancelAlarm(context: Context) {
val intent = Intent(context, AlarmSyncBroadcastReceiver::class.java)
val pIntent = PendingIntent.getBroadcast(context, AlarmSyncBroadcastReceiver.REQUEST_CODE,
intent, PendingIntent.FLAG_UPDATE_CURRENT)
val pIntent = PendingIntent.getBroadcast(context, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmMgr.cancel(pIntent)
}

View File

@ -15,21 +15,21 @@
* limitations under the License.
*/

package im.vector.riotredesign.receiver
package im.vector.riotredesign.fdroid.receiver

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import im.vector.riotredesign.core.di.HasVectorInjector
import im.vector.riotredesign.core.services.AlarmSyncBroadcastReceiver
import timber.log.Timber

class OnApplicationUpgradeOrRebootReceiver : BroadcastReceiver() {

override fun onReceive(context: Context, intent: Intent) {
Timber.v("## onReceive() ${intent.action}")
if (context is HasVectorInjector) {
val activeSession = context.injector().activeSessionHolder().getSafeActiveSession()
val appContext = context.applicationContext
if (appContext is HasVectorInjector) {
val activeSession = appContext.injector().activeSessionHolder().getSafeActiveSession()
if (activeSession != null) {
AlarmSyncBroadcastReceiver.scheduleAlarm(context, activeSession.myUserId, 10)
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotredesign.core.services
package im.vector.riotredesign.fdroid.service

import android.app.NotificationManager
import android.content.Context
@ -28,12 +28,12 @@ import timber.log.Timber
class VectorSyncService : SyncService() {

override fun onCreate() {
Timber.v("VectorSyncService - onCreate ")
Timber.v("VectorSyncService - onCreate")
super.onCreate()
}

override fun onDestroy() {
Timber.v("VectorSyncService - onDestroy ")
Timber.v("VectorSyncService - onDestroy")
removeForegroundNotif()
super.onDestroy()
}
@ -57,7 +57,7 @@ class VectorSyncService : SyncService() {
}

/**
* If the service is bounded and the service was previously started we can remove foreground notif
* If the service is bounded and the service was previously started we can remove foreground notification
*/
override fun onBind(intent: Intent?): IBinder {
Timber.v("VectorSyncService - onBind ")

View File

@ -18,9 +18,15 @@ package im.vector.riotredesign.push.fcm

import android.app.Activity
import android.content.Context

import im.vector.riotredesign.core.di.ActiveSessionHolder
import im.vector.riotredesign.core.pushers.PushersManager
import im.vector.riotredesign.fdroid.receiver.AlarmSyncBroadcastReceiver
import im.vector.riotredesign.features.settings.PreferencesManager
import timber.log.Timber

/**
* This class has an alter ego in the gplay variant.
*/
object FcmHelper {

fun isPushSupported(): Boolean = false
@ -52,4 +58,17 @@ object FcmHelper {
fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager) {
// No op
}

fun onEnterForeground(context: Context) {
AlarmSyncBroadcastReceiver.cancelAlarm(context)
}

fun onEnterBackground(context: Context, activeSessionHolder: ActiveSessionHolder) {
//We need to use alarm in this mode
if (PreferencesManager.areNotificationEnabledForDevice(context) && activeSessionHolder.hasActiveSession()) {
val currentSession = activeSessionHolder.getActiveSession()
AlarmSyncBroadcastReceiver.scheduleAlarm(context, currentSession.myUserId, 4_000L)
Timber.i("Alarm scheduled to restart service")
}
}
}

View File

@ -17,9 +17,9 @@ package im.vector.riotredesign.push.fcm

import androidx.fragment.app.Fragment
import im.vector.matrix.android.api.session.Session
import im.vector.riotredesign.fdroid.features.settings.troubleshoot.TestAutoStartBoot
import im.vector.riotredesign.fdroid.features.settings.troubleshoot.TestBackgroundRestrictions
import im.vector.riotredesign.features.settings.troubleshoot.*
import im.vector.riotredesign.push.fcm.troubleshoot.TestAutoStartBoot
import im.vector.riotredesign.push.fcm.troubleshoot.TestBackgroundRestrictions

class NotificationTroubleshootTestManagerFactory {


View File

@ -9,7 +9,7 @@
android:name="firebase_analytics_collection_deactivated"
android:value="true" />

<service android:name=".push.fcm.VectorFirebaseMessagingService">
<service android:name=".gplay.push.fcm.VectorFirebaseMessagingService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>

View File

@ -0,0 +1,74 @@
/*
* Copyright 2018 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.gplay.features.settings.troubleshoot

import androidx.appcompat.app.AppCompatActivity
import com.google.firebase.iid.FirebaseInstanceId
import im.vector.riotredesign.R
import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.core.utils.startAddGoogleAccountIntent
import im.vector.riotredesign.features.settings.troubleshoot.NotificationTroubleshootTestManager
import im.vector.riotredesign.features.settings.troubleshoot.TroubleshootTest
import im.vector.riotredesign.push.fcm.FcmHelper
import timber.log.Timber
import javax.inject.Inject

/*
* Test that app can successfully retrieve a token via firebase
*/
class TestFirebaseToken @Inject constructor(private val context: AppCompatActivity,
private val stringProvider: StringProvider) : TroubleshootTest(R.string.settings_troubleshoot_test_fcm_title) {

override fun perform() {
status = TestStatus.RUNNING
try {
FirebaseInstanceId.getInstance().instanceId
.addOnCompleteListener(context) { task ->
if (!task.isSuccessful) {
val errorMsg = if (task.exception == null) "Unknown" else task.exception!!.localizedMessage
//Can't find where this constant is (not documented -or deprecated in docs- and all obfuscated)
if ("SERVICE_NOT_AVAILABLE".equals(errorMsg)) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed_service_not_available, errorMsg)
} else if ("TOO_MANY_REGISTRATIONS".equals(errorMsg)) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed_too_many_registration, errorMsg)
} else if ("ACCOUNT_MISSING".equals(errorMsg)) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed_account_missing, errorMsg)
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_fcm_failed_account_missing_quick_fix) {
override fun doFix() {
startAddGoogleAccountIntent(context, NotificationTroubleshootTestManager.REQ_CODE_FIX)
}
}
} else {
description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed, errorMsg)
}
status = TestStatus.FAILED
} else {
task.result?.token?.let {
val tok = it.substring(0, Math.min(8, it.length)) + "********************"
description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_success, tok)
Timber.e("Retrieved FCM token success [$it].")
FcmHelper.storeFcmToken(context, tok)
}
status = TestStatus.SUCCESS
}
}
} catch (e: Throwable) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed, e.localizedMessage)
status = TestStatus.FAILED
}
}

}

View File

@ -13,40 +13,41 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotredesign.push.fcm.troubleshoot
package im.vector.riotredesign.gplay.features.settings.troubleshoot

import androidx.fragment.app.Fragment
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
import im.vector.riotredesign.R
import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.features.settings.troubleshoot.TroubleshootTest
import timber.log.Timber
import javax.inject.Inject

/*
* Check that the play services APK is available an up-to-date. If needed provide quick fix to install it.
*/
class TestPlayServices(val fragment: Fragment) : TroubleshootTest(R.string.settings_troubleshoot_test_play_services_title) {
class TestPlayServices @Inject constructor(private val context: AppCompatActivity,
private val stringProvider: StringProvider) : TroubleshootTest(R.string.settings_troubleshoot_test_play_services_title) {

override fun perform() {
val apiAvailability = GoogleApiAvailability.getInstance()
val resultCode = apiAvailability.isGooglePlayServicesAvailable(fragment.context)
val resultCode = apiAvailability.isGooglePlayServicesAvailable(context)
if (resultCode == ConnectionResult.SUCCESS) {
quickFix = null
description = fragment.getString(R.string.settings_troubleshoot_test_play_services_success)
description = stringProvider.getString(R.string.settings_troubleshoot_test_play_services_success)
status = TestStatus.SUCCESS
} else {
if (apiAvailability.isUserResolvableError(resultCode)) {
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_play_services_quickfix) {
override fun doFix() {
fragment.activity?.let {
apiAvailability.getErrorDialog(it, resultCode, 9000 /*hey does the magic number*/).show()
}
apiAvailability.getErrorDialog(context, resultCode, 9000 /*hey does the magic number*/).show()
}
}
Timber.e("Play Services apk error $resultCode -> ${apiAvailability.getErrorString(resultCode)}.")
}

description = fragment.getString(R.string.settings_troubleshoot_test_play_services_failed, apiAvailability.getErrorString(resultCode))
description = stringProvider.getString(R.string.settings_troubleshoot_test_play_services_failed, apiAvailability.getErrorString(resultCode))
status = TestStatus.FAILED
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright 2018 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.gplay.features.settings.troubleshoot

import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.work.WorkInfo
import androidx.work.WorkManager
import im.vector.matrix.android.api.session.pushers.PusherState
import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.ActiveSessionHolder
import im.vector.riotredesign.core.pushers.PushersManager
import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.features.settings.troubleshoot.TroubleshootTest
import im.vector.riotredesign.push.fcm.FcmHelper
import javax.inject.Inject

/**
* Force registration of the token to HomeServer
*/
class TestTokenRegistration @Inject constructor(private val context: AppCompatActivity,
private val stringProvider: StringProvider,
private val pushersManager: PushersManager,
private val activeSessionHolder: ActiveSessionHolder) : TroubleshootTest(R.string.settings_troubleshoot_test_token_registration_title) {

override fun perform() {
//Check if we have a registered pusher for this token
val fcmToken = FcmHelper.getFcmToken(context) ?: run {
status = TestStatus.FAILED
return
}
val session = activeSessionHolder.getSafeActiveSession() ?: run {
status = TestStatus.FAILED
return
}
val pusher = session.pushers().filter {
it.pushKey == fcmToken && it.state == PusherState.REGISTERED
}
if (pusher.isEmpty()) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_token_registration_failed, null)
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_token_registration_quick_fix) {
override fun doFix() {
val workId = pushersManager.registerPusherWithFcmKey(fcmToken)
WorkManager.getInstance().getWorkInfoByIdLiveData(workId).observe(context, Observer { workInfo ->
if (workInfo != null) {
if (workInfo.state == WorkInfo.State.SUCCEEDED) {
manager?.retry()
} else if (workInfo.state == WorkInfo.State.FAILED) {
manager?.retry()
}
}
})
}

}

status = TestStatus.FAILED

} else {
description = stringProvider.getString(R.string.settings_troubleshoot_test_token_registration_success)
status = TestStatus.SUCCESS
}

}

}

View File

@ -0,0 +1,21 @@
/*
* 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.
*/

/**
* Code exclusively used by the GPlay build and not referenced on the main source code
*/
package im.vector.riotredesign.gplay

View File

@ -17,7 +17,7 @@
* limitations under the License.
*/

package im.vector.riotredesign.push.fcm
package im.vector.riotredesign.gplay.push.fcm

import android.os.Handler
import android.os.Looper
@ -26,11 +26,12 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner
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.riotredesign.BuildConfig
import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.ActiveSessionHolder
import im.vector.riotredesign.core.extensions.vectorComponent
import im.vector.riotredesign.core.preference.BingRule
import im.vector.riotredesign.core.pushers.PushersManager
import im.vector.riotredesign.features.badge.BadgeProxy
@ -38,35 +39,31 @@ import im.vector.riotredesign.features.notifications.NotifiableEventResolver
import im.vector.riotredesign.features.notifications.NotifiableMessageEvent
import im.vector.riotredesign.features.notifications.NotificationDrawerManager
import im.vector.riotredesign.features.notifications.SimpleNotifiableEvent
<<<<<<< HEAD
=======
import im.vector.riotredesign.features.settings.PreferencesManager
import org.koin.android.ext.android.inject
>>>>>>> develop
import im.vector.riotredesign.push.fcm.FcmHelper
import timber.log.Timber
import javax.inject.Inject

/**
* Class extending FirebaseMessagingService.
*/
class VectorFirebaseMessagingService : FirebaseMessagingService() {

<<<<<<< HEAD
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
@Inject lateinit var notifiableEventResolver: NotifiableEventResolver
@Inject lateinit var pusherManager: PushersManager
@Inject lateinit var activeSessionHolder: ActiveSessionHolder

private val notifiableEventResolver by lazy {
NotifiableEventResolver(this)
}
=======
private val notificationDrawerManager by inject<NotificationDrawerManager>()
private val pusherManager by inject<PushersManager>()
>>>>>>> develop

private val notifiableEventResolver by inject<NotifiableEventResolver>()
// UI handler
private val mUIHandler by lazy {
Handler(Looper.getMainLooper())
}

override fun onCreate() {
super.onCreate()
vectorComponent().inject(this)
}

/**
* Called when message is received.
*
@ -103,7 +100,6 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
* you retrieve the token.
*/
override fun onNewToken(refreshedToken: String?) {
if (Matrix.getInstance().currentSession == null) return
Timber.i("onNewToken: FCM Token has been updated")
FcmHelper.storeFcmToken(this, refreshedToken)
if (refreshedToken == null) {
@ -149,7 +145,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
val unreadCount = data.get("unread")?.let { Integer.parseInt(it) } ?: 0
BadgeProxy.updateBadgeCount(applicationContext, unreadCount)

val session = safeGetCurrentSession()
val session = activeSessionHolder.getSafeActiveSession()

if (session == null) {
Timber.w("## Can't sync from push, no current session")
@ -167,21 +163,12 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
}
}

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 {
val session = safeGetCurrentSession() ?: return false
val session = activeSessionHolder.getSafeActiveSession() ?: return false
val room = session.getRoom(roomId) ?: return false
return room.getTimeLineEvent(eventId) != null
} catch (e: Exception) {
@ -241,7 +228,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
if (notifiableEvent is NotifiableMessageEvent) {
if (TextUtils.isEmpty(notifiableEvent.senderName)) {
notifiableEvent.senderName = data["sender_display_name"]
?: data["sender"] ?: ""
?: data["sender"] ?: ""
}
if (TextUtils.isEmpty(notifiableEvent.roomName)) {
notifiableEvent.roomName = findRoomNameBestEffort(data, session) ?: ""
@ -287,11 +274,11 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {

try {
return Event(eventId = data["event_id"],
senderId = data["sender"],
roomId = data["room_id"],
type = data.getValue("type"),
senderId = data["sender"],
roomId = data["room_id"],
type = data.getValue("type"),
// TODO content = data.getValue("content"),
originServerTs = System.currentTimeMillis())
originServerTs = System.currentTimeMillis())
} catch (e: Exception) {
Timber.e(e, "buildEvent fails ")
}

View File

@ -25,6 +25,7 @@ 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.di.ActiveSessionHolder
import im.vector.riotredesign.core.pushers.PushersManager
import timber.log.Timber

@ -99,4 +100,12 @@ object FcmHelper {
val resultCode = apiAvailability.isGooglePlayServicesAvailable(activity)
return resultCode == ConnectionResult.SUCCESS
}

fun onEnterForeground(context: Context) {
// No op
}

fun onEnterBackground(context: Context, activeSessionHolder: ActiveSessionHolder) {
// TODO FCM fallback
}
}

View File

@ -16,30 +16,34 @@
package im.vector.riotredesign.push.fcm

import androidx.fragment.app.Fragment
import im.vector.matrix.android.api.session.Session
import im.vector.riotredesign.features.settings.troubleshoot.*
import im.vector.riotredesign.push.fcm.troubleshoot.TestFirebaseToken
import im.vector.riotredesign.push.fcm.troubleshoot.TestPlayServices
import im.vector.riotredesign.push.fcm.troubleshoot.TestTokenRegistration
import im.vector.riotredesign.features.settings.troubleshoot.NotificationTroubleshootTestManager
import im.vector.riotredesign.features.settings.troubleshoot.TestAccountSettings
import im.vector.riotredesign.features.settings.troubleshoot.TestBingRulesSettings
import im.vector.riotredesign.features.settings.troubleshoot.TestDeviceSettings
import im.vector.riotredesign.features.settings.troubleshoot.TestSystemSettings
import im.vector.riotredesign.gplay.features.settings.troubleshoot.TestFirebaseToken
import im.vector.riotredesign.gplay.features.settings.troubleshoot.TestPlayServices
import im.vector.riotredesign.gplay.features.settings.troubleshoot.TestTokenRegistration
import javax.inject.Inject

class NotificationTroubleshootTestManagerFactory {
class NotificationTroubleshootTestManagerFactory @Inject constructor(private val testSystemSettings: TestSystemSettings,
private val testAccountSettings: TestAccountSettings,
private val testDeviceSettings: TestDeviceSettings,
private val testBingRulesSettings: TestBingRulesSettings,
private val testPlayServices: TestPlayServices,
private val testFirebaseToken: TestFirebaseToken,
private val testTokenRegistration: TestTokenRegistration) {

companion object {
fun createTestManager(fragment: Fragment, session: Session?): NotificationTroubleshootTestManager {
val mgr = NotificationTroubleshootTestManager(fragment)
mgr.addTest(TestSystemSettings(fragment))
if (session != null) {
mgr.addTest(TestAccountSettings(fragment, session))
}
mgr.addTest(TestDeviceSettings(fragment))
if (session != null) {
mgr.addTest(TestBingRulesSettings(fragment, session))
}
mgr.addTest(TestPlayServices(fragment))
mgr.addTest(TestFirebaseToken(fragment))
mgr.addTest(TestTokenRegistration(fragment))
return mgr
}
fun create(fragment: Fragment): NotificationTroubleshootTestManager {
val mgr = NotificationTroubleshootTestManager(fragment)
mgr.addTest(testSystemSettings)
mgr.addTest(testAccountSettings)
mgr.addTest(testDeviceSettings)
mgr.addTest(testBingRulesSettings)
mgr.addTest(testPlayServices)
mgr.addTest(testFirebaseToken)
mgr.addTest(testTokenRegistration)
return mgr
}

}

View File

@ -1,74 +0,0 @@
/*
* Copyright 2018 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.push.fcm.troubleshoot

import androidx.fragment.app.Fragment
import com.google.firebase.iid.FirebaseInstanceId
import im.vector.riotredesign.R
import im.vector.riotredesign.core.utils.startAddGoogleAccountIntent
import im.vector.riotredesign.features.settings.troubleshoot.NotificationTroubleshootTestManager
import im.vector.riotredesign.features.settings.troubleshoot.TroubleshootTest
import timber.log.Timber

/*
* Test that app can successfully retrieve a token via firebase
*/
class TestFirebaseToken(val fragment: Fragment) : TroubleshootTest(R.string.settings_troubleshoot_test_fcm_title) {

override fun perform() {
status = TestStatus.RUNNING
val activity = fragment.activity
if (activity != null) {
try {
FirebaseInstanceId.getInstance().instanceId
.addOnCompleteListener(activity) { task ->
if (!task.isSuccessful) {
val errorMsg = if (task.exception == null) "Unknown" else task.exception!!.localizedMessage
//Can't find where this constant is (not documented -or deprecated in docs- and all obfuscated)
if ("SERVICE_NOT_AVAILABLE".equals(errorMsg)) {
description = fragment.getString(R.string.settings_troubleshoot_test_fcm_failed_service_not_available, errorMsg)
} else if ("TOO_MANY_REGISTRATIONS".equals(errorMsg)) {
description = fragment.getString(R.string.settings_troubleshoot_test_fcm_failed_too_many_registration, errorMsg)
} else if ("ACCOUNT_MISSING".equals(errorMsg)) {
description = fragment.getString(R.string.settings_troubleshoot_test_fcm_failed_account_missing, errorMsg)
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_fcm_failed_account_missing_quick_fix) {
override fun doFix() {
startAddGoogleAccountIntent(fragment, NotificationTroubleshootTestManager.REQ_CODE_FIX)
}
}
} else {
description = fragment.getString(R.string.settings_troubleshoot_test_fcm_failed, errorMsg)
}
status = TestStatus.FAILED
} else {
task.result?.token?.let {
val tok = it.substring(0, Math.min(8, it.length)) + "********************"
description = fragment.getString(R.string.settings_troubleshoot_test_fcm_success, tok)
Timber.e("Retrieved FCM token success [$it].")
}
status = TestStatus.SUCCESS
}
}
} catch (e: Throwable) {
description = fragment.getString(R.string.settings_troubleshoot_test_fcm_failed, e.localizedMessage)
status = TestStatus.FAILED
}
} else {
status = TestStatus.FAILED
}
}

}

View File

@ -1,57 +0,0 @@
/*
* Copyright 2018 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.push.fcm.troubleshoot

import androidx.fragment.app.Fragment
import im.vector.riotredesign.R
import im.vector.riotredesign.features.settings.troubleshoot.TroubleshootTest

/**
* Force registration of the token to HomeServer
*/
class TestTokenRegistration(val fragment: Fragment) : TroubleshootTest(R.string.settings_troubleshoot_test_token_registration_title) {

override fun perform() {
/*
TODO
Matrix.getInstance(VectorApp.getInstance().baseContext).pushManager.forceSessionsRegistration(object : MatrixCallback<Unit> {
override fun onSuccess(info: Void?) {
description = fragment.getString(R.string.settings_troubleshoot_test_token_registration_success)
status = TestStatus.SUCCESS
}

override fun onNetworkError(e: Exception?) {
description = fragment.getString(R.string.settings_troubleshoot_test_token_registration_failed, e?.localizedMessage)
status = TestStatus.FAILED
}

override fun onMatrixError(e: MatrixError?) {
description = fragment.getString(R.string.settings_troubleshoot_test_token_registration_failed, e?.localizedMessage)
status = TestStatus.FAILED
}

override fun onUnexpectedError(e: Exception?) {
description = fragment.getString(R.string.settings_troubleshoot_test_token_registration_failed, e?.localizedMessage)
status = TestStatus.FAILED
}
})
*/

status = TestStatus.FAILED

}

}

View File

@ -68,15 +68,6 @@
<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>

<!-- Receivers -->


View File

@ -42,7 +42,6 @@ import im.vector.riotredesign.core.di.DaggerVectorComponent
import im.vector.riotredesign.core.di.HasVectorInjector
import im.vector.riotredesign.core.di.VectorComponent
import im.vector.riotredesign.core.extensions.configureAndStart
import im.vector.riotredesign.core.services.AlarmSyncBroadcastReceiver
import im.vector.riotredesign.features.configuration.VectorConfiguration
import im.vector.riotredesign.features.lifecycle.VectorActivityLifecycleCallbacks
import im.vector.riotredesign.features.notifications.NotificationDrawerManager
@ -50,7 +49,6 @@ 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.settings.PreferencesManager
import im.vector.riotredesign.features.version.getVersion
import im.vector.riotredesign.push.fcm.FcmHelper
import timber.log.Timber
@ -113,7 +111,7 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.

@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun entersForeground() {
AlarmSyncBroadcastReceiver.cancelAlarm(appContext)
FcmHelper.onEnterForeground(appContext)
activeSessionHolder.getSafeActiveSession()?.also {
it.stopAnyBackgroundSync()
}
@ -123,19 +121,8 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
fun entersBackground() {
Timber.i("App entered background") // call persistInfo
notificationDrawerManager.persistInfo()
if (FcmHelper.isPushSupported()) {
//TODO FCM fallback
} else {
//TODO check if notifications are enabled for this device
//We need to use alarm in this mode
val activeSession = activeSessionHolder.getSafeActiveSession()
if (activeSession != null && PreferencesManager.areNotificationEnabledForDevice(applicationContext)) {
AlarmSyncBroadcastReceiver.scheduleAlarm(applicationContext, activeSession.myUserId, 4_000L)
Timber.i("Alarm scheduled to restart service")
}
}
FcmHelper.onEnterBackground(appContext, activeSessionHolder)
}

})
}


View File

@ -22,6 +22,7 @@ import dagger.BindsInstance
import dagger.Component
import im.vector.fragments.keysbackup.restore.KeysBackupRestoreFromPassphraseFragment
import im.vector.matrix.android.api.session.Session
import im.vector.riotredesign.core.preference.UserAvatarPreference
import im.vector.riotredesign.features.MainActivity
import im.vector.riotredesign.features.crypto.keysbackup.restore.KeysBackupRestoreFromKeyFragment
import im.vector.riotredesign.features.crypto.keysbackup.restore.KeysBackupRestoreSuccessFragment
@ -54,6 +55,7 @@ import im.vector.riotredesign.features.roomdirectory.picker.RoomDirectoryPickerF
import im.vector.riotredesign.features.roomdirectory.roompreview.RoomPreviewNoPreviewFragment
import im.vector.riotredesign.features.settings.VectorSettingsActivity
import im.vector.riotredesign.features.settings.VectorSettingsNotificationPreferenceFragment
import im.vector.riotredesign.features.settings.VectorSettingsNotificationsTroubleshootFragment
import im.vector.riotredesign.features.settings.VectorSettingsPreferencesFragment

@Component(dependencies = [VectorComponent::class], modules = [ViewModelModule::class, HomeModule::class])
@ -122,8 +124,6 @@ interface ScreenComponent {

fun inject(mainActivity: MainActivity)

fun inject(vectorSettingsPreferencesFragment: VectorSettingsPreferencesFragment)

fun inject(roomDirectoryActivity: RoomDirectoryActivity)

fun inject(bugReportActivity: BugReportActivity)
@ -136,6 +136,12 @@ interface ScreenComponent {

fun inject(vectorSettingsNotificationPreferenceFragment: VectorSettingsNotificationPreferenceFragment)

fun inject(vectorSettingsPreferencesFragment: VectorSettingsPreferencesFragment)

fun inject(userAvatarPreference: UserAvatarPreference)

fun inject(vectorSettingsNotificationsTroubleshootFragment: VectorSettingsNotificationsTroubleshootFragment)

@Component.Factory
interface Factory {
fun create(vectorComponent: VectorComponent,

View File

@ -39,6 +39,7 @@ import im.vector.riotredesign.features.notifications.PushRuleTriggerListener
import im.vector.riotredesign.features.rageshake.BugReporter
import im.vector.riotredesign.features.rageshake.RageShake
import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.riotredesign.gplay.push.fcm.VectorFirebaseMessagingService
import javax.inject.Singleton

@Component(modules = [VectorModule::class])
@ -49,6 +50,8 @@ interface VectorComponent {

fun inject(vectorApplication: VectorApplication)

fun inject(vectorFirebaseMessagingService: VectorFirebaseMessagingService)

fun matrix(): Matrix

fun currentSession(): Session
@ -61,6 +64,8 @@ interface VectorComponent {

fun vectorConfiguration(): VectorConfiguration

fun avatarRenderer(): AvatarRenderer

fun activeSessionHolder(): ActiveSessionHolder

fun emojiCompatFontProvider(): EmojiCompatFontProvider

View File

@ -0,0 +1,30 @@
/*
* 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.extensions

import android.content.Context
import im.vector.riotredesign.core.di.HasVectorInjector
import im.vector.riotredesign.core.di.VectorComponent

fun Context.vectorComponent(): VectorComponent {
val appContext = applicationContext
if (appContext is HasVectorInjector) {
return appContext.injector()
} else {
throw IllegalStateException("Your application context doesn't implement HasVectorInjector")
}
}

View File

@ -24,6 +24,8 @@ import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import im.vector.matrix.android.api.session.Session
import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.vectorComponent
import im.vector.riotredesign.features.home.AvatarRenderer

open class UserAvatarPreference : Preference {

@ -31,6 +33,8 @@ open class UserAvatarPreference : Preference {
internal var mSession: Session? = null
private var mLoadingProgressBar: ProgressBar? = null

private var avatarRenderer: AvatarRenderer = context.vectorComponent().avatarRenderer()

constructor(context: Context) : super(context)

constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
@ -52,11 +56,14 @@ open class UserAvatarPreference : Preference {
}

open fun refreshAvatar() {
if (null != mAvatarView && null != mSession) {
// TODO
// val myUser = session!!.myUser
// VectorUtils.loadUserAvatar(context, session, mAvatarView, myUser.avatarUrl, myUser.user_id, myUser.displayname)
val session = mSession ?: return
val view = mAvatarView ?: return
session.getUser(session.sessionParams.credentials.userId)?.let {
avatarRenderer.render(it, view)
} ?: run {
avatarRenderer.render(null, session.sessionParams.credentials.userId, null, view)
}

}

fun setSession(session: Session) {

View File

@ -1,26 +1,28 @@
package im.vector.riotredesign.core.pushers

import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session
import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.ActiveSessionHolder
import im.vector.riotredesign.core.resources.AppNameProvider
import im.vector.riotredesign.core.resources.LocaleProvider
import im.vector.riotredesign.core.resources.StringProvider
import java.util.*
import javax.inject.Inject

private const val DEFAULT_PUSHER_FILE_TAG = "mobile"

class PushersManager @Inject constructor(
private val currentSession: Session,
private val activeSessionHolder: ActiveSessionHolder,
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())
fun registerPusherWithFcmKey(pushKey: String): UUID {
val currentSession = activeSessionHolder.getActiveSession()
var profileTag = DEFAULT_PUSHER_FILE_TAG + "_" + Math.abs(currentSession.myUserId.hashCode())

currentSession.addHttpPusher(
return currentSession.addHttpPusher(
pushKey,
stringProvider.getString(R.string.pusher_app_id),
profileTag,
@ -34,6 +36,7 @@ class PushersManager @Inject constructor(
}

fun unregisterPusher(pushKey: String, callback: MatrixCallback<Unit>) {
currentSession.removeHttpPusher(pushKey, stringProvider.getString(R.string.pusher_app_id),callback)
val currentSession = activeSessionHolder.getActiveSession()
currentSession.removeHttpPusher(pushKey, stringProvider.getString(R.string.pusher_app_id), callback)
}
}

View File

@ -18,12 +18,17 @@ package im.vector.riotredesign.core.utils

import android.annotation.TargetApi
import android.app.Activity
import android.content.*
import android.content.ActivityNotFoundException
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.PowerManager
import android.provider.Settings
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import im.vector.riotredesign.R
import im.vector.riotredesign.features.notifications.supportNotificationChannels
@ -44,7 +49,7 @@ import java.util.*
fun isIgnoringBatteryOptimizations(context: Context): Boolean {
// no issue before Android M, battery optimisations did not exist
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M
|| (context.getSystemService(Context.POWER_SERVICE) as PowerManager?)?.isIgnoringBatteryOptimizations(context.packageName) == true
|| (context.getSystemService(Context.POWER_SERVICE) as PowerManager?)?.isIgnoringBatteryOptimizations(context.packageName) == true
}

/**
@ -109,22 +114,22 @@ fun getDeviceLocale(context: Context): Locale {
* Shows notification settings for the current app.
* In android O will directly opens the notification settings, in lower version it will show the App settings
*/
fun startNotificationSettingsIntent(fragment: Fragment, requestCode: Int) {
fun startNotificationSettingsIntent(activity: AppCompatActivity, requestCode: Int) {
val intent = Intent()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS
intent.putExtra(Settings.EXTRA_APP_PACKAGE, fragment.context?.packageName)
intent.putExtra(Settings.EXTRA_APP_PACKAGE, activity.packageName)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS
intent.putExtra("app_package", fragment.context?.packageName)
intent.putExtra("app_uid", fragment.context?.applicationInfo?.uid)
intent.putExtra("app_package", activity.packageName)
intent.putExtra("app_uid", activity.applicationInfo?.uid)
} else {
intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
intent.addCategory(Intent.CATEGORY_DEFAULT);
val uri = Uri.fromParts("package", fragment.activity?.packageName, null)
val uri = Uri.fromParts("package", activity.packageName, null)
intent.data = uri
}
fragment.startActivityForResult(intent, requestCode)
activity.startActivityForResult(intent, requestCode)
}

/**
@ -140,13 +145,13 @@ fun startNotificationChannelSettingsIntent(fragment: Fragment, channelID: String
fragment.startActivity(intent)
}

fun startAddGoogleAccountIntent(fragment: Fragment, requestCode: Int) {
fun startAddGoogleAccountIntent(context: AppCompatActivity, requestCode: Int) {
try {
val intent = Intent(Settings.ACTION_ADD_ACCOUNT)
intent.putExtra(Settings.EXTRA_ACCOUNT_TYPES, arrayOf("com.google"))
fragment.startActivityForResult(intent, requestCode)
context.startActivityForResult(intent, requestCode)
} catch (activityNotFoundException: ActivityNotFoundException) {
fragment.activity?.toast(R.string.error_no_external_application_found)
context.toast(R.string.error_no_external_application_found)
}
}


View File

@ -512,6 +512,7 @@ class RoomDetailFragment :
} else if (state.asyncInviter.complete) {
vectorBaseActivity.finish()
}
composerLayout.setRoomEncrypted(state.isEncrypted)
}

private fun renderRoomSummary(state: RoomDetailViewState) {

View File

@ -496,7 +496,10 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
private fun observeRoomSummary() {
room.rx().liveRoomSummary()
.execute { async ->
copy(asyncRoomSummary = async)
copy(
asyncRoomSummary = async,
isEncrypted = room.isEncrypted()
)
}
}


View File

@ -46,7 +46,8 @@ data class RoomDetailViewState(
val asyncInviter: Async<User> = Uninitialized,
val asyncRoomSummary: Async<RoomSummary> = Uninitialized,
val sendMode: SendMode = SendMode.REGULAR,
val selectedEvent: TimelineEvent? = null
val selectedEvent: TimelineEvent? = null,
val isEncrypted: Boolean = false
) : MvRxState {

constructor(args: RoomDetailArgs) : this(roomId = args.roomId, eventId = args.eventId)

View File

@ -9,7 +9,6 @@ import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.view.isVisible
import androidx.transition.AutoTransition
import androidx.transition.Transition
import androidx.transition.TransitionManager
@ -113,4 +112,13 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib
it.applyTo(this)
}
}

fun setRoomEncrypted(isEncrypted: Boolean) {
composerEditText.setHint(
if (isEncrypted) {
R.string.room_message_placeholder_encrypted
} else {
R.string.room_message_placeholder_not_encrypted
})
}
}

View File

@ -89,14 +89,14 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
val event = session.getRoom(roomId)?.getTimeLineEvent(eventId) ?: return state
var body: CharSequence? = null
val originTs = event.root.originServerTs
when (event.root.type) {
when (event.root.getClearType()) {
EventType.MESSAGE -> {
val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel()
?: event.root.content.toModel()
?: event.root.getClearContent().toModel()
body = messageContent?.body
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
body = eventHtmlRenderer.render(messageContent.formattedBody
?: messageContent.body)
?: messageContent.body)
}
}
EventType.STATE_ROOM_NAME,

View File

@ -64,12 +64,9 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat
// TODO This is not correct format for error, change it

val informationData = messageInformationDataFactory.create(event, nextEvent)
return NoticeItem_()
.avatarRenderer(avatarRenderer)
.noticeText(spannableStr)

return MessageTextItem_()
.message(spannableStr)
.avatarRenderer(avatarRenderer)
.informationData(informationData)
.highlighted(highlight)
.avatarCallback(callback)

View File

@ -25,7 +25,7 @@ import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.room.Room
import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.ActiveSessionHolder
import im.vector.riotredesign.core.di.HasVectorInjector
import im.vector.riotredesign.core.extensions.vectorComponent
import timber.log.Timber
import java.util.*
import javax.inject.Inject
@ -42,10 +42,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent == null || context == null) return
Timber.v("NotificationBroadcastReceiver received : $intent")
val appContext = context.applicationContext
if (appContext is HasVectorInjector) {
appContext.injector().inject(this)
}
context.vectorComponent().inject(this)
when (intent.action) {
NotificationUtils.SMART_REPLY_ACTION ->
handleSmartReply(intent, context)

View File

@ -376,7 +376,7 @@ object NotificationUtils {
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
// Build the pending intent for when the notification is clicked
val openRoomIntent = buildOpenRoomIntent(context, roomInfo.roomId)
val smallIcon = if (roomInfo.shouldBing) R.drawable.icon_notif_important else R.drawable.logo_transparent
val smallIcon = R.drawable.ic_status_bar

val channelID = if (roomInfo.shouldBing) NOISY_NOTIFICATION_CHANNEL_ID else SILENT_NOTIFICATION_CHANNEL_ID
return NotificationCompat.Builder(context, channelID)
@ -479,7 +479,7 @@ object NotificationUtils {
fun buildSimpleEventNotification(context: Context, simpleNotifiableEvent: NotifiableEvent, largeIcon: Bitmap?, matrixId: String): Notification? {
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
// Build the pending intent for when the notification is clicked
val smallIcon = if (simpleNotifiableEvent.noisy) R.drawable.icon_notif_important else R.drawable.logo_transparent
val smallIcon = R.drawable.ic_status_bar

val channelID = if (simpleNotifiableEvent.noisy) NOISY_NOTIFICATION_CHANNEL_ID else SILENT_NOTIFICATION_CHANNEL_ID

@ -611,7 +611,7 @@ object NotificationUtils {
noisy: Boolean,
lastMessageTimestamp: Long): Notification? {
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
val smallIcon = if (noisy) R.drawable.icon_notif_important else R.drawable.logo_transparent
val smallIcon = R.drawable.ic_status_bar

return NotificationCompat.Builder(context, if (noisy) NOISY_NOTIFICATION_CHANNEL_ID else SILENT_NOTIFICATION_CHANNEL_ID)
// used in compat < N, after summary is built based on child notifications

View File

@ -28,7 +28,7 @@ import javax.inject.Singleton
@Singleton
class PushRuleTriggerListener @Inject constructor(
private val resolver: NotifiableEventResolver,
private val drawerManager: NotificationDrawerManager
private val notificationDrawerManager: NotificationDrawerManager
) : PushRuleService.PushRuleListener {


@ -42,14 +42,14 @@ class PushRuleTriggerListener @Inject constructor(
}
val notificationAction = NotificationAction.extractFrom(actions)
if (notificationAction.shouldNotify) {
val resolveEvent = resolver.resolveEvent(event, session!!)
if (resolveEvent == null) {
val notifiableEvent = resolver.resolveEvent(event, session!!)
if (notifiableEvent == null) {
Timber.v("## Failed to resolve event")
//TODO
} else {
resolveEvent.noisy = !notificationAction.soundName.isNullOrBlank()
Timber.v("New event to notify $resolveEvent tweaks:$notificationAction")
drawerManager.onNotifiableEventReceived(resolveEvent)
notifiableEvent.noisy = !notificationAction.soundName.isNullOrBlank()
Timber.v("New event to notify $notifiableEvent tweaks:$notificationAction")
notificationDrawerManager.onNotifiableEventReceived(notifiableEvent)
}
} else {
Timber.v("Matched push rule is set to not notify")
@ -57,7 +57,7 @@ class PushRuleTriggerListener @Inject constructor(
}

override fun batchFinish() {
drawerManager.refreshNotificationDrawer()
notificationDrawerManager.refreshNotificationDrawer()
}

fun startWithSession(session: Session) {
@ -71,7 +71,7 @@ class PushRuleTriggerListener @Inject constructor(
fun stop() {
session?.removePushRuleListener(this)
session = null
drawerManager.clearAllEvents()
notificationDrawerManager.clearAllEvents()
}

}

View File

@ -25,14 +25,15 @@ import android.net.Uri;
import android.provider.MediaStore;
import android.text.TextUtils;

import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;

import java.io.File;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
import im.vector.riotredesign.R;
import im.vector.riotredesign.features.homeserver.ServerUrlsRepository;
import im.vector.riotredesign.features.themes.ThemeUtils;
@ -51,8 +52,6 @@ public class PreferencesManager {
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";
public static final String SETTINGS_THIRD_PARTY_NOTICES_PREFERENCE_KEY = "SETTINGS_THIRD_PARTY_NOTICES_PREFERENCE_KEY";
@ -119,7 +118,7 @@ public class PreferencesManager {
public static final String SETTINGS_NOTIFICATIONS_KEY = "SETTINGS_NOTIFICATIONS_KEY";
public static final String SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY = "SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY";
public static final String SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY = "SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY";
public static final String SETTINGS_TURN_SCREEN_ON_PREFERENCE_KEY = "SETTINGS_TURN_SCREEN_ON_PREFERENCE_KEY";
// public static final String SETTINGS_TURN_SCREEN_ON_PREFERENCE_KEY = "SETTINGS_TURN_SCREEN_ON_PREFERENCE_KEY";
public static final String SETTINGS_SYSTEM_CALL_NOTIFICATION_PREFERENCE_KEY = "SETTINGS_SYSTEM_CALL_NOTIFICATION_PREFERENCE_KEY";
public static final String SETTINGS_SYSTEM_NOISY_NOTIFICATION_PREFERENCE_KEY = "SETTINGS_SYSTEM_NOISY_NOTIFICATION_PREFERENCE_KEY";
public static final String SETTINGS_SYSTEM_SILENT_NOTIFICATION_PREFERENCE_KEY = "SETTINGS_SYSTEM_SILENT_NOTIFICATION_PREFERENCE_KEY";
@ -247,6 +246,13 @@ public class PreferencesManager {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY, true);
}

public static void setNotificationEnabledForDevice(Context context, Boolean enabled) {
PreferenceManager.getDefaultSharedPreferences(context)
.edit()
.putBoolean(SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY, enabled)
.apply();
}

/**
* Tells if we have already asked the user to disable battery optimisations on android >= M devices.
*

View File

@ -53,20 +53,15 @@ class VectorSettingsActivity : VectorBaseActivity(),
override fun initUiAndData() {
configureToolbar(settingsToolbar)

var vectorSettingsPreferencesFragment: Fragment? = null
if (isFirstCreation()) {
vectorSettingsPreferencesFragment = VectorSettingsPreferencesFragmentV2.newInstance()
val vectorSettingsPreferencesFragment = VectorSettingsRootFragment.newInstance()
// display the fragment
supportFragmentManager.beginTransaction()
.replace(R.id.vector_settings_page, vectorSettingsPreferencesFragment, FRAGMENT_TAG)
.commit()
} else {
vectorSettingsPreferencesFragment = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG)
}


supportFragmentManager.addOnBackStackChangedListener(this)

}

override fun onDestroy() {
@ -83,9 +78,7 @@ class VectorSettingsActivity : VectorBaseActivity(),
override fun onPreferenceStartFragment(caller: PreferenceFragmentCompat?, pref: Preference?): Boolean {
var oFragment: Fragment? = null

if ("Legacy" == pref?.title) {
oFragment = VectorSettingsPreferencesFragment.newInstance(session.sessionParams.credentials.userId)
} else if (PreferencesManager.SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY == pref?.key) {
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)

View File

@ -36,11 +36,7 @@ import im.vector.riotredesign.features.notifications.NotificationUtils
import im.vector.riotredesign.features.notifications.supportNotificationChannels
import javax.inject.Inject

class VectorSettingsAdvancedNotificationPreferenceFragment : VectorPreferenceFragment() {

// members
@Inject lateinit var session: Session
private var mLoadingView: View? = null
class VectorSettingsAdvancedNotificationPreferenceFragment : VectorSettingsBaseFragment() {

// events listener
/* TODO
@ -53,10 +49,9 @@ 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)
override val preferenceXmlRes = R.xml.vector_settings_notification_advanced_preferences

override fun bindPref() {
val callNotificationsSystemOptions = findPreference(PreferencesManager.SETTINGS_SYSTEM_CALL_NOTIFICATION_PREFERENCE_KEY)
if (supportNotificationChannels()) {
callNotificationsSystemOptions.onPreferenceClickListener = Preference.OnPreferenceClickListener {
@ -177,33 +172,6 @@ class VectorSettingsAdvancedNotificationPreferenceFragment : VectorPreferenceFra
}
}

override fun onResume() {
super.onResume()
// find the view from parent activity
mLoadingView = activity!!.findViewById(R.id.vector_settings_spinner_views)

/* TODO
if (session.isAlive) {

session.dataHandler.addListener(mEventsListener)

// refresh anything else
refreshPreferences()
refreshDisplay()
}
*/
}

override fun onPause() {
super.onPause()

/* TODO
if (session.isAlive) {
session.dataHandler.removeListener(mEventsListener)
}
*/
}

/**
* Refresh the known information about the account
*/
@ -246,30 +214,6 @@ class VectorSettingsAdvancedNotificationPreferenceFragment : VectorPreferenceFra
}
}


//==============================================================================================================
// Display methods
//==============================================================================================================

/**
* Display the loading view.
*/
private fun displayLoadingView() {
if (null != mLoadingView) {
mLoadingView!!.visibility = View.VISIBLE
}
}

/**
* Hide the loading view.
*/
private fun hideLoadingView() {
if (null != mLoadingView) {
mLoadingView!!.visibility = View.GONE
}
}


/* ==========================================================================================
* Companion
* ========================================================================================== */

View File

@ -0,0 +1,145 @@
/*
* 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

import android.content.Context
import android.os.Bundle
import android.text.TextUtils
import android.view.View
import androidx.annotation.CallSuper
import androidx.preference.PreferenceFragmentCompat
import im.vector.matrix.android.api.session.Session
import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.DaggerScreenComponent
import im.vector.riotredesign.core.di.HasScreenInjector
import im.vector.riotredesign.core.di.ScreenComponent
import im.vector.riotredesign.core.platform.VectorBaseActivity
import im.vector.riotredesign.core.utils.toast
import timber.log.Timber

abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), HasScreenInjector {

val vectorActivity: VectorBaseActivity by lazy {
activity as VectorBaseActivity
}

private var mLoadingView: View? = null

// members
protected lateinit var session: Session
private lateinit var screenComponent: ScreenComponent

abstract val preferenceXmlRes: Int

@CallSuper
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(preferenceXmlRes)
bindPref()
}

override fun onAttach(context: Context) {
screenComponent = DaggerScreenComponent.factory().create(vectorActivity.getVectorComponent(), vectorActivity)
super.onAttach(context)
session = screenComponent.session()
injectWith(injector())
}

protected open fun injectWith(injector: ScreenComponent) = Unit

override fun injector(): ScreenComponent {
return screenComponent
}

override fun onResume() {
super.onResume()
Timber.v("onResume Fragment ${this.javaClass.simpleName}")
vectorActivity.supportActionBar?.setTitle(titleRes)
// find the view from parent activity
mLoadingView = vectorActivity.findViewById(R.id.vector_settings_spinner_views)
}

abstract fun bindPref()

abstract var titleRes: Int

/* ==========================================================================================
* Protected
* ========================================================================================== */

protected fun notImplemented() {
// Snackbar cannot be display on PreferenceFragment
// Snackbar.make(view!!, R.string.not_implemented, Snackbar.LENGTH_SHORT)
activity?.toast(R.string.not_implemented)
}

/**
* Display the loading view.
*/
protected fun displayLoadingView() {
// search the loading view from the upper view
if (null == mLoadingView) {
var parent = view

while (parent != null && mLoadingView == null) {
mLoadingView = parent.findViewById(R.id.vector_settings_spinner_views)
parent = parent.parent as View
}
} else {
mLoadingView?.visibility = View.VISIBLE
}
}

/**
* Hide the loading view.
*/
protected fun hideLoadingView() {
mLoadingView?.visibility = View.GONE
}

/**
* Hide the loading view and refresh the preferences.
*
* @param refresh true to refresh the display
*/
protected fun hideLoadingView(refresh: Boolean) {
mLoadingView?.visibility = View.GONE

if (refresh) {
// TODO refreshDisplay()
}
}

/**
* A request has been processed.
* Display a toast if there is a an error message
*
* @param errorMessage the error message
*/
protected fun onCommonDone(errorMessage: String?) {
if (!isAdded) {
return
}
activity?.runOnUiThread {
if (!TextUtils.isEmpty(errorMessage) && errorMessage != null) {
activity?.toast(errorMessage!!)
}
hideLoadingView()
}
}


}

View File

@ -0,0 +1,171 @@
/*
* 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

import androidx.preference.PreferenceCategory
import im.vector.riotredesign.R
import im.vector.riotredesign.core.preference.ProgressBarPreference

class VectorSettingsFlairFragment : VectorSettingsBaseFragment() {

override var titleRes = R.string.settings_flair
override val preferenceXmlRes = R.xml.vector_settings_flair

// current publicised group list
private var mPublicisedGroups: MutableSet<String>? = null

// Group Flairs
private val mGroupsFlairCategory by lazy {
findPreference(PreferencesManager.SETTINGS_GROUPS_FLAIR_KEY) as PreferenceCategory
}

override fun bindPref() {
// Flair
refreshGroupFlairsList()
}

//==============================================================================================================
// Group flairs management
//==============================================================================================================

/**
* Force the refresh of the devices list.<br></br>
* The devices list is the list of the devices where the user as looged in.
* It can be any mobile device, as any browser.
*/
private fun refreshGroupFlairsList() {
// display a spinner while refreshing
if (0 == mGroupsFlairCategory.preferenceCount) {
activity?.let {
val preference = ProgressBarPreference(it)
mGroupsFlairCategory.addPreference(preference)
}
}

/*
TODO
session.groupsManager.getUserPublicisedGroups(session.myUserId, true, object : MatrixCallback<Set<String>> {
override fun onSuccess(publicisedGroups: Set<String>) {
// clear everything
mGroupsFlairCategory.removeAll()

if (publicisedGroups.isEmpty()) {
val vectorGroupPreference = Preference(activity)
vectorGroupPreference.title = resources.getString(R.string.settings_without_flair)
mGroupsFlairCategory.addPreference(vectorGroupPreference)
} else {
buildGroupsList(publicisedGroups)
}
}

override fun onNetworkError(e: Exception) {
// NOP
}

override fun onMatrixError(e: MatrixError) {
// NOP
}

override fun onUnexpectedError(e: Exception) {
// NOP
}
})
*/
}

/**
* Build the groups list.
*
* @param publicisedGroups the publicised groups list.
*/
private fun buildGroupsList(publicisedGroups: Set<String>) {
var isNewList = true

mPublicisedGroups?.let {
if (it.size == publicisedGroups.size) {
isNewList = !it.containsAll(publicisedGroups)
}
}

if (isNewList) {
/*
TODO
val joinedGroups = ArrayList(session.groupsManager.joinedGroups)
Collections.sort(joinedGroups, Group.mGroupsComparator)

mPublicisedGroups = publicisedGroups.toMutableSet()

for ((prefIndex, group) in joinedGroups.withIndex()) {
val vectorGroupPreference = VectorGroupPreference(activity!!)
vectorGroupPreference.key = DEVICES_PREFERENCE_KEY_BASE + prefIndex

vectorGroupPreference.setGroup(group, session)
vectorGroupPreference.title = group.displayName
vectorGroupPreference.summary = group.groupId

vectorGroupPreference.isChecked = publicisedGroups.contains(group.groupId)
mGroupsFlairCategory.addPreference(vectorGroupPreference)

vectorGroupPreference.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
if (newValue is Boolean) {
/*
* if mPublicisedGroup is null somehow, then
* we cant check it contains groupId or not
* so set isFlaired to false
*/
val isFlaired = mPublicisedGroups?.contains(group.groupId) ?: false

if (newValue != isFlaired) {
displayLoadingView()
session.groupsManager.updateGroupPublicity(group.groupId, newValue, object : MatrixCallback<Unit> {
override fun onSuccess(info: Void?) {
hideLoadingView()
if (newValue) {
mPublicisedGroups?.add(group.groupId)
} else {
mPublicisedGroups?.remove(group.groupId)
}
}

private fun onError() {
hideLoadingView()
// restore default value
vectorGroupPreference.isChecked = publicisedGroups.contains(group.groupId)
}

override fun onNetworkError(e: Exception) {
onError()
}

override fun onMatrixError(e: MatrixError) {
onError()
}

override fun onUnexpectedError(e: Exception) {
onError()
}
})
}
}
true
}
}
*/
}
}

}

View File

@ -0,0 +1,893 @@
/*
* 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

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.AsyncTask
import android.text.Editable
import android.text.TextUtils
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.ImageView
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.preference.EditTextPreference
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.showPassword
import im.vector.riotredesign.core.platform.SimpleTextWatcher
import im.vector.riotredesign.core.preference.UserAvatarPreference
import im.vector.riotredesign.core.preference.VectorPreference
import im.vector.riotredesign.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
import im.vector.riotredesign.core.utils.allGranted
import im.vector.riotredesign.core.utils.copyToClipboard
import im.vector.riotredesign.core.utils.toast
import im.vector.riotredesign.features.MainActivity
import im.vector.riotredesign.features.themes.ThemeUtils
import java.lang.ref.WeakReference
import java.util.*

class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {

override var titleRes = R.string.settings_general_title
override val preferenceXmlRes = R.xml.vector_settings_general

private var mDisplayedEmails = ArrayList<String>()
private var mDisplayedPhoneNumber = ArrayList<String>()

private val mUserSettingsCategory by lazy {
findPreference(PreferencesManager.SETTINGS_USER_SETTINGS_PREFERENCE_KEY) as PreferenceCategory
}
private val mUserAvatarPreference by lazy {
findPreference(PreferencesManager.SETTINGS_PROFILE_PICTURE_PREFERENCE_KEY) as UserAvatarPreference
}
private val mDisplayNamePreference by lazy {
findPreference(PreferencesManager.SETTINGS_DISPLAY_NAME_PREFERENCE_KEY) as EditTextPreference
}
private val mPasswordPreference by lazy {
findPreference(PreferencesManager.SETTINGS_CHANGE_PASSWORD_PREFERENCE_KEY)
}

// Local contacts
private val mContactSettingsCategory by lazy {
findPreference(PreferencesManager.SETTINGS_CONTACT_PREFERENCE_KEYS) as PreferenceCategory
}

private val mContactPhonebookCountryPreference by lazy {
findPreference(PreferencesManager.SETTINGS_CONTACTS_PHONEBOOK_COUNTRY_PREFERENCE_KEY)
}


override fun bindPref() {
// Avatar
mUserAvatarPreference.let {
it.setSession(session)
it.onPreferenceClickListener = Preference.OnPreferenceClickListener {
onUpdateAvatarClick()
false
}
}

// Display name
mDisplayNamePreference.let {
it.summary = session.getUser(session.sessionParams.credentials.userId)?.displayName ?: ""
it.text = it.summary.toString()
it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
onDisplayNameClick(newValue?.let { (it as String).trim() })
false
}
}

// Password
mPasswordPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
notImplemented()
// onPasswordUpdateClick()
false
}

// Add Email
(findPreference(ADD_EMAIL_PREFERENCE_KEY) as EditTextPreference).let {
// It does not work on XML, do it here
it.icon = activity?.let {
ThemeUtils.tintDrawable(it,
ContextCompat.getDrawable(it, R.drawable.ic_add_black)!!, R.attr.vctr_settings_icon_tint_color)
}

// Unfortunately, this is not supported in lib v7
// it.editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS

it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
notImplemented()
//addEmail((newValue as String).trim())
false
}
}

// Add phone number
findPreference(ADD_PHONE_NUMBER_PREFERENCE_KEY).let {
// It does not work on XML, do it here
it.icon = activity?.let {
ThemeUtils.tintDrawable(it,
ContextCompat.getDrawable(it, R.drawable.ic_add_black)!!, R.attr.vctr_settings_icon_tint_color)
}

it.setOnPreferenceClickListener {
notImplemented()
// TODO val intent = PhoneNumberAdditionActivity.getIntent(activity, session.credentials.userId)
// startActivityForResult(intent, REQUEST_NEW_PHONE_NUMBER)
true
}
}

// Advanced settings

// user account
findPreference(PreferencesManager.SETTINGS_LOGGED_IN_PREFERENCE_KEY)
.summary = session.sessionParams.credentials.userId

// home server
findPreference(PreferencesManager.SETTINGS_HOME_SERVER_PREFERENCE_KEY)
.summary = session.sessionParams.homeServerConnectionConfig.homeServerUri.toString()

// identity server
findPreference(PreferencesManager.SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY)
.summary = session.sessionParams.homeServerConnectionConfig.identityServerUri.toString()


refreshEmailsList()
refreshPhoneNumbersList()
// Contacts
setContactsPreferences()

// clear cache
findPreference(PreferencesManager.SETTINGS_CLEAR_CACHE_PREFERENCE_KEY).let {
/*
TODO
MXSession.getApplicationSizeCaches(activity, object : SimpleApiCallback<Long>() {
override fun onSuccess(size: Long) {
if (null != activity) {
it.summary = android.text.format.Formatter.formatFileSize(activity, size)
}
}
})
*/

it.onPreferenceClickListener = Preference.OnPreferenceClickListener {
displayLoadingView()
MainActivity.restartApp(activity!!, clearCache = true, clearCredentials = false)
false
}
}

// clear medias cache
findPreference(PreferencesManager.SETTINGS_CLEAR_MEDIA_CACHE_PREFERENCE_KEY).let {
/*
TODO
MXMediaCache.getCachesSize(activity, object : SimpleApiCallback<Long>() {
override fun onSuccess(size: Long) {
if (null != activity) {
it.summary = android.text.format.Formatter.formatFileSize(activity, size)
}
}
})
*/

it.onPreferenceClickListener = Preference.OnPreferenceClickListener {
notImplemented()
/* TODO
displayLoadingView()

val task = ClearMediaCacheAsyncTask(
backgroundTask = {
session.mediaCache.clear()
activity?.let { it -> Glide.get(it).clearDiskCache() }
},
onCompleteTask = {
hideLoadingView()

MXMediaCache.getCachesSize(activity, object : SimpleApiCallback<Long>() {
override fun onSuccess(size: Long) {
it.summary = Formatter.formatFileSize(activity, size)
}
})
}
)

try {
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
} catch (e: Exception) {
Timber.e(e, "## session.getMediaCache().clear() failed " + e.message)
task.cancel(true)
hideLoadingView()
}
*/

false
}
}

// Deactivate account section

// deactivate account
findPreference(PreferencesManager.SETTINGS_DEACTIVATE_ACCOUNT_KEY)
.onPreferenceClickListener = Preference.OnPreferenceClickListener {
activity?.let {
notImplemented()
// TODO startActivity(DeactivateAccountActivity.getIntent(it))
}

false
}
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
if (allGranted(grantResults)) {
if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) {
changeAvatar()
}
}
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)

if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
REQUEST_NEW_PHONE_NUMBER -> refreshPhoneNumbersList()
REQUEST_PHONEBOOK_COUNTRY -> onPhonebookCountryUpdate(data)
/* TODO
VectorUtils.TAKE_IMAGE -> {
val thumbnailUri = VectorUtils.getThumbnailUriFromIntent(activity, data, session.mediaCache)

if (null != thumbnailUri) {
displayLoadingView()

val resource = ResourceUtils.openResource(activity, thumbnailUri, null)

if (null != resource) {
session.mediaCache.uploadContent(resource.mContentStream, null, resource.mMimeType, null, object : MXMediaUploadListener() {

override fun onUploadError(uploadId: String?, serverResponseCode: Int, serverErrorMessage: String?) {
activity?.runOnUiThread { onCommonDone(serverResponseCode.toString() + " : " + serverErrorMessage) }
}

override fun onUploadComplete(uploadId: String?, contentUri: String?) {
activity?.runOnUiThread {
session.myUser.updateAvatarUrl(contentUri, object : MatrixCallback<Unit> {
override fun onSuccess(info: Void?) {
onCommonDone(null)
refreshDisplay()
}

override fun onNetworkError(e: Exception) {
onCommonDone(e.localizedMessage)
}

override fun onMatrixError(e: MatrixError) {
if (MatrixError.M_CONSENT_NOT_GIVEN == e.errcode) {
activity?.runOnUiThread {
hideLoadingView()
(activity as VectorAppCompatActivity).consentNotGivenHelper.displayDialog(e)
}
} else {
onCommonDone(e.localizedMessage)
}
}

override fun onUnexpectedError(e: Exception) {
onCommonDone(e.localizedMessage)
}
})
}
}
})
}
}
}
*/
}
}
}

/**
* Update the avatar.
*/
private fun onUpdateAvatarClick() {
notImplemented()

/* TODO
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
changeAvatar()
}
*/
}

private fun changeAvatar() {
/* TODO
val intent = Intent(activity, VectorMediaPickerActivity::class.java)
intent.putExtra(VectorMediaPickerActivity.EXTRA_AVATAR_MODE, true)
startActivityForResult(intent, VectorUtils.TAKE_IMAGE)
*/
}


//==============================================================================================================
// contacts management
//==============================================================================================================

private fun setContactsPreferences() {
/* TODO
// Permission
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// on Android >= 23, use the system one
mContactSettingsCategory.removePreference(findPreference(ContactsManager.CONTACTS_BOOK_ACCESS_KEY))
}
// Phonebook country
mContactPhonebookCountryPreference.summary = PhoneNumberUtils.getHumanCountryCode(PhoneNumberUtils.getCountryCode(activity))

mContactPhonebookCountryPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
val intent = CountryPickerActivity.getIntent(activity, true)
startActivityForResult(intent, REQUEST_PHONEBOOK_COUNTRY)
true
}
*/
}

private fun onPhonebookCountryUpdate(data: Intent?) {
/* TODO
if (data != null && data.hasExtra(CountryPickerActivity.EXTRA_OUT_COUNTRY_NAME)
&& data.hasExtra(CountryPickerActivity.EXTRA_OUT_COUNTRY_CODE)) {
val countryCode = data.getStringExtra(CountryPickerActivity.EXTRA_OUT_COUNTRY_CODE)
if (!TextUtils.equals(countryCode, PhoneNumberUtils.getCountryCode(activity))) {
PhoneNumberUtils.setCountryCode(activity, countryCode)
mContactPhonebookCountryPreference.summary = data.getStringExtra(CountryPickerActivity.EXTRA_OUT_COUNTRY_NAME)
}
}
*/
}

//==============================================================================================================
// Phone number management
//==============================================================================================================

/**
* Refresh phone number list
*/
private fun refreshPhoneNumbersList() {
/* TODO
val currentPhoneNumber3PID = ArrayList(session.myUser.getlinkedPhoneNumbers())

val phoneNumberList = ArrayList<String>()
for (identifier in currentPhoneNumber3PID) {
phoneNumberList.add(identifier.address)
}

// check first if there is an update
var isNewList = true
if (phoneNumberList.size == mDisplayedPhoneNumber.size) {
isNewList = !mDisplayedPhoneNumber.containsAll(phoneNumberList)
}

if (isNewList) {
// remove the displayed one
run {
var index = 0
while (true) {
val preference = mUserSettingsCategory.findPreference(PHONE_NUMBER_PREFERENCE_KEY_BASE + index)

if (null != preference) {
mUserSettingsCategory.removePreference(preference)
} else {
break
}
index++
}
}

// add new phone number list
mDisplayedPhoneNumber = phoneNumberList

val addPhoneBtn = mUserSettingsCategory.findPreference(ADD_PHONE_NUMBER_PREFERENCE_KEY)
?: return

var order = addPhoneBtn.order

for ((index, phoneNumber3PID) in currentPhoneNumber3PID.withIndex()) {
val preference = VectorPreference(activity!!)

preference.title = getString(R.string.settings_phone_number)
var phoneNumberFormatted = phoneNumber3PID.address
try {
// Attempt to format phone number
val phoneNumber = PhoneNumberUtil.getInstance().parse("+$phoneNumberFormatted", null)
phoneNumberFormatted = PhoneNumberUtil.getInstance().format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL)
} catch (e: NumberParseException) {
// Do nothing, we will display raw version
}

preference.summary = phoneNumberFormatted
preference.key = PHONE_NUMBER_PREFERENCE_KEY_BASE + index
preference.order = order

preference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
displayDelete3PIDConfirmationDialog(phoneNumber3PID, preference.summary)
true
}

preference.onPreferenceLongClickListener = object : VectorPreference.OnPreferenceLongClickListener {
override fun onPreferenceLongClick(preference: Preference): Boolean {
activity?.let { copyToClipboard(it, phoneNumber3PID.address) }
return true
}
}

order++
mUserSettingsCategory.addPreference(preference)
}

addPhoneBtn.order = order
} */
}

//==============================================================================================================
// Email management
//==============================================================================================================

/**
* Refresh the emails list
*/
private fun refreshEmailsList() {
val currentEmail3PID = emptyList<String>() // TODO ArrayList(session.myUser.getlinkedEmails())

val newEmailsList = ArrayList<String>()
for (identifier in currentEmail3PID) {
// TODO newEmailsList.add(identifier.address)
}

// check first if there is an update
var isNewList = true
if (newEmailsList.size == mDisplayedEmails.size) {
isNewList = !mDisplayedEmails.containsAll(newEmailsList)
}

if (isNewList) {
// remove the displayed one
run {
var index = 0
while (true) {
val preference = mUserSettingsCategory.findPreference(EMAIL_PREFERENCE_KEY_BASE + index)

if (null != preference) {
mUserSettingsCategory.removePreference(preference)
} else {
break
}
index++
}
}

// add new emails list
mDisplayedEmails = newEmailsList

val addEmailBtn = mUserSettingsCategory.findPreference(ADD_EMAIL_PREFERENCE_KEY)
?: return

var order = addEmailBtn.order

for ((index, email3PID) in currentEmail3PID.withIndex()) {
val preference = VectorPreference(activity!!)

preference.title = getString(R.string.settings_email_address)
preference.summary = "TODO" // email3PID.address
preference.key = EMAIL_PREFERENCE_KEY_BASE + index
preference.order = order

preference.onPreferenceClickListener = Preference.OnPreferenceClickListener { pref ->
displayDelete3PIDConfirmationDialog(/* TODO email3PID, */ pref.summary)
true
}

preference.onPreferenceLongClickListener = object : VectorPreference.OnPreferenceLongClickListener {
override fun onPreferenceLongClick(preference: Preference): Boolean {
activity?.let { copyToClipboard(it, "TODO") } //email3PID.address) }
return true
}
}

mUserSettingsCategory.addPreference(preference)

order++
}

addEmailBtn.order = order
}
}

/**
* Attempt to add a new email to the account
*
* @param email the email to add.
*/
private fun addEmail(email: String) {
// check first if the email syntax is valid
// if email is null , then also its invalid email
if (TextUtils.isEmpty(email) || !android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
activity?.toast(R.string.auth_invalid_email)
return
}

// check first if the email syntax is valid
if (mDisplayedEmails.indexOf(email) >= 0) {
activity?.toast(R.string.auth_email_already_defined)
return
}

notImplemented()
/* TODO
val pid = ThreePid(email, ThreePid.MEDIUM_EMAIL)

displayLoadingView()

session.myUser.requestEmailValidationToken(pid, object : MatrixCallback<Unit> {
override fun onSuccess(info: Void?) {
activity?.runOnUiThread { showEmailValidationDialog(pid) }
}

override fun onNetworkError(e: Exception) {
onCommonDone(e.localizedMessage)
}

override fun onMatrixError(e: MatrixError) {
if (TextUtils.equals(MatrixError.THREEPID_IN_USE, e.errcode)) {
onCommonDone(getString(R.string.account_email_already_used_error))
} else {
onCommonDone(e.localizedMessage)
}
}

override fun onUnexpectedError(e: Exception) {
onCommonDone(e.localizedMessage)
}
})
*/
}

/**
* Show an email validation dialog to warn the user tho valid his email link.
*
* @param pid the used pid.
*/
/* TODO
private fun showEmailValidationDialog(pid: ThreePid) {
activity?.let {
AlertDialog.Builder(it)
.setTitle(R.string.account_email_validation_title)
.setMessage(R.string.account_email_validation_message)
.setPositiveButton(R.string._continue) { _, _ ->
session.myUser.add3Pid(pid, true, object : MatrixCallback<Unit> {
override fun onSuccess(info: Void?) {
it.runOnUiThread {
hideLoadingView()
refreshEmailsList()
}
}

override fun onNetworkError(e: Exception) {
onCommonDone(e.localizedMessage)
}

override fun onMatrixError(e: MatrixError) {
if (TextUtils.equals(e.errcode, MatrixError.THREEPID_AUTH_FAILED)) {
it.runOnUiThread {
hideLoadingView()
it.toast(R.string.account_email_validation_error)
}
} else {
onCommonDone(e.localizedMessage)
}
}

override fun onUnexpectedError(e: Exception) {
onCommonDone(e.localizedMessage)
}
})
}
.setNegativeButton(R.string.cancel) { _, _ ->
hideLoadingView()
}
.show()
}
} */


/**
* Display a dialog which asks confirmation for the deletion of a 3pid
*
* @param pid the 3pid to delete
* @param preferenceSummary the displayed 3pid
*/
private fun displayDelete3PIDConfirmationDialog(/* TODO pid: ThirdPartyIdentifier,*/ preferenceSummary: CharSequence) {
val mediumFriendlyName = "TODO" // ThreePid.getMediumFriendlyName(pid.medium, activity).toLowerCase(VectorLocale.applicationLocale)
val dialogMessage = getString(R.string.settings_delete_threepid_confirmation, mediumFriendlyName, preferenceSummary)

activity?.let {
AlertDialog.Builder(it)
.setTitle(R.string.dialog_title_confirmation)
.setMessage(dialogMessage)
.setPositiveButton(R.string.remove) { _, _ ->
notImplemented()
/* TODO
displayLoadingView()

session.myUser.delete3Pid(pid, object : MatrixCallback<Unit> {
override fun onSuccess(info: Void?) {
when (pid.medium) {
ThreePid.MEDIUM_EMAIL -> refreshEmailsList()
ThreePid.MEDIUM_MSISDN -> refreshPhoneNumbersList()
}
onCommonDone(null)
}

override fun onNetworkError(e: Exception) {
onCommonDone(e.localizedMessage)
}

override fun onMatrixError(e: MatrixError) {
onCommonDone(e.localizedMessage)
}

override fun onUnexpectedError(e: Exception) {
onCommonDone(e.localizedMessage)
}
})
*/
}
.setNegativeButton(R.string.cancel, null)
.show()
}
}


/**
* Update the password.
*/
private fun onPasswordUpdateClick() {
activity?.let { activity ->
val view: ViewGroup = activity.layoutInflater.inflate(R.layout.dialog_change_password, null) as ViewGroup

val showPassword: ImageView = view.findViewById(R.id.change_password_show_passwords)
val oldPasswordTil: TextInputLayout = view.findViewById(R.id.change_password_old_pwd_til)
val oldPasswordText: TextInputEditText = view.findViewById(R.id.change_password_old_pwd_text)
val newPasswordText: TextInputEditText = view.findViewById(R.id.change_password_new_pwd_text)
val confirmNewPasswordTil: TextInputLayout = view.findViewById(R.id.change_password_confirm_new_pwd_til)
val confirmNewPasswordText: TextInputEditText = view.findViewById(R.id.change_password_confirm_new_pwd_text)
val changePasswordLoader: View = view.findViewById(R.id.change_password_loader)

var passwordShown = false

showPassword.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
passwordShown = !passwordShown

oldPasswordText.showPassword(passwordShown)
newPasswordText.showPassword(passwordShown)
confirmNewPasswordText.showPassword(passwordShown)

showPassword.setImageResource(if (passwordShown) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black)
}
})

val dialog = AlertDialog.Builder(activity)
.setView(view)
.setPositiveButton(R.string.settings_change_password_submit, null)
.setNegativeButton(R.string.cancel, null)
.setOnDismissListener {
val imm = activity.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(view.applicationWindowToken, 0)
}
.create()

dialog.setOnShowListener {
val updateButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
updateButton.isEnabled = false

fun updateUi() {
val oldPwd = oldPasswordText.text.toString().trim()
val newPwd = newPasswordText.text.toString().trim()
val newConfirmPwd = confirmNewPasswordText.text.toString().trim()

updateButton.isEnabled = oldPwd.isNotEmpty() && newPwd.isNotEmpty() && TextUtils.equals(newPwd, newConfirmPwd)

if (newPwd.isNotEmpty() && newConfirmPwd.isNotEmpty() && !TextUtils.equals(newPwd, newConfirmPwd)) {
confirmNewPasswordTil.error = getString(R.string.passwords_do_not_match)
}
}

oldPasswordText.addTextChangedListener(object : SimpleTextWatcher() {
override fun afterTextChanged(s: Editable) {
oldPasswordTil.error = null
updateUi()
}
})

newPasswordText.addTextChangedListener(object : SimpleTextWatcher() {
override fun afterTextChanged(s: Editable) {
confirmNewPasswordTil.error = null
updateUi()
}
})

confirmNewPasswordText.addTextChangedListener(object : SimpleTextWatcher() {
override fun afterTextChanged(s: Editable) {
confirmNewPasswordTil.error = null
updateUi()
}
})

fun showPasswordLoadingView(toShow: Boolean) {
if (toShow) {
showPassword.isEnabled = false
oldPasswordText.isEnabled = false
newPasswordText.isEnabled = false
confirmNewPasswordText.isEnabled = false
changePasswordLoader.isVisible = true
updateButton.isEnabled = false
} else {
showPassword.isEnabled = true
oldPasswordText.isEnabled = true
newPasswordText.isEnabled = true
confirmNewPasswordText.isEnabled = true
changePasswordLoader.isVisible = false
updateButton.isEnabled = true
}
}

updateButton.setOnClickListener {
if (passwordShown) {
// Hide passwords during processing
showPassword.performClick()
}

val imm = activity.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(view.applicationWindowToken, 0)

val oldPwd = oldPasswordText.text.toString().trim()
val newPwd = newPasswordText.text.toString().trim()

notImplemented()
/* TODO
showPasswordLoadingView(true)

session.updatePassword(oldPwd, newPwd, object : MatrixCallback<Unit> {
private fun onDone(@StringRes textResId: Int) {
showPasswordLoadingView(false)

if (textResId == R.string.settings_fail_to_update_password_invalid_current_password) {
oldPasswordTil.error = getString(textResId)
} else {
dialog.dismiss()
activity.toast(textResId, Toast.LENGTH_LONG)
}
}

override fun onSuccess(info: Void?) {
onDone(R.string.settings_password_updated)
}

override fun onNetworkError(e: Exception) {
onDone(R.string.settings_fail_to_update_password)
}

override fun onMatrixError(e: MatrixError) {
if (e.error == "Invalid password") {
onDone(R.string.settings_fail_to_update_password_invalid_current_password)
} else {
dialog.dismiss()
onDone(R.string.settings_fail_to_update_password)
}
}

override fun onUnexpectedError(e: Exception) {
onDone(R.string.settings_fail_to_update_password)
}
})
*/
}
}
dialog.show()
}
}

/**
* Update the displayname.
*/
private fun onDisplayNameClick(value: String?) {
notImplemented()
/* TODO
if (!TextUtils.equals(session.myUser.displayname, value)) {
displayLoadingView()

session.myUser.updateDisplayName(value, object : MatrixCallback<Unit> {
override fun onSuccess(info: Void?) {
// refresh the settings value
PreferenceManager.getDefaultSharedPreferences(activity).edit {
putString(PreferencesManager.SETTINGS_DISPLAY_NAME_PREFERENCE_KEY, value)
}

onCommonDone(null)

refreshDisplay()
}

override fun onNetworkError(e: Exception) {
onCommonDone(e.localizedMessage)
}

override fun onMatrixError(e: MatrixError) {
if (MatrixError.M_CONSENT_NOT_GIVEN == e.errcode) {
activity?.runOnUiThread {
hideLoadingView()
(activity as VectorAppCompatActivity).consentNotGivenHelper.displayDialog(e)
}
} else {
onCommonDone(e.localizedMessage)
}
}

override fun onUnexpectedError(e: Exception) {
onCommonDone(e.localizedMessage)
}
})
}
*/
}

private class ClearMediaCacheAsyncTask internal constructor(
backgroundTask: () -> Unit,
onCompleteTask: () -> Unit
) : AsyncTask<Unit, Unit, Unit>() {

private val backgroundTaskReference = WeakReference(backgroundTask)
private val onCompleteTaskReference = WeakReference(onCompleteTask)
override fun doInBackground(vararg params: Unit?) {
backgroundTaskReference.get()?.invoke()
}

override fun onPostExecute(result: Unit?) {
super.onPostExecute(result)
onCompleteTaskReference.get()?.invoke()
}
}

companion object {
private const val ADD_EMAIL_PREFERENCE_KEY = "ADD_EMAIL_PREFERENCE_KEY"
private const val ADD_PHONE_NUMBER_PREFERENCE_KEY = "ADD_PHONE_NUMBER_PREFERENCE_KEY"

private const val EMAIL_PREFERENCE_KEY_BASE = "EMAIL_PREFERENCE_KEY_BASE"
private const val PHONE_NUMBER_PREFERENCE_KEY_BASE = "PHONE_NUMBER_PREFERENCE_KEY_BASE"

private const val REQUEST_NEW_PHONE_NUMBER = 456
private const val REQUEST_PHONEBOOK_COUNTRY = 789
}
}

View File

@ -0,0 +1,118 @@
/*
* 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

import android.content.Intent
import android.net.Uri
import android.provider.Settings
import androidx.preference.Preference
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
import im.vector.matrix.android.api.Matrix
import im.vector.riotredesign.R
import im.vector.riotredesign.core.utils.copyToClipboard
import im.vector.riotredesign.core.utils.displayInWebView
import im.vector.riotredesign.features.version.getVersion

class VectorSettingsHelpAboutFragment : VectorSettingsBaseFragment() {

override var titleRes = R.string.preference_root_help_about
override val preferenceXmlRes = R.xml.vector_settings_help_about

override fun bindPref() {
// preference to start the App info screen, to facilitate App permissions access
findPreference(APP_INFO_LINK_PREFERENCE_KEY)
.onPreferenceClickListener = Preference.OnPreferenceClickListener {

activity?.let {
val intent = Intent().apply {
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)

val uri = Uri.fromParts("package", requireContext().packageName, null)

data = uri
}
it.applicationContext.startActivity(intent)
}

true
}

// application version
(findPreference(PreferencesManager.SETTINGS_VERSION_PREFERENCE_KEY)).let {
it.summary = getVersion(longFormat = false, useBuildNumber = true)

it.setOnPreferenceClickListener { pref ->
copyToClipboard(requireContext(), pref.summary)
true
}
}

// SDK version
(findPreference(PreferencesManager.SETTINGS_SDK_VERSION_PREFERENCE_KEY)).let {
it.summary = Matrix.getSdkVersion()

it.setOnPreferenceClickListener { pref ->
copyToClipboard(requireContext(), pref.summary)
true
}
}

// olm version
findPreference(PreferencesManager.SETTINGS_OLM_VERSION_PREFERENCE_KEY)
.summary = session.getCryptoVersion(requireContext(), false)

// copyright
findPreference(PreferencesManager.SETTINGS_COPYRIGHT_PREFERENCE_KEY)
.onPreferenceClickListener = Preference.OnPreferenceClickListener {
activity?.displayInWebView(VectorSettingsUrls.COPYRIGHT)
false
}

// terms & conditions
findPreference(PreferencesManager.SETTINGS_APP_TERM_CONDITIONS_PREFERENCE_KEY)
.onPreferenceClickListener = Preference.OnPreferenceClickListener {
activity?.displayInWebView(VectorSettingsUrls.TAC)
false
}

// privacy policy
findPreference(PreferencesManager.SETTINGS_PRIVACY_POLICY_PREFERENCE_KEY)
.onPreferenceClickListener = Preference.OnPreferenceClickListener {
activity?.displayInWebView(VectorSettingsUrls.PRIVACY_POLICY)
false
}

// third party notice
findPreference(PreferencesManager.SETTINGS_THIRD_PARTY_NOTICES_PREFERENCE_KEY)
.onPreferenceClickListener = Preference.OnPreferenceClickListener {
activity?.displayInWebView(VectorSettingsUrls.THIRD_PARTY_LICENSES)
false
}

findPreference(PreferencesManager.SETTINGS_OTHER_THIRD_PARTY_NOTICES_PREFERENCE_KEY)
.onPreferenceClickListener = Preference.OnPreferenceClickListener {
// See https://developers.google.com/android/guides/opensource
startActivity(Intent(requireActivity(), OssLicensesMenuActivity::class.java))
false
}
}

companion object {
private const val APP_INFO_LINK_PREFERENCE_KEY = "APP_INFO_LINK_PREFERENCE_KEY"
}
}

View File

@ -0,0 +1,124 @@
/*
* 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

import androidx.appcompat.app.AlertDialog
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import im.vector.riotredesign.R
import java.util.ArrayList
import kotlin.Comparator
import kotlin.String
import kotlin.getValue
import kotlin.lazy
import kotlin.let

class VectorSettingsIgnoredUsersFragment : VectorSettingsBaseFragment() {

override var titleRes = R.string.settings_ignored_users
override val preferenceXmlRes = R.xml.vector_settings_ignored_users

// displayed the ignored users list
private val mIgnoredUserSettingsCategoryDivider by lazy {
findPreference(PreferencesManager.SETTINGS_IGNORE_USERS_DIVIDER_PREFERENCE_KEY)
}
private val mIgnoredUserSettingsCategory by lazy {
findPreference(PreferencesManager.SETTINGS_IGNORED_USERS_PREFERENCE_KEY) as PreferenceCategory
}

override fun bindPref() {
// Ignore users
refreshIgnoredUsersList()
}

//==============================================================================================================
// ignored users list management
//==============================================================================================================

/**
* Refresh the ignored users list
*/
private fun refreshIgnoredUsersList() {
val ignoredUsersList = mutableListOf<String>() // TODO session.dataHandler.ignoredUserIds

ignoredUsersList.sortWith(Comparator { u1, u2 ->
u1.toLowerCase(VectorLocale.applicationLocale).compareTo(u2.toLowerCase(VectorLocale.applicationLocale))
})

val preferenceScreen = preferenceScreen

preferenceScreen.removePreference(mIgnoredUserSettingsCategory)
preferenceScreen.removePreference(mIgnoredUserSettingsCategoryDivider)
mIgnoredUserSettingsCategory.removeAll()

if (ignoredUsersList.size > 0) {
preferenceScreen.addPreference(mIgnoredUserSettingsCategoryDivider)
preferenceScreen.addPreference(mIgnoredUserSettingsCategory)

for (userId in ignoredUsersList) {
val preference = Preference(activity)

preference.title = userId
preference.key = IGNORED_USER_KEY_BASE + userId

preference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
activity?.let {
AlertDialog.Builder(it)
.setMessage(getString(R.string.settings_unignore_user, userId))
.setPositiveButton(R.string.yes) { _, _ ->
displayLoadingView()

val idsList = ArrayList<String>()
idsList.add(userId)

notImplemented()
/* TODO
session.unIgnoreUsers(idsList, object : MatrixCallback<Unit> {
override fun onSuccess(info: Void?) {
onCommonDone(null)
}

override fun onNetworkError(e: Exception) {
onCommonDone(e.localizedMessage)
}

override fun onMatrixError(e: MatrixError) {
onCommonDone(e.localizedMessage)
}

override fun onUnexpectedError(e: Exception) {
onCommonDone(e.localizedMessage)
}
})
*/
}
.setNegativeButton(R.string.no, null)
.show()
}

false
}

mIgnoredUserSettingsCategory.addPreference(preference)
}
}
}

companion object {
private const val IGNORED_USER_KEY_BASE = "IGNORED_USER_KEY_BASE"
}
}

View File

@ -0,0 +1,128 @@
/*
* 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

import android.text.TextUtils
import androidx.appcompat.app.AlertDialog
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.SwitchPreference
import im.vector.riotredesign.R

class VectorSettingsLabsFragment : VectorSettingsBaseFragment() {

override var titleRes = R.string.room_settings_labs_pref_title
override val preferenceXmlRes = R.xml.vector_settings_labs

private val mLabsCategory by lazy {
findPreference(PreferencesManager.SETTINGS_LABS_PREFERENCE_KEY) as PreferenceCategory
}

override fun bindPref() {
// Lab
val useCryptoPref = findPreference(PreferencesManager.SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_PREFERENCE_KEY) as SwitchPreference
val cryptoIsEnabledPref = findPreference(PreferencesManager.SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_IS_ACTIVE_PREFERENCE_KEY)


if (session.isCryptoEnabled()) {
mLabsCategory.removePreference(useCryptoPref)

cryptoIsEnabledPref.isEnabled = false
} else {
mLabsCategory.removePreference(cryptoIsEnabledPref)

useCryptoPref.isChecked = false

useCryptoPref.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValueAsVoid ->
if (TextUtils.isEmpty(session.sessionParams.credentials.deviceId)) {
activity?.let { activity ->
AlertDialog.Builder(activity)
.setMessage(R.string.room_settings_labs_end_to_end_warnings)
.setPositiveButton(R.string.logout) { _, _ ->
notImplemented()
// TODO CommonActivityUtils.logout(activity)
}
.setNegativeButton(R.string.cancel) { _, _ ->
useCryptoPref.isChecked = false
}
.setOnCancelListener {
useCryptoPref.isChecked = false
}
.show()
}
} else {
val newValue = newValueAsVoid as Boolean

if (session.isCryptoEnabled() != newValue) {
notImplemented()
/* TODO
displayLoadingView()

session.enableCrypto(newValue, object : MatrixCallback<Unit> {
private fun refresh() {
activity?.runOnUiThread {
hideLoadingView()
useCryptoPref.isChecked = session.isCryptoEnabled

if (session.isCryptoEnabled) {
mLabsCategory.removePreference(useCryptoPref)
mLabsCategory.addPreference(cryptoIsEnabledPref)
}
}
}

override fun onSuccess(info: Void?) {
useCryptoPref.isEnabled = false
refresh()
}

override fun onNetworkError(e: Exception) {
useCryptoPref.isChecked = false
}

override fun onMatrixError(e: MatrixError) {
useCryptoPref.isChecked = false
}

override fun onUnexpectedError(e: Exception) {
useCryptoPref.isChecked = false
}
})
*/
}
}

true
}
}

// SaveMode Management
findPreference(PreferencesManager.SETTINGS_DATA_SAVE_MODE_PREFERENCE_KEY)
.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
notImplemented()
/* TODO
val sessions = Matrix.getMXSessions(activity)
for (session in sessions) {
session.setUseDataSaveMode(newValue as Boolean)
}
*/

true
}
}

}

View File

@ -16,68 +16,117 @@

package im.vector.riotredesign.features.settings

import android.content.Context
import android.os.Bundle
import android.widget.Toast
import androidx.preference.Preference
import androidx.preference.SwitchPreference
import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.pushrules.RuleIds
import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.ActiveSessionHolder
import im.vector.riotredesign.core.di.DaggerScreenComponent
import im.vector.riotredesign.core.platform.VectorPreferenceFragment
import im.vector.riotredesign.core.di.ScreenComponent
import im.vector.riotredesign.core.pushers.PushersManager
import im.vector.riotredesign.push.fcm.FcmHelper
import javax.inject.Inject

// Referenced in vector_settings_preferences_root.xml
class VectorSettingsNotificationPreferenceFragment : VectorPreferenceFragment() {

class VectorSettingsNotificationPreferenceFragment : VectorSettingsBaseFragment() {

override var titleRes: Int = R.string.settings_notifications
override val preferenceXmlRes = R.xml.vector_settings_notifications

@Inject lateinit var pushManager: PushersManager
@Inject lateinit var activeSessionHolder: ActiveSessionHolder

override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.vector_settings_notifications)
override fun bindPref() {
findPreference(PreferencesManager.SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY).let { pref ->
val pushRuleService = session
val mRuleMaster = pushRuleService.getPushRules()
.find { it.ruleId == RuleIds.RULE_ID_DISABLE_ALL }

if (mRuleMaster == null) {
pref.isVisible = false
return
}

val areNotifEnabledAtAccountLevelt = !mRuleMaster.enabled
(pref as SwitchPreference).isChecked = areNotifEnabledAtAccountLevelt
}
}

override fun onAttach(context: Context) {
val screenComponent = DaggerScreenComponent.factory().create(vectorActivity.getVectorComponent(), vectorActivity)
super.onAttach(context)
screenComponent.inject(this)
override fun injectWith(injector: ScreenComponent) {
injector.inject(this)
}


override fun onResume() {
super.onResume()
activeSessionHolder.getSafeActiveSession()?.refreshPushers()
}

override fun onPreferenceTreeClick(preference: Preference?): Boolean {
if (preference?.key == PreferencesManager.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY) {
val switchPref = preference as SwitchPreference
if (switchPref.isChecked) {
FcmHelper.getFcmToken(requireContext())?.let {
if (PreferencesManager.areNotificationEnabledForDevice(requireContext())) {
pushManager.registerPusherWithFcmKey(it)
}
}
} else {
FcmHelper.getFcmToken(requireContext())?.let {
pushManager.unregisterPusher(it, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
super.onSuccess(data)
}

override fun onFailure(failure: Throwable) {
super.onFailure(failure)
}
})
}
return when (preference?.key) {
PreferencesManager.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY -> {
updateEnabledForDevice(preference)
true
}
PreferencesManager.SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY -> {
updateEnabledForAccount(preference)
true
}
else -> {
return super.onPreferenceTreeClick(preference)
}
}
return super.onPreferenceTreeClick(preference)

}

private fun updateEnabledForDevice(preference: Preference?) {
val switchPref = preference as SwitchPreference
if (switchPref.isChecked) {
FcmHelper.getFcmToken(requireContext())?.let {
if (PreferencesManager.areNotificationEnabledForDevice(requireContext())) {
pushManager.registerPusherWithFcmKey(it)
}
}
} else {
FcmHelper.getFcmToken(requireContext())?.let {
pushManager.unregisterPusher(it, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
session.refreshPushers()
super.onSuccess(data)
}

override fun onFailure(failure: Throwable) {
session.refreshPushers()
Toast.makeText(activity, R.string.unknown_error, Toast.LENGTH_SHORT).show()
}
})
}
}
}


private fun updateEnabledForAccount(preference: Preference?) {
val pushRuleService = session
val switchPref = preference as SwitchPreference
pushRuleService.getPushRules()
.find { it.ruleId == RuleIds.RULE_ID_DISABLE_ALL }
?.let {
//Trick, we must enable this room to disable notifications
pushRuleService.updatePushRuleEnableStatus("override", it, !switchPref.isChecked,
object : MatrixCallback<Unit> {

override fun onSuccess(data: Unit) {
pushRuleService.fetchPushRules()
}

override fun onFailure(failure: Throwable) {
//revert the check box
switchPref.isChecked = !switchPref.isChecked
Toast.makeText(activity, R.string.unknown_error, Toast.LENGTH_SHORT).show()
}
})
}

}
}

View File

@ -30,6 +30,7 @@ import androidx.transition.TransitionManager
import butterknife.BindView
import im.vector.matrix.android.api.session.Session
import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.ScreenComponent
import im.vector.riotredesign.core.extensions.withArgs
import im.vector.riotredesign.core.platform.VectorBaseActivity
import im.vector.riotredesign.core.platform.VectorBaseFragment
@ -56,15 +57,15 @@ class VectorSettingsNotificationsTroubleshootFragment : VectorBaseFragment() {
// members
@Inject lateinit var session: Session
@Inject lateinit var bugReporter: BugReporter
@Inject lateinit var testManagerFactory: NotificationTroubleshootTestManagerFactory


override fun getLayoutResId() = R.layout.fragment_settings_notifications_troubleshoot

private var interactionListener: VectorSettingsFragmentInteractionListener? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val appContext = activity!!.applicationContext
override fun injectWith(injector: ScreenComponent) {
injector.inject(this)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -74,7 +75,7 @@ class VectorSettingsNotificationsTroubleshootFragment : VectorBaseFragment() {
mRecyclerView.layoutManager = layoutManager

val dividerItemDecoration = DividerItemDecoration(mRecyclerView.context,
layoutManager.orientation)
layoutManager.orientation)
mRecyclerView.addItemDecoration(dividerItemDecoration)


@ -91,10 +92,8 @@ class VectorSettingsNotificationsTroubleshootFragment : VectorBaseFragment() {
private fun startUI() {

mSummaryDescription.text = getString(R.string.settings_troubleshoot_diagnostic_running_status,
0, 0)

testManager = NotificationTroubleshootTestManagerFactory.createTestManager(this, session)

0, 0)
testManager = testManagerFactory.create(this)
testManager?.statusListener = { troubleshootTestManager ->
if (isAdded) {
TransitionManager.beginDelayedTransition(mBottomView)
@ -104,7 +103,7 @@ class VectorSettingsNotificationsTroubleshootFragment : VectorBaseFragment() {
mSummaryButton.visibility = View.GONE
mRunButton.visibility = View.VISIBLE
}
TroubleshootTest.TestStatus.RUNNING -> {
TroubleshootTest.TestStatus.RUNNING -> {
//Forces int type because it's breaking lint
val size: Int = troubleshootTestManager.testList.size
val currentTestIndex: Int = troubleshootTestManager.currentTestIndex
@ -116,7 +115,7 @@ class VectorSettingsNotificationsTroubleshootFragment : VectorBaseFragment() {
mSummaryButton.visibility = View.GONE
mRunButton.visibility = View.GONE
}
TroubleshootTest.TestStatus.FAILED -> {
TroubleshootTest.TestStatus.FAILED -> {
//check if there are quick fixes
var hasQuickFix = false
testManager?.testList?.let {
@ -135,7 +134,7 @@ class VectorSettingsNotificationsTroubleshootFragment : VectorBaseFragment() {
mSummaryButton.visibility = View.VISIBLE
mRunButton.visibility = View.VISIBLE
}
TroubleshootTest.TestStatus.SUCCESS -> {
TroubleshootTest.TestStatus.SUCCESS -> {
mSummaryDescription.text = getString(R.string.settings_troubleshoot_diagnostic_success_status)
mSummaryButton.visibility = View.VISIBLE
mRunButton.visibility = View.VISIBLE

File diff suppressed because it is too large Load Diff

View File

@ -16,22 +16,20 @@

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() {
class VectorSettingsRootFragment : VectorSettingsBaseFragment() {

override var titleRes: Int = R.string.title_activity_settings
override val preferenceXmlRes = R.xml.vector_settings_root

override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.vector_settings_preferences_root)
override fun bindPref() {
// Nothing to do
}


companion object {
fun newInstance() = VectorSettingsPreferencesFragmentV2()
fun newInstance() = VectorSettingsRootFragment()
.withArgs {
//putString(ARG_MATRIX_ID, matrixId)
}

View File

@ -0,0 +1,868 @@
/*
* 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

import android.annotation.SuppressLint
import android.app.Activity
import android.content.DialogInterface
import android.content.Intent
import android.graphics.Typeface
import android.text.TextUtils
import android.view.KeyEvent
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.SwitchPreference
import com.google.android.material.textfield.TextInputEditText
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.extensions.getFingerprintHumanReadable
import im.vector.matrix.android.api.extensions.sortByLastSeen
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
import im.vector.riotredesign.R
import im.vector.riotredesign.core.dialogs.ExportKeysDialog
import im.vector.riotredesign.core.intent.ExternalIntentData
import im.vector.riotredesign.core.intent.analyseIntent
import im.vector.riotredesign.core.intent.getFilenameFromUri
import im.vector.riotredesign.core.platform.SimpleTextWatcher
import im.vector.riotredesign.core.preference.ProgressBarPreference
import im.vector.riotredesign.core.preference.VectorPreference
import im.vector.riotredesign.core.utils.*
import im.vector.riotredesign.features.crypto.keys.KeysExporter
import im.vector.riotredesign.features.crypto.keys.KeysImporter
import im.vector.riotredesign.features.crypto.keysbackup.settings.KeysBackupManageActivity
import timber.log.Timber
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*

class VectorSettingsSecurityPrivacyFragment : VectorSettingsBaseFragment() {

override var titleRes = R.string.settings_security_and_privacy
override val preferenceXmlRes = R.xml.vector_settings_security_privacy

// used to avoid requesting to enter the password for each deletion
private var mAccountPassword: String = ""

// devices: device IDs and device names
private var mDevicesNameList: List<DeviceInfo> = ArrayList()

private var mMyDeviceInfo: DeviceInfo? = null


// cryptography
private val mCryptographyCategory by lazy {
findPreference(PreferencesManager.SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY) as PreferenceCategory
}
private val mCryptographyCategoryDivider by lazy {
findPreference(PreferencesManager.SETTINGS_CRYPTOGRAPHY_DIVIDER_PREFERENCE_KEY)
}
// cryptography manage
private val mCryptographyManageCategory by lazy {
findPreference(PreferencesManager.SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY) as PreferenceCategory
}
private val mCryptographyManageCategoryDivider by lazy {
findPreference(PreferencesManager.SETTINGS_CRYPTOGRAPHY_MANAGE_DIVIDER_PREFERENCE_KEY)
}
// displayed pushers
private val mPushersSettingsDivider by lazy {
findPreference(PreferencesManager.SETTINGS_NOTIFICATIONS_TARGET_DIVIDER_PREFERENCE_KEY)
}
private val mPushersSettingsCategory by lazy {
findPreference(PreferencesManager.SETTINGS_NOTIFICATIONS_TARGETS_PREFERENCE_KEY) as PreferenceCategory
}
private val mDevicesListSettingsCategory by lazy {
findPreference(PreferencesManager.SETTINGS_DEVICES_LIST_PREFERENCE_KEY) as PreferenceCategory
}
private val mDevicesListSettingsCategoryDivider by lazy {
findPreference(PreferencesManager.SETTINGS_DEVICES_DIVIDER_PREFERENCE_KEY)
}
private val cryptoInfoDeviceNamePreference by lazy {
findPreference(PreferencesManager.SETTINGS_ENCRYPTION_INFORMATION_DEVICE_NAME_PREFERENCE_KEY) as VectorPreference
}

private val cryptoInfoDeviceIdPreference by lazy {
findPreference(PreferencesManager.SETTINGS_ENCRYPTION_INFORMATION_DEVICE_ID_PREFERENCE_KEY)
}

private val manageBackupPref by lazy {
findPreference(PreferencesManager.SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY)
}

private val exportPref by lazy {
findPreference(PreferencesManager.SETTINGS_ENCRYPTION_EXPORT_E2E_ROOM_KEYS_PREFERENCE_KEY)
}

private val importPref by lazy {
findPreference(PreferencesManager.SETTINGS_ENCRYPTION_IMPORT_E2E_ROOM_KEYS_PREFERENCE_KEY)
}

private val cryptoInfoTextPreference by lazy {
findPreference(PreferencesManager.SETTINGS_ENCRYPTION_INFORMATION_DEVICE_KEY_PREFERENCE_KEY)
}
// encrypt to unverified devices
private val sendToUnverifiedDevicesPref by lazy {
findPreference(PreferencesManager.SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY) as SwitchPreference
}

override fun bindPref() {
// Push target
refreshPushersList()

// Device list
refreshDevicesList()

//Refresh Key Management section
refreshKeysManagementSection()

// Analytics

// Analytics tracking management
(findPreference(PreferencesManager.SETTINGS_USE_ANALYTICS_KEY) as SwitchPreference).let {
// On if the analytics tracking is activated
it.isChecked = PreferencesManager.useAnalytics(requireContext())

it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
PreferencesManager.setUseAnalytics(requireContext(), newValue as Boolean)
true
}
}

// Rageshake Management
(findPreference(PreferencesManager.SETTINGS_USE_RAGE_SHAKE_KEY) as SwitchPreference).let {
it.isChecked = PreferencesManager.useRageshake(requireContext())

it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
PreferencesManager.setUseRageshake(requireContext(), newValue as Boolean)
true
}
}
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
if (allGranted(grantResults)) {
if (requestCode == PERMISSION_REQUEST_CODE_EXPORT_KEYS) {
exportKeys()
}
}
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)

if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
REQUEST_E2E_FILE_REQUEST_CODE -> importKeys(data)
}
}
}


private fun refreshKeysManagementSection() {
//If crypto is not enabled parent section will be removed
//TODO notice that this will not work when no network
manageBackupPref.onPreferenceClickListener = Preference.OnPreferenceClickListener {
context?.let {
startActivity(KeysBackupManageActivity.intent(it))
}
false
}

exportPref.onPreferenceClickListener = Preference.OnPreferenceClickListener {
exportKeys()
true
}

importPref.onPreferenceClickListener = Preference.OnPreferenceClickListener {
importKeys()
true
}
}

/**
* Manage the e2e keys export.
*/
private fun exportKeys() {
// We need WRITE_EXTERNAL permission
if (checkPermissions(PERMISSIONS_FOR_WRITING_FILES, this, PERMISSION_REQUEST_CODE_EXPORT_KEYS)) {
activity?.let { activity ->
ExportKeysDialog().show(activity, object : ExportKeysDialog.ExportKeyDialogListener {
override fun onPassphrase(passphrase: String) {
displayLoadingView()

KeysExporter(session)
.export(requireContext(),
passphrase,
object : MatrixCallback<String> {
override fun onSuccess(data: String) {
if (isAdded) {
hideLoadingView()

AlertDialog.Builder(activity)
.setMessage(getString(R.string.encryption_export_saved_as, data))
.setCancelable(false)
.setPositiveButton(R.string.ok, null)
.show()
}
}

override fun onFailure(failure: Throwable) {
onCommonDone(failure.localizedMessage)
}

})
}
})
}
}
}

/**
* Manage the e2e keys import.
*/
@SuppressLint("NewApi")
private fun importKeys() {
activity?.let { openFileSelection(it, this, false, REQUEST_E2E_FILE_REQUEST_CODE) }
}

/**
* Manage the e2e keys import.
*
* @param intent the intent result
*/
private fun importKeys(intent: Intent?) {
// sanity check
if (null == intent) {
return
}

val sharedDataItems = analyseIntent(intent)
val thisActivity = activity

if (sharedDataItems.isNotEmpty() && thisActivity != null) {
val sharedDataItem = sharedDataItems[0]

val uri = when (sharedDataItem) {
is ExternalIntentData.IntentDataUri -> sharedDataItem.uri
is ExternalIntentData.IntentDataClipData -> sharedDataItem.clipDataItem.uri
else -> null
}

val mimetype = when (sharedDataItem) {
is ExternalIntentData.IntentDataClipData -> sharedDataItem.mimeType
else -> null
}

if (uri == null) {
return
}

val appContext = thisActivity.applicationContext

val filename = getFilenameFromUri(appContext, uri)

val dialogLayout = thisActivity.layoutInflater.inflate(R.layout.dialog_import_e2e_keys, null)

val textView = dialogLayout.findViewById<TextView>(R.id.dialog_e2e_keys_passphrase_filename)

if (filename.isNullOrBlank()) {
textView.isVisible = false
} else {
textView.isVisible = true
textView.text = getString(R.string.import_e2e_keys_from_file, filename)
}

val builder = AlertDialog.Builder(thisActivity)
.setTitle(R.string.encryption_import_room_keys)
.setView(dialogLayout)

val passPhraseEditText = dialogLayout.findViewById<TextInputEditText>(R.id.dialog_e2e_keys_passphrase_edit_text)
val importButton = dialogLayout.findViewById<Button>(R.id.dialog_e2e_keys_import_button)

passPhraseEditText.addTextChangedListener(object : SimpleTextWatcher() {
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
importButton.isEnabled = !TextUtils.isEmpty(passPhraseEditText.text)
}
})

val importDialog = builder.show()

importButton.setOnClickListener(View.OnClickListener {
val password = passPhraseEditText.text.toString()

displayLoadingView()

KeysImporter(session)
.import(requireContext(),
uri,
mimetype,
password,
object : MatrixCallback<ImportRoomKeysResult> {
override fun onSuccess(data: ImportRoomKeysResult) {
if (!isAdded) {
return
}

hideLoadingView()

AlertDialog.Builder(thisActivity)
.setMessage(getString(R.string.encryption_import_room_keys_success,
data.successfullyNumberOfImportedKeys,
data.totalNumberOfKeys))
.setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
.show()
}

override fun onFailure(failure: Throwable) {
appContext.toast(failure.localizedMessage)
hideLoadingView()
}
})

importDialog.dismiss()
})
}
}

//==============================================================================================================
// Cryptography
//==============================================================================================================

private fun removeCryptographyPreference() {
preferenceScreen.let {
it.removePreference(mCryptographyCategory)
it.removePreference(mCryptographyCategoryDivider)

// Also remove keys management section
it.removePreference(mCryptographyManageCategory)
it.removePreference(mCryptographyManageCategoryDivider)
}
}

/**
* Build the cryptography preference section.
*
* @param aMyDeviceInfo the device info
*/
private fun refreshCryptographyPreference(aMyDeviceInfo: DeviceInfo?) {
val userId = session.sessionParams.credentials.userId
val deviceId = session.sessionParams.credentials.deviceId

// device name
if (null != aMyDeviceInfo) {
cryptoInfoDeviceNamePreference.summary = aMyDeviceInfo.displayName

cryptoInfoDeviceNamePreference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
displayDeviceRenameDialog(aMyDeviceInfo)
true
}

cryptoInfoDeviceNamePreference.onPreferenceLongClickListener = object : VectorPreference.OnPreferenceLongClickListener {
override fun onPreferenceLongClick(preference: Preference): Boolean {
activity?.let { copyToClipboard(it, aMyDeviceInfo.displayName!!) }
return true
}
}
}

// crypto section: device ID
if (!TextUtils.isEmpty(deviceId)) {
cryptoInfoDeviceIdPreference.summary = deviceId

cryptoInfoDeviceIdPreference.setOnPreferenceClickListener {
activity?.let { copyToClipboard(it, deviceId!!) }
true
}
}

// crypto section: device key (fingerprint)
if (!TextUtils.isEmpty(deviceId) && !TextUtils.isEmpty(userId)) {
val deviceInfo = session.getDeviceInfo(userId, deviceId)

if (null != deviceInfo && !TextUtils.isEmpty(deviceInfo.fingerprint())) {
cryptoInfoTextPreference.summary = deviceInfo.getFingerprintHumanReadable()

cryptoInfoTextPreference.setOnPreferenceClickListener {
deviceInfo.fingerprint()?.let {
copyToClipboard(requireActivity(), it)
}
true
}
}
}

sendToUnverifiedDevicesPref.isChecked = false

sendToUnverifiedDevicesPref.isChecked = session.getGlobalBlacklistUnverifiedDevices()

sendToUnverifiedDevicesPref.onPreferenceClickListener = Preference.OnPreferenceClickListener {
session.setGlobalBlacklistUnverifiedDevices(sendToUnverifiedDevicesPref.isChecked)

true
}
}


//==============================================================================================================
// devices list
//==============================================================================================================

private fun removeDevicesPreference() {
preferenceScreen.let {
it.removePreference(mDevicesListSettingsCategory)
it.removePreference(mDevicesListSettingsCategoryDivider)
}
}

/**
* Force the refresh of the devices list.<br></br>
* The devices list is the list of the devices where the user as looged in.
* It can be any mobile device, as any browser.
*/
private fun refreshDevicesList() {
if (session.isCryptoEnabled() && !TextUtils.isEmpty(session.sessionParams.credentials.deviceId)) {
// display a spinner while loading the devices list
if (0 == mDevicesListSettingsCategory.preferenceCount) {
activity?.let {
val preference = ProgressBarPreference(it)
mDevicesListSettingsCategory.addPreference(preference)
}
}

session.getDevicesList(object : MatrixCallback<DevicesListResponse> {
override fun onSuccess(data: DevicesListResponse) {
if (!isAdded) {
return
}

if (data.devices?.isEmpty() == true) {
removeDevicesPreference()
} else {
buildDevicesSettings(data.devices!!)
}
}

override fun onFailure(failure: Throwable) {
if (!isAdded) {
return
}

removeDevicesPreference()
onCommonDone(failure.message)
}
})
} else {
removeDevicesPreference()
removeCryptographyPreference()
}
}

/**
* Build the devices portion of the settings.<br></br>
* Each row correspond to a device ID and its corresponding device name. Clicking on the row
* display a dialog containing: the device ID, the device name and the "last seen" information.
*
* @param aDeviceInfoList the list of the devices
*/
private fun buildDevicesSettings(aDeviceInfoList: List<DeviceInfo>) {
var preference: VectorPreference
var typeFaceHighlight: Int
var isNewList = true
val myDeviceId = session.sessionParams.credentials.deviceId

if (aDeviceInfoList.size == mDevicesNameList.size) {
isNewList = !mDevicesNameList.containsAll(aDeviceInfoList)
}

if (isNewList) {
var prefIndex = 0
mDevicesNameList = aDeviceInfoList

// sort before display: most recent first
mDevicesNameList.sortByLastSeen()

// start from scratch: remove the displayed ones
mDevicesListSettingsCategory.removeAll()

for (deviceInfo in mDevicesNameList) {
// set bold to distinguish current device ID
if (null != myDeviceId && myDeviceId == deviceInfo.deviceId) {
mMyDeviceInfo = deviceInfo
typeFaceHighlight = Typeface.BOLD
} else {
typeFaceHighlight = Typeface.NORMAL
}

// add the edit text preference
preference = VectorPreference(requireActivity()).apply {
mTypeface = typeFaceHighlight
}

if (null == deviceInfo.deviceId && null == deviceInfo.displayName) {
continue
} else {
if (null != deviceInfo.deviceId) {
preference.title = deviceInfo.deviceId
}

// display name parameter can be null (new JSON API)
if (null != deviceInfo.displayName) {
preference.summary = deviceInfo.displayName
}
}

preference.key = DEVICES_PREFERENCE_KEY_BASE + prefIndex
prefIndex++

// onClick handler: display device details dialog
preference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
displayDeviceDetailsDialog(deviceInfo)
true
}

mDevicesListSettingsCategory.addPreference(preference)
}

refreshCryptographyPreference(mMyDeviceInfo)
}
}

/**
* Display a dialog containing the device ID, the device name and the "last seen" information.<>
* This dialog allow to delete the corresponding device (see [.displayDeviceDeletionDialog])
*
* @param aDeviceInfo the device information
*/
private fun displayDeviceDetailsDialog(aDeviceInfo: DeviceInfo) {

activity?.let {

val builder = AlertDialog.Builder(it)
val inflater = it.layoutInflater
val layout = inflater.inflate(R.layout.dialog_device_details, null)
var textView = layout.findViewById<TextView>(R.id.device_id)

textView.text = aDeviceInfo.deviceId

// device name
textView = layout.findViewById(R.id.device_name)
val displayName = if (TextUtils.isEmpty(aDeviceInfo.displayName)) LABEL_UNAVAILABLE_DATA else aDeviceInfo.displayName
textView.text = displayName

// last seen info
textView = layout.findViewById(R.id.device_last_seen)

val lastSeenIp = aDeviceInfo.lastSeenIp?.takeIf { ip -> ip.isNotBlank() } ?: "-"

val lastSeenTime = aDeviceInfo.lastSeenTs?.let { ts ->
val dateFormatTime = SimpleDateFormat("HH:mm:ss")
val date = Date(ts)

val time = dateFormatTime.format(date)
val dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault())

dateFormat.format(date) + ", " + time
} ?: "-"

val lastSeenInfo = getString(R.string.devices_details_last_seen_format, lastSeenIp, lastSeenTime)
textView.text = lastSeenInfo

// title & icon
builder.setTitle(R.string.devices_details_dialog_title)
.setIcon(android.R.drawable.ic_dialog_info)
.setView(layout)
.setPositiveButton(R.string.rename) { _, _ -> displayDeviceRenameDialog(aDeviceInfo) }

// disable the deletion for our own device
if (!TextUtils.equals(session.getMyDevice()?.deviceId, aDeviceInfo.deviceId)) {
builder.setNegativeButton(R.string.delete) { _, _ -> deleteDevice(aDeviceInfo) }
}

builder.setNeutralButton(R.string.cancel, null)
.setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event ->
if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
dialog.cancel()
return@OnKeyListener true
}
false
})
.show()
}
}

/**
* Display an alert dialog to rename a device
*
* @param aDeviceInfoToRename device info
*/
private fun displayDeviceRenameDialog(aDeviceInfoToRename: DeviceInfo) {
activity?.let {
val inflater = it.layoutInflater
val layout = inflater.inflate(R.layout.dialog_base_edit_text, null)

val input = layout.findViewById<EditText>(R.id.edit_text)
input.setText(aDeviceInfoToRename.displayName)

AlertDialog.Builder(it)
.setTitle(R.string.devices_details_device_name)
.setView(layout)
.setPositiveButton(R.string.ok) { _, _ ->
displayLoadingView()

val newName = input.text.toString()

session.setDeviceName(aDeviceInfoToRename.deviceId!!, newName, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
hideLoadingView()

// search which preference is updated
val count = mDevicesListSettingsCategory.preferenceCount

for (i in 0 until count) {
val pref = mDevicesListSettingsCategory.getPreference(i)

if (TextUtils.equals(aDeviceInfoToRename.deviceId, pref.title)) {
pref.summary = newName
}
}

// detect if the updated device is the current account one
if (TextUtils.equals(cryptoInfoDeviceIdPreference.summary, aDeviceInfoToRename.deviceId)) {
cryptoInfoDeviceNamePreference.summary = newName
}

// Also change the display name in aDeviceInfoToRename, in case of multiple renaming
aDeviceInfoToRename.displayName = newName
}

override fun onFailure(failure: Throwable) {
onCommonDone(failure.localizedMessage)
}
})
}
.setNegativeButton(R.string.cancel, null)
.show()
}
}

/**
* Try to delete a device.
*
* @param deviceInfo the device to delete
*/
private fun deleteDevice(deviceInfo: DeviceInfo) {
val deviceId = deviceInfo.deviceId
if (deviceId == null) {
Timber.e("## displayDeviceDeletionDialog(): sanity check failure")
return
}

displayLoadingView()
session.deleteDevice(deviceId, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
hideLoadingView()
// force settings update
refreshDevicesList()
}

override fun onFailure(failure: Throwable) {
var isPasswordRequestFound = false

if (failure is Failure.RegistrationFlowError) {
// We only support LoginFlowTypes.PASSWORD
// Check if we can provide the user password
failure.registrationFlowResponse.flows?.forEach { interactiveAuthenticationFlow ->
isPasswordRequestFound = isPasswordRequestFound || interactiveAuthenticationFlow.stages?.any { it == LoginFlowTypes.PASSWORD } == true
}

if (isPasswordRequestFound) {
maybeShowDeleteDeviceWithPasswordDialog(deviceId, failure.registrationFlowResponse.session)
}

}

if (!isPasswordRequestFound) {
// LoginFlowTypes.PASSWORD not supported, and this is the only one RiotX supports so far...
onCommonDone(failure.localizedMessage)
}
}
})
}

/**
* Show a dialog to ask for user password, or use a previously entered password.
*/
private fun maybeShowDeleteDeviceWithPasswordDialog(deviceId: String, authSession: String?) {
if (!TextUtils.isEmpty(mAccountPassword)) {
deleteDeviceWithPassword(deviceId, authSession, mAccountPassword)
} else {
activity?.let {
val inflater = it.layoutInflater
val layout = inflater.inflate(R.layout.dialog_device_delete, null)
val passwordEditText = layout.findViewById<EditText>(R.id.delete_password)

AlertDialog.Builder(it)
.setIcon(android.R.drawable.ic_dialog_alert)
.setTitle(R.string.devices_delete_dialog_title)
.setView(layout)
.setPositiveButton(R.string.devices_delete_submit_button_label, DialogInterface.OnClickListener { _, _ ->
if (TextUtils.isEmpty(passwordEditText.toString())) {
it.toast(R.string.error_empty_field_your_password)
return@OnClickListener
}
mAccountPassword = passwordEditText.text.toString()
deleteDeviceWithPassword(deviceId, authSession, mAccountPassword)
})
.setNegativeButton(R.string.cancel) { _, _ ->
hideLoadingView()
}
.setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event ->
if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
dialog.cancel()
hideLoadingView()
return@OnKeyListener true
}
false
})
.show()
}
}
}

private fun deleteDeviceWithPassword(deviceId: String, authSession: String?, accountPassword: String) {
session.deleteDeviceWithUserPassword(deviceId, authSession, accountPassword, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
hideLoadingView()
// force settings update
refreshDevicesList()
}

override fun onFailure(failure: Throwable) {
// Password is maybe not good
onCommonDone(failure.localizedMessage)
mAccountPassword = ""
}
})
}

//==============================================================================================================
// pushers list management
//==============================================================================================================

/**
* Refresh the pushers list
*/
private fun refreshPushersList() {
activity?.let { activity ->
/* TODO
val pushManager = Matrix.getInstance(activity).pushManager
val pushersList = ArrayList(pushManager.mPushersList)

if (pushersList.isEmpty()) {
preferenceScreen.removePreference(mPushersSettingsCategory)
preferenceScreen.removePreference(mPushersSettingsDivider)
return
}

// check first if there is an update
var isNewList = true
if (pushersList.size == mDisplayedPushers.size) {
isNewList = !mDisplayedPushers.containsAll(pushersList)
}

if (isNewList) {
// remove the displayed one
mPushersSettingsCategory.removeAll()

// add new emails list
mDisplayedPushers = pushersList

var index = 0

for (pushRule in mDisplayedPushers) {
if (null != pushRule.lang) {
val isThisDeviceTarget = TextUtils.equals(pushManager.currentRegistrationToken, pushRule.pushkey)

val preference = VectorPreference(activity).apply {
mTypeface = if (isThisDeviceTarget) Typeface.BOLD else Typeface.NORMAL
}
preference.title = pushRule.deviceDisplayName
preference.summary = pushRule.appDisplayName
preference.key = PUSHER_PREFERENCE_KEY_BASE + index
index++
mPushersSettingsCategory.addPreference(preference)

// the user cannot remove the self device target
if (!isThisDeviceTarget) {
preference.onPreferenceLongClickListener = object : VectorPreference.OnPreferenceLongClickListener {
override fun onPreferenceLongClick(preference: Preference): Boolean {
AlertDialog.Builder(activity)
.setTitle(R.string.dialog_title_confirmation)
.setMessage(R.string.settings_delete_notification_targets_confirmation)
.setPositiveButton(R.string.remove)
{ _, _ ->
displayLoadingView()
pushManager.unregister(session, pushRule, object : MatrixCallback<Unit> {
override fun onSuccess(info: Void?) {
refreshPushersList()
onCommonDone(null)
}

override fun onNetworkError(e: Exception) {
onCommonDone(e.localizedMessage)
}

override fun onMatrixError(e: MatrixError) {
onCommonDone(e.localizedMessage)
}

override fun onUnexpectedError(e: Exception) {
onCommonDone(e.localizedMessage)
}
})
}
.setNegativeButton(R.string.cancel, null)
.show()
return true
}
}
}
}
}
}
*/
}
}

companion object {
private const val REQUEST_E2E_FILE_REQUEST_CODE = 123

private const val PUSHER_PREFERENCE_KEY_BASE = "PUSHER_PREFERENCE_KEY_BASE"
private const val DEVICES_PREFERENCE_KEY_BASE = "DEVICES_PREFERENCE_KEY_BASE"

// TODO i18n
private const val LABEL_UNAVAILABLE_DATA = "none"
}
}

View File

@ -0,0 +1,92 @@
/*
* 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

import android.app.Activity
import android.content.Intent
import android.media.RingtoneManager
import android.net.Uri
import androidx.preference.Preference
import androidx.preference.SwitchPreference
import im.vector.riotredesign.R
import im.vector.riotredesign.core.utils.getCallRingtoneName
import im.vector.riotredesign.core.utils.getCallRingtoneUri
import im.vector.riotredesign.core.utils.setCallRingtoneUri
import im.vector.riotredesign.core.utils.setUseRiotDefaultRingtone

class VectorSettingsVoiceVideoFragment : VectorSettingsBaseFragment() {

override var titleRes = R.string.preference_voice_and_video
override val preferenceXmlRes = R.xml.vector_settings_voice_video

private val mUseRiotCallRingtonePreference by lazy {
findPreference(PreferencesManager.SETTINGS_CALL_RINGTONE_USE_RIOT_PREFERENCE_KEY) as SwitchPreference
}
private val mCallRingtonePreference by lazy {
findPreference(PreferencesManager.SETTINGS_CALL_RINGTONE_URI_PREFERENCE_KEY)
}

override fun bindPref() {
// Incoming call sounds
mUseRiotCallRingtonePreference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
activity?.let { setUseRiotDefaultRingtone(it, mUseRiotCallRingtonePreference.isChecked) }
false
}

mCallRingtonePreference.let {
activity?.let { activity -> it.summary = getCallRingtoneName(activity) }
it.onPreferenceClickListener = Preference.OnPreferenceClickListener {
displayRingtonePicker()
false
}
}
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)

if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
REQUEST_CALL_RINGTONE -> {
val callRingtoneUri: Uri? = data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
val thisActivity = activity
if (callRingtoneUri != null && thisActivity != null) {
setCallRingtoneUri(thisActivity, callRingtoneUri)
mCallRingtonePreference.summary = getCallRingtoneName(thisActivity)
}
}
}
}
}


private fun displayRingtonePicker() {
val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, getString(R.string.settings_call_ringtone_dialog_title))
putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false)
putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true)
putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_RINGTONE)
activity?.let { putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, getCallRingtoneUri(it)) }
}
startActivityForResult(intent, REQUEST_CALL_RINGTONE)
}

companion object {
private const val REQUEST_CALL_RINGTONE = 999
}

}

View File

@ -15,45 +15,48 @@
*/
package im.vector.riotredesign.features.settings.troubleshoot

import androidx.fragment.app.Fragment
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.pushrules.RuleIds
import im.vector.matrix.android.api.session.Session
import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.ActiveSessionHolder
import im.vector.riotredesign.core.resources.StringProvider
import javax.inject.Inject

/**
* Check that the main pushRule (RULE_ID_DISABLE_ALL) is correctly setup
*/
class TestAccountSettings(val fragment: Fragment, val session: Session)
class TestAccountSettings @Inject constructor(private val stringProvider: StringProvider,
private val activeSessionHolder: ActiveSessionHolder)
: TroubleshootTest(R.string.settings_troubleshoot_test_account_settings_title) {

override fun perform() {
val session = activeSessionHolder.getSafeActiveSession() ?: return
val defaultRule = session.getPushRules()
.find { it.ruleId == RuleIds.RULE_ID_DISABLE_ALL }

if (defaultRule != null) {
if (!defaultRule.enabled) {
description = fragment.getString(R.string.settings_troubleshoot_test_account_settings_success)
description = stringProvider.getString(R.string.settings_troubleshoot_test_account_settings_success)
quickFix = null
status = TestStatus.SUCCESS
} else {
description = fragment.getString(R.string.settings_troubleshoot_test_account_settings_failed)
description = stringProvider.getString(R.string.settings_troubleshoot_test_account_settings_failed)
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_account_settings_quickfix) {
override fun doFix() {
if (manager?.diagStatus == TestStatus.RUNNING) return //wait before all is finished

// TODO Use constant for kind
session.updatePushRuleEnableStatus("override", defaultRule, !defaultRule.enabled,
object : MatrixCallback<Unit> {
object : MatrixCallback<Unit> {

override fun onSuccess(data: Unit) {
manager?.retry()
}
override fun onSuccess(data: Unit) {
manager?.retry()
}

override fun onFailure(failure: Throwable) {
manager?.retry()
}
})
override fun onFailure(failure: Throwable) {
manager?.retry()
}
})
}
}
status = TestStatus.FAILED

View File

@ -15,66 +15,71 @@
*/
package im.vector.riotredesign.features.settings.troubleshoot

import androidx.fragment.app.Fragment
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.pushrules.Action
import im.vector.matrix.android.api.pushrules.RuleIds
import im.vector.riotredesign.R
import im.vector.riotredesign.features.settings.PreferencesManager
import im.vector.riotredesign.features.settings.VectorSettingsFragmentInteractionListener
import im.vector.riotredesign.core.di.ActiveSessionHolder
import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.features.notifications.NotificationAction
import javax.inject.Inject

class TestBingRulesSettings(val fragment: Fragment, val session: Session) : TroubleshootTest(R.string.settings_troubleshoot_test_bing_settings_title) {
class TestBingRulesSettings @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
private val stringProvider: StringProvider) : TroubleshootTest(R.string.settings_troubleshoot_test_bing_settings_title) {

private val testedRules =
listOf(RuleIds.RULE_ID_CONTAIN_DISPLAY_NAME,
RuleIds.RULE_ID_CONTAIN_USER_NAME,
RuleIds.RULE_ID_ONE_TO_ONE_ROOM,
RuleIds.RULE_ID_ALL_OTHER_MESSAGES_ROOMS)

private val testedRules = emptyArray<String>()
/* TODO
arrayOf(BingRule.RULE_ID_CONTAIN_DISPLAY_NAME,
BingRule.RULE_ID_CONTAIN_USER_NAME,
BingRule.RULE_ID_ONE_TO_ONE_ROOM,
BingRule.RULE_ID_ALL_OTHER_MESSAGES_ROOMS)
*/

val ruleSettingsName = arrayOf(R.string.settings_containing_my_display_name,
R.string.settings_containing_my_user_name,
R.string.settings_messages_in_one_to_one,
R.string.settings_messages_in_group_chat)
R.string.settings_containing_my_user_name,
R.string.settings_messages_in_one_to_one,
R.string.settings_messages_in_group_chat)

override fun perform() {
val pushRules = null // TODO session.dataHandler.pushRules()
val session = activeSessionHolder.getSafeActiveSession() ?: return
val pushRules = session.getPushRules()
if (pushRules == null) {
description = fragment.getString(R.string.settings_troubleshoot_test_bing_settings_failed_to_load_rules)
description = stringProvider.getString(R.string.settings_troubleshoot_test_bing_settings_failed_to_load_rules)
status = TestStatus.FAILED
} else {
var oneOrMoreRuleIsOff = false
var oneOrMoreRuleAreSilent = false
for ((index, ruleId) in testedRules.withIndex()) {
/* TODO
pushRules.findDefaultRule(ruleId)?.let { rule ->
if (!rule.isEnabled || rule.shouldNotNotify()) {
pushRules.find { it.ruleId == ruleId }?.let { rule ->
val actions = Action.mapFrom(rule) ?: return@let
val notifAction = NotificationAction.extractFrom(actions)
if (!rule.enabled || !notifAction.shouldNotify) {
//off
oneOrMoreRuleIsOff = true
} else if (rule.notificationSound == null) {
} else if (notifAction.soundName == null) {
//silent
oneOrMoreRuleAreSilent = true
} else {
//noisy
}
}
*/

}

if (oneOrMoreRuleIsOff) {
description = fragment.getString(R.string.settings_troubleshoot_test_bing_settings_failed)
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_bing_settings_quickfix) {
override fun doFix() {
val activity = fragment.activity
if (activity is VectorSettingsFragmentInteractionListener) {
activity.requestHighlightPreferenceKeyOnResume(PreferencesManager.SETTINGS_NOTIFICATION_ADVANCED_PREFERENCE_KEY)
}
activity?.supportFragmentManager?.popBackStack()
}
}
description = stringProvider.getString(R.string.settings_troubleshoot_test_bing_settings_failed)
//TODO
// quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_bing_settings_quickfix) {
// override fun doFix() {
// val activity = fragment.activity
// if (activity is VectorSettingsFragmentInteractionListener) {
// activity.requestHighlightPreferenceKeyOnResume(PreferencesManager.SETTINGS_NOTIFICATION_ADVANCED_PREFERENCE_KEY)
// }
// activity?.supportFragmentManager?.popBackStack()
// }
// }
status = TestStatus.FAILED
} else {
if (oneOrMoreRuleAreSilent) {
description = fragment.getString(R.string.settings_troubleshoot_test_bing_settings_success_with_warn)
description = stringProvider.getString(R.string.settings_troubleshoot_test_bing_settings_success_with_warn)
} else {
description = null
}

View File

@ -15,33 +15,33 @@
*/
package im.vector.riotredesign.features.settings.troubleshoot

import androidx.fragment.app.Fragment
import androidx.appcompat.app.AppCompatActivity
import im.vector.riotredesign.R
import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.features.settings.PreferencesManager
import javax.inject.Inject

/**
* Checks if notifications are enable in the system settings for this app.
*/
class TestDeviceSettings(val fragment: Fragment) : TroubleshootTest(R.string.settings_troubleshoot_test_device_settings_title) {
class TestDeviceSettings @Inject constructor(private val context: AppCompatActivity,
private val stringProvider: StringProvider) : TroubleshootTest(R.string.settings_troubleshoot_test_device_settings_title) {

override fun perform() {
/* TODO
val pushManager = Matrix.getInstance(fragment.activity).pushManager
if (pushManager.areDeviceNotificationsAllowed()) {
description = fragment.getString(R.string.settings_troubleshoot_test_device_settings_success)

if (PreferencesManager.areNotificationEnabledForDevice(context)) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_device_settings_success)
quickFix = null
status = TestStatus.SUCCESS
} else {
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_device_settings_quickfix) {
override fun doFix() {
pushManager.setDeviceNotificationsAllowed(true)
PreferencesManager.setNotificationEnabledForDevice(context, true)
manager?.retry()
}

}
description = fragment.getString(R.string.settings_troubleshoot_test_device_settings_failed)
description = stringProvider.getString(R.string.settings_troubleshoot_test_device_settings_failed)
status = TestStatus.FAILED
}
*/
status = TestStatus.FAILED
}
}

View File

@ -15,27 +15,30 @@
*/
package im.vector.riotredesign.features.settings.troubleshoot

import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.NotificationManagerCompat
import androidx.fragment.app.Fragment
import im.vector.riotredesign.R
import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.core.utils.startNotificationSettingsIntent
import javax.inject.Inject

/**
* Checks if notifications are enable in the system settings for this app.
*/
class TestSystemSettings(val fragment: Fragment) : TroubleshootTest(R.string.settings_troubleshoot_test_system_settings_title) {
class TestSystemSettings @Inject constructor(private val context: AppCompatActivity,
private val stringProvider: StringProvider) : TroubleshootTest(R.string.settings_troubleshoot_test_system_settings_title) {

override fun perform() {
if (NotificationManagerCompat.from(fragment.context!!).areNotificationsEnabled()) {
description = fragment.getString(R.string.settings_troubleshoot_test_system_settings_success)
if (NotificationManagerCompat.from(context).areNotificationsEnabled()) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_system_settings_success)
quickFix = null
status = TestStatus.SUCCESS
} else {
description = fragment.getString(R.string.settings_troubleshoot_test_system_settings_failed)
description = stringProvider.getString(R.string.settings_troubleshoot_test_system_settings_failed)
quickFix = object : TroubleshootQuickFix(R.string.open_settings) {
override fun doFix() {
if (manager?.diagStatus == TestStatus.RUNNING) return //wait before all is finished
startNotificationSettingsIntent(fragment, NotificationTroubleshootTestManager.REQ_CODE_FIX)
startNotificationSettingsIntent(context, NotificationTroubleshootTestManager.REQ_CODE_FIX)
}

}

View File

@ -0,0 +1,32 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:pathData="m103.426,146.731c10.234,10.234 27.539,9.521 38.653,-1.592s11.826,-28.419 1.592,-38.653c-10.234,-10.234 -72.627,-72.746 -72.627,-72.746l-7.951,9.069s1.497,6.213 -0.319,8.284c-1.816,2.071 -9.701,3.147 -9.701,3.147l-16.509,18.833s-1.861,4.935 -1.092,5.704z"
android:fillColor="#000"
android:fillAlpha=".7"
android:fillType="evenOdd"/>
<path
android:pathData="m47.713,42.317v7.766l7.886,-0.008c0.1,0 0.192,-0.003 0.282,-0.009 2.073,-0.137 3.694,-1.837 3.694,-3.87 0,-2.139 -1.78,-3.879 -3.969,-3.879zM39.915,80.675c-4.307,0 -7.799,-3.413 -7.799,-7.623v-14.528c-0.028,-0.263 -0.043,-0.531 -0.043,-0.802 -0,-0.276 0.014,-0.549 0.043,-0.817v-22.21c0,-4.21 3.492,-7.624 7.799,-7.624h15.692c10.789,0 19.567,8.58 19.567,19.126 0,10.027 -8.012,18.408 -18.24,19.081 -0.435,0.029 -0.883,0.044 -1.327,0.044l-7.893,0.008v7.723c0,4.21 -3.491,7.623 -7.799,7.623z"
android:fillColor="#a2ddef"
android:fillType="evenOdd"/>
<path
android:pathData="m47.713,42.317v7.766l7.886,-0.008c0.1,0 0.192,-0.003 0.282,-0.009 2.073,-0.137 3.694,-1.837 3.694,-3.87 0,-2.139 -1.78,-3.879 -3.969,-3.879zM39.915,80.675c-4.307,0 -7.799,-3.413 -7.799,-7.623v-38.358c0,-4.21 3.492,-7.624 7.799,-7.624h15.692c10.789,0 19.567,8.58 19.567,19.126 0,10.027 -8.012,18.408 -18.24,19.081 -0.435,0.029 -0.883,0.044 -1.327,0.044l-7.893,0.008v7.723c0,4.21 -3.491,7.623 -7.799,7.623z"
android:strokeWidth="1.372009"
android:fillColor="#00000000"
android:strokeColor="#368bd6"
android:fillType="evenOdd"/>
<path
android:pathData="m39.915,73.052v-38.358h15.692c6.499,0 11.767,5.15 11.767,11.502 0,6.089 -4.84,11.073 -10.964,11.476 -0.266,0.018 -0.533,0.027 -0.803,0.027h-15.692"
android:strokeWidth="1.372009"
android:fillColor="#00000000"
android:strokeColor="#368bd6"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="m46.341,30.322c1.196,1.668 1.655,3.691 1.293,5.695 -0.362,2.005 -1.502,3.752 -3.21,4.92 -3.525,2.411 -8.402,1.572 -10.87,-1.871 -1.196,-1.668 -1.655,-3.691 -1.293,-5.696 0.362,-2.005 1.502,-3.752 3.21,-4.92 3.525,-2.411 8.401,-1.572 10.869,1.871zM67.439,80.671c-2.458,0 -4.876,-1.132 -6.394,-3.249l-11.022,-15.375c-2.472,-3.448 -1.616,-8.202 1.911,-10.617 3.527,-2.417 8.39,-1.58 10.862,1.868l11.022,15.375c2.472,3.448 1.616,8.202 -1.911,10.617 -1.362,0.933 -2.923,1.381 -4.469,1.381z"
android:fillColor="#368bd6"
android:fillType="evenOdd"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 908 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1022 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 596 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

View File

@ -1,34 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -1,170 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillColor="#26A69A"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:pathData="m0,0h108v108h-108z"
android:fillColor="#27303a"
android:fillType="evenOdd"/>
</vector>

View File

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

View File

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

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="22dp"
android:height="22dp"
android:viewportWidth="22"
android:viewportHeight="22">
<path
android:pathData="M21,15.92v3a2,2 0,0 1,-2.18 2,19.79 19.79,0 0,1 -8.63,-3.07 19.5,19.5 0,0 1,-6 -6,19.79 19.79,0 0,1 -3.07,-8.67A2,2 0,0 1,3.11 1h3a2,2 0,0 1,2 1.72c0.127,0.96 0.361,1.903 0.7,2.81a2,2 0,0 1,-0.45 2.11L7.09,8.91a16,16 0,0 0,6 6l1.27,-1.27a2,2 0,0 1,2.11 -0.45c0.907,0.339 1.85,0.573 2.81,0.7A2,2 0,0 1,21 15.92z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#7E899C"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,22 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="24dp"
android:viewportWidth="16"
android:viewportHeight="24">
<path
android:pathData="M8,8m-7,0a7,7 0,1 1,14 0a7,7 0,1 1,-14 0"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#7E899C"
android:strokeLineCap="round"/>
<path
android:pathData="M4.21,13.89L3,23l5,-3 5,3 -1.21,-9.12"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#7E899C"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,22 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,12m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#7E899C"
android:strokeLineCap="round"/>
<path
android:pathData="M19.4,15a1.65,1.65 0,0 0,0.33 1.82l0.06,0.06a2,2 0,1 1,-2.83 2.83l-0.06,-0.06a1.65,1.65 0,0 0,-1.82 -0.33,1.65 1.65,0 0,0 -1,1.51L14.08,21a2,2 0,1 1,-4 0v-0.09A1.65,1.65 0,0 0,9 19.4a1.65,1.65 0,0 0,-1.82 0.33l-0.06,0.06a2,2 0,1 1,-2.83 -2.83l0.06,-0.06a1.65,1.65 0,0 0,0.33 -1.82,1.65 1.65,0 0,0 -1.51,-1L3,14.08a2,2 0,1 1,0 -4h0.09A1.65,1.65 0,0 0,4.6 9a1.65,1.65 0,0 0,-0.33 -1.82l-0.06,-0.06a2,2 0,1 1,2.83 -2.83l0.06,0.06a1.65,1.65 0,0 0,1.82 0.33L9,4.68a1.65,1.65 0,0 0,1 -1.51L10,3a2,2 0,1 1,4 0v0.09a1.65,1.65 0,0 0,1 1.51,1.65 1.65,0 0,0 1.82,-0.33l0.06,-0.06a2,2 0,1 1,2.83 2.83l-0.06,0.06a1.65,1.65 0,0 0,-0.33 1.82L19.32,9c0.26,0.604 0.852,0.997 1.51,1L21,10a2,2 0,1 1,0 4h-0.09a1.65,1.65 0,0 0,-1.51 1z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#7E899C"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="22dp"
android:height="22dp"
android:viewportWidth="22"
android:viewportHeight="22">
<path
android:pathData="M11,11m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#7E899C"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="M8.09,8C8.5754,6.62 9.9854,5.7914 11.4272,6.0387C12.869,6.286 13.9222,7.5372 13.92,9C13.92,11 10.92,12 10.92,12"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#7E899C"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="M11.0002,15.9913L11,15.9913"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#7E899C"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="17dp"
android:height="20dp"
android:viewportWidth="17"
android:viewportHeight="20">
<path
android:pathData="M16,19v-2a4,4 0,0 0,-4 -4H5a4,4 0,0 0,-4 4v2"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#7E899C"
android:strokeLineCap="round"/>
<path
android:pathData="M8.5,5m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#7E899C"
android:strokeLineCap="round"/>
<path
android:pathData="M5.672,2.172l5.656,5.656"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#7E899C"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="22dp"
android:viewportWidth="18"
android:viewportHeight="22">
<path
android:pathData="M1,14s1,-1 4,-1 5,2 8,2 4,-1 4,-1V2s-1,1 -4,1 -5,-2 -8,-2 -4,1 -4,1v12zM1,21v-7"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#7E899C"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="20dp"
android:viewportWidth="24"
android:viewportHeight="20">
<path
android:pathData="M21,6v13H3V6M1,1h22v5H1zM10,10h4"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#7E899C"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="22dp"
android:viewportWidth="20"
android:viewportHeight="22">
<path
android:pathData="M16,7A6,6 0,1 0,4 7c0,7 -3,9 -3,9h18s-3,-2 -3,-9M11.73,20a2,2 0,0 1,-3.46 0"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#7E899C"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="20dp"
android:viewportWidth="24"
android:viewportHeight="20">
<path
android:pathData="M4,19v-7M4,8V1M12,19v-9M12,6V1M20,19v-5M20,10V1M1,12h6M9,6h6M17,14h6"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#7E899C"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,22 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="22dp"
android:viewportWidth="20"
android:viewportHeight="22">
<path
android:pathData="M3,10L17,10A2,2 0,0 1,19 12L19,19A2,2 0,0 1,17 21L3,21A2,2 0,0 1,1 19L1,12A2,2 0,0 1,3 10z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#7E899C"
android:strokeLineCap="round"/>
<path
android:pathData="M5,10L5,6a5,5 0,1 1,10 0v4"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#7E899C"
android:strokeLineCap="round"/>
</vector>

View File

@ -27,7 +27,8 @@
android:id="@+id/logoImageView"
android:layout_width="wrap_content"
android:layout_height="100dp"
android:src="@drawable/logo_login" />
android:layout_gravity="center_horizontal"
android:src="@drawable/riot_splash_0_blue" />

<com.google.android.material.textfield.TextInputLayout
style="@style/VectorTextInputLayout"

View File

@ -116,12 +116,12 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:hint="@string/room_message_placeholder_not_encrypted"
android:nextFocusLeft="@id/composerEditText"
android:nextFocusUp="@id/composerEditText"
android:padding="8dp"
android:textColor="?vctr_message_text_color"
android:textSize="14sp"
tools:hint="@string/room_message_placeholder_not_encrypted"
tools:ignore="MissingConstraints" />

</merge>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Some files were not shown because too many files have changed in this diff Show More