mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-23 01:45:52 +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 🙌:
|
||||
- Handle navigation to room via room alias (#201)
|
||||
- Open matrix.to link in RiotX (#57)
|
||||
- Limit sticker size in the timeline
|
||||
|
||||
Other changes:
|
||||
- Use same default room colors than Riot-Web
|
||||
|
@ -14,7 +15,9 @@ Other changes:
|
|||
Bugfix 🐛:
|
||||
- Scroll breadcrumbs to top when opened
|
||||
- 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 🗣:
|
||||
-
|
||||
|
|
|
@ -30,7 +30,7 @@ data class ContentAttachmentData(
|
|||
val exifOrientation: Int = ExifInterface.ORIENTATION_UNDEFINED,
|
||||
val name: String? = null,
|
||||
val path: String,
|
||||
val mimeType: String,
|
||||
val mimeType: String?,
|
||||
val type: Type
|
||||
) : Parcelable {
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ data class VideoInfo(
|
|||
/**
|
||||
* 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.
|
||||
|
|
|
@ -104,7 +104,7 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? {
|
|||
root.getClearContent().toModel<MessageStickerContent>()
|
||||
} else {
|
||||
annotations?.editSummary?.aggregatedContent?.toModel()
|
||||
?: root.getClearContent().toModel()
|
||||
?: root.getClearContent().toModel()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,7 +116,7 @@ fun TimelineEvent.getLastMessageBody(): String? {
|
|||
|
||||
if (lastMessageContent != null) {
|
||||
return lastMessageContent.newContent?.toModel<MessageContent>()?.body
|
||||
?: lastMessageContent.body
|
||||
?: lastMessageContent.body
|
||||
}
|
||||
|
||||
return null
|
||||
|
|
|
@ -49,7 +49,7 @@ object MXEncryptedAttachments {
|
|||
* @param mimetype the mime type
|
||||
* @return the encryption file info
|
||||
*/
|
||||
fun encryptAttachment(attachmentStream: InputStream, mimetype: String): EncryptionResult {
|
||||
fun encryptAttachment(attachmentStream: InputStream, mimetype: String?): EncryptionResult {
|
||||
val t0 = System.currentTimeMillis()
|
||||
val secureRandom = SecureRandom()
|
||||
|
||||
|
|
|
@ -83,15 +83,13 @@ internal class DefaultFileService @Inject constructor(private val context: Conte
|
|||
|
||||
if (elementToDecrypt != null) {
|
||||
Timber.v("## decrypt file")
|
||||
MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt) ?: throw IllegalStateException("Decryption error")
|
||||
} else {
|
||||
inputStream
|
||||
MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt)
|
||||
?: throw IllegalStateException("Decryption error")
|
||||
}
|
||||
|
||||
writeToFile(inputStream, destFile)
|
||||
destFile
|
||||
}
|
||||
.map { inputStream ->
|
||||
writeToFile(inputStream, destFile)
|
||||
destFile
|
||||
}
|
||||
} else {
|
||||
Try.just(destFile)
|
||||
}
|
||||
|
|
|
@ -43,9 +43,9 @@ internal class FileUploader @Inject constructor(@Authenticated
|
|||
|
||||
suspend fun uploadFile(file: File,
|
||||
filename: String?,
|
||||
mimeType: String,
|
||||
mimeType: String?,
|
||||
progressListener: ProgressRequestBody.Listener? = null): ContentUploadResponse {
|
||||
val uploadBody = file.asRequestBody(mimeType.toMediaTypeOrNull())
|
||||
val uploadBody = file.asRequestBody(mimeType?.toMediaTypeOrNull())
|
||||
return upload(uploadBody, filename, progressListener)
|
||||
}
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ internal class DefaultCreateRoomTask @Inject constructor(private val roomAPI: Ro
|
|||
apiCall = roomAPI.createRoom(params)
|
||||
}
|
||||
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 ->
|
||||
realm.where(RoomEntity::class.java)
|
||||
.equalTo(RoomEntityFields.ROOM_ID, roomId)
|
||||
|
|
|
@ -251,7 +251,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
type = MessageType.MSGTYPE_AUDIO,
|
||||
body = attachment.name ?: "audio",
|
||||
audioInfo = AudioInfo(
|
||||
mimeType = attachment.mimeType.takeIf { it.isNotBlank() } ?: "audio/mpeg",
|
||||
mimeType = attachment.mimeType?.takeIf { it.isNotBlank() } ?: "audio/mpeg",
|
||||
size = attachment.size
|
||||
),
|
||||
url = attachment.path
|
||||
|
@ -264,7 +264,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
type = MessageType.MSGTYPE_FILE,
|
||||
body = attachment.name ?: "file",
|
||||
info = FileInfo(
|
||||
mimeType = attachment.mimeType.takeIf { it.isNotBlank() }
|
||||
mimeType = attachment.mimeType?.takeIf { it.isNotBlank() }
|
||||
?: "application/octet-stream",
|
||||
size = attachment.size
|
||||
),
|
||||
|
|
|
@ -293,6 +293,7 @@ dependencies {
|
|||
implementation 'me.gujun.android:span:1.7'
|
||||
implementation "io.noties.markwon:core:$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 'com.google.android:flexbox:1.1.1'
|
||||
implementation "androidx.autofill:autofill:$autofill_version"
|
||||
|
|
|
@ -31,4 +31,8 @@
|
|||
<issue id="ViewConstructor" severity="error" />
|
||||
<issue id="UseValueOf" severity="error" />
|
||||
|
||||
<!-- Ignore error from HtmlCompressor lib -->
|
||||
<issue id="InvalidPackage">
|
||||
<ignore path="**/htmlcompressor-1.4.jar"/>
|
||||
</issue>
|
||||
</lint>
|
||||
|
|
|
@ -359,6 +359,11 @@ SOFTWARE.
|
|||
<br/>
|
||||
Copyright 2018 Kumar Bibek
|
||||
</li>
|
||||
<li>
|
||||
<b>htmlcompressor</b>
|
||||
<br/>
|
||||
Copyright 2017 Sergiy Kovalchuk
|
||||
</li>
|
||||
</ul>
|
||||
<pre>
|
||||
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.group.SelectedGroupDataSource
|
||||
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.notifications.*
|
||||
import im.vector.riotx.features.rageshake.BugReporter
|
||||
|
@ -87,6 +88,8 @@ interface VectorComponent {
|
|||
|
||||
fun eventHtmlRenderer(): EventHtmlRenderer
|
||||
|
||||
fun vectorHtmlCompressor(): VectorHtmlCompressor
|
||||
|
||||
fun navigator(): Navigator
|
||||
|
||||
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() {
|
||||
super.onResume()
|
||||
|
||||
Timber.v("onResume Activity ${this.javaClass.simpleName}")
|
||||
Timber.i("onResume Activity ${this.javaClass.simpleName}")
|
||||
|
||||
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.ScreenComponent
|
||||
import im.vector.riotx.core.utils.DimensionConverter
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Add MvRx capabilities to bottomsheetdialog (like BaseMvRxFragment)
|
||||
|
@ -80,6 +81,11 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment()
|
|||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
Timber.i("onResume BottomSheet ${this.javaClass.simpleName}")
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return super.onCreateDialog(savedInstanceState).apply {
|
||||
val dialog = this as? BottomSheetDialog
|
||||
|
|
|
@ -104,7 +104,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
|
|||
@CallSuper
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
Timber.v("onResume Fragment ${this.javaClass.simpleName}")
|
||||
Timber.i("onResume Fragment ${this.javaClass.simpleName}")
|
||||
}
|
||||
|
||||
@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 im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
import timber.log.Timber
|
||||
|
||||
fun ChosenContact.toContactAttachment(): ContactAttachment {
|
||||
return ContactAttachment(
|
||||
|
@ -29,6 +30,7 @@ fun ChosenContact.toContactAttachment(): ContactAttachment {
|
|||
}
|
||||
|
||||
fun ChosenFile.toContentAttachmentData(): ContentAttachmentData {
|
||||
if (mimeType == null) Timber.w("No mimeType")
|
||||
return ContentAttachmentData(
|
||||
path = originalPath,
|
||||
mimeType = mimeType,
|
||||
|
@ -40,6 +42,7 @@ fun ChosenFile.toContentAttachmentData(): ContentAttachmentData {
|
|||
}
|
||||
|
||||
fun ChosenAudio.toContentAttachmentData(): ContentAttachmentData {
|
||||
if (mimeType == null) Timber.w("No mimeType")
|
||||
return ContentAttachmentData(
|
||||
path = originalPath,
|
||||
mimeType = mimeType,
|
||||
|
@ -51,16 +54,17 @@ fun ChosenAudio.toContentAttachmentData(): ContentAttachmentData {
|
|||
)
|
||||
}
|
||||
|
||||
fun ChosenFile.mapType(): ContentAttachmentData.Type {
|
||||
private fun ChosenFile.mapType(): ContentAttachmentData.Type {
|
||||
return when {
|
||||
mimeType.startsWith("image/") -> ContentAttachmentData.Type.IMAGE
|
||||
mimeType.startsWith("video/") -> ContentAttachmentData.Type.VIDEO
|
||||
mimeType.startsWith("audio/") -> ContentAttachmentData.Type.AUDIO
|
||||
else -> ContentAttachmentData.Type.FILE
|
||||
mimeType?.startsWith("image/") == true -> ContentAttachmentData.Type.IMAGE
|
||||
mimeType?.startsWith("video/") == true -> ContentAttachmentData.Type.VIDEO
|
||||
mimeType?.startsWith("audio/") == true -> ContentAttachmentData.Type.AUDIO
|
||||
else -> ContentAttachmentData.Type.FILE
|
||||
}
|
||||
}
|
||||
|
||||
fun ChosenImage.toContentAttachmentData(): ContentAttachmentData {
|
||||
if (mimeType == null) Timber.w("No mimeType")
|
||||
return ContentAttachmentData(
|
||||
path = originalPath,
|
||||
mimeType = mimeType,
|
||||
|
@ -75,6 +79,7 @@ fun ChosenImage.toContentAttachmentData(): ContentAttachmentData {
|
|||
}
|
||||
|
||||
fun ChosenVideo.toContentAttachmentData(): ContentAttachmentData {
|
||||
if (mimeType == null) Timber.w("No mimeType")
|
||||
return ContentAttachmentData(
|
||||
path = originalPath,
|
||||
mimeType = mimeType,
|
||||
|
|
|
@ -68,14 +68,14 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro
|
|||
}
|
||||
|
||||
private fun observeSelectionState() {
|
||||
selectSubscribe(GroupListViewState::selectedGroup) {
|
||||
if (it != null) {
|
||||
selectSubscribe(GroupListViewState::selectedGroup) { groupSummary ->
|
||||
if (groupSummary != null) {
|
||||
val selectedGroup = _openGroupLiveData.value?.peekContent()
|
||||
// We only wan to open group if the updated selectedGroup is a different one.
|
||||
if (selectedGroup?.groupId != it.groupId) {
|
||||
_openGroupLiveData.postLiveEvent(it)
|
||||
// We only want to open group if the updated selectedGroup is a different one.
|
||||
if (selectedGroup?.groupId != groupSummary.groupId) {
|
||||
_openGroupLiveData.postLiveEvent(groupSummary)
|
||||
}
|
||||
val optionGroup = Option.fromNullable(it)
|
||||
val optionGroup = Option.just(groupSummary)
|
||||
selectedGroupStore.post(optionGroup)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.riotx.features.home.room.breadcrumbs
|
|||
import android.view.View
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
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.features.home.AvatarRenderer
|
||||
import javax.inject.Inject
|
||||
|
@ -45,9 +46,13 @@ class BreadcrumbsController @Inject constructor(
|
|||
override fun buildModels() {
|
||||
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,
|
||||
// this one is added to the breadcrumbs
|
||||
|
||||
safeViewState.asyncBreadcrumbs.invoke()
|
||||
?.forEach {
|
||||
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.command.Command
|
||||
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.room.detail.composer.TextComposerAction
|
||||
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.VideoMediaViewerActivity
|
||||
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.settings.VectorPreferences
|
||||
import im.vector.riotx.features.share.SharedData
|
||||
|
@ -416,8 +416,10 @@ class RoomDetailFragment @Inject constructor(
|
|||
composerLayout.composerRelatedMessageAvatar
|
||||
)
|
||||
composerLayout.expand {
|
||||
// need to do it here also when not using quick reply
|
||||
focusComposerAndShowKeyboard()
|
||||
if (isAdded) {
|
||||
// need to do it here also when not using quick reply
|
||||
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.item.MessageInformationData
|
||||
import im.vector.riotx.features.html.EventHtmlRenderer
|
||||
import im.vector.riotx.features.html.VectorHtmlCompressor
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
|
@ -82,6 +83,7 @@ data class MessageActionState(
|
|||
class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||
initialState: MessageActionState,
|
||||
private val eventHtmlRenderer: Lazy<EventHtmlRenderer>,
|
||||
private val htmlCompressor: VectorHtmlCompressor,
|
||||
private val session: Session,
|
||||
private val noticeEventFormatter: NoticeEventFormatter,
|
||||
private val stringProvider: StringProvider
|
||||
|
@ -100,6 +102,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
|||
|
||||
val quickEmojis = listOf("👍", "👎", "😄", "🎉", "😕", "❤️", "🚀", "👀")
|
||||
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: MessageActionState): MessageActionsViewModel? {
|
||||
val fragment: MessageActionsBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
|
||||
return fragment.messageActionViewModelFactory.create(state)
|
||||
|
@ -167,11 +170,16 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
|||
|
||||
private fun computeMessageBody(timelineEvent: Async<TimelineEvent>): CharSequence? {
|
||||
return when (timelineEvent()?.root?.getClearType()) {
|
||||
EventType.MESSAGE -> {
|
||||
EventType.MESSAGE,
|
||||
EventType.STICKER -> {
|
||||
val messageContent: MessageContent? = timelineEvent()?.getLastMessageContent()
|
||||
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
|
||||
eventHtmlRenderer.get().render(messageContent.formattedBody
|
||||
?: messageContent.body)
|
||||
val html = messageContent.formattedBody
|
||||
?.takeIf { it.isNotBlank() }
|
||||
?.let { htmlCompressor.compress(it) }
|
||||
?: messageContent.body
|
||||
|
||||
eventHtmlRenderer.get().render(html)
|
||||
} else {
|
||||
messageContent?.body
|
||||
}
|
||||
|
|
|
@ -61,6 +61,7 @@ class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted
|
|||
|
||||
companion object : MvRxViewModelFactory<ViewEditHistoryViewModel, ViewEditHistoryViewState> {
|
||||
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: ViewEditHistoryViewState): ViewEditHistoryViewModel? {
|
||||
val fragment: ViewEditHistoryBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
|
||||
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.html.CodeVisitor
|
||||
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.VideoContentRenderer
|
||||
import me.gujun.android.span.span
|
||||
|
@ -57,6 +58,7 @@ class MessageItemFactory @Inject constructor(
|
|||
private val dimensionConverter: DimensionConverter,
|
||||
private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
|
||||
private val htmlRenderer: Lazy<EventHtmlRenderer>,
|
||||
private val htmlCompressor: VectorHtmlCompressor,
|
||||
private val stringProvider: StringProvider,
|
||||
private val imageContentRenderer: ImageContentRenderer,
|
||||
private val messageInformationDataFactory: MessageInformationDataFactory,
|
||||
|
@ -179,10 +181,16 @@ class MessageItemFactory @Inject constructor(
|
|||
.playable(messageContent.info?.mimeType == "image/gif")
|
||||
.highlighted(highlight)
|
||||
.mediaData(data)
|
||||
.clickListener(
|
||||
DebouncedClickListener(View.OnClickListener { view ->
|
||||
callback?.onImageMessageClicked(messageContent, data, view)
|
||||
}))
|
||||
.apply {
|
||||
if (messageContent.type == MessageType.MSGTYPE_STICKER_LOCAL) {
|
||||
mode(ImageContentRenderer.Mode.STICKER)
|
||||
} else {
|
||||
clickListener(
|
||||
DebouncedClickListener(View.OnClickListener { view ->
|
||||
callback?.onImageMessageClicked(messageContent, data, view)
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildVideoMessageItem(messageContent: MessageVideoContent,
|
||||
|
@ -227,6 +235,7 @@ class MessageItemFactory @Inject constructor(
|
|||
attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? {
|
||||
val isFormatted = messageContent.formattedBody.isNullOrBlank().not()
|
||||
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 codeVisitor = CodeVisitor()
|
||||
codeVisitor.visit(localFormattedBody)
|
||||
|
@ -240,7 +249,8 @@ class MessageItemFactory @Inject constructor(
|
|||
buildMessageTextItem(codeFormatted, false, informationData, highlight, callback, attributes)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,8 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
|
|||
@EpoxyAttribute
|
||||
var playable: Boolean = false
|
||||
@EpoxyAttribute
|
||||
var mode = ImageContentRenderer.Mode.THUMBNAIL
|
||||
@EpoxyAttribute
|
||||
var clickListener: View.OnClickListener? = null
|
||||
@EpoxyAttribute
|
||||
lateinit var imageContentRenderer: ImageContentRenderer
|
||||
|
@ -44,7 +46,7 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
|
|||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
imageContentRenderer.render(mediaData, ImageContentRenderer.Mode.THUMBNAIL, holder.imageView)
|
||||
imageContentRenderer.render(mediaData, mode, holder.imageView)
|
||||
if (!attributes.informationData.sendState.hasFailed()) {
|
||||
contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, mediaData.isLocalFile(), holder.progressLayout)
|
||||
} else {
|
||||
|
|
|
@ -68,6 +68,7 @@ class ViewReactionsViewModel @AssistedInject constructor(@Assisted
|
|||
|
||||
companion object : MvRxViewModelFactory<ViewReactionsViewModel, DisplayReactionsViewState> {
|
||||
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: DisplayReactionsViewState): ViewReactionsViewModel? {
|
||||
val fragment: ViewReactionsBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
|
||||
return fragment.viewReactionsViewModelFactory.create(state)
|
||||
|
|
|
@ -37,6 +37,7 @@ class RoomListQuickActionsViewModel @AssistedInject constructor(@Assisted initia
|
|||
|
||||
companion object : MvRxViewModelFactory<RoomListQuickActionsViewModel, RoomListQuickActionsState> {
|
||||
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: RoomListQuickActionsState): RoomListQuickActionsViewModel? {
|
||||
val fragment: RoomListQuickActionsBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
|
||||
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.glide.GlideApp
|
||||
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.isLocalFile
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.min
|
||||
|
||||
class ImageContentRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
|
||||
private val dimensionConverter: DimensionConverter) {
|
||||
|
@ -56,17 +58,18 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
|
|||
|
||||
enum class Mode {
|
||||
FULL_SIZE,
|
||||
THUMBNAIL
|
||||
THUMBNAIL,
|
||||
STICKER
|
||||
}
|
||||
|
||||
fun render(data: Data, mode: Mode, imageView: ImageView) {
|
||||
val (width, height) = processSize(data, mode)
|
||||
imageView.layoutParams.height = height
|
||||
imageView.layoutParams.width = width
|
||||
val size = processSize(data, mode)
|
||||
imageView.layoutParams.width = size.width
|
||||
imageView.layoutParams.height = size.height
|
||||
// a11y
|
||||
imageView.contentDescription = data.filename
|
||||
|
||||
createGlideRequest(data, mode, imageView, width, height)
|
||||
createGlideRequest(data, mode, imageView, size)
|
||||
.dontAnimate()
|
||||
.transform(RoundedCorners(dimensionConverter.dpToPx(8)))
|
||||
.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) {
|
||||
val (width, height) = processSize(data, mode)
|
||||
val size = processSize(data, mode)
|
||||
|
||||
// a11y
|
||||
imageView.contentDescription = data.filename
|
||||
|
||||
createGlideRequest(data, mode, imageView, width, height)
|
||||
createGlideRequest(data, mode, imageView, size)
|
||||
.listener(object : RequestListener<Drawable> {
|
||||
override fun onLoadFailed(e: GlideException?,
|
||||
model: Any?,
|
||||
|
@ -102,7 +105,7 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
|
|||
.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) {
|
||||
// Encrypted image
|
||||
GlideApp
|
||||
|
@ -112,8 +115,9 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
|
|||
// Clear image
|
||||
val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
|
||||
val resolvedUrl = when (mode) {
|
||||
Mode.FULL_SIZE -> contentUrlResolver.resolveFullSize(data.url)
|
||||
Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
|
||||
Mode.FULL_SIZE,
|
||||
Mode.STICKER -> contentUrlResolver.resolveFullSize(data.url)
|
||||
Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, size.width, size.height, ContentUrlResolver.ThumbnailMethod.SCALE)
|
||||
}
|
||||
// Fallback to base 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 maxImageHeight = data.maxHeight
|
||||
val width = data.width ?: maxImageWidth
|
||||
val height = data.height ?: maxImageHeight
|
||||
var finalHeight = -1
|
||||
var finalWidth = -1
|
||||
var finalHeight = -1
|
||||
|
||||
// if the image size is known
|
||||
// compute the expected height
|
||||
if (width > 0 && height > 0) {
|
||||
if (mode == Mode.FULL_SIZE) {
|
||||
finalHeight = height
|
||||
finalWidth = width
|
||||
} else {
|
||||
finalHeight = Math.min(maxImageWidth * height / width, maxImageHeight)
|
||||
finalWidth = finalHeight * width / height
|
||||
when (mode) {
|
||||
Mode.FULL_SIZE -> {
|
||||
finalHeight = height
|
||||
finalWidth = width
|
||||
}
|
||||
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
|
||||
|
@ -170,6 +183,6 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
|
|||
if (finalWidth < 0) {
|
||||
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.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.app.TaskStackBuilder
|
||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
||||
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.utils.toast
|
||||
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 javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import androidx.core.app.TaskStackBuilder
|
||||
|
||||
@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) {
|
||||
if (sessionHolder.getSafeActiveSession()?.getRoom(roomId) == null) {
|
||||
fatalError("Trying to open an unknown room $roomId")
|
||||
return
|
||||
}
|
||||
|
||||
val args = RoomDetailArgs(roomId, eventId)
|
||||
val intent = RoomDetailActivity.newIntent(context, args)
|
||||
if (buildTask) {
|
||||
|
|
|
@ -57,7 +57,7 @@ class PermalinkHandler @Inject constructor(private val session: Session,
|
|||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.map {
|
||||
val roomId = it.getOrNull()
|
||||
if (navigateToRoomInterceptor?.navToRoom(roomId) != true) {
|
||||
if (navigateToRoomInterceptor?.navToRoom(roomId, permalinkData.eventId) != true) {
|
||||
openRoom(context, roomId, permalinkData.eventId, buildTask)
|
||||
}
|
||||
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) {
|
||||
navigator.openRoom(context, roomId, eventId, buildTask)
|
||||
} else {
|
||||
|
|
|
@ -27,7 +27,6 @@ import im.vector.riotx.core.utils.toast
|
|||
import im.vector.riotx.features.home.LoadingFragment
|
||||
import im.vector.riotx.features.login.LoginActivity
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import kotlinx.android.synthetic.debug.activity_test_material_theme.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
|
@ -77,8 +77,7 @@ class BugReportActivity : VectorBaseActivity() {
|
|||
|
||||
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
||||
menu.findItem(R.id.ic_action_send_bug_report)?.let {
|
||||
val isValid = bug_report_edit_text.text.toString().trim().length > 10
|
||||
&& !bug_report_mask_view.isVisible
|
||||
val isValid = !bug_report_mask_view.isVisible
|
||||
|
||||
it.isEnabled = isValid
|
||||
it.icon.alpha = if (isValid) 255 else 100
|
||||
|
@ -90,7 +89,11 @@ class BugReportActivity : VectorBaseActivity() {
|
|||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -150,7 +153,7 @@ class BugReportActivity : VectorBaseActivity() {
|
|||
val myProgress = progress.coerceIn(0, 100)
|
||||
|
||||
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() {
|
||||
|
@ -179,7 +182,7 @@ class BugReportActivity : VectorBaseActivity() {
|
|||
|
||||
@OnTextChanged(R.id.bug_report_edit_text)
|
||||
internal fun textChanged() {
|
||||
invalidateOptionsMenu()
|
||||
bug_report_text_input_layout.error = null
|
||||
}
|
||||
|
||||
@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.utils.getDeviceLocale
|
||||
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.version.VersionProvider
|
||||
import okhttp3.Call
|
||||
|
@ -44,12 +45,15 @@ import okhttp3.Response
|
|||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
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.util.Locale
|
||||
import java.util.*
|
||||
import java.util.zip.GZIPOutputStream
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
/**
|
||||
* BugReporter creates and sends the bug reports.
|
||||
|
@ -57,6 +61,7 @@ import javax.inject.Singleton
|
|||
@Singleton
|
||||
class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
|
||||
private val versionProvider: VersionProvider,
|
||||
private val vectorPreferences: VectorPreferences,
|
||||
private val vectorFileLogger: VectorFileLogger) {
|
||||
var inMultiWindowMode = false
|
||||
|
||||
|
@ -230,7 +235,7 @@ class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSes
|
|||
.addFormDataPart("matrix_sdk_version", Matrix.getSdkVersion())
|
||||
.addFormDataPart("olm_version", olmVersion)
|
||||
.addFormDataPart("device", Build.MODEL.trim())
|
||||
.addFormDataPart("lazy_loading", true.toOnOff())
|
||||
.addFormDataPart("verbose_log", vectorPreferences.labAllowedExtendedLogging().toOnOff())
|
||||
.addFormDataPart("multi_window", inMultiWindowMode.toOnOff())
|
||||
.addFormDataPart("os", Build.VERSION.RELEASE + " (API " + Build.VERSION.SDK_INT + ") "
|
||||
+ Build.VERSION.INCREMENTAL + "-" + Build.VERSION.CODENAME)
|
||||
|
|
|
@ -24,9 +24,7 @@ import java.io.File
|
|||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
import java.util.*
|
||||
import java.util.logging.*
|
||||
import java.util.logging.Formatter
|
||||
import javax.inject.Inject
|
||||
|
@ -83,7 +81,8 @@ class VectorFileLogger @Inject constructor(val context: Context, private val vec
|
|||
return if (vectorPreferences.labAllowedExtendedLogging()) {
|
||||
false
|
||||
} 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> {
|
||||
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: EmojiSearchResultViewState): EmojiSearchResultViewModel? {
|
||||
val activity: EmojiReactionPickerActivity = (viewModelContext as ActivityViewModelContext).activity()
|
||||
return activity.emojiSearchResultViewModelFactory.create(state)
|
||||
|
|
|
@ -178,7 +178,9 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
|
|||
copy(
|
||||
asyncPublicRoomsRequest = Success(data.chunk!!),
|
||||
// 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
|
||||
)
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), HasScree
|
|||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
Timber.v("onResume Fragment ${this.javaClass.simpleName}")
|
||||
Timber.i("onResume Fragment ${this.javaClass.simpleName}")
|
||||
vectorActivity.supportActionBar?.setTitle(titleRes)
|
||||
// find the view from parent activity
|
||||
mLoadingView = vectorActivity.findViewById(R.id.vector_settings_spinner_views)
|
||||
|
|
|
@ -40,6 +40,7 @@ class PushGatewaysViewModel @AssistedInject constructor(@Assisted initialState:
|
|||
|
||||
companion object : MvRxViewModelFactory<PushGatewaysViewModel, PushGatewayViewState> {
|
||||
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: PushGatewayViewState): PushGatewaysViewModel? {
|
||||
val fragment: PushGatewaysFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
||||
return fragment.pushGatewaysViewModelFactory.create(state)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -82,7 +83,9 @@
|
|||
android:layout_marginEnd="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
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
|
||||
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 -->
|
||||
<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_submit">Next</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="permalink_malformed">Your matrix.to link was malformed</string>
|
||||
<string name="bug_report_error_too_short">The description is too short</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue