mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-23 09:56:00 +03:00
Merge pull request #779 from vector-im/feature/fix_some_crashes
Fix some crashes and issues
This commit is contained in:
commit
0d36e9d8a6
44 changed files with 302 additions and 87 deletions
|
@ -7,6 +7,7 @@ Features ✨:
|
||||||
Improvements 🙌:
|
Improvements 🙌:
|
||||||
- Handle navigation to room via room alias (#201)
|
- Handle navigation to room via room alias (#201)
|
||||||
- Open matrix.to link in RiotX (#57)
|
- Open matrix.to link in RiotX (#57)
|
||||||
|
- Limit sticker size in the timeline
|
||||||
|
|
||||||
Other changes:
|
Other changes:
|
||||||
- Use same default room colors than Riot-Web
|
- Use same default room colors than Riot-Web
|
||||||
|
@ -14,7 +15,9 @@ Other changes:
|
||||||
Bugfix 🐛:
|
Bugfix 🐛:
|
||||||
- Scroll breadcrumbs to top when opened
|
- Scroll breadcrumbs to top when opened
|
||||||
- Render default room name when it starts with an emoji (#477)
|
- Render default room name when it starts with an emoji (#477)
|
||||||
- Do not display " (IRC)") in display names https://github.com/vector-im/riot-android/issues/444
|
- Do not display " (IRC)" in display names https://github.com/vector-im/riot-android/issues/444
|
||||||
|
- Fix rendering issue with HTML formatted body
|
||||||
|
- Disable click on Stickers (#703)
|
||||||
|
|
||||||
Translations 🗣:
|
Translations 🗣:
|
||||||
-
|
-
|
||||||
|
|
|
@ -30,7 +30,7 @@ data class ContentAttachmentData(
|
||||||
val exifOrientation: Int = ExifInterface.ORIENTATION_UNDEFINED,
|
val exifOrientation: Int = ExifInterface.ORIENTATION_UNDEFINED,
|
||||||
val name: String? = null,
|
val name: String? = null,
|
||||||
val path: String,
|
val path: String,
|
||||||
val mimeType: String,
|
val mimeType: String?,
|
||||||
val type: Type
|
val type: Type
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ data class VideoInfo(
|
||||||
/**
|
/**
|
||||||
* The mimetype of the video e.g. "video/mp4".
|
* The mimetype of the video e.g. "video/mp4".
|
||||||
*/
|
*/
|
||||||
@Json(name = "mimetype") val mimeType: String,
|
@Json(name = "mimetype") val mimeType: String?,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The width of the video in pixels.
|
* The width of the video in pixels.
|
||||||
|
|
|
@ -104,7 +104,7 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? {
|
||||||
root.getClearContent().toModel<MessageStickerContent>()
|
root.getClearContent().toModel<MessageStickerContent>()
|
||||||
} else {
|
} else {
|
||||||
annotations?.editSummary?.aggregatedContent?.toModel()
|
annotations?.editSummary?.aggregatedContent?.toModel()
|
||||||
?: root.getClearContent().toModel()
|
?: root.getClearContent().toModel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ fun TimelineEvent.getLastMessageBody(): String? {
|
||||||
|
|
||||||
if (lastMessageContent != null) {
|
if (lastMessageContent != null) {
|
||||||
return lastMessageContent.newContent?.toModel<MessageContent>()?.body
|
return lastMessageContent.newContent?.toModel<MessageContent>()?.body
|
||||||
?: lastMessageContent.body
|
?: lastMessageContent.body
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
|
|
|
@ -49,7 +49,7 @@ object MXEncryptedAttachments {
|
||||||
* @param mimetype the mime type
|
* @param mimetype the mime type
|
||||||
* @return the encryption file info
|
* @return the encryption file info
|
||||||
*/
|
*/
|
||||||
fun encryptAttachment(attachmentStream: InputStream, mimetype: String): EncryptionResult {
|
fun encryptAttachment(attachmentStream: InputStream, mimetype: String?): EncryptionResult {
|
||||||
val t0 = System.currentTimeMillis()
|
val t0 = System.currentTimeMillis()
|
||||||
val secureRandom = SecureRandom()
|
val secureRandom = SecureRandom()
|
||||||
|
|
||||||
|
|
|
@ -83,15 +83,13 @@ internal class DefaultFileService @Inject constructor(private val context: Conte
|
||||||
|
|
||||||
if (elementToDecrypt != null) {
|
if (elementToDecrypt != null) {
|
||||||
Timber.v("## decrypt file")
|
Timber.v("## decrypt file")
|
||||||
MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt) ?: throw IllegalStateException("Decryption error")
|
MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt)
|
||||||
} else {
|
?: throw IllegalStateException("Decryption error")
|
||||||
inputStream
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
writeToFile(inputStream, destFile)
|
||||||
|
destFile
|
||||||
}
|
}
|
||||||
.map { inputStream ->
|
|
||||||
writeToFile(inputStream, destFile)
|
|
||||||
destFile
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Try.just(destFile)
|
Try.just(destFile)
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,9 +43,9 @@ internal class FileUploader @Inject constructor(@Authenticated
|
||||||
|
|
||||||
suspend fun uploadFile(file: File,
|
suspend fun uploadFile(file: File,
|
||||||
filename: String?,
|
filename: String?,
|
||||||
mimeType: String,
|
mimeType: String?,
|
||||||
progressListener: ProgressRequestBody.Listener? = null): ContentUploadResponse {
|
progressListener: ProgressRequestBody.Listener? = null): ContentUploadResponse {
|
||||||
val uploadBody = file.asRequestBody(mimeType.toMediaTypeOrNull())
|
val uploadBody = file.asRequestBody(mimeType?.toMediaTypeOrNull())
|
||||||
return upload(uploadBody, filename, progressListener)
|
return upload(uploadBody, filename, progressListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ internal class DefaultCreateRoomTask @Inject constructor(private val roomAPI: Ro
|
||||||
apiCall = roomAPI.createRoom(params)
|
apiCall = roomAPI.createRoom(params)
|
||||||
}
|
}
|
||||||
val roomId = createRoomResponse.roomId!!
|
val roomId = createRoomResponse.roomId!!
|
||||||
// Wait for room to come back from the sync (but it can maybe be in the DB is the sync response is received before)
|
// Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before)
|
||||||
val rql = RealmQueryLatch<RoomEntity>(realmConfiguration) { realm ->
|
val rql = RealmQueryLatch<RoomEntity>(realmConfiguration) { realm ->
|
||||||
realm.where(RoomEntity::class.java)
|
realm.where(RoomEntity::class.java)
|
||||||
.equalTo(RoomEntityFields.ROOM_ID, roomId)
|
.equalTo(RoomEntityFields.ROOM_ID, roomId)
|
||||||
|
|
|
@ -251,7 +251,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
type = MessageType.MSGTYPE_AUDIO,
|
type = MessageType.MSGTYPE_AUDIO,
|
||||||
body = attachment.name ?: "audio",
|
body = attachment.name ?: "audio",
|
||||||
audioInfo = AudioInfo(
|
audioInfo = AudioInfo(
|
||||||
mimeType = attachment.mimeType.takeIf { it.isNotBlank() } ?: "audio/mpeg",
|
mimeType = attachment.mimeType?.takeIf { it.isNotBlank() } ?: "audio/mpeg",
|
||||||
size = attachment.size
|
size = attachment.size
|
||||||
),
|
),
|
||||||
url = attachment.path
|
url = attachment.path
|
||||||
|
@ -264,7 +264,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
type = MessageType.MSGTYPE_FILE,
|
type = MessageType.MSGTYPE_FILE,
|
||||||
body = attachment.name ?: "file",
|
body = attachment.name ?: "file",
|
||||||
info = FileInfo(
|
info = FileInfo(
|
||||||
mimeType = attachment.mimeType.takeIf { it.isNotBlank() }
|
mimeType = attachment.mimeType?.takeIf { it.isNotBlank() }
|
||||||
?: "application/octet-stream",
|
?: "application/octet-stream",
|
||||||
size = attachment.size
|
size = attachment.size
|
||||||
),
|
),
|
||||||
|
|
|
@ -293,6 +293,7 @@ dependencies {
|
||||||
implementation 'me.gujun.android:span:1.7'
|
implementation 'me.gujun.android:span:1.7'
|
||||||
implementation "io.noties.markwon:core:$markwon_version"
|
implementation "io.noties.markwon:core:$markwon_version"
|
||||||
implementation "io.noties.markwon:html:$markwon_version"
|
implementation "io.noties.markwon:html:$markwon_version"
|
||||||
|
implementation 'com.googlecode.htmlcompressor:htmlcompressor:1.4'
|
||||||
implementation 'me.saket:better-link-movement-method:2.2.0'
|
implementation 'me.saket:better-link-movement-method:2.2.0'
|
||||||
implementation 'com.google.android:flexbox:1.1.1'
|
implementation 'com.google.android:flexbox:1.1.1'
|
||||||
implementation "androidx.autofill:autofill:$autofill_version"
|
implementation "androidx.autofill:autofill:$autofill_version"
|
||||||
|
|
|
@ -31,4 +31,8 @@
|
||||||
<issue id="ViewConstructor" severity="error" />
|
<issue id="ViewConstructor" severity="error" />
|
||||||
<issue id="UseValueOf" severity="error" />
|
<issue id="UseValueOf" severity="error" />
|
||||||
|
|
||||||
|
<!-- Ignore error from HtmlCompressor lib -->
|
||||||
|
<issue id="InvalidPackage">
|
||||||
|
<ignore path="**/htmlcompressor-1.4.jar"/>
|
||||||
|
</issue>
|
||||||
</lint>
|
</lint>
|
||||||
|
|
|
@ -359,6 +359,11 @@ SOFTWARE.
|
||||||
<br/>
|
<br/>
|
||||||
Copyright 2018 Kumar Bibek
|
Copyright 2018 Kumar Bibek
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>htmlcompressor</b>
|
||||||
|
<br/>
|
||||||
|
Copyright 2017 Sergiy Kovalchuk
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<pre>
|
<pre>
|
||||||
Apache License
|
Apache License
|
||||||
|
|
|
@ -38,6 +38,7 @@ import im.vector.riotx.features.home.AvatarRenderer
|
||||||
import im.vector.riotx.features.home.HomeRoomListDataSource
|
import im.vector.riotx.features.home.HomeRoomListDataSource
|
||||||
import im.vector.riotx.features.home.group.SelectedGroupDataSource
|
import im.vector.riotx.features.home.group.SelectedGroupDataSource
|
||||||
import im.vector.riotx.features.html.EventHtmlRenderer
|
import im.vector.riotx.features.html.EventHtmlRenderer
|
||||||
|
import im.vector.riotx.features.html.VectorHtmlCompressor
|
||||||
import im.vector.riotx.features.navigation.Navigator
|
import im.vector.riotx.features.navigation.Navigator
|
||||||
import im.vector.riotx.features.notifications.*
|
import im.vector.riotx.features.notifications.*
|
||||||
import im.vector.riotx.features.rageshake.BugReporter
|
import im.vector.riotx.features.rageshake.BugReporter
|
||||||
|
@ -87,6 +88,8 @@ interface VectorComponent {
|
||||||
|
|
||||||
fun eventHtmlRenderer(): EventHtmlRenderer
|
fun eventHtmlRenderer(): EventHtmlRenderer
|
||||||
|
|
||||||
|
fun vectorHtmlCompressor(): VectorHtmlCompressor
|
||||||
|
|
||||||
fun navigator(): Navigator
|
fun navigator(): Navigator
|
||||||
|
|
||||||
fun errorFormatter(): ErrorFormatter
|
fun errorFormatter(): ErrorFormatter
|
||||||
|
|
30
vector/src/main/java/im/vector/riotx/core/epoxy/ZeroItem.kt
Normal file
30
vector/src/main/java/im/vector/riotx/core/epoxy/ZeroItem.kt
Normal 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.riotx.core.epoxy
|
||||||
|
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.riotx.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Item of size (0, 0).
|
||||||
|
* It can be useful to avoid automatic scroll of RecyclerView with Epoxy controller, when the first valuable item changes.
|
||||||
|
*/
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_zero)
|
||||||
|
abstract class ZeroItem : VectorEpoxyModel<ZeroItem.Holder>() {
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder()
|
||||||
|
}
|
31
vector/src/main/java/im/vector/riotx/core/error/fatal.kt
Normal file
31
vector/src/main/java/im/vector/riotx/core/error/fatal.kt
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* 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.riotx.core.error
|
||||||
|
|
||||||
|
import im.vector.riotx.BuildConfig
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
/**
|
||||||
|
* throw in debug, only log in production. As this method does not always throw, next statement should be a return
|
||||||
|
*/
|
||||||
|
fun fatalError(message: String) {
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
error(message)
|
||||||
|
} else {
|
||||||
|
Timber.e(message)
|
||||||
|
}
|
||||||
|
}
|
|
@ -222,8 +222,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
Timber.i("onResume Activity ${this.javaClass.simpleName}")
|
||||||
Timber.v("onResume Activity ${this.javaClass.simpleName}")
|
|
||||||
|
|
||||||
configurationViewModel.onActivityResumed()
|
configurationViewModel.onActivityResumed()
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
import im.vector.riotx.core.di.DaggerScreenComponent
|
import im.vector.riotx.core.di.DaggerScreenComponent
|
||||||
import im.vector.riotx.core.di.ScreenComponent
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
import im.vector.riotx.core.utils.DimensionConverter
|
import im.vector.riotx.core.utils.DimensionConverter
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add MvRx capabilities to bottomsheetdialog (like BaseMvRxFragment)
|
* Add MvRx capabilities to bottomsheetdialog (like BaseMvRxFragment)
|
||||||
|
@ -80,6 +81,11 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
Timber.i("onResume BottomSheet ${this.javaClass.simpleName}")
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
return super.onCreateDialog(savedInstanceState).apply {
|
return super.onCreateDialog(savedInstanceState).apply {
|
||||||
val dialog = this as? BottomSheetDialog
|
val dialog = this as? BottomSheetDialog
|
||||||
|
|
|
@ -104,7 +104,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
|
||||||
@CallSuper
|
@CallSuper
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
Timber.v("onResume Fragment ${this.javaClass.simpleName}")
|
Timber.i("onResume Fragment ${this.javaClass.simpleName}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
|
|
20
vector/src/main/java/im/vector/riotx/core/ui/model/Size.kt
Normal file
20
vector/src/main/java/im/vector/riotx/core/ui/model/Size.kt
Normal 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotx.core.ui.model
|
||||||
|
|
||||||
|
// android.util.Size in API 21+
|
||||||
|
data class Size(val width: Int, val height: Int)
|
|
@ -18,6 +18,7 @@ package im.vector.riotx.features.attachments
|
||||||
|
|
||||||
import com.kbeanie.multipicker.api.entity.*
|
import com.kbeanie.multipicker.api.entity.*
|
||||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
fun ChosenContact.toContactAttachment(): ContactAttachment {
|
fun ChosenContact.toContactAttachment(): ContactAttachment {
|
||||||
return ContactAttachment(
|
return ContactAttachment(
|
||||||
|
@ -29,6 +30,7 @@ fun ChosenContact.toContactAttachment(): ContactAttachment {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ChosenFile.toContentAttachmentData(): ContentAttachmentData {
|
fun ChosenFile.toContentAttachmentData(): ContentAttachmentData {
|
||||||
|
if (mimeType == null) Timber.w("No mimeType")
|
||||||
return ContentAttachmentData(
|
return ContentAttachmentData(
|
||||||
path = originalPath,
|
path = originalPath,
|
||||||
mimeType = mimeType,
|
mimeType = mimeType,
|
||||||
|
@ -40,6 +42,7 @@ fun ChosenFile.toContentAttachmentData(): ContentAttachmentData {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ChosenAudio.toContentAttachmentData(): ContentAttachmentData {
|
fun ChosenAudio.toContentAttachmentData(): ContentAttachmentData {
|
||||||
|
if (mimeType == null) Timber.w("No mimeType")
|
||||||
return ContentAttachmentData(
|
return ContentAttachmentData(
|
||||||
path = originalPath,
|
path = originalPath,
|
||||||
mimeType = mimeType,
|
mimeType = mimeType,
|
||||||
|
@ -51,16 +54,17 @@ fun ChosenAudio.toContentAttachmentData(): ContentAttachmentData {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ChosenFile.mapType(): ContentAttachmentData.Type {
|
private fun ChosenFile.mapType(): ContentAttachmentData.Type {
|
||||||
return when {
|
return when {
|
||||||
mimeType.startsWith("image/") -> ContentAttachmentData.Type.IMAGE
|
mimeType?.startsWith("image/") == true -> ContentAttachmentData.Type.IMAGE
|
||||||
mimeType.startsWith("video/") -> ContentAttachmentData.Type.VIDEO
|
mimeType?.startsWith("video/") == true -> ContentAttachmentData.Type.VIDEO
|
||||||
mimeType.startsWith("audio/") -> ContentAttachmentData.Type.AUDIO
|
mimeType?.startsWith("audio/") == true -> ContentAttachmentData.Type.AUDIO
|
||||||
else -> ContentAttachmentData.Type.FILE
|
else -> ContentAttachmentData.Type.FILE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ChosenImage.toContentAttachmentData(): ContentAttachmentData {
|
fun ChosenImage.toContentAttachmentData(): ContentAttachmentData {
|
||||||
|
if (mimeType == null) Timber.w("No mimeType")
|
||||||
return ContentAttachmentData(
|
return ContentAttachmentData(
|
||||||
path = originalPath,
|
path = originalPath,
|
||||||
mimeType = mimeType,
|
mimeType = mimeType,
|
||||||
|
@ -75,6 +79,7 @@ fun ChosenImage.toContentAttachmentData(): ContentAttachmentData {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ChosenVideo.toContentAttachmentData(): ContentAttachmentData {
|
fun ChosenVideo.toContentAttachmentData(): ContentAttachmentData {
|
||||||
|
if (mimeType == null) Timber.w("No mimeType")
|
||||||
return ContentAttachmentData(
|
return ContentAttachmentData(
|
||||||
path = originalPath,
|
path = originalPath,
|
||||||
mimeType = mimeType,
|
mimeType = mimeType,
|
||||||
|
|
|
@ -68,14 +68,14 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeSelectionState() {
|
private fun observeSelectionState() {
|
||||||
selectSubscribe(GroupListViewState::selectedGroup) {
|
selectSubscribe(GroupListViewState::selectedGroup) { groupSummary ->
|
||||||
if (it != null) {
|
if (groupSummary != null) {
|
||||||
val selectedGroup = _openGroupLiveData.value?.peekContent()
|
val selectedGroup = _openGroupLiveData.value?.peekContent()
|
||||||
// We only wan to open group if the updated selectedGroup is a different one.
|
// We only want to open group if the updated selectedGroup is a different one.
|
||||||
if (selectedGroup?.groupId != it.groupId) {
|
if (selectedGroup?.groupId != groupSummary.groupId) {
|
||||||
_openGroupLiveData.postLiveEvent(it)
|
_openGroupLiveData.postLiveEvent(groupSummary)
|
||||||
}
|
}
|
||||||
val optionGroup = Option.fromNullable(it)
|
val optionGroup = Option.just(groupSummary)
|
||||||
selectedGroupStore.post(optionGroup)
|
selectedGroupStore.post(optionGroup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.riotx.features.home.room.breadcrumbs
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.airbnb.epoxy.EpoxyController
|
import com.airbnb.epoxy.EpoxyController
|
||||||
import im.vector.matrix.android.api.util.toMatrixItem
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
|
import im.vector.riotx.core.epoxy.zeroItem
|
||||||
import im.vector.riotx.core.utils.DebouncedClickListener
|
import im.vector.riotx.core.utils.DebouncedClickListener
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -45,9 +46,13 @@ class BreadcrumbsController @Inject constructor(
|
||||||
override fun buildModels() {
|
override fun buildModels() {
|
||||||
val safeViewState = viewState ?: return
|
val safeViewState = viewState ?: return
|
||||||
|
|
||||||
|
// Add a ZeroItem to avoid automatic scroll when the breadcrumbs are updated from another client
|
||||||
|
zeroItem {
|
||||||
|
id("top")
|
||||||
|
}
|
||||||
|
|
||||||
// An empty breadcrumbs list can only be temporary because when entering in a room,
|
// An empty breadcrumbs list can only be temporary because when entering in a room,
|
||||||
// this one is added to the breadcrumbs
|
// this one is added to the breadcrumbs
|
||||||
|
|
||||||
safeViewState.asyncBreadcrumbs.invoke()
|
safeViewState.asyncBreadcrumbs.invoke()
|
||||||
?.forEach {
|
?.forEach {
|
||||||
breadcrumbsItem {
|
breadcrumbsItem {
|
||||||
|
|
|
@ -86,8 +86,6 @@ import im.vector.riotx.features.autocomplete.command.CommandAutocompletePolicy
|
||||||
import im.vector.riotx.features.autocomplete.user.AutocompleteUserPresenter
|
import im.vector.riotx.features.autocomplete.user.AutocompleteUserPresenter
|
||||||
import im.vector.riotx.features.command.Command
|
import im.vector.riotx.features.command.Command
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
import im.vector.riotx.features.permalink.NavigateToRoomInterceptor
|
|
||||||
import im.vector.riotx.features.permalink.PermalinkHandler
|
|
||||||
import im.vector.riotx.features.home.getColorFromUserId
|
import im.vector.riotx.features.home.getColorFromUserId
|
||||||
import im.vector.riotx.features.home.room.detail.composer.TextComposerAction
|
import im.vector.riotx.features.home.room.detail.composer.TextComposerAction
|
||||||
import im.vector.riotx.features.home.room.detail.composer.TextComposerView
|
import im.vector.riotx.features.home.room.detail.composer.TextComposerView
|
||||||
|
@ -109,6 +107,8 @@ import im.vector.riotx.features.media.ImageMediaViewerActivity
|
||||||
import im.vector.riotx.features.media.VideoContentRenderer
|
import im.vector.riotx.features.media.VideoContentRenderer
|
||||||
import im.vector.riotx.features.media.VideoMediaViewerActivity
|
import im.vector.riotx.features.media.VideoMediaViewerActivity
|
||||||
import im.vector.riotx.features.notifications.NotificationDrawerManager
|
import im.vector.riotx.features.notifications.NotificationDrawerManager
|
||||||
|
import im.vector.riotx.features.permalink.NavigateToRoomInterceptor
|
||||||
|
import im.vector.riotx.features.permalink.PermalinkHandler
|
||||||
import im.vector.riotx.features.reactions.EmojiReactionPickerActivity
|
import im.vector.riotx.features.reactions.EmojiReactionPickerActivity
|
||||||
import im.vector.riotx.features.settings.VectorPreferences
|
import im.vector.riotx.features.settings.VectorPreferences
|
||||||
import im.vector.riotx.features.share.SharedData
|
import im.vector.riotx.features.share.SharedData
|
||||||
|
@ -416,8 +416,10 @@ class RoomDetailFragment @Inject constructor(
|
||||||
composerLayout.composerRelatedMessageAvatar
|
composerLayout.composerRelatedMessageAvatar
|
||||||
)
|
)
|
||||||
composerLayout.expand {
|
composerLayout.expand {
|
||||||
// need to do it here also when not using quick reply
|
if (isAdded) {
|
||||||
focusComposerAndShowKeyboard()
|
// need to do it here also when not using quick reply
|
||||||
|
focusComposerAndShowKeyboard()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
focusComposerAndShowKeyboard()
|
focusComposerAndShowKeyboard()
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ import im.vector.riotx.core.resources.StringProvider
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventFormatter
|
import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventFormatter
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
import im.vector.riotx.features.html.EventHtmlRenderer
|
import im.vector.riotx.features.html.EventHtmlRenderer
|
||||||
|
import im.vector.riotx.features.html.VectorHtmlCompressor
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
@ -82,6 +83,7 @@ data class MessageActionState(
|
||||||
class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
initialState: MessageActionState,
|
initialState: MessageActionState,
|
||||||
private val eventHtmlRenderer: Lazy<EventHtmlRenderer>,
|
private val eventHtmlRenderer: Lazy<EventHtmlRenderer>,
|
||||||
|
private val htmlCompressor: VectorHtmlCompressor,
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
private val noticeEventFormatter: NoticeEventFormatter,
|
private val noticeEventFormatter: NoticeEventFormatter,
|
||||||
private val stringProvider: StringProvider
|
private val stringProvider: StringProvider
|
||||||
|
@ -100,6 +102,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
|
|
||||||
val quickEmojis = listOf("👍", "👎", "😄", "🎉", "😕", "❤️", "🚀", "👀")
|
val quickEmojis = listOf("👍", "👎", "😄", "🎉", "😕", "❤️", "🚀", "👀")
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
override fun create(viewModelContext: ViewModelContext, state: MessageActionState): MessageActionsViewModel? {
|
override fun create(viewModelContext: ViewModelContext, state: MessageActionState): MessageActionsViewModel? {
|
||||||
val fragment: MessageActionsBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
|
val fragment: MessageActionsBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
|
||||||
return fragment.messageActionViewModelFactory.create(state)
|
return fragment.messageActionViewModelFactory.create(state)
|
||||||
|
@ -167,11 +170,16 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
|
|
||||||
private fun computeMessageBody(timelineEvent: Async<TimelineEvent>): CharSequence? {
|
private fun computeMessageBody(timelineEvent: Async<TimelineEvent>): CharSequence? {
|
||||||
return when (timelineEvent()?.root?.getClearType()) {
|
return when (timelineEvent()?.root?.getClearType()) {
|
||||||
EventType.MESSAGE -> {
|
EventType.MESSAGE,
|
||||||
|
EventType.STICKER -> {
|
||||||
val messageContent: MessageContent? = timelineEvent()?.getLastMessageContent()
|
val messageContent: MessageContent? = timelineEvent()?.getLastMessageContent()
|
||||||
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
|
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
|
||||||
eventHtmlRenderer.get().render(messageContent.formattedBody
|
val html = messageContent.formattedBody
|
||||||
?: messageContent.body)
|
?.takeIf { it.isNotBlank() }
|
||||||
|
?.let { htmlCompressor.compress(it) }
|
||||||
|
?: messageContent.body
|
||||||
|
|
||||||
|
eventHtmlRenderer.get().render(html)
|
||||||
} else {
|
} else {
|
||||||
messageContent?.body
|
messageContent?.body
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,7 @@ class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted
|
||||||
|
|
||||||
companion object : MvRxViewModelFactory<ViewEditHistoryViewModel, ViewEditHistoryViewState> {
|
companion object : MvRxViewModelFactory<ViewEditHistoryViewModel, ViewEditHistoryViewState> {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
override fun create(viewModelContext: ViewModelContext, state: ViewEditHistoryViewState): ViewEditHistoryViewModel? {
|
override fun create(viewModelContext: ViewModelContext, state: ViewEditHistoryViewState): ViewEditHistoryViewModel? {
|
||||||
val fragment: ViewEditHistoryBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
|
val fragment: ViewEditHistoryBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
|
||||||
return fragment.viewEditHistoryViewModelFactory.create(state)
|
return fragment.viewEditHistoryViewModelFactory.create(state)
|
||||||
|
|
|
@ -46,6 +46,7 @@ import im.vector.riotx.features.home.room.detail.timeline.tools.createLinkMoveme
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.tools.linkify
|
import im.vector.riotx.features.home.room.detail.timeline.tools.linkify
|
||||||
import im.vector.riotx.features.html.CodeVisitor
|
import im.vector.riotx.features.html.CodeVisitor
|
||||||
import im.vector.riotx.features.html.EventHtmlRenderer
|
import im.vector.riotx.features.html.EventHtmlRenderer
|
||||||
|
import im.vector.riotx.features.html.VectorHtmlCompressor
|
||||||
import im.vector.riotx.features.media.ImageContentRenderer
|
import im.vector.riotx.features.media.ImageContentRenderer
|
||||||
import im.vector.riotx.features.media.VideoContentRenderer
|
import im.vector.riotx.features.media.VideoContentRenderer
|
||||||
import me.gujun.android.span.span
|
import me.gujun.android.span.span
|
||||||
|
@ -57,6 +58,7 @@ class MessageItemFactory @Inject constructor(
|
||||||
private val dimensionConverter: DimensionConverter,
|
private val dimensionConverter: DimensionConverter,
|
||||||
private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
|
private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
|
||||||
private val htmlRenderer: Lazy<EventHtmlRenderer>,
|
private val htmlRenderer: Lazy<EventHtmlRenderer>,
|
||||||
|
private val htmlCompressor: VectorHtmlCompressor,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val imageContentRenderer: ImageContentRenderer,
|
private val imageContentRenderer: ImageContentRenderer,
|
||||||
private val messageInformationDataFactory: MessageInformationDataFactory,
|
private val messageInformationDataFactory: MessageInformationDataFactory,
|
||||||
|
@ -179,10 +181,16 @@ class MessageItemFactory @Inject constructor(
|
||||||
.playable(messageContent.info?.mimeType == "image/gif")
|
.playable(messageContent.info?.mimeType == "image/gif")
|
||||||
.highlighted(highlight)
|
.highlighted(highlight)
|
||||||
.mediaData(data)
|
.mediaData(data)
|
||||||
.clickListener(
|
.apply {
|
||||||
DebouncedClickListener(View.OnClickListener { view ->
|
if (messageContent.type == MessageType.MSGTYPE_STICKER_LOCAL) {
|
||||||
callback?.onImageMessageClicked(messageContent, data, view)
|
mode(ImageContentRenderer.Mode.STICKER)
|
||||||
}))
|
} else {
|
||||||
|
clickListener(
|
||||||
|
DebouncedClickListener(View.OnClickListener { view ->
|
||||||
|
callback?.onImageMessageClicked(messageContent, data, view)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildVideoMessageItem(messageContent: MessageVideoContent,
|
private fun buildVideoMessageItem(messageContent: MessageVideoContent,
|
||||||
|
@ -227,6 +235,7 @@ class MessageItemFactory @Inject constructor(
|
||||||
attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? {
|
attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? {
|
||||||
val isFormatted = messageContent.formattedBody.isNullOrBlank().not()
|
val isFormatted = messageContent.formattedBody.isNullOrBlank().not()
|
||||||
return if (isFormatted) {
|
return if (isFormatted) {
|
||||||
|
// First detect if the message contains some code block(s) or inline code
|
||||||
val localFormattedBody = htmlRenderer.get().parse(messageContent.body) as Document
|
val localFormattedBody = htmlRenderer.get().parse(messageContent.body) as Document
|
||||||
val codeVisitor = CodeVisitor()
|
val codeVisitor = CodeVisitor()
|
||||||
codeVisitor.visit(localFormattedBody)
|
codeVisitor.visit(localFormattedBody)
|
||||||
|
@ -240,7 +249,8 @@ class MessageItemFactory @Inject constructor(
|
||||||
buildMessageTextItem(codeFormatted, false, informationData, highlight, callback, attributes)
|
buildMessageTextItem(codeFormatted, false, informationData, highlight, callback, attributes)
|
||||||
}
|
}
|
||||||
CodeVisitor.Kind.NONE -> {
|
CodeVisitor.Kind.NONE -> {
|
||||||
val formattedBody = htmlRenderer.get().render(messageContent.formattedBody!!)
|
val compressed = htmlCompressor.compress(messageContent.formattedBody!!)
|
||||||
|
val formattedBody = htmlRenderer.get().render(compressed)
|
||||||
buildMessageTextItem(formattedBody, true, informationData, highlight, callback, attributes)
|
buildMessageTextItem(formattedBody, true, informationData, highlight, callback, attributes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,8 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var playable: Boolean = false
|
var playable: Boolean = false
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
|
var mode = ImageContentRenderer.Mode.THUMBNAIL
|
||||||
|
@EpoxyAttribute
|
||||||
var clickListener: View.OnClickListener? = null
|
var clickListener: View.OnClickListener? = null
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
lateinit var imageContentRenderer: ImageContentRenderer
|
lateinit var imageContentRenderer: ImageContentRenderer
|
||||||
|
@ -44,7 +46,7 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
imageContentRenderer.render(mediaData, ImageContentRenderer.Mode.THUMBNAIL, holder.imageView)
|
imageContentRenderer.render(mediaData, mode, holder.imageView)
|
||||||
if (!attributes.informationData.sendState.hasFailed()) {
|
if (!attributes.informationData.sendState.hasFailed()) {
|
||||||
contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, mediaData.isLocalFile(), holder.progressLayout)
|
contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, mediaData.isLocalFile(), holder.progressLayout)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -68,6 +68,7 @@ class ViewReactionsViewModel @AssistedInject constructor(@Assisted
|
||||||
|
|
||||||
companion object : MvRxViewModelFactory<ViewReactionsViewModel, DisplayReactionsViewState> {
|
companion object : MvRxViewModelFactory<ViewReactionsViewModel, DisplayReactionsViewState> {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
override fun create(viewModelContext: ViewModelContext, state: DisplayReactionsViewState): ViewReactionsViewModel? {
|
override fun create(viewModelContext: ViewModelContext, state: DisplayReactionsViewState): ViewReactionsViewModel? {
|
||||||
val fragment: ViewReactionsBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
|
val fragment: ViewReactionsBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
|
||||||
return fragment.viewReactionsViewModelFactory.create(state)
|
return fragment.viewReactionsViewModelFactory.create(state)
|
||||||
|
|
|
@ -37,6 +37,7 @@ class RoomListQuickActionsViewModel @AssistedInject constructor(@Assisted initia
|
||||||
|
|
||||||
companion object : MvRxViewModelFactory<RoomListQuickActionsViewModel, RoomListQuickActionsState> {
|
companion object : MvRxViewModelFactory<RoomListQuickActionsViewModel, RoomListQuickActionsState> {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
override fun create(viewModelContext: ViewModelContext, state: RoomListQuickActionsState): RoomListQuickActionsViewModel? {
|
override fun create(viewModelContext: ViewModelContext, state: RoomListQuickActionsState): RoomListQuickActionsViewModel? {
|
||||||
val fragment: RoomListQuickActionsBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
|
val fragment: RoomListQuickActionsBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
|
||||||
return fragment.roomListActionsViewModelFactory.create(state)
|
return fragment.roomListActionsViewModelFactory.create(state)
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* 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.riotx.features.html
|
||||||
|
|
||||||
|
import com.googlecode.htmlcompressor.compressor.Compressor
|
||||||
|
import com.googlecode.htmlcompressor.compressor.HtmlCompressor
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class VectorHtmlCompressor @Inject constructor() {
|
||||||
|
|
||||||
|
// All default options are suitable so far
|
||||||
|
private val htmlCompressor: Compressor = HtmlCompressor()
|
||||||
|
|
||||||
|
fun compress(html: String): String {
|
||||||
|
var result = htmlCompressor.compress(html)
|
||||||
|
|
||||||
|
// Trim space after <br> and <p>, unfortunately the method setRemoveSurroundingSpaces() from the doc does not exist
|
||||||
|
result = result.replace("<br> ", "<br>")
|
||||||
|
result = result.replace("<br/> ", "<br/>")
|
||||||
|
result = result.replace("<p> ", "<p>")
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,11 +31,13 @@ import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
||||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||||
import im.vector.riotx.core.glide.GlideApp
|
import im.vector.riotx.core.glide.GlideApp
|
||||||
import im.vector.riotx.core.glide.GlideRequest
|
import im.vector.riotx.core.glide.GlideRequest
|
||||||
|
import im.vector.riotx.core.ui.model.Size
|
||||||
import im.vector.riotx.core.utils.DimensionConverter
|
import im.vector.riotx.core.utils.DimensionConverter
|
||||||
import im.vector.riotx.core.utils.isLocalFile
|
import im.vector.riotx.core.utils.isLocalFile
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
class ImageContentRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
|
class ImageContentRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
|
||||||
private val dimensionConverter: DimensionConverter) {
|
private val dimensionConverter: DimensionConverter) {
|
||||||
|
@ -56,17 +58,18 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
|
||||||
|
|
||||||
enum class Mode {
|
enum class Mode {
|
||||||
FULL_SIZE,
|
FULL_SIZE,
|
||||||
THUMBNAIL
|
THUMBNAIL,
|
||||||
|
STICKER
|
||||||
}
|
}
|
||||||
|
|
||||||
fun render(data: Data, mode: Mode, imageView: ImageView) {
|
fun render(data: Data, mode: Mode, imageView: ImageView) {
|
||||||
val (width, height) = processSize(data, mode)
|
val size = processSize(data, mode)
|
||||||
imageView.layoutParams.height = height
|
imageView.layoutParams.width = size.width
|
||||||
imageView.layoutParams.width = width
|
imageView.layoutParams.height = size.height
|
||||||
// a11y
|
// a11y
|
||||||
imageView.contentDescription = data.filename
|
imageView.contentDescription = data.filename
|
||||||
|
|
||||||
createGlideRequest(data, mode, imageView, width, height)
|
createGlideRequest(data, mode, imageView, size)
|
||||||
.dontAnimate()
|
.dontAnimate()
|
||||||
.transform(RoundedCorners(dimensionConverter.dpToPx(8)))
|
.transform(RoundedCorners(dimensionConverter.dpToPx(8)))
|
||||||
.thumbnail(0.3f)
|
.thumbnail(0.3f)
|
||||||
|
@ -74,12 +77,12 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
|
||||||
}
|
}
|
||||||
|
|
||||||
fun renderFitTarget(data: Data, mode: Mode, imageView: ImageView, callback: ((Boolean) -> Unit)? = null) {
|
fun renderFitTarget(data: Data, mode: Mode, imageView: ImageView, callback: ((Boolean) -> Unit)? = null) {
|
||||||
val (width, height) = processSize(data, mode)
|
val size = processSize(data, mode)
|
||||||
|
|
||||||
// a11y
|
// a11y
|
||||||
imageView.contentDescription = data.filename
|
imageView.contentDescription = data.filename
|
||||||
|
|
||||||
createGlideRequest(data, mode, imageView, width, height)
|
createGlideRequest(data, mode, imageView, size)
|
||||||
.listener(object : RequestListener<Drawable> {
|
.listener(object : RequestListener<Drawable> {
|
||||||
override fun onLoadFailed(e: GlideException?,
|
override fun onLoadFailed(e: GlideException?,
|
||||||
model: Any?,
|
model: Any?,
|
||||||
|
@ -102,7 +105,7 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
|
||||||
.into(imageView)
|
.into(imageView)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createGlideRequest(data: Data, mode: Mode, imageView: ImageView, width: Int, height: Int): GlideRequest<Drawable> {
|
private fun createGlideRequest(data: Data, mode: Mode, imageView: ImageView, size: Size): GlideRequest<Drawable> {
|
||||||
return if (data.elementToDecrypt != null) {
|
return if (data.elementToDecrypt != null) {
|
||||||
// Encrypted image
|
// Encrypted image
|
||||||
GlideApp
|
GlideApp
|
||||||
|
@ -112,8 +115,9 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
|
||||||
// Clear image
|
// Clear image
|
||||||
val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
|
val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
|
||||||
val resolvedUrl = when (mode) {
|
val resolvedUrl = when (mode) {
|
||||||
Mode.FULL_SIZE -> contentUrlResolver.resolveFullSize(data.url)
|
Mode.FULL_SIZE,
|
||||||
Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
|
Mode.STICKER -> contentUrlResolver.resolveFullSize(data.url)
|
||||||
|
Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, size.width, size.height, ContentUrlResolver.ThumbnailMethod.SCALE)
|
||||||
}
|
}
|
||||||
// Fallback to base url
|
// Fallback to base url
|
||||||
?: data.url
|
?: data.url
|
||||||
|
@ -144,23 +148,32 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processSize(data: Data, mode: Mode): Pair<Int, Int> {
|
private fun processSize(data: Data, mode: Mode): Size {
|
||||||
val maxImageWidth = data.maxWidth
|
val maxImageWidth = data.maxWidth
|
||||||
val maxImageHeight = data.maxHeight
|
val maxImageHeight = data.maxHeight
|
||||||
val width = data.width ?: maxImageWidth
|
val width = data.width ?: maxImageWidth
|
||||||
val height = data.height ?: maxImageHeight
|
val height = data.height ?: maxImageHeight
|
||||||
var finalHeight = -1
|
|
||||||
var finalWidth = -1
|
var finalWidth = -1
|
||||||
|
var finalHeight = -1
|
||||||
|
|
||||||
// if the image size is known
|
// if the image size is known
|
||||||
// compute the expected height
|
// compute the expected height
|
||||||
if (width > 0 && height > 0) {
|
if (width > 0 && height > 0) {
|
||||||
if (mode == Mode.FULL_SIZE) {
|
when (mode) {
|
||||||
finalHeight = height
|
Mode.FULL_SIZE -> {
|
||||||
finalWidth = width
|
finalHeight = height
|
||||||
} else {
|
finalWidth = width
|
||||||
finalHeight = Math.min(maxImageWidth * height / width, maxImageHeight)
|
}
|
||||||
finalWidth = finalHeight * width / height
|
Mode.THUMBNAIL -> {
|
||||||
|
finalHeight = min(maxImageWidth * height / width, maxImageHeight)
|
||||||
|
finalWidth = finalHeight * width / height
|
||||||
|
}
|
||||||
|
Mode.STICKER -> {
|
||||||
|
// limit on width
|
||||||
|
val maxWidthDp = min(dimensionConverter.dpToPx(120), maxImageWidth / 2)
|
||||||
|
finalWidth = min(dimensionConverter.dpToPx(width), maxWidthDp)
|
||||||
|
finalHeight = finalWidth * height / width
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ensure that some values are properly initialized
|
// ensure that some values are properly initialized
|
||||||
|
@ -170,6 +183,6 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
|
||||||
if (finalWidth < 0) {
|
if (finalWidth < 0) {
|
||||||
finalWidth = maxImageWidth
|
finalWidth = maxImageWidth
|
||||||
}
|
}
|
||||||
return Pair(finalWidth, finalHeight)
|
return Size(finalWidth, finalHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,11 @@ package im.vector.riotx.features.navigation
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import androidx.core.app.TaskStackBuilder
|
||||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||||
|
import im.vector.riotx.core.error.fatalError
|
||||||
import im.vector.riotx.core.platform.VectorBaseActivity
|
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||||
import im.vector.riotx.core.utils.toast
|
import im.vector.riotx.core.utils.toast
|
||||||
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity
|
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity
|
||||||
|
@ -38,12 +41,18 @@ import im.vector.riotx.features.share.SharedData
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
import androidx.core.app.TaskStackBuilder
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class DefaultNavigator @Inject constructor() : Navigator {
|
class DefaultNavigator @Inject constructor(
|
||||||
|
private val sessionHolder: ActiveSessionHolder
|
||||||
|
) : Navigator {
|
||||||
|
|
||||||
override fun openRoom(context: Context, roomId: String, eventId: String?, buildTask: Boolean) {
|
override fun openRoom(context: Context, roomId: String, eventId: String?, buildTask: Boolean) {
|
||||||
|
if (sessionHolder.getSafeActiveSession()?.getRoom(roomId) == null) {
|
||||||
|
fatalError("Trying to open an unknown room $roomId")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val args = RoomDetailArgs(roomId, eventId)
|
val args = RoomDetailArgs(roomId, eventId)
|
||||||
val intent = RoomDetailActivity.newIntent(context, args)
|
val intent = RoomDetailActivity.newIntent(context, args)
|
||||||
if (buildTask) {
|
if (buildTask) {
|
||||||
|
|
|
@ -57,7 +57,7 @@ class PermalinkHandler @Inject constructor(private val session: Session,
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.map {
|
.map {
|
||||||
val roomId = it.getOrNull()
|
val roomId = it.getOrNull()
|
||||||
if (navigateToRoomInterceptor?.navToRoom(roomId) != true) {
|
if (navigateToRoomInterceptor?.navToRoom(roomId, permalinkData.eventId) != true) {
|
||||||
openRoom(context, roomId, permalinkData.eventId, buildTask)
|
openRoom(context, roomId, permalinkData.eventId, buildTask)
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
|
@ -87,9 +87,9 @@ class PermalinkHandler @Inject constructor(private val session: Session,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open room either joined, or not unknown
|
* Open room either joined, or not
|
||||||
*/
|
*/
|
||||||
private fun openRoom(context: Context, roomId: String?, eventId: String? = null, buildTask: Boolean) {
|
private fun openRoom(context: Context, roomId: String?, eventId: String?, buildTask: Boolean) {
|
||||||
return if (roomId != null && session.getRoom(roomId) != null) {
|
return if (roomId != null && session.getRoom(roomId) != null) {
|
||||||
navigator.openRoom(context, roomId, eventId, buildTask)
|
navigator.openRoom(context, roomId, eventId, buildTask)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -27,7 +27,6 @@ import im.vector.riotx.core.utils.toast
|
||||||
import im.vector.riotx.features.home.LoadingFragment
|
import im.vector.riotx.features.home.LoadingFragment
|
||||||
import im.vector.riotx.features.login.LoginActivity
|
import im.vector.riotx.features.login.LoginActivity
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import kotlinx.android.synthetic.debug.activity_test_material_theme.*
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
|
@ -77,8 +77,7 @@ class BugReportActivity : VectorBaseActivity() {
|
||||||
|
|
||||||
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
||||||
menu.findItem(R.id.ic_action_send_bug_report)?.let {
|
menu.findItem(R.id.ic_action_send_bug_report)?.let {
|
||||||
val isValid = bug_report_edit_text.text.toString().trim().length > 10
|
val isValid = !bug_report_mask_view.isVisible
|
||||||
&& !bug_report_mask_view.isVisible
|
|
||||||
|
|
||||||
it.isEnabled = isValid
|
it.isEnabled = isValid
|
||||||
it.icon.alpha = if (isValid) 255 else 100
|
it.icon.alpha = if (isValid) 255 else 100
|
||||||
|
@ -90,7 +89,11 @@ class BugReportActivity : VectorBaseActivity() {
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.ic_action_send_bug_report -> {
|
R.id.ic_action_send_bug_report -> {
|
||||||
sendBugReport()
|
if (bug_report_edit_text.text.toString().trim().length >= 10) {
|
||||||
|
sendBugReport()
|
||||||
|
} else {
|
||||||
|
bug_report_text_input_layout.error = getString(R.string.bug_report_error_too_short)
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,7 +153,7 @@ class BugReportActivity : VectorBaseActivity() {
|
||||||
val myProgress = progress.coerceIn(0, 100)
|
val myProgress = progress.coerceIn(0, 100)
|
||||||
|
|
||||||
bug_report_progress_view.progress = myProgress
|
bug_report_progress_view.progress = myProgress
|
||||||
bug_report_progress_text_view.text = getString(R.string.send_bug_report_progress, "$myProgress")
|
bug_report_progress_text_view.text = getString(R.string.send_bug_report_progress, myProgress.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUploadSucceed() {
|
override fun onUploadSucceed() {
|
||||||
|
@ -179,7 +182,7 @@ class BugReportActivity : VectorBaseActivity() {
|
||||||
|
|
||||||
@OnTextChanged(R.id.bug_report_edit_text)
|
@OnTextChanged(R.id.bug_report_edit_text)
|
||||||
internal fun textChanged() {
|
internal fun textChanged() {
|
||||||
invalidateOptionsMenu()
|
bug_report_text_input_layout.error = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnCheckedChanged(R.id.bug_report_button_include_screenshot)
|
@OnCheckedChanged(R.id.bug_report_button_include_screenshot)
|
||||||
|
|
|
@ -33,6 +33,7 @@ import im.vector.riotx.core.di.ActiveSessionHolder
|
||||||
import im.vector.riotx.core.extensions.toOnOff
|
import im.vector.riotx.core.extensions.toOnOff
|
||||||
import im.vector.riotx.core.utils.getDeviceLocale
|
import im.vector.riotx.core.utils.getDeviceLocale
|
||||||
import im.vector.riotx.features.settings.VectorLocale
|
import im.vector.riotx.features.settings.VectorLocale
|
||||||
|
import im.vector.riotx.features.settings.VectorPreferences
|
||||||
import im.vector.riotx.features.themes.ThemeUtils
|
import im.vector.riotx.features.themes.ThemeUtils
|
||||||
import im.vector.riotx.features.version.VersionProvider
|
import im.vector.riotx.features.version.VersionProvider
|
||||||
import okhttp3.Call
|
import okhttp3.Call
|
||||||
|
@ -44,12 +45,15 @@ import okhttp3.Response
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.*
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.OutputStreamWriter
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
import java.util.Locale
|
import java.util.*
|
||||||
import java.util.zip.GZIPOutputStream
|
import java.util.zip.GZIPOutputStream
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BugReporter creates and sends the bug reports.
|
* BugReporter creates and sends the bug reports.
|
||||||
|
@ -57,6 +61,7 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
|
class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
|
||||||
private val versionProvider: VersionProvider,
|
private val versionProvider: VersionProvider,
|
||||||
|
private val vectorPreferences: VectorPreferences,
|
||||||
private val vectorFileLogger: VectorFileLogger) {
|
private val vectorFileLogger: VectorFileLogger) {
|
||||||
var inMultiWindowMode = false
|
var inMultiWindowMode = false
|
||||||
|
|
||||||
|
@ -230,7 +235,7 @@ class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSes
|
||||||
.addFormDataPart("matrix_sdk_version", Matrix.getSdkVersion())
|
.addFormDataPart("matrix_sdk_version", Matrix.getSdkVersion())
|
||||||
.addFormDataPart("olm_version", olmVersion)
|
.addFormDataPart("olm_version", olmVersion)
|
||||||
.addFormDataPart("device", Build.MODEL.trim())
|
.addFormDataPart("device", Build.MODEL.trim())
|
||||||
.addFormDataPart("lazy_loading", true.toOnOff())
|
.addFormDataPart("verbose_log", vectorPreferences.labAllowedExtendedLogging().toOnOff())
|
||||||
.addFormDataPart("multi_window", inMultiWindowMode.toOnOff())
|
.addFormDataPart("multi_window", inMultiWindowMode.toOnOff())
|
||||||
.addFormDataPart("os", Build.VERSION.RELEASE + " (API " + Build.VERSION.SDK_INT + ") "
|
.addFormDataPart("os", Build.VERSION.RELEASE + " (API " + Build.VERSION.SDK_INT + ") "
|
||||||
+ Build.VERSION.INCREMENTAL + "-" + Build.VERSION.CODENAME)
|
+ Build.VERSION.INCREMENTAL + "-" + Build.VERSION.CODENAME)
|
||||||
|
|
|
@ -24,9 +24,7 @@ import java.io.File
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
import java.io.StringWriter
|
import java.io.StringWriter
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.*
|
||||||
import java.util.Locale
|
|
||||||
import java.util.TimeZone
|
|
||||||
import java.util.logging.*
|
import java.util.logging.*
|
||||||
import java.util.logging.Formatter
|
import java.util.logging.Formatter
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -83,7 +81,8 @@ class VectorFileLogger @Inject constructor(val context: Context, private val vec
|
||||||
return if (vectorPreferences.labAllowedExtendedLogging()) {
|
return if (vectorPreferences.labAllowedExtendedLogging()) {
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
priority < Log.ERROR
|
// Exclude debug and verbose logs
|
||||||
|
priority <= Log.DEBUG
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,7 @@ class EmojiSearchResultViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
companion object : MvRxViewModelFactory<EmojiSearchResultViewModel, EmojiSearchResultViewState> {
|
companion object : MvRxViewModelFactory<EmojiSearchResultViewModel, EmojiSearchResultViewState> {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
override fun create(viewModelContext: ViewModelContext, state: EmojiSearchResultViewState): EmojiSearchResultViewModel? {
|
override fun create(viewModelContext: ViewModelContext, state: EmojiSearchResultViewState): EmojiSearchResultViewModel? {
|
||||||
val activity: EmojiReactionPickerActivity = (viewModelContext as ActivityViewModelContext).activity()
|
val activity: EmojiReactionPickerActivity = (viewModelContext as ActivityViewModelContext).activity()
|
||||||
return activity.emojiSearchResultViewModelFactory.create(state)
|
return activity.emojiSearchResultViewModelFactory.create(state)
|
||||||
|
|
|
@ -178,7 +178,9 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
|
||||||
copy(
|
copy(
|
||||||
asyncPublicRoomsRequest = Success(data.chunk!!),
|
asyncPublicRoomsRequest = Success(data.chunk!!),
|
||||||
// It's ok to append at the end of the list, so I use publicRooms.size()
|
// It's ok to append at the end of the list, so I use publicRooms.size()
|
||||||
publicRooms = publicRooms.appendAt(data.chunk!!, publicRooms.size),
|
publicRooms = publicRooms.appendAt(data.chunk!!, publicRooms.size)
|
||||||
|
// Rageshake #8206 tells that we can have several times the same room
|
||||||
|
.distinctBy { it.roomId },
|
||||||
hasMore = since != null
|
hasMore = since != null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,7 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), HasScree
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
Timber.v("onResume Fragment ${this.javaClass.simpleName}")
|
Timber.i("onResume Fragment ${this.javaClass.simpleName}")
|
||||||
vectorActivity.supportActionBar?.setTitle(titleRes)
|
vectorActivity.supportActionBar?.setTitle(titleRes)
|
||||||
// find the view from parent activity
|
// find the view from parent activity
|
||||||
mLoadingView = vectorActivity.findViewById(R.id.vector_settings_spinner_views)
|
mLoadingView = vectorActivity.findViewById(R.id.vector_settings_spinner_views)
|
||||||
|
|
|
@ -40,6 +40,7 @@ class PushGatewaysViewModel @AssistedInject constructor(@Assisted initialState:
|
||||||
|
|
||||||
companion object : MvRxViewModelFactory<PushGatewaysViewModel, PushGatewayViewState> {
|
companion object : MvRxViewModelFactory<PushGatewaysViewModel, PushGatewayViewState> {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
override fun create(viewModelContext: ViewModelContext, state: PushGatewayViewState): PushGatewaysViewModel? {
|
override fun create(viewModelContext: ViewModelContext, state: PushGatewayViewState): PushGatewaysViewModel? {
|
||||||
val fragment: PushGatewaysFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
val fragment: PushGatewaysFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
||||||
return fragment.pushGatewaysViewModelFactory.create(state)
|
return fragment.pushGatewaysViewModelFactory.create(state)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -82,7 +83,9 @@
|
||||||
android:layout_marginEnd="10dp"
|
android:layout_marginEnd="10dp"
|
||||||
android:layout_marginRight="10dp"
|
android:layout_marginRight="10dp"
|
||||||
android:hint="@string/send_bug_report_placeholder"
|
android:hint="@string/send_bug_report_placeholder"
|
||||||
android:textColorHint="?attr/vctr_default_text_hint_color">
|
android:textColorHint="?attr/vctr_default_text_hint_color"
|
||||||
|
app:counterEnabled="true"
|
||||||
|
app:counterMaxLength="500">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/bug_report_edit_text"
|
android:id="@+id/bug_report_edit_text"
|
||||||
|
|
4
vector/src/main/res/layout/item_zero.xml
Normal file
4
vector/src/main/res/layout/item_zero.xml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp" />
|
|
@ -114,7 +114,7 @@
|
||||||
|
|
||||||
<!-- Replaced string is the homeserver url -->
|
<!-- Replaced string is the homeserver url -->
|
||||||
<string name="login_signup_to">Sign up to %1$s</string>
|
<string name="login_signup_to">Sign up to %1$s</string>
|
||||||
<string name="login_signup_username_hint">Username</string>
|
<string name="login_signup_username_hint">Username or email</string>
|
||||||
<string name="login_signup_password_hint">Password</string>
|
<string name="login_signup_password_hint">Password</string>
|
||||||
<string name="login_signup_submit">Next</string>
|
<string name="login_signup_submit">Next</string>
|
||||||
<string name="login_signup_error_user_in_use">That username is taken</string>
|
<string name="login_signup_error_user_in_use">That username is taken</string>
|
||||||
|
@ -162,4 +162,5 @@
|
||||||
<string name="soft_logout_sso_not_same_user_error">The current session is for user %1$s and you provide credentials for user %2$s. This is not supported by RiotX.\nPlease first clear data, then sign in again on another account.</string>
|
<string name="soft_logout_sso_not_same_user_error">The current session is for user %1$s and you provide credentials for user %2$s. This is not supported by RiotX.\nPlease first clear data, then sign in again on another account.</string>
|
||||||
|
|
||||||
<string name="permalink_malformed">Your matrix.to link was malformed</string>
|
<string name="permalink_malformed">Your matrix.to link was malformed</string>
|
||||||
|
<string name="bug_report_error_too_short">The description is too short</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue