Merge pull request #779 from vector-im/feature/fix_some_crashes

Fix some crashes and issues
This commit is contained in:
Benoit Marty 2019-12-19 14:02:19 +01:00 committed by GitHub
commit 0d36e9d8a6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 302 additions and 87 deletions

View file

@ -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 🗣:
- -

View file

@ -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 {

View file

@ -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.

View file

@ -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

View file

@ -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()

View file

@ -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)
} }

View file

@ -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)
} }

View file

@ -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)

View file

@ -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
), ),

View file

@ -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"

View file

@ -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>

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,30 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.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()
}

View 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)
}
}

View file

@ -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()

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,20 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.core.ui.model
// android.util.Size in API 21+
data class Size(val width: Int, val height: Int)

View file

@ -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,

View file

@ -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)
} }
} }

View file

@ -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 {

View file

@ -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()
} }

View file

@ -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
} }

View file

@ -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)

View file

@ -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)
} }
} }

View file

@ -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 {

View file

@ -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)

View file

@ -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)

View file

@ -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
}
}

View file

@ -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)
} }
} }

View file

@ -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) {

View file

@ -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 {

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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
} }
} }

View file

@ -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)

View file

@ -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
) )
} }

View file

@ -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)

View file

@ -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)

View file

@ -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"

View 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" />

View file

@ -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>