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 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> { override val query = Monarchy.Query<EventEntity> {


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

View File

@ -112,4 +112,8 @@ internal class DefaultPusherService @Inject constructor(
{ it.asDomain() } { 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' kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.4.0'


// gplay flavor only // gplay flavor only
gplayImplementation 'com.google.firebase:firebase-core:16.0.8' gplayImplementation('com.google.firebase:firebase-messaging:19.0.1') {
gplayImplementation 'com.google.firebase:firebase-messaging:17.5.0' 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 // TESTS
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'

View File

@ -8,7 +8,7 @@


<application> <application>


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


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


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

</application> </application>


</manifest> </manifest>

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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 androidx.fragment.app.Fragment
import im.vector.riotredesign.R import im.vector.riotredesign.R

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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.content.Context
import android.net.ConnectivityManager import android.net.ConnectivityManager

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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 androidx.fragment.app.Fragment
import im.vector.riotredesign.R 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.AlarmManager
import android.app.PendingIntent import android.app.PendingIntent
@ -9,6 +25,7 @@ import android.os.Build
import android.os.PowerManager import android.os.PowerManager
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import im.vector.matrix.android.internal.session.sync.job.SyncService import im.vector.matrix.android.internal.session.sync.job.SyncService
import im.vector.riotredesign.fdroid.service.VectorSyncService
import timber.log.Timber import timber.log.Timber


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


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


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


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

View File

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


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


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


class OnApplicationUpgradeOrRebootReceiver : BroadcastReceiver() { class OnApplicationUpgradeOrRebootReceiver : BroadcastReceiver() {


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

View File

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


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


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


override fun onDestroy() { override fun onDestroy() {
Timber.v("VectorSyncService - onDestroy ") Timber.v("VectorSyncService - onDestroy")
removeForegroundNotif() removeForegroundNotif()
super.onDestroy() 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 { override fun onBind(intent: Intent?): IBinder {
Timber.v("VectorSyncService - onBind ") Timber.v("VectorSyncService - onBind ")

View File

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


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

import im.vector.riotredesign.core.di.ActiveSessionHolder
import im.vector.riotredesign.core.pushers.PushersManager 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 { object FcmHelper {


fun isPushSupported(): Boolean = false fun isPushSupported(): Boolean = false
@ -52,4 +58,17 @@ object FcmHelper {
fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager) { fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager) {
// No op // 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 androidx.fragment.app.Fragment
import im.vector.matrix.android.api.session.Session 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.features.settings.troubleshoot.*
import im.vector.riotredesign.push.fcm.troubleshoot.TestAutoStartBoot
import im.vector.riotredesign.push.fcm.troubleshoot.TestBackgroundRestrictions


class NotificationTroubleshootTestManagerFactory { class NotificationTroubleshootTestManagerFactory {



View File

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


<service android:name=".push.fcm.VectorFirebaseMessagingService"> <service android:name=".gplay.push.fcm.VectorFirebaseMessagingService">
<intent-filter> <intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" /> <action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter> </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 * See the License for the specific language governing permissions and
* limitations under the License. * 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.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability import com.google.android.gms.common.GoogleApiAvailability
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.features.settings.troubleshoot.TroubleshootTest import im.vector.riotredesign.features.settings.troubleshoot.TroubleshootTest
import timber.log.Timber 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. * 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() { override fun perform() {
val apiAvailability = GoogleApiAvailability.getInstance() val apiAvailability = GoogleApiAvailability.getInstance()
val resultCode = apiAvailability.isGooglePlayServicesAvailable(fragment.context) val resultCode = apiAvailability.isGooglePlayServicesAvailable(context)
if (resultCode == ConnectionResult.SUCCESS) { if (resultCode == ConnectionResult.SUCCESS) {
quickFix = null 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 status = TestStatus.SUCCESS
} else { } else {
if (apiAvailability.isUserResolvableError(resultCode)) { if (apiAvailability.isUserResolvableError(resultCode)) {
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_play_services_quickfix) { quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_play_services_quickfix) {
override fun doFix() { override fun doFix() {
fragment.activity?.let { apiAvailability.getErrorDialog(context, resultCode, 9000 /*hey does the magic number*/).show()
apiAvailability.getErrorDialog(it, resultCode, 9000 /*hey does the magic number*/).show()
}
} }
} }
Timber.e("Play Services apk error $resultCode -> ${apiAvailability.getErrorString(resultCode)}.") 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 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. * limitations under the License.
*/ */


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


import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
@ -26,11 +26,12 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner
import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage 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.Session
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.riotredesign.BuildConfig import im.vector.riotredesign.BuildConfig
import im.vector.riotredesign.R 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.preference.BingRule
import im.vector.riotredesign.core.pushers.PushersManager import im.vector.riotredesign.core.pushers.PushersManager
import im.vector.riotredesign.features.badge.BadgeProxy 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.NotifiableMessageEvent
import im.vector.riotredesign.features.notifications.NotificationDrawerManager import im.vector.riotredesign.features.notifications.NotificationDrawerManager
import im.vector.riotredesign.features.notifications.SimpleNotifiableEvent import im.vector.riotredesign.features.notifications.SimpleNotifiableEvent
<<<<<<< HEAD
=======
import im.vector.riotredesign.features.settings.PreferencesManager import im.vector.riotredesign.features.settings.PreferencesManager
import org.koin.android.ext.android.inject import im.vector.riotredesign.push.fcm.FcmHelper
>>>>>>> develop
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject


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


<<<<<<< HEAD
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager @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 // UI handler
private val mUIHandler by lazy { private val mUIHandler by lazy {
Handler(Looper.getMainLooper()) Handler(Looper.getMainLooper())
} }


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

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


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


if (session == null) { if (session == null) {
Timber.w("## Can't sync from push, no current session") 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 // check if the event was not yet received
// a previous catchup might have already retrieved the notified event // a previous catchup might have already retrieved the notified event
private fun isEventAlreadyKnown(eventId: String?, roomId: String?): Boolean { private fun isEventAlreadyKnown(eventId: String?, roomId: String?): Boolean {
if (null != eventId && null != roomId) { if (null != eventId && null != roomId) {
try { try {
val session = safeGetCurrentSession() ?: return false val session = activeSessionHolder.getSafeActiveSession() ?: return false
val room = session.getRoom(roomId) ?: return false val room = session.getRoom(roomId) ?: return false
return room.getTimeLineEvent(eventId) != null return room.getTimeLineEvent(eventId) != null
} catch (e: Exception) { } catch (e: Exception) {
@ -241,7 +228,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
if (notifiableEvent is NotifiableMessageEvent) { if (notifiableEvent is NotifiableMessageEvent) {
if (TextUtils.isEmpty(notifiableEvent.senderName)) { if (TextUtils.isEmpty(notifiableEvent.senderName)) {
notifiableEvent.senderName = data["sender_display_name"] notifiableEvent.senderName = data["sender_display_name"]
?: data["sender"] ?: "" ?: data["sender"] ?: ""
} }
if (TextUtils.isEmpty(notifiableEvent.roomName)) { if (TextUtils.isEmpty(notifiableEvent.roomName)) {
notifiableEvent.roomName = findRoomNameBestEffort(data, session) ?: "" notifiableEvent.roomName = findRoomNameBestEffort(data, session) ?: ""
@ -287,11 +274,11 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {


try { try {
return Event(eventId = data["event_id"], return Event(eventId = data["event_id"],
senderId = data["sender"], senderId = data["sender"],
roomId = data["room_id"], roomId = data["room_id"],
type = data.getValue("type"), type = data.getValue("type"),
// TODO content = data.getValue("content"), // TODO content = data.getValue("content"),
originServerTs = System.currentTimeMillis()) originServerTs = System.currentTimeMillis())
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "buildEvent fails ") 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.android.gms.common.GoogleApiAvailability
import com.google.firebase.iid.FirebaseInstanceId import com.google.firebase.iid.FirebaseInstanceId
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.ActiveSessionHolder
import im.vector.riotredesign.core.pushers.PushersManager import im.vector.riotredesign.core.pushers.PushersManager
import timber.log.Timber import timber.log.Timber


@ -99,4 +100,12 @@ object FcmHelper {
val resultCode = apiAvailability.isGooglePlayServicesAvailable(activity) val resultCode = apiAvailability.isGooglePlayServicesAvailable(activity)
return resultCode == ConnectionResult.SUCCESS 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 package im.vector.riotredesign.push.fcm


import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import im.vector.matrix.android.api.session.Session import im.vector.riotredesign.features.settings.troubleshoot.NotificationTroubleshootTestManager
import im.vector.riotredesign.features.settings.troubleshoot.* import im.vector.riotredesign.features.settings.troubleshoot.TestAccountSettings
import im.vector.riotredesign.push.fcm.troubleshoot.TestFirebaseToken import im.vector.riotredesign.features.settings.troubleshoot.TestBingRulesSettings
import im.vector.riotredesign.push.fcm.troubleshoot.TestPlayServices import im.vector.riotredesign.features.settings.troubleshoot.TestDeviceSettings
import im.vector.riotredesign.push.fcm.troubleshoot.TestTokenRegistration 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 create(fragment: Fragment): NotificationTroubleshootTestManager {
fun createTestManager(fragment: Fragment, session: Session?): NotificationTroubleshootTestManager { val mgr = NotificationTroubleshootTestManager(fragment)
val mgr = NotificationTroubleshootTestManager(fragment) mgr.addTest(testSystemSettings)
mgr.addTest(TestSystemSettings(fragment)) mgr.addTest(testAccountSettings)
if (session != null) { mgr.addTest(testDeviceSettings)
mgr.addTest(TestAccountSettings(fragment, session)) mgr.addTest(testBingRulesSettings)
} mgr.addTest(testPlayServices)
mgr.addTest(TestDeviceSettings(fragment)) mgr.addTest(testFirebaseToken)
if (session != null) { mgr.addTest(testTokenRegistration)
mgr.addTest(TestBingRulesSettings(fragment, session)) return mgr
}
mgr.addTest(TestPlayServices(fragment))
mgr.addTest(TestFirebaseToken(fragment))
mgr.addTest(TestTokenRegistration(fragment))
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 <service
android:name=".core.services.CallService" android:name=".core.services.CallService"
android:exported="false" /> 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 --> <!-- 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.HasVectorInjector
import im.vector.riotredesign.core.di.VectorComponent import im.vector.riotredesign.core.di.VectorComponent
import im.vector.riotredesign.core.extensions.configureAndStart 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.configuration.VectorConfiguration
import im.vector.riotredesign.features.lifecycle.VectorActivityLifecycleCallbacks import im.vector.riotredesign.features.lifecycle.VectorActivityLifecycleCallbacks
import im.vector.riotredesign.features.notifications.NotificationDrawerManager 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.notifications.PushRuleTriggerListener
import im.vector.riotredesign.features.rageshake.VectorFileLogger import im.vector.riotredesign.features.rageshake.VectorFileLogger
import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler 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.features.version.getVersion
import im.vector.riotredesign.push.fcm.FcmHelper import im.vector.riotredesign.push.fcm.FcmHelper
import timber.log.Timber import timber.log.Timber
@ -113,7 +111,7 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.


@OnLifecycleEvent(Lifecycle.Event.ON_RESUME) @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun entersForeground() { fun entersForeground() {
AlarmSyncBroadcastReceiver.cancelAlarm(appContext) FcmHelper.onEnterForeground(appContext)
activeSessionHolder.getSafeActiveSession()?.also { activeSessionHolder.getSafeActiveSession()?.also {
it.stopAnyBackgroundSync() it.stopAnyBackgroundSync()
} }
@ -123,19 +121,8 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
fun entersBackground() { fun entersBackground() {
Timber.i("App entered background") // call persistInfo Timber.i("App entered background") // call persistInfo
notificationDrawerManager.persistInfo() notificationDrawerManager.persistInfo()
if (FcmHelper.isPushSupported()) { FcmHelper.onEnterBackground(appContext, activeSessionHolder)
//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")
}
}
} }

}) })
} }



View File

@ -22,6 +22,7 @@ import dagger.BindsInstance
import dagger.Component import dagger.Component
import im.vector.fragments.keysbackup.restore.KeysBackupRestoreFromPassphraseFragment import im.vector.fragments.keysbackup.restore.KeysBackupRestoreFromPassphraseFragment
import im.vector.matrix.android.api.session.Session 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.MainActivity
import im.vector.riotredesign.features.crypto.keysbackup.restore.KeysBackupRestoreFromKeyFragment import im.vector.riotredesign.features.crypto.keysbackup.restore.KeysBackupRestoreFromKeyFragment
import im.vector.riotredesign.features.crypto.keysbackup.restore.KeysBackupRestoreSuccessFragment 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.roomdirectory.roompreview.RoomPreviewNoPreviewFragment
import im.vector.riotredesign.features.settings.VectorSettingsActivity import im.vector.riotredesign.features.settings.VectorSettingsActivity
import im.vector.riotredesign.features.settings.VectorSettingsNotificationPreferenceFragment import im.vector.riotredesign.features.settings.VectorSettingsNotificationPreferenceFragment
import im.vector.riotredesign.features.settings.VectorSettingsNotificationsTroubleshootFragment
import im.vector.riotredesign.features.settings.VectorSettingsPreferencesFragment import im.vector.riotredesign.features.settings.VectorSettingsPreferencesFragment


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


fun inject(mainActivity: MainActivity) fun inject(mainActivity: MainActivity)


fun inject(vectorSettingsPreferencesFragment: VectorSettingsPreferencesFragment)

fun inject(roomDirectoryActivity: RoomDirectoryActivity) fun inject(roomDirectoryActivity: RoomDirectoryActivity)


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


fun inject(vectorSettingsNotificationPreferenceFragment: VectorSettingsNotificationPreferenceFragment) fun inject(vectorSettingsNotificationPreferenceFragment: VectorSettingsNotificationPreferenceFragment)


fun inject(vectorSettingsPreferencesFragment: VectorSettingsPreferencesFragment)

fun inject(userAvatarPreference: UserAvatarPreference)

fun inject(vectorSettingsNotificationsTroubleshootFragment: VectorSettingsNotificationsTroubleshootFragment)

@Component.Factory @Component.Factory
interface Factory { interface Factory {
fun create(vectorComponent: VectorComponent, 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.BugReporter
import im.vector.riotredesign.features.rageshake.RageShake import im.vector.riotredesign.features.rageshake.RageShake
import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.riotredesign.gplay.push.fcm.VectorFirebaseMessagingService
import javax.inject.Singleton import javax.inject.Singleton


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


fun inject(vectorApplication: VectorApplication) fun inject(vectorApplication: VectorApplication)


fun inject(vectorFirebaseMessagingService: VectorFirebaseMessagingService)

fun matrix(): Matrix fun matrix(): Matrix


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


fun vectorConfiguration(): VectorConfiguration fun vectorConfiguration(): VectorConfiguration


fun avatarRenderer(): AvatarRenderer

fun activeSessionHolder(): ActiveSessionHolder fun activeSessionHolder(): ActiveSessionHolder


fun emojiCompatFontProvider(): EmojiCompatFontProvider 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 androidx.preference.PreferenceViewHolder
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.vectorComponent
import im.vector.riotredesign.features.home.AvatarRenderer


open class UserAvatarPreference : Preference { open class UserAvatarPreference : Preference {


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


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

constructor(context: Context) : super(context) constructor(context: Context) : super(context)


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


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

} }


fun setSession(session: Session) { fun setSession(session: Session) {

View File

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


import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session
import im.vector.riotredesign.R 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.AppNameProvider
import im.vector.riotredesign.core.resources.LocaleProvider import im.vector.riotredesign.core.resources.LocaleProvider
import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.core.resources.StringProvider
import java.util.*
import javax.inject.Inject import javax.inject.Inject


private const val DEFAULT_PUSHER_FILE_TAG = "mobile" private const val DEFAULT_PUSHER_FILE_TAG = "mobile"


class PushersManager @Inject constructor( class PushersManager @Inject constructor(
private val currentSession: Session, private val activeSessionHolder: ActiveSessionHolder,
private val localeProvider: LocaleProvider, private val localeProvider: LocaleProvider,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val appNameProvider: AppNameProvider private val appNameProvider: AppNameProvider
) { ) {


fun registerPusherWithFcmKey(pushKey: String) { fun registerPusherWithFcmKey(pushKey: String): UUID {
var profileTag = DEFAULT_PUSHER_FILE_TAG + "_" + Math.abs(currentSession.sessionParams.credentials.userId.hashCode()) val currentSession = activeSessionHolder.getActiveSession()
var profileTag = DEFAULT_PUSHER_FILE_TAG + "_" + Math.abs(currentSession.myUserId.hashCode())


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


fun unregisterPusher(pushKey: String, callback: MatrixCallback<Unit>) { 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.annotation.TargetApi
import android.app.Activity 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.net.Uri
import android.os.Build import android.os.Build
import android.os.PowerManager import android.os.PowerManager
import android.provider.Settings import android.provider.Settings
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.features.notifications.supportNotificationChannels import im.vector.riotredesign.features.notifications.supportNotificationChannels
@ -44,7 +49,7 @@ import java.util.*
fun isIgnoringBatteryOptimizations(context: Context): Boolean { fun isIgnoringBatteryOptimizations(context: Context): Boolean {
// no issue before Android M, battery optimisations did not exist // no issue before Android M, battery optimisations did not exist
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M 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. * 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 * 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() val intent = Intent()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS 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) { } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS
intent.putExtra("app_package", fragment.context?.packageName) intent.putExtra("app_package", activity.packageName)
intent.putExtra("app_uid", fragment.context?.applicationInfo?.uid) intent.putExtra("app_uid", activity.applicationInfo?.uid)
} else { } else {
intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
intent.addCategory(Intent.CATEGORY_DEFAULT); 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 intent.data = uri
} }
fragment.startActivityForResult(intent, requestCode) activity.startActivityForResult(intent, requestCode)
} }


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


fun startAddGoogleAccountIntent(fragment: Fragment, requestCode: Int) { fun startAddGoogleAccountIntent(context: AppCompatActivity, requestCode: Int) {
try { try {
val intent = Intent(Settings.ACTION_ADD_ACCOUNT) val intent = Intent(Settings.ACTION_ADD_ACCOUNT)
intent.putExtra(Settings.EXTRA_ACCOUNT_TYPES, arrayOf("com.google")) intent.putExtra(Settings.EXTRA_ACCOUNT_TYPES, arrayOf("com.google"))
fragment.startActivityForResult(intent, requestCode) context.startActivityForResult(intent, requestCode)
} catch (activityNotFoundException: ActivityNotFoundException) { } 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) { } else if (state.asyncInviter.complete) {
vectorBaseActivity.finish() vectorBaseActivity.finish()
} }
composerLayout.setRoomEncrypted(state.isEncrypted)
} }


private fun renderRoomSummary(state: RoomDetailViewState) { private fun renderRoomSummary(state: RoomDetailViewState) {

View File

@ -496,7 +496,10 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
private fun observeRoomSummary() { private fun observeRoomSummary() {
room.rx().liveRoomSummary() room.rx().liveRoomSummary()
.execute { async -> .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 asyncInviter: Async<User> = Uninitialized,
val asyncRoomSummary: Async<RoomSummary> = Uninitialized, val asyncRoomSummary: Async<RoomSummary> = Uninitialized,
val sendMode: SendMode = SendMode.REGULAR, val sendMode: SendMode = SendMode.REGULAR,
val selectedEvent: TimelineEvent? = null val selectedEvent: TimelineEvent? = null,
val isEncrypted: Boolean = false
) : MvRxState { ) : MvRxState {


constructor(args: RoomDetailArgs) : this(roomId = args.roomId, eventId = args.eventId) 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 android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.view.isVisible
import androidx.transition.AutoTransition import androidx.transition.AutoTransition
import androidx.transition.Transition import androidx.transition.Transition
import androidx.transition.TransitionManager import androidx.transition.TransitionManager
@ -113,4 +112,13 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib
it.applyTo(this) 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 val event = session.getRoom(roomId)?.getTimeLineEvent(eventId) ?: return state
var body: CharSequence? = null var body: CharSequence? = null
val originTs = event.root.originServerTs val originTs = event.root.originServerTs
when (event.root.type) { when (event.root.getClearType()) {
EventType.MESSAGE -> { EventType.MESSAGE -> {
val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel() val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel()
?: event.root.content.toModel() ?: event.root.getClearContent().toModel()
body = messageContent?.body body = messageContent?.body
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) { if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
body = eventHtmlRenderer.render(messageContent.formattedBody body = eventHtmlRenderer.render(messageContent.formattedBody
?: messageContent.body) ?: messageContent.body)
} }
} }
EventType.STATE_ROOM_NAME, 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 // TODO This is not correct format for error, change it


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

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

View File

@ -376,7 +376,7 @@ object NotificationUtils {
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color) val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
// Build the pending intent for when the notification is clicked // Build the pending intent for when the notification is clicked
val openRoomIntent = buildOpenRoomIntent(context, roomInfo.roomId) 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 val channelID = if (roomInfo.shouldBing) NOISY_NOTIFICATION_CHANNEL_ID else SILENT_NOTIFICATION_CHANNEL_ID
return NotificationCompat.Builder(context, channelID) return NotificationCompat.Builder(context, channelID)
@ -479,7 +479,7 @@ object NotificationUtils {
fun buildSimpleEventNotification(context: Context, simpleNotifiableEvent: NotifiableEvent, largeIcon: Bitmap?, matrixId: String): Notification? { fun buildSimpleEventNotification(context: Context, simpleNotifiableEvent: NotifiableEvent, largeIcon: Bitmap?, matrixId: String): Notification? {
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color) val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
// Build the pending intent for when the notification is clicked // 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 val channelID = if (simpleNotifiableEvent.noisy) NOISY_NOTIFICATION_CHANNEL_ID else SILENT_NOTIFICATION_CHANNEL_ID


@ -611,7 +611,7 @@ object NotificationUtils {
noisy: Boolean, noisy: Boolean,
lastMessageTimestamp: Long): Notification? { lastMessageTimestamp: Long): Notification? {
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color) 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) 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 // used in compat < N, after summary is built based on child notifications

View File

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




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


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


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


} }

View File

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


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

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


import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
import im.vector.riotredesign.R; import im.vector.riotredesign.R;
import im.vector.riotredesign.features.homeserver.ServerUrlsRepository; import im.vector.riotredesign.features.homeserver.ServerUrlsRepository;
import im.vector.riotredesign.features.themes.ThemeUtils; 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_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"; 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_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_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"; 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_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_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_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_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_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"; 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); 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. * 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() { override fun initUiAndData() {
configureToolbar(settingsToolbar) configureToolbar(settingsToolbar)


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



supportFragmentManager.addOnBackStackChangedListener(this) supportFragmentManager.addOnBackStackChangedListener(this)

} }


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


if ("Legacy" == pref?.title) { if (PreferencesManager.SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY == pref?.key) {
oFragment = VectorSettingsPreferencesFragment.newInstance(session.sessionParams.credentials.userId)
} else if (PreferencesManager.SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY == pref?.key) {
oFragment = VectorSettingsNotificationsTroubleshootFragment.newInstance(session.sessionParams.credentials.userId) oFragment = VectorSettingsNotificationsTroubleshootFragment.newInstance(session.sessionParams.credentials.userId)
} else if (PreferencesManager.SETTINGS_NOTIFICATION_ADVANCED_PREFERENCE_KEY == pref?.key) { } else if (PreferencesManager.SETTINGS_NOTIFICATION_ADVANCED_PREFERENCE_KEY == pref?.key) {
oFragment = VectorSettingsAdvancedNotificationPreferenceFragment.newInstance(session.sessionParams.credentials.userId) 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 im.vector.riotredesign.features.notifications.supportNotificationChannels
import javax.inject.Inject import javax.inject.Inject


class VectorSettingsAdvancedNotificationPreferenceFragment : VectorPreferenceFragment() { class VectorSettingsAdvancedNotificationPreferenceFragment : VectorSettingsBaseFragment() {

// members
@Inject lateinit var session: Session
private var mLoadingView: View? = null


// events listener // events listener
/* TODO /* TODO
@ -53,10 +49,9 @@ class VectorSettingsAdvancedNotificationPreferenceFragment : VectorPreferenceFra


override var titleRes: Int = R.string.settings_notification_advanced override var titleRes: Int = R.string.settings_notification_advanced


override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override val preferenceXmlRes = R.xml.vector_settings_notification_advanced_preferences
// define the layout
addPreferencesFromResource(R.xml.vector_settings_notification_advanced_preferences)


override fun bindPref() {
val callNotificationsSystemOptions = findPreference(PreferencesManager.SETTINGS_SYSTEM_CALL_NOTIFICATION_PREFERENCE_KEY) val callNotificationsSystemOptions = findPreference(PreferencesManager.SETTINGS_SYSTEM_CALL_NOTIFICATION_PREFERENCE_KEY)
if (supportNotificationChannels()) { if (supportNotificationChannels()) {
callNotificationsSystemOptions.onPreferenceClickListener = Preference.OnPreferenceClickListener { 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 * 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 * 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 package im.vector.riotredesign.features.settings


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


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



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


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


override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun bindPref() {
addPreferencesFromResource(R.xml.vector_settings_notifications) 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) { override fun injectWith(injector: ScreenComponent) {
val screenComponent = DaggerScreenComponent.factory().create(vectorActivity.getVectorComponent(), vectorActivity) injector.inject(this)
super.onAttach(context)
screenComponent.inject(this)
} }



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


override fun onPreferenceTreeClick(preference: Preference?): Boolean { 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) { return when (preference?.key) {
super.onFailure(failure) 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 butterknife.BindView
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.ScreenComponent
import im.vector.riotredesign.core.extensions.withArgs import im.vector.riotredesign.core.extensions.withArgs
import im.vector.riotredesign.core.platform.VectorBaseActivity import im.vector.riotredesign.core.platform.VectorBaseActivity
import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.core.platform.VectorBaseFragment
@ -56,15 +57,15 @@ class VectorSettingsNotificationsTroubleshootFragment : VectorBaseFragment() {
// members // members
@Inject lateinit var session: Session @Inject lateinit var session: Session
@Inject lateinit var bugReporter: BugReporter @Inject lateinit var bugReporter: BugReporter
@Inject lateinit var testManagerFactory: NotificationTroubleshootTestManagerFactory



override fun getLayoutResId() = R.layout.fragment_settings_notifications_troubleshoot override fun getLayoutResId() = R.layout.fragment_settings_notifications_troubleshoot


private var interactionListener: VectorSettingsFragmentInteractionListener? = null private var interactionListener: VectorSettingsFragmentInteractionListener? = null


override fun onCreate(savedInstanceState: Bundle?) { override fun injectWith(injector: ScreenComponent) {
super.onCreate(savedInstanceState) injector.inject(this)

val appContext = activity!!.applicationContext
} }


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


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




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


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

testManager = testManagerFactory.create(this)
testManager = NotificationTroubleshootTestManagerFactory.createTestManager(this, session)

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


import android.os.Bundle
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.withArgs 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 var titleRes: Int = R.string.title_activity_settings
override val preferenceXmlRes = R.xml.vector_settings_root


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



companion object { companion object {
fun newInstance() = VectorSettingsPreferencesFragmentV2() fun newInstance() = VectorSettingsRootFragment()
.withArgs { .withArgs {
//putString(ARG_MATRIX_ID, matrixId) //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 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.MatrixCallback
import im.vector.matrix.android.api.pushrules.RuleIds 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.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 * 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) { : TroubleshootTest(R.string.settings_troubleshoot_test_account_settings_title) {


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


if (defaultRule != null) { if (defaultRule != null) {
if (!defaultRule.enabled) { 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 quickFix = null
status = TestStatus.SUCCESS status = TestStatus.SUCCESS
} else { } 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) { quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_account_settings_quickfix) {
override fun doFix() { override fun doFix() {
if (manager?.diagStatus == TestStatus.RUNNING) return //wait before all is finished if (manager?.diagStatus == TestStatus.RUNNING) return //wait before all is finished


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


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


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

View File

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


import androidx.fragment.app.Fragment import im.vector.matrix.android.api.pushrules.Action
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.pushrules.RuleIds
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.features.settings.PreferencesManager import im.vector.riotredesign.core.di.ActiveSessionHolder
import im.vector.riotredesign.features.settings.VectorSettingsFragmentInteractionListener 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, val ruleSettingsName = arrayOf(R.string.settings_containing_my_display_name,
R.string.settings_containing_my_user_name, R.string.settings_containing_my_user_name,
R.string.settings_messages_in_one_to_one, R.string.settings_messages_in_one_to_one,
R.string.settings_messages_in_group_chat) R.string.settings_messages_in_group_chat)


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


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

View File

@ -15,33 +15,33 @@
*/ */
package im.vector.riotredesign.features.settings.troubleshoot 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.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. * 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() { override fun perform() {
/* TODO
val pushManager = Matrix.getInstance(fragment.activity).pushManager if (PreferencesManager.areNotificationEnabledForDevice(context)) {
if (pushManager.areDeviceNotificationsAllowed()) { description = stringProvider.getString(R.string.settings_troubleshoot_test_device_settings_success)
description = fragment.getString(R.string.settings_troubleshoot_test_device_settings_success)
quickFix = null quickFix = null
status = TestStatus.SUCCESS status = TestStatus.SUCCESS
} else { } else {
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_device_settings_quickfix) { quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_device_settings_quickfix) {
override fun doFix() { override fun doFix() {
pushManager.setDeviceNotificationsAllowed(true) PreferencesManager.setNotificationEnabledForDevice(context, true)
manager?.retry() 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
} }
*/
status = TestStatus.FAILED
} }
} }

View File

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


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


/** /**
* Checks if notifications are enable in the system settings for this app. * 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() { override fun perform() {
if (NotificationManagerCompat.from(fragment.context!!).areNotificationsEnabled()) { if (NotificationManagerCompat.from(context).areNotificationsEnabled()) {
description = fragment.getString(R.string.settings_troubleshoot_test_system_settings_success) description = stringProvider.getString(R.string.settings_troubleshoot_test_system_settings_success)
quickFix = null quickFix = null
status = TestStatus.SUCCESS status = TestStatus.SUCCESS
} else { } 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) { quickFix = object : TroubleshootQuickFix(R.string.open_settings) {
override fun doFix() { override fun doFix() {
if (manager?.diagStatus == TestStatus.RUNNING) return //wait before all is finished 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" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp" android:width="108dp"
android:height="108dp" android:height="108dp"
android:viewportHeight="108" android:viewportWidth="108"
android:viewportWidth="108"> android:viewportHeight="108">
<path <path
android:fillColor="#26A69A" android:pathData="m0,0h108v108h-108z"
android:pathData="M0,0h108v108h-108z" /> android:fillColor="#27303a"
<path android:fillType="evenOdd"/>
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" />
</vector> </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:id="@+id/logoImageView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="100dp" 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 <com.google.android.material.textfield.TextInputLayout
style="@style/VectorTextInputLayout" style="@style/VectorTextInputLayout"

View File

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


</merge> </merge>

View File

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

View File

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