Merge branch 'develop' into feature/mention_display_name

This commit is contained in:
Benoit Marty 2020-08-26 17:35:16 +02:00 committed by GitHub
commit 013f51f0c4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 348 additions and 175 deletions

View file

@ -5,11 +5,15 @@ Features ✨:
- -
Improvements 🙌: Improvements 🙌:
- - Add long click gesture to copy userId, user display name, room name, room topic and room alias (#1774)
Bugfix 🐛: Bugfix 🐛:
- Display name not shown under Settings/General (#1926) - Display name not shown under Settings/General (#1926)
- Words containing my name should not trigger notifications (#1781) - Words containing my name should not trigger notifications (#1781)
- Fix changing language issue
- Fix FontSize issue (#1483, #1787)
- Fix bad color for settings icon on Android < 24 (#1786)
- Change user or room avatar: when selecting Gallery, I'm not proposed to crop the selected image (#1590)
Translations 🗣: Translations 🗣:
- -
@ -21,7 +25,8 @@ Build 🧱:
- Some dependencies have been upgraded (coroutine, recyclerView, appCompat, core-ktx, firebase-messaging) - Some dependencies have been upgraded (coroutine, recyclerView, appCompat, core-ktx, firebase-messaging)
Other changes: Other changes:
- - Use File extension functions to make code more concise (#1996)
- Create a script to import SAS strings (#1909)
Changes in Element 1.0.5 (2020-08-21) Changes in Element 1.0.5 (2020-08-21)
=================================================== ===================================================

View file

@ -29,7 +29,6 @@ import org.junit.FixMethodOrder
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.runners.MethodSorters import org.junit.runners.MethodSorters
import java.io.ByteArrayInputStream
import java.io.InputStream import java.io.InputStream
/** /**
@ -46,7 +45,7 @@ class AttachmentEncryptionTest {
val inputStream: InputStream val inputStream: InputStream
inputStream = if (`in`.isEmpty()) { inputStream = if (`in`.isEmpty()) {
ByteArrayInputStream(`in`) `in`.inputStream()
} else { } else {
val memoryFile = MemoryFile("file" + System.currentTimeMillis(), `in`.size) val memoryFile = MemoryFile("file" + System.currentTimeMillis(), `in`.size)
memoryFile.outputStream.write(`in`) memoryFile.outputStream.write(`in`)

View file

@ -21,7 +21,6 @@ import android.util.Base64
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileKey import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileKey
import timber.log.Timber import timber.log.Timber
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.InputStream import java.io.InputStream
import java.security.MessageDigest import java.security.MessageDigest
@ -179,7 +178,7 @@ internal object MXEncryptedAttachments {
return null return null
} }
return ByteArrayInputStream(outputStream.toByteArray()) return outputStream.toByteArray().inputStream()
.also { Timber.v("Decrypt in ${System.currentTimeMillis() - t0}ms") } .also { Timber.v("Decrypt in ${System.currentTimeMillis() - t0}ms") }
} catch (oom: OutOfMemoryError) { } catch (oom: OutOfMemoryError) {
Timber.e(oom, "## decryptAttachment() failed: OOM") Timber.e(oom, "## decryptAttachment() failed: OOM")

View file

@ -21,7 +21,6 @@ import android.util.Base64
import io.realm.Realm import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import io.realm.RealmObject import io.realm.RealmObject
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.ObjectOutputStream import java.io.ObjectOutputStream
import java.util.zip.GZIPInputStream import java.util.zip.GZIPInputStream
@ -96,7 +95,7 @@ fun <T> deserializeFromRealm(string: String?): T? {
} }
val decodedB64 = Base64.decode(string.toByteArray(), Base64.DEFAULT) val decodedB64 = Base64.decode(string.toByteArray(), Base64.DEFAULT)
val bais = ByteArrayInputStream(decodedB64) val bais = decodedB64.inputStream()
val gzis = GZIPInputStream(bais) val gzis = GZIPInputStream(bais)
val ois = SafeObjectInputStream(gzis) val ois = SafeObjectInputStream(gzis)
return ois.use { return ois.use {

View file

@ -42,10 +42,7 @@ import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
import org.matrix.android.sdk.internal.worker.getSessionComponent import org.matrix.android.sdk.internal.worker.getSessionComponent
import timber.log.Timber import timber.log.Timber
import java.io.ByteArrayInputStream
import java.io.File import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.util.UUID import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
@ -130,7 +127,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
val contentUploadResponse = if (params.isEncrypted) { val contentUploadResponse = if (params.isEncrypted) {
Timber.v("Encrypt thumbnail") Timber.v("Encrypt thumbnail")
notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) } notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) }
val encryptionResult = MXEncryptedAttachments.encryptAttachment(ByteArrayInputStream(thumbnailData.bytes), thumbnailData.mimeType) val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream(), thumbnailData.mimeType)
uploadedThumbnailEncryptedFileInfo = encryptionResult.encryptedFileInfo uploadedThumbnailEncryptedFileInfo = encryptionResult.encryptedFileInfo
fileUploader.uploadByteArray(encryptionResult.encryptedByteArray, fileUploader.uploadByteArray(encryptionResult.encryptedByteArray,
"thumb_${attachment.name}", "thumb_${attachment.name}",
@ -176,7 +173,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
cacheFile.createNewFile() cacheFile.createNewFile()
cacheFile.deleteOnExit() cacheFile.deleteOnExit()
val outputStream = FileOutputStream(cacheFile) val outputStream = cacheFile.outputStream()
outputStream.use { outputStream.use {
inputStream.copyTo(outputStream) inputStream.copyTo(outputStream)
} }
@ -203,7 +200,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
Timber.v("Encrypt file") Timber.v("Encrypt file")
notifyTracker(params) { contentUploadStateTracker.setEncrypting(it) } notifyTracker(params) { contentUploadStateTracker.setEncrypting(it) }
val encryptionResult = MXEncryptedAttachments.encryptAttachment(FileInputStream(cacheFile), attachment.getSafeMimeType()) val encryptionResult = MXEncryptedAttachments.encryptAttachment(cacheFile.inputStream(), attachment.getSafeMimeType())
uploadedFileEncryptedFileInfo = encryptionResult.encryptedFileInfo uploadedFileEncryptedFileInfo = encryptionResult.encryptedFileInfo
fileUploader fileUploader

View file

@ -17,7 +17,7 @@
package org.matrix.android.sdk.internal.session.room.membership package org.matrix.android.sdk.internal.session.room.membership
import android.content.Context import io.realm.Realm
import org.matrix.android.sdk.R import org.matrix.android.sdk.R
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
@ -34,13 +34,14 @@ import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.query.getOrNull import org.matrix.android.sdk.internal.database.query.getOrNull
import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.UserId
import io.realm.Realm import org.matrix.android.sdk.internal.util.StringProvider
import javax.inject.Inject import javax.inject.Inject
/** /**
* This class computes room display name * This class computes room display name
*/ */
internal class RoomDisplayNameResolver @Inject constructor(private val context: Context, internal class RoomDisplayNameResolver @Inject constructor(
private val stringProvider: StringProvider,
@UserId private val userId: String @UserId private val userId: String
) { ) {
@ -89,7 +90,7 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
.findFirst() .findFirst()
?.displayName ?.displayName
} else { } else {
context.getString(R.string.room_displayname_room_invite) stringProvider.getString(R.string.room_displayname_room_invite)
} }
} else if (roomEntity?.membership == Membership.JOIN) { } else if (roomEntity?.membership == Membership.JOIN) {
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
@ -108,13 +109,13 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
} }
val otherMembersCount = otherMembersSubset.count() val otherMembersCount = otherMembersSubset.count()
name = when (otherMembersCount) { name = when (otherMembersCount) {
0 -> context.getString(R.string.room_displayname_empty_room) 0 -> stringProvider.getString(R.string.room_displayname_empty_room)
1 -> resolveRoomMemberName(otherMembersSubset[0], roomMembers) 1 -> resolveRoomMemberName(otherMembersSubset[0], roomMembers)
2 -> context.getString(R.string.room_displayname_two_members, 2 -> stringProvider.getString(R.string.room_displayname_two_members,
resolveRoomMemberName(otherMembersSubset[0], roomMembers), resolveRoomMemberName(otherMembersSubset[0], roomMembers),
resolveRoomMemberName(otherMembersSubset[1], roomMembers) resolveRoomMemberName(otherMembersSubset[1], roomMembers)
) )
else -> context.resources.getQuantityString(R.plurals.room_displayname_three_and_more_members, else -> stringProvider.getQuantityString(R.plurals.room_displayname_three_and_more_members,
roomMembers.getNumberOfJoinedMembers() - 1, roomMembers.getNumberOfJoinedMembers() - 1,
resolveRoomMemberName(otherMembersSubset[0], roomMembers), resolveRoomMemberName(otherMembersSubset[0], roomMembers),
roomMembers.getNumberOfJoinedMembers() - 1) roomMembers.getNumberOfJoinedMembers() - 1)

View file

@ -219,7 +219,7 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
@RequiresApi(Build.VERSION_CODES.M) @RequiresApi(Build.VERSION_CODES.M)
private fun decryptStringM(encryptedChunk: ByteArray, keyAlias: String): String { private fun decryptStringM(encryptedChunk: ByteArray, keyAlias: String): String {
val (iv, encryptedText) = formatMExtract(ByteArrayInputStream(encryptedChunk)) val (iv, encryptedText) = formatMExtract(encryptedChunk.inputStream())
val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias) val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias)

View file

@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.util
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import java.io.File import java.io.File
import java.io.FileOutputStream
import java.io.InputStream import java.io.InputStream
/** /**
@ -27,7 +26,7 @@ import java.io.InputStream
*/ */
@WorkerThread @WorkerThread
fun writeToFile(inputStream: InputStream, outputFile: File) { fun writeToFile(inputStream: InputStream, outputFile: File) {
FileOutputStream(outputFile).use { outputFile.outputStream().use {
inputStream.copyTo(it) inputStream.copyTo(it)
} }
} }

View file

@ -18,8 +18,8 @@
package org.matrix.android.sdk.internal.util package org.matrix.android.sdk.internal.util
import android.content.res.Resources import android.content.res.Resources
import androidx.annotation.ArrayRes
import androidx.annotation.NonNull import androidx.annotation.NonNull
import androidx.annotation.PluralsRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import dagger.Reusable import dagger.Reusable
import javax.inject.Inject import javax.inject.Inject
@ -56,8 +56,8 @@ internal class StringProvider @Inject constructor(private val resources: Resourc
return resources.getString(resId, *formatArgs) return resources.getString(resId, *formatArgs)
} }
@Throws(Resources.NotFoundException::class) @NonNull
fun getStringArray(@ArrayRes id: Int): Array<String> { fun getQuantityString(@PluralsRes resId: Int, quantity: Int, vararg formatArgs: Any?): String {
return resources.getStringArray(id) return resources.getQuantityString(resId, quantity, *formatArgs)
} }
} }

103
tools/import_sas_strings.py Executable file
View file

@ -0,0 +1,103 @@
#!/usr/bin/env python3
# Copyright (c) 2020 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.
import argparse
import json
import os
import os.path
# Run `pip3 install requests` if not installed yet
import requests
### Arguments
parser = argparse.ArgumentParser(description='Download sas string from matrix-doc.')
parser.add_argument('-v',
'--verbose',
help="increase output verbosity.",
action="store_true")
args = parser.parse_args()
if args.verbose:
print("Argument:")
print(args)
base_url = "https://raw.githubusercontent.com/matrix-org/matrix-doc/master/data-definitions/sas-emoji.json"
print("Downloading " + base_url + "")
r0 = requests.get(base_url)
data0 = json.loads(r0.content.decode())
if args.verbose:
print("Json data:")
print(data0)
print()
# emoji -> translation
default = dict()
# Language -> emoji -> translation
cumul = dict()
for emoji in data0:
description = emoji["description"]
if args.verbose:
print("Description: " + description)
default[description] = description
for lang in emoji["translated_descriptions"]:
if args.verbose:
print("Lang: " + lang)
if not (lang in cumul):
cumul[lang] = dict()
cumul[lang][description] = emoji["translated_descriptions"][lang]
if args.verbose:
print(default)
print(cumul)
def write_file(file, dict):
print("Writing file " + file)
if args.verbose:
print("With")
print(dict)
os.makedirs(os.path.dirname(file), exist_ok=True)
with open(file, mode="w", encoding="utf8") as o:
o.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
o.write("<resources>\n")
o.write(" <!-- Generated file, do not edit -->\n")
for key in dict:
if dict[key] is None:
continue
o.write(" <string name=\"verification_emoji_" + key.lower().replace(" ", "_") + "\">" + dict[key].replace("'", "\\'") + "</string>\n")
o.write("</resources>\n")
scripts_dir = os.path.dirname(os.path.abspath(__file__))
data_defs_dir = os.path.join(scripts_dir, "../matrix-sdk-android/src/main/res")
# Write default file
write_file(os.path.join(data_defs_dir, "values/strings_sas.xml"), default)
# Write each language file
for lang in cumul:
androidLang = lang\
.replace("_", "-r")\
.replace("zh-rHans", "zh-rCN")
write_file(os.path.join(data_defs_dir, "values-" + androidLang + "/strings_sas.xml"), cumul[lang])
print()
print("Success!")

View file

@ -378,7 +378,7 @@ dependencies {
implementation "com.github.bumptech.glide:glide:$glide_version" implementation "com.github.bumptech.glide:glide:$glide_version"
kapt "com.github.bumptech.glide:compiler:$glide_version" kapt "com.github.bumptech.glide:compiler:$glide_version"
implementation 'com.danikula:videocache:2.7.1' implementation 'com.danikula:videocache:2.7.1'
implementation 'com.github.yalantis:ucrop:2.2.4' implementation 'com.github.yalantis:ucrop:2.2.6'
// Badge for compatibility // Badge for compatibility
implementation 'me.leolin:ShortcutBadger:1.1.22@aar' implementation 'me.leolin:ShortcutBadger:1.1.22@aar'

View file

@ -32,10 +32,6 @@ import com.airbnb.epoxy.EpoxyAsyncUtil
import com.airbnb.epoxy.EpoxyController import com.airbnb.epoxy.EpoxyController
import com.facebook.stetho.Stetho import com.facebook.stetho.Stetho
import com.gabrielittner.threetenbp.LazyThreeTen import com.gabrielittner.threetenbp.LazyThreeTen
import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.DaggerVectorComponent import im.vector.app.core.di.DaggerVectorComponent
import im.vector.app.core.di.HasVectorInjector import im.vector.app.core.di.HasVectorInjector
@ -50,16 +46,21 @@ import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.pin.PinLocker import im.vector.app.features.pin.PinLocker
import im.vector.app.features.popup.PopupAlertManager import im.vector.app.features.popup.PopupAlertManager
import im.vector.app.features.rageshake.VectorUncaughtExceptionHandler import im.vector.app.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.app.features.settings.VectorLocale
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.themes.ThemeUtils
import im.vector.app.features.version.VersionProvider import im.vector.app.features.version.VersionProvider
import im.vector.app.push.fcm.FcmHelper import im.vector.app.push.fcm.FcmHelper
import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
import timber.log.Timber import timber.log.Timber
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
import java.util.concurrent.Executors import java.util.concurrent.Executors
import javax.inject.Inject import javax.inject.Inject
import androidx.work.Configuration as WorkConfiguration import androidx.work.Configuration as WorkConfiguration
class VectorApplication : class VectorApplication :
@ -119,7 +120,9 @@ class VectorApplication :
R.array.com_google_android_gms_fonts_certs R.array.com_google_android_gms_fonts_certs
) )
FontsContractCompat.requestFont(this, fontRequest, emojiCompatFontProvider, getFontThreadHandler()) FontsContractCompat.requestFont(this, fontRequest, emojiCompatFontProvider, getFontThreadHandler())
vectorConfiguration.initConfiguration() VectorLocale.init(this)
ThemeUtils.init(this)
vectorConfiguration.applyToApplicationContext()
emojiCompatWrapper.init(fontRequest) emojiCompatWrapper.init(fontRequest)

View file

@ -102,6 +102,7 @@ import im.vector.app.features.settings.devtools.OutgoingKeyRequestListFragment
import im.vector.app.features.settings.ignored.VectorSettingsIgnoredUsersFragment import im.vector.app.features.settings.ignored.VectorSettingsIgnoredUsersFragment
import im.vector.app.features.settings.locale.LocalePickerFragment import im.vector.app.features.settings.locale.LocalePickerFragment
import im.vector.app.features.settings.push.PushGatewaysFragment import im.vector.app.features.settings.push.PushGatewaysFragment
import im.vector.app.features.settings.push.PushRulesFragment
import im.vector.app.features.share.IncomingShareFragment import im.vector.app.features.share.IncomingShareFragment
import im.vector.app.features.signout.soft.SoftLogoutFragment import im.vector.app.features.signout.soft.SoftLogoutFragment
import im.vector.app.features.terms.ReviewTermsFragment import im.vector.app.features.terms.ReviewTermsFragment
@ -282,6 +283,11 @@ interface FragmentModule {
@FragmentKey(VectorSettingsLabsFragment::class) @FragmentKey(VectorSettingsLabsFragment::class)
fun bindVectorSettingsLabsFragment(fragment: VectorSettingsLabsFragment): Fragment fun bindVectorSettingsLabsFragment(fragment: VectorSettingsLabsFragment): Fragment
@Binds
@IntoMap
@FragmentKey(PushRulesFragment::class)
fun bindPushRulesFragment(fragment: PushRulesFragment): Fragment
@Binds @Binds
@IntoMap @IntoMap
@FragmentKey(VectorSettingsPreferencesFragment::class) @FragmentKey(VectorSettingsPreferencesFragment::class)

View file

@ -24,7 +24,9 @@ import android.widget.TextView
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.google.android.material.snackbar.Snackbar
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.utils.copyToClipboard
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeUtils
/** /**
@ -68,3 +70,18 @@ fun TextView.setTextWithColoredPart(@StringRes fullTextRes: Int,
} }
} }
} }
/**
* Set long click listener to copy the current text of the TextView to the clipboard and show a Snackbar
*/
fun TextView.copyOnLongClick() {
setOnLongClickListener { view ->
(view as? TextView)
?.text
?.let { text ->
copyToClipboard(view.context, text, false)
Snackbar.make(view, view.resources.getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT).show()
}
true
}
}

View file

@ -31,7 +31,6 @@ import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.io.FileInputStream
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
@ -97,7 +96,7 @@ class VectorGlideDataFetcher(private val activeSessionHolder: ActiveSessionHolde
Timber.v("Load data: $data") Timber.v("Load data: $data")
if (data.isLocalFile() && data.url != null) { if (data.isLocalFile() && data.url != null) {
val initialFile = File(data.url) val initialFile = File(data.url)
callback.onDataReady(FileInputStream(initialFile)) callback.onDataReady(initialFile.inputStream())
return return
} }
val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver() val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()

View file

@ -60,6 +60,7 @@ import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.observeEvent import im.vector.app.core.extensions.observeEvent
import im.vector.app.core.extensions.observeNotNull import im.vector.app.core.extensions.observeNotNull
import im.vector.app.core.extensions.restart
import im.vector.app.core.extensions.vectorComponent import im.vector.app.core.extensions.vectorComponent
import im.vector.app.core.utils.toast import im.vector.app.core.utils.toast
import im.vector.app.features.MainActivity import im.vector.app.features.MainActivity
@ -75,14 +76,15 @@ import im.vector.app.features.rageshake.BugReportActivity
import im.vector.app.features.rageshake.BugReporter import im.vector.app.features.rageshake.BugReporter
import im.vector.app.features.rageshake.RageShake import im.vector.app.features.rageshake.RageShake
import im.vector.app.features.session.SessionListener import im.vector.app.features.session.SessionListener
import im.vector.app.features.settings.FontScale
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.themes.ActivityOtherThemes import im.vector.app.features.themes.ActivityOtherThemes
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeUtils
import im.vector.app.receivers.DebugReceiver import im.vector.app.receivers.DebugReceiver
import org.matrix.android.sdk.api.failure.GlobalError
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import org.matrix.android.sdk.api.failure.GlobalError
import timber.log.Timber import timber.log.Timber
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
@ -198,8 +200,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
configurationViewModel.activityRestarter.observe(this, Observer { configurationViewModel.activityRestarter.observe(this, Observer {
if (!it.hasBeenHandled) { if (!it.hasBeenHandled) {
// Recreate the Activity because configuration has changed // Recreate the Activity because configuration has changed
startActivity(intent) restart()
finish()
} }
}) })
pinLocker.getLiveState().observeNotNull(this) { pinLocker.getLiveState().observeNotNull(this) {
@ -219,6 +220,9 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
doBeforeSetContentView() doBeforeSetContentView()
// Hack for font size
applyFontSize()
if (getLayoutRes() != -1) { if (getLayoutRes() != -1) {
setContentView(getLayoutRes()) setContentView(getLayoutRes())
} }
@ -239,6 +243,16 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
} }
} }
/**
* This method has to be called for the font size setting be supported correctly.
*/
private fun applyFontSize() {
resources.configuration.fontScale = FontScale.getFontScaleValue(this).scale
@Suppress("DEPRECATION")
resources.updateConfiguration(resources.configuration, resources.displayMetrics)
}
private fun handleGlobalError(globalError: GlobalError) { private fun handleGlobalError(globalError: GlobalError) {
when (globalError) { when (globalError) {
is GlobalError.InvalidToken -> is GlobalError.InvalidToken ->

View file

@ -20,7 +20,6 @@ import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import android.widget.RadioGroup import android.widget.RadioGroup
import android.widget.TextView
import androidx.preference.PreferenceViewHolder import androidx.preference.PreferenceViewHolder
import im.vector.app.R import im.vector.app.R
import org.matrix.android.sdk.api.pushrules.Action import org.matrix.android.sdk.api.pushrules.Action
@ -164,7 +163,7 @@ class PushRulePreference : VectorPreference {
override fun onBindViewHolder(holder: PreferenceViewHolder) { override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder) super.onBindViewHolder(holder)
holder.itemView.findViewById<TextView>(android.R.id.summary)?.visibility = View.GONE holder.findViewById(android.R.id.summary)?.visibility = View.GONE
holder.itemView.setOnClickListener(null) holder.itemView.setOnClickListener(null)
holder.itemView.setOnLongClickListener(null) holder.itemView.setOnLongClickListener(null)

View file

@ -45,7 +45,7 @@ class VectorEditTextPreference : EditTextPreference {
override fun onBindViewHolder(holder: PreferenceViewHolder) { override fun onBindViewHolder(holder: PreferenceViewHolder) {
// display the title in multi-line to avoid ellipsis. // display the title in multi-line to avoid ellipsis.
try { try {
holder.itemView.findViewById<TextView>(android.R.id.title)?.isSingleLine = false (holder.findViewById(android.R.id.title) as? TextView)?.isSingleLine = false
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "onBindView") Timber.e(e, "onBindView")
} }

View file

@ -20,12 +20,15 @@ import android.animation.Animator
import android.animation.ArgbEvaluator import android.animation.ArgbEvaluator
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.content.Context import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color import android.graphics.Color
import android.graphics.Typeface import android.graphics.Typeface
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.core.animation.doOnEnd import androidx.core.animation.doOnEnd
import androidx.core.widget.ImageViewCompat
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder import androidx.preference.PreferenceViewHolder
import im.vector.app.R import im.vector.app.R
@ -76,6 +79,12 @@ open class VectorPreference : Preference {
notifyChanged() notifyChanged()
} }
var tintIcon = false
set(value) {
field = value
notifyChanged()
}
var currentHighlightAnimator: Animator? = null var currentHighlightAnimator: Animator? = null
override fun onBindViewHolder(holder: PreferenceViewHolder) { override fun onBindViewHolder(holder: PreferenceViewHolder) {
@ -84,15 +93,23 @@ open class VectorPreference : Preference {
// display the title in multi-line to avoid ellipsis. // display the title in multi-line to avoid ellipsis.
try { try {
val title = itemView.findViewById<TextView>(android.R.id.title) val title = holder.findViewById(android.R.id.title) as? TextView
val summary = itemView.findViewById<TextView>(android.R.id.summary) val summary = holder.findViewById(android.R.id.summary) as? TextView
if (title != null) { if (title != null) {
title.isSingleLine = false title.isSingleLine = false
title.setTypeface(null, mTypeface) title.setTypeface(null, mTypeface)
} }
if (title !== summary) { summary?.setTypeface(null, mTypeface)
summary.setTypeface(null, mTypeface)
if (tintIcon) {
// Tint icons (See #1786)
val icon = holder.findViewById(android.R.id.icon) as? ImageView
icon?.let {
val color = ThemeUtils.getColor(context, R.attr.riotx_header_panel_text_secondary)
ImageViewCompat.setImageTintList(it, ColorStateList.valueOf(color))
}
} }
// cancel existing animation (find a way to resume if happens during anim?) // cancel existing animation (find a way to resume if happens during anim?)

View file

@ -45,7 +45,7 @@ class VectorPreferenceCategory : PreferenceCategory {
override fun onBindViewHolder(holder: PreferenceViewHolder) { override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder) super.onBindViewHolder(holder)
val titleTextView = holder.itemView.findViewById<TextView>(android.R.id.title) val titleTextView = holder.findViewById(android.R.id.title) as? TextView
titleTextView?.setTypeface(null, Typeface.BOLD) titleTextView?.setTypeface(null, Typeface.BOLD)
titleTextView?.setTextColor(ThemeUtils.getColor(context, R.attr.riotx_text_primary)) titleTextView?.setTextColor(ThemeUtils.getColor(context, R.attr.riotx_text_primary))

View file

@ -58,7 +58,7 @@ class VectorSwitchPreference : SwitchPreference {
override fun onBindViewHolder(holder: PreferenceViewHolder) { override fun onBindViewHolder(holder: PreferenceViewHolder) {
// display the title in multi-line to avoid ellipsis. // display the title in multi-line to avoid ellipsis.
holder.itemView.findViewById<TextView>(android.R.id.title)?.isSingleLine = false (holder.findViewById(android.R.id.title) as? TextView)?.isSingleLine = false
// cancel existing animation (find a way to resume if happens during anim?) // cancel existing animation (find a way to resume if happens during anim?)
currentHighlightAnimator?.cancel() currentHighlightAnimator?.cancel()

View file

@ -17,8 +17,10 @@
package im.vector.app.core.services package im.vector.app.core.services
import android.app.Service import android.app.Service
import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.IBinder import android.os.IBinder
import im.vector.app.core.extensions.vectorComponent
import timber.log.Timber import timber.log.Timber
/** /**
@ -31,6 +33,10 @@ abstract class VectorService : Service() {
*/ */
private var mIsSelfDestroyed = false private var mIsSelfDestroyed = false
override fun attachBaseContext(base: Context) {
super.attachBaseContext(vectorComponent().vectorConfiguration().getLocalisedContext(base))
}
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()

View file

@ -518,8 +518,8 @@ fun saveFileIntoLegacy(sourceFile: File, dstDirPath: File, outputFilename: Strin
var outputStream: FileOutputStream? = null var outputStream: FileOutputStream? = null
try { try {
dstFile.createNewFile() dstFile.createNewFile()
inputStream = FileInputStream(sourceFile) inputStream = sourceFile.inputStream()
outputStream = FileOutputStream(dstFile) outputStream = dstFile.outputStream()
val buffer = ByteArray(1024 * 10) val buffer = ByteArray(1024 * 10)
var len: Int var len: Int
while (inputStream.read(buffer).also { len = it } != -1) { while (inputStream.read(buffer).also { len = it } != -1) {

View file

@ -21,8 +21,6 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import im.vector.app.core.di.HasVectorInjector import im.vector.app.core.di.HasVectorInjector
import im.vector.app.features.call.WebRtcPeerConnectionManager import im.vector.app.features.call.WebRtcPeerConnectionManager
import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.settings.VectorLocale.context
import timber.log.Timber import timber.log.Timber
class CallHeadsUpActionReceiver : BroadcastReceiver() { class CallHeadsUpActionReceiver : BroadcastReceiver() {
@ -32,20 +30,14 @@ class CallHeadsUpActionReceiver : BroadcastReceiver() {
const val CALL_ACTION_REJECT = 0 const val CALL_ACTION_REJECT = 0
} }
private lateinit var peerConnectionManager: WebRtcPeerConnectionManager
private lateinit var notificationUtils: NotificationUtils
init {
val appContext = context.applicationContext
if (appContext is HasVectorInjector) {
peerConnectionManager = appContext.injector().webRtcPeerConnectionManager()
notificationUtils = appContext.injector().notificationUtils()
}
}
override fun onReceive(context: Context, intent: Intent?) { override fun onReceive(context: Context, intent: Intent?) {
val peerConnectionManager = (context.applicationContext as? HasVectorInjector)
?.injector()
?.webRtcPeerConnectionManager()
?: return
when (intent?.getIntExtra(EXTRA_CALL_ACTION_KEY, 0)) { when (intent?.getIntExtra(EXTRA_CALL_ACTION_KEY, 0)) {
CALL_ACTION_REJECT -> onCallRejectClicked() CALL_ACTION_REJECT -> onCallRejectClicked(peerConnectionManager)
} }
// Not sure why this should be needed // Not sure why this should be needed
@ -56,7 +48,7 @@ class CallHeadsUpActionReceiver : BroadcastReceiver() {
// context.stopService(Intent(context, CallHeadsUpService::class.java)) // context.stopService(Intent(context, CallHeadsUpService::class.java))
} }
private fun onCallRejectClicked() { private fun onCallRejectClicked(peerConnectionManager: WebRtcPeerConnectionManager) {
Timber.d("onCallRejectClicked") Timber.d("onCallRejectClicked")
peerConnectionManager.endCall() peerConnectionManager.endCall()
} }

View file

@ -16,10 +16,11 @@
package im.vector.app.features.configuration package im.vector.app.features.configuration
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Build import android.os.Build
import android.os.LocaleList
import androidx.annotation.RequiresApi
import im.vector.app.features.settings.FontScale import im.vector.app.features.settings.FontScale
import im.vector.app.features.settings.VectorLocale import im.vector.app.features.settings.VectorLocale
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeUtils
@ -40,14 +41,9 @@ class VectorConfiguration @Inject constructor(private val context: Context) {
} }
} }
/** fun applyToApplicationContext() {
* Init the configuration from the saved one
*/
fun initConfiguration() {
VectorLocale.init(context)
val locale = VectorLocale.applicationLocale val locale = VectorLocale.applicationLocale
val fontScale = FontScale.getFontScaleValue(context) val fontScale = FontScale.getFontScaleValue(context)
val theme = ThemeUtils.getApplicationTheme(context)
Locale.setDefault(locale) Locale.setDefault(locale)
val config = Configuration(context.resources.configuration) val config = Configuration(context.resources.configuration)
@ -56,9 +52,6 @@ class VectorConfiguration @Inject constructor(private val context: Context) {
config.fontScale = fontScale.scale config.fontScale = fontScale.scale
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
context.resources.updateConfiguration(config, context.resources.displayMetrics) context.resources.updateConfiguration(config, context.resources.displayMetrics)
// init the theme
ThemeUtils.setApplicationTheme(context, theme)
} }
/** /**
@ -67,26 +60,22 @@ class VectorConfiguration @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return the localised context * @return the localised context
*/ */
@SuppressLint("NewApi")
fun getLocalisedContext(context: Context): Context { fun getLocalisedContext(context: Context): Context {
try { try {
val resources = context.resources
val locale = VectorLocale.applicationLocale val locale = VectorLocale.applicationLocale
val configuration = resources.configuration
// create new configuration passing old configuration from original Context
val configuration = Configuration(context.resources.configuration)
configuration.fontScale = FontScale.getFontScaleValue(context).scale configuration.fontScale = FontScale.getFontScaleValue(context).scale
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
setLocaleForApi24(configuration, locale)
} else {
configuration.setLocale(locale) configuration.setLocale(locale)
}
configuration.setLayoutDirection(locale) configuration.setLayoutDirection(locale)
return context.createConfigurationContext(configuration) return context.createConfigurationContext(configuration)
} else {
@Suppress("DEPRECATION")
configuration.locale = locale
configuration.setLayoutDirection(locale)
@Suppress("DEPRECATION")
resources.updateConfiguration(configuration, resources.displayMetrics)
return context
}
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## getLocalisedContext() failed") Timber.e(e, "## getLocalisedContext() failed")
} }
@ -94,6 +83,20 @@ class VectorConfiguration @Inject constructor(private val context: Context) {
return context return context
} }
@RequiresApi(Build.VERSION_CODES.N)
private fun setLocaleForApi24(config: Configuration, locale: Locale) {
val set: MutableSet<Locale> = LinkedHashSet()
// bring the user locale to the front of the list
set.add(locale)
val all = LocaleList.getDefault()
for (i in 0 until all.size()) {
// append other locales supported by the user
set.add(all[i])
}
val locales = set.toTypedArray()
config.setLocales(LocaleList(*locales))
}
/** /**
* Compute the locale status value * Compute the locale status value
* @return the local status value * @return the local status value

View file

@ -144,9 +144,7 @@ class BigImageViewerActivity : VectorBaseActivity() {
.get(MultiPicker.IMAGE) .get(MultiPicker.IMAGE)
.getSelectedFiles(this, requestCode, resultCode, data) .getSelectedFiles(this, requestCode, resultCode, data)
.firstOrNull()?.let { .firstOrNull()?.let {
// TODO. UCrop library cannot read from Gallery. For now, we will set avatar as it is. onRoomAvatarSelected(it)
// onRoomAvatarSelected(it)
onAvatarCropped(it.contentUri)
} }
} }
UCrop.REQUEST_CROP -> data?.let { onAvatarCropped(UCrop.getOutput(it)) } UCrop.REQUEST_CROP -> data?.let { onAvatarCropped(UCrop.getOutput(it)) }

View file

@ -32,7 +32,6 @@ import org.matrix.android.sdk.api.session.content.ContentUrlResolver
import me.gujun.android.span.span import me.gujun.android.span.span
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream import java.io.FileOutputStream
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -494,7 +493,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
try { try {
val file = File(context.applicationContext.cacheDir, ROOMS_NOTIFICATIONS_FILE_NAME) val file = File(context.applicationContext.cacheDir, ROOMS_NOTIFICATIONS_FILE_NAME)
if (file.exists()) { if (file.exists()) {
FileInputStream(file).use { file.inputStream().use {
val events: ArrayList<NotifiableEvent>? = currentSession?.loadSecureSecret(it, KEY_ALIAS_SECRET_STORAGE) val events: ArrayList<NotifiableEvent>? = currentSession?.loadSecureSecret(it, KEY_ALIAS_SECRET_STORAGE)
if (events != null) { if (events != null) {
return events.toMutableList() return events.toMutableList()

View file

@ -35,6 +35,7 @@ import im.vector.app.core.animations.MatrixItemAppBarStateChangeListener
import im.vector.app.core.dialogs.ConfirmationDialogBuilder import im.vector.app.core.dialogs.ConfirmationDialogBuilder
import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.configureWith
import im.vector.app.core.extensions.copyOnLongClick
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.core.platform.StateView import im.vector.app.core.platform.StateView
@ -44,11 +45,11 @@ import im.vector.app.features.crypto.verification.VerificationBottomSheet
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.roommemberprofile.devices.DeviceListBottomSheet import im.vector.app.features.roommemberprofile.devices.DeviceListBottomSheet
import im.vector.app.features.roommemberprofile.powerlevel.EditPowerLevelDialogs import im.vector.app.features.roommemberprofile.powerlevel.EditPowerLevelDialogs
import org.matrix.android.sdk.api.session.room.powerlevels.Role
import org.matrix.android.sdk.api.util.MatrixItem
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_matrix_profile.* import kotlinx.android.synthetic.main.fragment_matrix_profile.*
import kotlinx.android.synthetic.main.view_stub_room_member_profile_header.* import kotlinx.android.synthetic.main.view_stub_room_member_profile_header.*
import org.matrix.android.sdk.api.session.room.powerlevels.Role
import org.matrix.android.sdk.api.util.MatrixItem
import javax.inject.Inject import javax.inject.Inject
@Parcelize @Parcelize
@ -110,6 +111,12 @@ class RoomMemberProfileFragment @Inject constructor(
is RoomMemberProfileViewEvents.OnInviteActionSuccess -> Unit is RoomMemberProfileViewEvents.OnInviteActionSuccess -> Unit
}.exhaustive }.exhaustive
} }
setupLongClicks()
}
private fun setupLongClicks() {
memberProfileNameView.copyOnLongClick()
memberProfileIdView.copyOnLongClick()
} }
private fun handleShowPowerLevelDemoteWarning(event: RoomMemberProfileViewEvents.ShowPowerLevelDemoteWarning) { private fun handleShowPowerLevelDemoteWarning(event: RoomMemberProfileViewEvents.ShowPowerLevelDemoteWarning) {

View file

@ -34,16 +34,12 @@ import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import com.yalantis.ucrop.UCrop import com.yalantis.ucrop.UCrop
import im.vector.lib.multipicker.MultiPicker
import im.vector.lib.multipicker.entity.MultiPickerImageType
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.animations.AppBarStateChangeListener import im.vector.app.core.animations.AppBarStateChangeListener
import im.vector.app.core.animations.MatrixItemAppBarStateChangeListener import im.vector.app.core.animations.MatrixItemAppBarStateChangeListener
import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.configureWith
import im.vector.app.core.extensions.copyOnLongClick
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.core.intent.getFilenameFromUri import im.vector.app.core.intent.getFilenameFromUri
@ -62,9 +58,14 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
import im.vector.app.features.media.BigImageViewerActivity import im.vector.app.features.media.BigImageViewerActivity
import im.vector.app.features.media.createUCropWithDefaultSettings import im.vector.app.features.media.createUCropWithDefaultSettings
import im.vector.lib.multipicker.MultiPicker
import im.vector.lib.multipicker.entity.MultiPickerImageType
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_matrix_profile.* import kotlinx.android.synthetic.main.fragment_matrix_profile.*
import kotlinx.android.synthetic.main.view_stub_room_profile_header.* import kotlinx.android.synthetic.main.view_stub_room_profile_header.*
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
@ -120,6 +121,13 @@ class RoomProfileFragment @Inject constructor(
.observe() .observe()
.subscribe { handleQuickActions(it) } .subscribe { handleQuickActions(it) }
.disposeOnDestroyView() .disposeOnDestroyView()
setupLongClicks()
}
private fun setupLongClicks() {
roomProfileNameView.copyOnLongClick()
roomProfileAliasView.copyOnLongClick()
roomProfileTopicView.copyOnLongClick()
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
@ -301,9 +309,7 @@ class RoomProfileFragment @Inject constructor(
.get(MultiPicker.IMAGE) .get(MultiPicker.IMAGE)
.getSelectedFiles(requireContext(), requestCode, resultCode, data) .getSelectedFiles(requireContext(), requestCode, resultCode, data)
.firstOrNull()?.let { .firstOrNull()?.let {
// TODO. UCrop library cannot read from Gallery. For now, we will set avatar as it is. onRoomAvatarSelected(it)
// onRoomAvatarSelected(it)
onAvatarCropped(it.contentUri)
} }
} }
UCrop.REQUEST_CROP -> data?.let { onAvatarCropped(UCrop.getOutput(it)) } UCrop.REQUEST_CROP -> data?.let { onAvatarCropped(UCrop.getOutput(it)) }

View file

@ -52,7 +52,7 @@ object VectorLocale {
var applicationLocale = defaultLocale var applicationLocale = defaultLocale
private set private set
lateinit var context: Context private lateinit var context: Context
/** /**
* Init this object * Init this object

View file

@ -356,8 +356,7 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
.get(MultiPicker.IMAGE) .get(MultiPicker.IMAGE)
.getSelectedFiles(requireContext(), requestCode, resultCode, data) .getSelectedFiles(requireContext(), requestCode, resultCode, data)
.firstOrNull()?.let { .firstOrNull()?.let {
// TODO. UCrop library cannot read from Gallery. For now, we will set avatar as it is. onAvatarSelected(it)
onAvatarCropped(it.contentUri)
} }
} }
UCrop.REQUEST_CROP -> data?.let { onAvatarCropped(UCrop.getOutput(it)) } UCrop.REQUEST_CROP -> data?.let { onAvatarCropped(UCrop.getOutput(it)) }

View file

@ -168,6 +168,7 @@ class VectorSettingsPreferencesFragment @Inject constructor(
v.setOnClickListener { v.setOnClickListener {
dialog.dismiss() dialog.dismiss()
FontScale.updateFontScale(activity, i) FontScale.updateFontScale(activity, i)
vectorConfiguration.applyToApplicationContext()
activity.restart() activity.restart()
} }
} }

View file

@ -16,7 +16,9 @@
package im.vector.app.features.settings package im.vector.app.features.settings
import android.os.Build
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.preference.VectorPreference
import javax.inject.Inject import javax.inject.Inject
class VectorSettingsRootFragment @Inject constructor() : VectorSettingsBaseFragment() { class VectorSettingsRootFragment @Inject constructor() : VectorSettingsBaseFragment() {
@ -25,6 +27,15 @@ class VectorSettingsRootFragment @Inject constructor() : VectorSettingsBaseFragm
override val preferenceXmlRes = R.xml.vector_settings_root override val preferenceXmlRes = R.xml.vector_settings_root
override fun bindPref() { override fun bindPref() {
// Nothing to do // Tint icon on API < 24
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
tintIcons()
}
}
private fun tintIcons() {
for (i in 0 until preferenceScreen.preferenceCount) {
(preferenceScreen.getPreference(i) as? VectorPreference)?.let { it.tintIcon = true }
}
} }
} }

View file

@ -26,11 +26,13 @@ import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.configuration.VectorConfiguration
import im.vector.app.features.settings.VectorLocale import im.vector.app.features.settings.VectorLocale
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class LocalePickerViewModel @AssistedInject constructor( class LocalePickerViewModel @AssistedInject constructor(
@Assisted initialState: LocalePickerViewState @Assisted initialState: LocalePickerViewState,
private val vectorConfiguration: VectorConfiguration
) : VectorViewModel<LocalePickerViewState, LocalePickerAction, LocalePickerViewEvents>(initialState) { ) : VectorViewModel<LocalePickerViewState, LocalePickerAction, LocalePickerViewEvents>(initialState) {
@AssistedInject.Factory @AssistedInject.Factory
@ -70,6 +72,7 @@ class LocalePickerViewModel @AssistedInject constructor(
private fun handleSelectLocale(action: LocalePickerAction.SelectLocale) { private fun handleSelectLocale(action: LocalePickerAction.SelectLocale) {
VectorLocale.saveApplicationLocale(action.locale) VectorLocale.saveApplicationLocale(action.locale)
vectorConfiguration.applyToApplicationContext()
_viewEvents.post(LocalePickerViewEvents.RestartActivity) _viewEvents.post(LocalePickerViewEvents.RestartActivity)
} }
} }

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.settings.push
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.list.genericFooterItem
import javax.inject.Inject
class PushRulesController @Inject constructor(
private val stringProvider: StringProvider
) : TypedEpoxyController<PushRulesViewState>() {
override fun buildModels(data: PushRulesViewState?) {
data?.let {
it.rules.forEach {
pushRuleItem {
id(it.ruleId)
pushRule(it)
}
}
} ?: run {
genericFooterItem {
id("footer")
text(stringProvider.getString(R.string.settings_push_rules_no_rules))
}
}
}
}

View file

@ -17,7 +17,6 @@ package im.vector.app.features.settings.push
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.app.R import im.vector.app.R
@ -25,19 +24,18 @@ import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.list.genericFooterItem
import kotlinx.android.synthetic.main.fragment_generic_recycler.* import kotlinx.android.synthetic.main.fragment_generic_recycler.*
import javax.inject.Inject
// Referenced in vector_settings_notifications.xml // Referenced in vector_settings_notifications.xml
class PushRulesFragment : VectorBaseFragment() { class PushRulesFragment @Inject constructor(
private val epoxyController: PushRulesController
) : VectorBaseFragment() {
override fun getLayoutResId() = R.layout.fragment_generic_recycler override fun getLayoutResId() = R.layout.fragment_generic_recycler
private val viewModel: PushRulesViewModel by fragmentViewModel(PushRulesViewModel::class) private val viewModel: PushRulesViewModel by fragmentViewModel(PushRulesViewModel::class)
private val epoxyController by lazy { PushRulesController(StringProvider(requireContext().resources)) }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
(activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_push_rules) (activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_push_rules)
@ -56,23 +54,4 @@ class PushRulesFragment : VectorBaseFragment() {
override fun invalidate() = withState(viewModel) { state -> override fun invalidate() = withState(viewModel) { state ->
epoxyController.setData(state) epoxyController.setData(state)
} }
class PushRulesController(private val stringProvider: StringProvider) : TypedEpoxyController<PushRulesViewState>() {
override fun buildModels(data: PushRulesViewState?) {
data?.let {
it.rules.forEach {
pushRuleItem {
id(it.ruleId)
pushRule(it)
}
}
} ?: run {
genericFooterItem {
id("footer")
text(stringProvider.getString(R.string.settings_push_rules_no_rules))
}
}
}
}
} }

View file

@ -44,6 +44,12 @@ object ThemeUtils {
private val mColorByAttr = HashMap<Int, Int>() private val mColorByAttr = HashMap<Int, Int>()
// init the theme
fun init(context: Context) {
val theme = getApplicationTheme(context)
setApplicationTheme(context, theme)
}
/** /**
* @return true if current theme is Light or Status * @return true if current theme is Light or Status
*/ */

View file

@ -1,38 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="163dp"
android:height="127dp"
android:viewportWidth="163"
android:viewportHeight="127">
<path
android:pathData="M113.569,169.348C129.753,185.532 153.161,188.363 165.853,175.671C178.545,162.979 175.715,139.57 159.531,123.386C143.347,107.203 44.653,8.372 44.653,8.372L35.819,18.975C35.819,18.975 39.221,27.764 37.204,30.186C35.186,32.608 24.684,32.34 24.684,32.34L6.34,54.358C6.34,54.358 4.89,60.67 6.106,61.885C41.927,97.706 77.748,133.527 113.569,169.348Z"
android:strokeWidth="1"
android:fillColor="#000000"
android:fillAlpha="0.147508741"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
<path
android:pathData="M19.447,19.068L19.447,27.722L28.202,27.713C28.313,27.713 28.415,27.71 28.515,27.703C30.818,27.551 32.617,25.656 32.617,23.391C32.617,21.007 30.641,19.068 28.211,19.068L19.447,19.068ZM10.788,61.81C6.006,61.81 2.129,58.007 2.129,53.316L2.129,37.127C2.097,36.833 2.08,36.535 2.08,36.232C2.08,35.925 2.096,35.621 2.129,35.322L2.129,10.574C2.129,5.882 6.006,2.079 10.788,2.079L28.211,2.079C40.19,2.079 49.935,11.639 49.935,23.391C49.935,34.563 41.04,43.902 29.684,44.652C29.201,44.685 28.704,44.702 28.211,44.702L19.447,44.71L19.447,53.316C19.447,58.007 15.57,61.81 10.788,61.81L10.788,61.81Z"
android:strokeWidth="1"
android:fillColor="#A2DDEF"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
<path
android:pathData="M19.447,19.068L19.447,27.722L28.202,27.713C28.313,27.713 28.415,27.71 28.515,27.703C30.818,27.551 32.617,25.656 32.617,23.391C32.617,21.007 30.641,19.068 28.211,19.068L19.447,19.068ZM10.788,61.81C6.006,61.81 2.129,58.007 2.129,53.316L2.129,10.574C2.129,5.882 6.006,2.079 10.788,2.079L28.211,2.079C40.19,2.079 49.935,11.639 49.935,23.391C49.935,34.563 41.04,43.902 29.684,44.652C29.201,44.685 28.704,44.702 28.211,44.702L19.447,44.71L19.447,53.316C19.447,58.007 15.57,61.81 10.788,61.81Z"
android:strokeWidth="1.52445396"
android:fillColor="#00000000"
android:strokeColor="#368BD6"
android:fillType="evenOdd"/>
<path
android:pathData="M10.788,53.315L10.788,10.574L28.211,10.574C35.426,10.574 41.276,16.312 41.276,23.39C41.276,30.175 35.902,35.729 29.102,36.178C28.807,36.197 28.51,36.207 28.211,36.207L10.788,36.207"
android:strokeWidth="1.52445396"
android:fillColor="#00000000"
android:strokeColor="#368BD6"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="M17.923,5.702C19.25,7.56 19.76,9.815 19.358,12.048C18.956,14.283 17.691,16.229 15.795,17.531C11.881,20.217 6.467,19.282 3.726,15.445C2.399,13.587 1.889,11.333 2.291,9.099C2.693,6.864 3.958,4.917 5.854,3.617C9.768,0.93 15.182,1.865 17.923,5.702ZM41.347,61.805C38.618,61.805 35.934,60.543 34.248,58.185L22.011,41.052C19.266,37.21 20.217,31.913 24.133,29.222C28.049,26.528 33.449,27.461 36.193,31.303L48.431,48.435C51.175,52.277 50.225,57.574 46.309,60.266C44.797,61.306 43.063,61.805 41.347,61.805Z"
android:strokeWidth="1"
android:fillColor="#368BD6"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>