mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-16 12:00:03 +03:00
Merge branch 'feature/html_rendering' into develop
This commit is contained in:
commit
753e70775a
38 changed files with 801 additions and 77 deletions
|
@ -60,12 +60,16 @@ dependencies {
|
||||||
|
|
||||||
def epoxy_version = "3.0.0"
|
def epoxy_version = "3.0.0"
|
||||||
def arrow_version = "0.8.2"
|
def arrow_version = "0.8.2"
|
||||||
|
def coroutines_version = "1.0.1"
|
||||||
|
def markwon_version = '3.0.0-SNAPSHOT'
|
||||||
|
|
||||||
implementation project(":matrix-sdk-android")
|
implementation project(":matrix-sdk-android")
|
||||||
implementation project(":matrix-sdk-android-rx")
|
implementation project(":matrix-sdk-android-rx")
|
||||||
implementation 'com.android.support:multidex:1.0.3'
|
implementation 'com.android.support:multidex:1.0.3'
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||||
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.1.0-alpha01'
|
implementation 'androidx.appcompat:appcompat:1.1.0-alpha01'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
|
@ -97,6 +101,9 @@ dependencies {
|
||||||
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
||||||
implementation 'com.google.android.material:material:1.1.0-alpha02'
|
implementation 'com.google.android.material:material:1.1.0-alpha02'
|
||||||
implementation 'me.gujun.android:span:1.7'
|
implementation 'me.gujun.android:span:1.7'
|
||||||
|
implementation "ru.noties.markwon:core:$markwon_version"
|
||||||
|
implementation "ru.noties.markwon:html:$markwon_version"
|
||||||
|
|
||||||
|
|
||||||
// DI
|
// DI
|
||||||
implementation "org.koin:koin-android:$koin_version"
|
implementation "org.koin:koin-android:$koin_version"
|
||||||
|
|
|
@ -29,4 +29,8 @@ fun AppCompatActivity.replaceFragment(fragment: Fragment, frameId: Int) {
|
||||||
|
|
||||||
fun AppCompatActivity.addFragmentToBackstack(fragment: Fragment, frameId: Int, tag: String? = null) {
|
fun AppCompatActivity.addFragmentToBackstack(fragment: Fragment, frameId: Int, tag: String? = null) {
|
||||||
supportFragmentManager.inTransaction { replace(frameId, fragment).addToBackStack(tag) }
|
supportFragmentManager.inTransaction { replace(frameId, fragment).addToBackStack(tag) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun AppCompatActivity.hideKeyboard() {
|
||||||
|
currentFocus?.hideKeyboard()
|
||||||
}
|
}
|
|
@ -17,12 +17,9 @@
|
||||||
package im.vector.riotredesign.core.extensions
|
package im.vector.riotredesign.core.extensions
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import org.threeten.bp.Instant
|
import im.vector.riotredesign.core.resources.DateProvider
|
||||||
import org.threeten.bp.LocalDateTime
|
import org.threeten.bp.LocalDateTime
|
||||||
import org.threeten.bp.ZoneId
|
|
||||||
|
|
||||||
|
|
||||||
fun Event.localDateTime(): LocalDateTime {
|
fun Event.localDateTime(): LocalDateTime {
|
||||||
val instant = Instant.ofEpochMilli(originServerTs ?: 0)
|
return DateProvider.toLocalDateTime(originServerTs)
|
||||||
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault())
|
|
||||||
}
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotredesign.core.resources
|
||||||
|
|
||||||
|
import org.threeten.bp.Instant
|
||||||
|
import org.threeten.bp.LocalDateTime
|
||||||
|
import org.threeten.bp.ZoneId
|
||||||
|
|
||||||
|
object DateProvider {
|
||||||
|
|
||||||
|
private val zoneId = ZoneId.systemDefault()
|
||||||
|
|
||||||
|
fun toLocalDateTime(timestamp: Long?): LocalDateTime {
|
||||||
|
val instant = Instant.ofEpochMilli(timestamp ?: 0)
|
||||||
|
return LocalDateTime.ofInstant(instant, zoneId)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -16,16 +16,21 @@
|
||||||
|
|
||||||
package im.vector.riotredesign.features.home
|
package im.vector.riotredesign.features.home
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.amulyakhare.textdrawable.TextDrawable
|
import com.amulyakhare.textdrawable.TextDrawable
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import im.vector.matrix.android.api.Matrix
|
import im.vector.matrix.android.api.Matrix
|
||||||
|
import im.vector.matrix.android.api.MatrixPatterns
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
import im.vector.riotredesign.core.extensions.firstCharAsString
|
|
||||||
import im.vector.riotredesign.core.glide.GlideApp
|
import im.vector.riotredesign.core.glide.GlideApp
|
||||||
|
import im.vector.riotredesign.core.glide.GlideRequest
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
object AvatarRenderer {
|
object AvatarRenderer {
|
||||||
|
|
||||||
|
@ -41,17 +46,47 @@ object AvatarRenderer {
|
||||||
if (name.isNullOrEmpty()) {
|
if (name.isNullOrEmpty()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val resolvedUrl = Matrix.getInstance().currentSession.contentUrlResolver().resolveFullSize(avatarUrl)
|
val placeholder = buildPlaceholderDrawable(imageView.context, name)
|
||||||
val avatarColor = ContextCompat.getColor(imageView.context, R.color.pale_teal)
|
buildGlideRequest(imageView.context, avatarUrl)
|
||||||
val fallbackDrawable = TextDrawable.builder().buildRound(name.firstCharAsString().toUpperCase(), avatarColor)
|
.placeholder(placeholder)
|
||||||
|
|
||||||
GlideApp
|
|
||||||
.with(imageView)
|
|
||||||
.load(resolvedUrl)
|
|
||||||
.placeholder(fallbackDrawable)
|
|
||||||
.apply(RequestOptions.circleCropTransform())
|
|
||||||
.into(imageView)
|
.into(imageView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun load(context: Context, avatarUrl: String?, name: String?, size: Int, callback: Callback) {
|
||||||
|
if (name.isNullOrEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val request = buildGlideRequest(context, avatarUrl)
|
||||||
|
GlobalScope.launch {
|
||||||
|
val placeholder = buildPlaceholderDrawable(context, name)
|
||||||
|
callback.onDrawableUpdated(placeholder)
|
||||||
|
try {
|
||||||
|
val drawable = request.submit(size, size).get()
|
||||||
|
callback.onDrawableUpdated(drawable)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
callback.onDrawableUpdated(placeholder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildGlideRequest(context: Context, avatarUrl: String?): GlideRequest<Drawable> {
|
||||||
|
val resolvedUrl = Matrix.getInstance().currentSession.contentUrlResolver().resolveFullSize(avatarUrl)
|
||||||
|
return GlideApp
|
||||||
|
.with(context)
|
||||||
|
.load(resolvedUrl)
|
||||||
|
.apply(RequestOptions.circleCropTransform())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildPlaceholderDrawable(context: Context, name: String): Drawable {
|
||||||
|
val avatarColor = ContextCompat.getColor(context, R.color.pale_teal)
|
||||||
|
val isNameUserId = MatrixPatterns.isUserId(name)
|
||||||
|
val firstLetterIndex = if (isNameUserId) 1 else 0
|
||||||
|
val firstLetter = name[firstLetterIndex].toString().toUpperCase()
|
||||||
|
return TextDrawable.builder().buildRound(firstLetter, avatarColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
fun onDrawableUpdated(drawable: Drawable?)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -24,9 +24,11 @@ import android.view.MenuItem
|
||||||
import androidx.appcompat.app.ActionBarDrawerToggle
|
import androidx.appcompat.app.ActionBarDrawerToggle
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.core.view.GravityCompat
|
import androidx.core.view.GravityCompat
|
||||||
|
import androidx.drawerlayout.widget.DrawerLayout
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import com.airbnb.mvrx.viewModel
|
import com.airbnb.mvrx.viewModel
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
|
import im.vector.riotredesign.core.extensions.hideKeyboard
|
||||||
import im.vector.riotredesign.core.extensions.observeEvent
|
import im.vector.riotredesign.core.extensions.observeEvent
|
||||||
import im.vector.riotredesign.core.extensions.replaceFragment
|
import im.vector.riotredesign.core.extensions.replaceFragment
|
||||||
import im.vector.riotredesign.core.platform.OnBackPressed
|
import im.vector.riotredesign.core.platform.OnBackPressed
|
||||||
|
@ -44,11 +46,18 @@ class HomeActivity : RiotActivity(), ToolbarConfigurable {
|
||||||
private val homeActivityViewModel: HomeActivityViewModel by viewModel()
|
private val homeActivityViewModel: HomeActivityViewModel by viewModel()
|
||||||
private val homeNavigator by inject<HomeNavigator>()
|
private val homeNavigator by inject<HomeNavigator>()
|
||||||
|
|
||||||
|
private val drawerListener = object : DrawerLayout.SimpleDrawerListener() {
|
||||||
|
override fun onDrawerStateChanged(newState: Int) {
|
||||||
|
hideKeyboard()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
loadKoinModules(listOf(HomeModule().definition))
|
loadKoinModules(listOf(HomeModule(this).definition))
|
||||||
homeNavigator.activity = this
|
homeNavigator.activity = this
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_home)
|
setContentView(R.layout.activity_home)
|
||||||
|
drawerLayout.addDrawerListener(drawerListener)
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
val homeDrawerFragment = HomeDrawerFragment.newInstance()
|
val homeDrawerFragment = HomeDrawerFragment.newInstance()
|
||||||
val loadingDetail = LoadingRoomDetailFragment.newInstance()
|
val loadingDetail = LoadingRoomDetailFragment.newInstance()
|
||||||
|
@ -61,6 +70,7 @@ class HomeActivity : RiotActivity(), ToolbarConfigurable {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
drawerLayout.removeDrawerListener(drawerListener)
|
||||||
homeNavigator.activity = null
|
homeNavigator.activity = null
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,24 +16,43 @@
|
||||||
|
|
||||||
package im.vector.riotredesign.features.home
|
package im.vector.riotredesign.features.home
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.Matrix
|
||||||
import im.vector.riotredesign.features.home.group.SelectedGroupStore
|
import im.vector.riotredesign.features.home.group.SelectedGroupStore
|
||||||
import im.vector.riotredesign.features.home.room.VisibleRoomStore
|
import im.vector.riotredesign.features.home.room.VisibleRoomStore
|
||||||
import im.vector.riotredesign.features.home.room.detail.timeline.*
|
import im.vector.riotredesign.features.home.room.detail.timeline.CallItemFactory
|
||||||
|
import im.vector.riotredesign.features.home.room.detail.timeline.DefaultItemFactory
|
||||||
|
import im.vector.riotredesign.features.home.room.detail.timeline.MessageItemFactory
|
||||||
|
import im.vector.riotredesign.features.home.room.detail.timeline.RoomHistoryVisibilityItemFactory
|
||||||
|
import im.vector.riotredesign.features.home.room.detail.timeline.RoomMemberItemFactory
|
||||||
|
import im.vector.riotredesign.features.home.room.detail.timeline.RoomNameItemFactory
|
||||||
|
import im.vector.riotredesign.features.home.room.detail.timeline.RoomTopicItemFactory
|
||||||
|
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineDateFormatter
|
||||||
|
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
|
||||||
|
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineItemFactory
|
||||||
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
|
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
|
||||||
import im.vector.riotredesign.features.home.room.list.RoomSummaryComparator
|
import im.vector.riotredesign.features.home.room.list.RoomSummaryComparator
|
||||||
import im.vector.riotredesign.features.home.room.list.RoomSummaryController
|
import im.vector.riotredesign.features.home.room.list.RoomSummaryController
|
||||||
|
import im.vector.riotredesign.features.html.EventHtmlRenderer
|
||||||
import org.koin.dsl.module.module
|
import org.koin.dsl.module.module
|
||||||
|
|
||||||
class HomeModule {
|
class HomeModule(homeActivity: HomeActivity) {
|
||||||
|
|
||||||
val definition = module(override = true) {
|
val definition = module(override = true) {
|
||||||
|
|
||||||
|
single {
|
||||||
|
Matrix.getInstance().currentSession
|
||||||
|
}
|
||||||
|
|
||||||
single {
|
single {
|
||||||
TimelineDateFormatter(get())
|
TimelineDateFormatter(get())
|
||||||
}
|
}
|
||||||
|
|
||||||
single {
|
single {
|
||||||
MessageItemFactory(get(), get(), get())
|
EventHtmlRenderer(homeActivity, get())
|
||||||
|
}
|
||||||
|
|
||||||
|
single {
|
||||||
|
MessageItemFactory(get(), get(), get(), get())
|
||||||
}
|
}
|
||||||
|
|
||||||
single {
|
single {
|
||||||
|
|
|
@ -30,13 +30,13 @@ class HomeNavigator {
|
||||||
|
|
||||||
var activity: HomeActivity? = null
|
var activity: HomeActivity? = null
|
||||||
|
|
||||||
private var currentRoomId: String? = null
|
private var rootRoomId: String? = null
|
||||||
|
|
||||||
fun openRoomDetail(roomId: String,
|
fun openRoomDetail(roomId: String,
|
||||||
eventId: String?,
|
eventId: String?,
|
||||||
addToBackstack: Boolean = false) {
|
addToBackstack: Boolean = false) {
|
||||||
Timber.v("Open room detail $roomId - $eventId - $addToBackstack")
|
Timber.v("Open room detail $roomId - $eventId - $addToBackstack")
|
||||||
if (!addToBackstack && isRoomOpened(roomId)) {
|
if (!addToBackstack && isRoot(roomId)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
activity?.let {
|
activity?.let {
|
||||||
|
@ -46,7 +46,7 @@ class HomeNavigator {
|
||||||
if (addToBackstack) {
|
if (addToBackstack) {
|
||||||
it.addFragmentToBackstack(roomDetailFragment, R.id.homeDetailFragmentContainer, roomId)
|
it.addFragmentToBackstack(roomDetailFragment, R.id.homeDetailFragmentContainer, roomId)
|
||||||
} else {
|
} else {
|
||||||
currentRoomId = roomId
|
rootRoomId = roomId
|
||||||
clearBackStack(it.supportFragmentManager)
|
clearBackStack(it.supportFragmentManager)
|
||||||
it.replaceFragment(roomDetailFragment, R.id.homeDetailFragmentContainer)
|
it.replaceFragment(roomDetailFragment, R.id.homeDetailFragmentContainer)
|
||||||
}
|
}
|
||||||
|
@ -61,9 +61,7 @@ class HomeNavigator {
|
||||||
Timber.v("Open user detail $userId")
|
Timber.v("Open user detail $userId")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isRoomOpened(roomId: String): Boolean {
|
// Private Methods *****************************************************************************
|
||||||
return currentRoomId == roomId
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun clearBackStack(fragmentManager: FragmentManager) {
|
private fun clearBackStack(fragmentManager: FragmentManager) {
|
||||||
if (fragmentManager.backStackEntryCount > 0) {
|
if (fragmentManager.backStackEntryCount > 0) {
|
||||||
|
@ -72,4 +70,8 @@ class HomeNavigator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isRoot(roomId: String): Boolean {
|
||||||
|
return rootRoomId == roomId
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -29,12 +29,14 @@ import im.vector.riotredesign.core.epoxy.RiotEpoxyModel
|
||||||
import im.vector.riotredesign.core.extensions.localDateTime
|
import im.vector.riotredesign.core.extensions.localDateTime
|
||||||
import im.vector.riotredesign.core.resources.ColorProvider
|
import im.vector.riotredesign.core.resources.ColorProvider
|
||||||
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
|
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
|
||||||
|
import im.vector.riotredesign.features.html.EventHtmlRenderer
|
||||||
import im.vector.riotredesign.features.media.MediaContentRenderer
|
import im.vector.riotredesign.features.media.MediaContentRenderer
|
||||||
import me.gujun.android.span.span
|
import me.gujun.android.span.span
|
||||||
|
|
||||||
class MessageItemFactory(private val colorProvider: ColorProvider,
|
class MessageItemFactory(private val colorProvider: ColorProvider,
|
||||||
private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
|
private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
|
||||||
private val timelineDateFormatter: TimelineDateFormatter) {
|
private val timelineDateFormatter: TimelineDateFormatter,
|
||||||
|
private val htmlRenderer: EventHtmlRenderer) {
|
||||||
|
|
||||||
private val messagesDisplayedWithInformation = HashSet<String?>()
|
private val messagesDisplayedWithInformation = HashSet<String?>()
|
||||||
|
|
||||||
|
@ -102,9 +104,15 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
|
||||||
informationData: MessageInformationData,
|
informationData: MessageInformationData,
|
||||||
callback: TimelineEventController.Callback?): MessageTextItem? {
|
callback: TimelineEventController.Callback?): MessageTextItem? {
|
||||||
|
|
||||||
val message = linkifyBody(messageContent.body, callback)
|
val bodyToUse = messageContent.formattedBody
|
||||||
|
?.let {
|
||||||
|
htmlRenderer.render(it)
|
||||||
|
}
|
||||||
|
?: messageContent.body
|
||||||
|
|
||||||
|
val linkifiedBody = linkifyBody(bodyToUse, callback)
|
||||||
return MessageTextItem_()
|
return MessageTextItem_()
|
||||||
.message(message)
|
.message(linkifiedBody)
|
||||||
.informationData(informationData)
|
.informationData(informationData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,13 +37,14 @@ class TimelineEventController(private val roomId: String,
|
||||||
EpoxyAsyncUtil.getAsyncBackgroundHandler(),
|
EpoxyAsyncUtil.getAsyncBackgroundHandler(),
|
||||||
EpoxyAsyncUtil.getAsyncBackgroundHandler()
|
EpoxyAsyncUtil.getAsyncBackgroundHandler()
|
||||||
) {
|
) {
|
||||||
init {
|
|
||||||
setFilterDuplicates(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var isLoadingForward: Boolean = false
|
private var isLoadingForward: Boolean = false
|
||||||
private var isLoadingBackward: Boolean = false
|
private var isLoadingBackward: Boolean = false
|
||||||
private var hasReachedEnd: Boolean = false
|
private var hasReachedEnd: Boolean = true
|
||||||
|
|
||||||
|
init {
|
||||||
|
requestModelBuild()
|
||||||
|
}
|
||||||
|
|
||||||
var callback: Callback? = null
|
var callback: Callback? = null
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,183 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* * Copyright 2019 New Vector Ltd
|
||||||
|
* *
|
||||||
|
* * Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* * you may not use this file except in compliance with the License.
|
||||||
|
* * You may obtain a copy of the License at
|
||||||
|
* *
|
||||||
|
* * http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* *
|
||||||
|
* * Unless required by applicable law or agreed to in writing, software
|
||||||
|
* * distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* * See the License for the specific language governing permissions and
|
||||||
|
* * limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotredesign.features.html
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.text.style.ImageSpan
|
||||||
|
import im.vector.matrix.android.api.permalinks.PermalinkData
|
||||||
|
import im.vector.matrix.android.api.permalinks.PermalinkParser
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import org.commonmark.node.BlockQuote
|
||||||
|
import org.commonmark.node.HtmlBlock
|
||||||
|
import org.commonmark.node.HtmlInline
|
||||||
|
import org.commonmark.node.Node
|
||||||
|
import ru.noties.markwon.AbstractMarkwonPlugin
|
||||||
|
import ru.noties.markwon.Markwon
|
||||||
|
import ru.noties.markwon.MarkwonConfiguration
|
||||||
|
import ru.noties.markwon.MarkwonVisitor
|
||||||
|
import ru.noties.markwon.SpannableBuilder
|
||||||
|
import ru.noties.markwon.html.HtmlTag
|
||||||
|
import ru.noties.markwon.html.MarkwonHtmlParserImpl
|
||||||
|
import ru.noties.markwon.html.MarkwonHtmlRenderer
|
||||||
|
import ru.noties.markwon.html.TagHandler
|
||||||
|
import ru.noties.markwon.html.tag.BlockquoteHandler
|
||||||
|
import ru.noties.markwon.html.tag.EmphasisHandler
|
||||||
|
import ru.noties.markwon.html.tag.HeadingHandler
|
||||||
|
import ru.noties.markwon.html.tag.ImageHandler
|
||||||
|
import ru.noties.markwon.html.tag.LinkHandler
|
||||||
|
import ru.noties.markwon.html.tag.ListHandler
|
||||||
|
import ru.noties.markwon.html.tag.StrikeHandler
|
||||||
|
import ru.noties.markwon.html.tag.StrongEmphasisHandler
|
||||||
|
import ru.noties.markwon.html.tag.SubScriptHandler
|
||||||
|
import ru.noties.markwon.html.tag.SuperScriptHandler
|
||||||
|
import ru.noties.markwon.html.tag.UnderlineHandler
|
||||||
|
import java.util.Arrays.asList
|
||||||
|
|
||||||
|
class EventHtmlRenderer(private val context: Context,
|
||||||
|
private val session: Session) {
|
||||||
|
|
||||||
|
private val markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(MatrixPlugin.create(context, session))
|
||||||
|
.build()
|
||||||
|
|
||||||
|
fun render(text: String): CharSequence {
|
||||||
|
return markwon.toMarkdown(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MatrixPlugin private constructor(private val context: Context,
|
||||||
|
private val session: Session) : AbstractMarkwonPlugin() {
|
||||||
|
|
||||||
|
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
|
||||||
|
builder.htmlParser(MarkwonHtmlParserImpl.create())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun configureHtmlRenderer(builder: MarkwonHtmlRenderer.Builder) {
|
||||||
|
builder
|
||||||
|
.addHandler(
|
||||||
|
"img",
|
||||||
|
ImageHandler.create())
|
||||||
|
.addHandler(
|
||||||
|
"a",
|
||||||
|
MxLinkHandler(context, session))
|
||||||
|
.addHandler(
|
||||||
|
"blockquote",
|
||||||
|
BlockquoteHandler())
|
||||||
|
.addHandler(
|
||||||
|
"sub",
|
||||||
|
SubScriptHandler())
|
||||||
|
.addHandler(
|
||||||
|
"sup",
|
||||||
|
SuperScriptHandler())
|
||||||
|
.addHandler(
|
||||||
|
asList<String>("b", "strong"),
|
||||||
|
StrongEmphasisHandler())
|
||||||
|
.addHandler(
|
||||||
|
asList<String>("s", "del"),
|
||||||
|
StrikeHandler())
|
||||||
|
.addHandler(
|
||||||
|
asList<String>("u", "ins"),
|
||||||
|
UnderlineHandler())
|
||||||
|
.addHandler(
|
||||||
|
asList<String>("ul", "ol"),
|
||||||
|
ListHandler())
|
||||||
|
.addHandler(
|
||||||
|
asList<String>("i", "em", "cite", "dfn"),
|
||||||
|
EmphasisHandler())
|
||||||
|
.addHandler(
|
||||||
|
asList<String>("h1", "h2", "h3", "h4", "h5", "h6"),
|
||||||
|
HeadingHandler())
|
||||||
|
.addHandler("mx-reply",
|
||||||
|
MxReplyTagHandler())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun afterRender(node: Node, visitor: MarkwonVisitor) {
|
||||||
|
val configuration = visitor.configuration()
|
||||||
|
configuration.htmlRenderer().render(visitor, configuration.htmlParser())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun configureVisitor(builder: MarkwonVisitor.Builder) {
|
||||||
|
builder
|
||||||
|
.on(HtmlBlock::class.java) { visitor, htmlBlock -> visitHtml(visitor, htmlBlock.literal) }
|
||||||
|
.on(HtmlInline::class.java) { visitor, htmlInline -> visitHtml(visitor, htmlInline.literal) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun visitHtml(visitor: MarkwonVisitor, html: String?) {
|
||||||
|
if (html != null) {
|
||||||
|
visitor.configuration().htmlParser().processFragment(visitor.builder(), html)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun create(context: Context, session: Session): MatrixPlugin {
|
||||||
|
return MatrixPlugin(context, session)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MxLinkHandler(private val context: Context, private val session: Session) : TagHandler() {
|
||||||
|
|
||||||
|
private val linkHandler = LinkHandler()
|
||||||
|
|
||||||
|
override fun handle(visitor: MarkwonVisitor, renderer: MarkwonHtmlRenderer, tag: HtmlTag) {
|
||||||
|
val link = tag.attributes()["href"]
|
||||||
|
if (link != null) {
|
||||||
|
val permalinkData = PermalinkParser.parse(link)
|
||||||
|
when (permalinkData) {
|
||||||
|
is PermalinkData.UserLink -> {
|
||||||
|
val user = session.getUser(permalinkData.userId) ?: return
|
||||||
|
val drawable = PillDrawableFactory.create(context, permalinkData.userId, user)
|
||||||
|
val span = ImageSpan(drawable)
|
||||||
|
SpannableBuilder.setSpans(
|
||||||
|
visitor.builder(),
|
||||||
|
span,
|
||||||
|
tag.start(),
|
||||||
|
tag.end()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> linkHandler.handle(visitor, renderer, tag)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
linkHandler.handle(visitor, renderer, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MxReplyTagHandler : TagHandler() {
|
||||||
|
|
||||||
|
override fun handle(visitor: MarkwonVisitor, renderer: MarkwonHtmlRenderer, tag: HtmlTag) {
|
||||||
|
val configuration = visitor.configuration()
|
||||||
|
val factory = configuration.spansFactory().get(BlockQuote::class.java)
|
||||||
|
if (factory != null) {
|
||||||
|
SpannableBuilder.setSpans(
|
||||||
|
visitor.builder(),
|
||||||
|
factory.getSpans(configuration, visitor.renderProps()),
|
||||||
|
tag.start(),
|
||||||
|
tag.end()
|
||||||
|
)
|
||||||
|
val replyText = visitor.builder().removeFromEnd(tag.end())
|
||||||
|
visitor.builder().append("\n\n").append(replyText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotredesign.features.html
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import com.google.android.material.chip.ChipDrawable
|
||||||
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
import im.vector.riotredesign.R
|
||||||
|
import im.vector.riotredesign.features.home.AvatarRenderer
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
|
|
||||||
|
object PillDrawableFactory {
|
||||||
|
|
||||||
|
fun create(context: Context, userId: String, user: User?): Drawable {
|
||||||
|
val textPadding = context.resources.getDimension(R.dimen.pill_text_padding)
|
||||||
|
|
||||||
|
val chipDrawable = ChipDrawable.createFromResource(context, R.xml.pill_view).apply {
|
||||||
|
setText(user?.displayName ?: userId)
|
||||||
|
textEndPadding = textPadding
|
||||||
|
textStartPadding = textPadding
|
||||||
|
setChipMinHeightResource(R.dimen.pill_min_height)
|
||||||
|
setChipIconSizeResource(R.dimen.pill_avatar_size)
|
||||||
|
setBounds(0, 0, intrinsicWidth, intrinsicHeight)
|
||||||
|
}
|
||||||
|
val avatarRendererCallback = AvatarRendererChipCallback(chipDrawable)
|
||||||
|
AvatarRenderer.load(context, user?.avatarUrl, user?.displayName, 80, avatarRendererCallback)
|
||||||
|
return chipDrawable
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AvatarRendererChipCallback(chipDrawable: ChipDrawable) : AvatarRenderer.Callback {
|
||||||
|
|
||||||
|
private val weakChipDrawable = WeakReference<ChipDrawable>(chipDrawable)
|
||||||
|
|
||||||
|
override fun onDrawableUpdated(drawable: Drawable?) {
|
||||||
|
weakChipDrawable.get()?.apply {
|
||||||
|
chipIcon = drawable
|
||||||
|
setBounds(0, 0, intrinsicWidth, intrinsicHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -17,4 +17,9 @@
|
||||||
<color name="pale_grey_two">#ebedf8</color>
|
<color name="pale_grey_two">#ebedf8</color>
|
||||||
<color name="brown_grey">#a5a5a5</color>
|
<color name="brown_grey">#a5a5a5</color>
|
||||||
<color name="grey_lynch">#61708B</color>
|
<color name="grey_lynch">#61708B</color>
|
||||||
|
|
||||||
|
<color name="vector_silver_color">#FFC7C7C7</color>
|
||||||
|
<color name="vector_dark_grey_color">#FF999999</color>
|
||||||
|
<color name="vector_fuchsia_color">#FFF56679</color>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
7
app/src/main/res/values/dimens.xml
Normal file
7
app/src/main/res/values/dimens.xml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<dimen name="pill_avatar_size">16dp</dimen>
|
||||||
|
<dimen name="pill_min_height">20dp</dimen>
|
||||||
|
<dimen name="pill_text_padding">4dp</dimen>
|
||||||
|
</resources>
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="Base.V1.Theme.Riot" parent="Theme.AppCompat.Light.NoActionBar">
|
<style name="Base.V1.Theme.Riot" parent="Theme.MaterialComponents.Light.NoActionBar.Bridge">
|
||||||
<item name="colorPrimary">@color/dark</item>
|
<item name="colorPrimary">@color/dark</item>
|
||||||
<item name="colorPrimaryDark">@color/dark</item>
|
<item name="colorPrimaryDark">@color/dark</item>
|
||||||
<item name="colorAccent">@color/pale_teal</item>
|
<item name="colorAccent">@color/pale_teal</item>
|
||||||
|
@ -11,5 +11,4 @@
|
||||||
<style name="Base.Theme.Riot" parent="Base.V1.Theme.Riot" />
|
<style name="Base.Theme.Riot" parent="Base.V1.Theme.Riot" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
6
app/src/main/res/xml/pill_view.xml
Normal file
6
app/src/main/res/xml/pill_view.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<chip xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
style="@style/Widget.MaterialComponents.Chip.Entry"
|
||||||
|
android:checkable="false"
|
||||||
|
app:closeIcon="@null" />
|
|
@ -22,6 +22,7 @@ allprojects {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
maven { url 'https://jitpack.io' }
|
maven { url 'https://jitpack.io' }
|
||||||
|
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,12 +21,13 @@ import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||||
import im.vector.matrix.android.api.session.group.GroupService
|
import im.vector.matrix.android.api.session.group.GroupService
|
||||||
import im.vector.matrix.android.api.session.room.RoomService
|
import im.vector.matrix.android.api.session.room.RoomService
|
||||||
|
import im.vector.matrix.android.api.session.user.UserService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines interactions with a session.
|
* This interface defines interactions with a session.
|
||||||
* An instance of a session will be provided by the SDK.
|
* An instance of a session will be provided by the SDK.
|
||||||
*/
|
*/
|
||||||
interface Session : RoomService, GroupService {
|
interface Session : RoomService, GroupService, UserService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The params associated to the session
|
* The params associated to the session
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* * 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.matrix.android.api.session.user
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface defines methods to get users. It's implemented at the session level.
|
||||||
|
*/
|
||||||
|
interface UserService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a user from a userId
|
||||||
|
* @param userId the userId to look for.
|
||||||
|
* @return a user with userId or null
|
||||||
|
*/
|
||||||
|
fun getUser(userId: String): User?
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* * 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.matrix.android.api.session.user.model
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data class which holds information about a user.
|
||||||
|
* It can be retrieved with [im.vector.matrix.android.api.session.user.UserService]
|
||||||
|
*/
|
||||||
|
data class User(
|
||||||
|
val userId: String,
|
||||||
|
val displayName: String? = null,
|
||||||
|
val avatarUrl: String? = null
|
||||||
|
)
|
|
@ -16,11 +16,12 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.database
|
package im.vector.matrix.android.internal.database
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.lifecycle.Observer
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import io.realm.OrderedCollectionChangeSet
|
||||||
import io.realm.RealmObject
|
import io.realm.RealmObject
|
||||||
|
import io.realm.RealmResults
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
|
||||||
internal interface LiveEntityObserver {
|
internal interface LiveEntityObserver {
|
||||||
fun start()
|
fun start()
|
||||||
|
@ -28,38 +29,41 @@ internal interface LiveEntityObserver {
|
||||||
}
|
}
|
||||||
|
|
||||||
internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val monarchy: Monarchy)
|
internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val monarchy: Monarchy)
|
||||||
: Observer<Monarchy.ManagedChangeSet<T>>, LiveEntityObserver {
|
: LiveEntityObserver {
|
||||||
|
|
||||||
protected abstract val query: Monarchy.Query<T>
|
protected abstract val query: Monarchy.Query<T>
|
||||||
private val isStarted = AtomicBoolean(false)
|
private val isStarted = AtomicBoolean(false)
|
||||||
private val liveResults: LiveData<Monarchy.ManagedChangeSet<T>> by lazy {
|
private lateinit var results: AtomicReference<RealmResults<T>>
|
||||||
monarchy.findAllManagedWithChanges(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun start() {
|
override fun start() {
|
||||||
if (isStarted.compareAndSet(false, true)) {
|
if (isStarted.compareAndSet(false, true)) {
|
||||||
liveResults.observeForever(this)
|
monarchy.postToMonarchyThread {
|
||||||
|
val queryResults = query.createQuery(it).findAll()
|
||||||
|
queryResults.addChangeListener { t, changeSet ->
|
||||||
|
onChanged(t, changeSet)
|
||||||
|
}
|
||||||
|
results = AtomicReference(queryResults)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
if (isStarted.compareAndSet(true, false)) {
|
if (isStarted.compareAndSet(true, false)) {
|
||||||
liveResults.removeObserver(this)
|
monarchy.postToMonarchyThread {
|
||||||
|
results.getAndSet(null).removeAllChangeListeners()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PRIVATE
|
// PRIVATE
|
||||||
|
|
||||||
override fun onChanged(changeSet: Monarchy.ManagedChangeSet<T>?) {
|
private fun onChanged(realmResults: RealmResults<T>, changeSet: OrderedCollectionChangeSet) {
|
||||||
if (changeSet == null) {
|
val insertionIndexes = changeSet.insertions
|
||||||
return
|
val updateIndexes = changeSet.changes
|
||||||
}
|
val deletionIndexes = changeSet.deletions
|
||||||
val insertionIndexes = changeSet.orderedCollectionChangeSet.insertions
|
val inserted = realmResults.filterIndexed { index, _ -> insertionIndexes.contains(index) }
|
||||||
val updateIndexes = changeSet.orderedCollectionChangeSet.changes
|
val updated = realmResults.filterIndexed { index, _ -> updateIndexes.contains(index) }
|
||||||
val deletionIndexes = changeSet.orderedCollectionChangeSet.deletions
|
val deleted = realmResults.filterIndexed { index, _ -> deletionIndexes.contains(index) }
|
||||||
val inserted = changeSet.realmResults.filterIndexed { index, _ -> insertionIndexes.contains(index) }
|
|
||||||
val updated = changeSet.realmResults.filterIndexed { index, _ -> updateIndexes.contains(index) }
|
|
||||||
val deleted = changeSet.realmResults.filterIndexed { index, _ -> deletionIndexes.contains(index) }
|
|
||||||
process(inserted, updated, deleted)
|
process(inserted, updated, deleted)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import im.vector.matrix.android.internal.database.mapper.toEntity
|
||||||
import im.vector.matrix.android.internal.database.mapper.updateWith
|
import im.vector.matrix.android.internal.database.mapper.updateWith
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
|
import im.vector.matrix.android.internal.database.query.fastContains
|
||||||
|
|
||||||
|
|
||||||
internal fun RoomEntity.deleteOnCascade(chunkEntity: ChunkEntity) {
|
internal fun RoomEntity.deleteOnCascade(chunkEntity: ChunkEntity) {
|
||||||
|
@ -37,16 +38,17 @@ internal fun RoomEntity.addOrUpdate(chunkEntity: ChunkEntity) {
|
||||||
|
|
||||||
internal fun RoomEntity.addStateEvents(stateEvents: List<Event>,
|
internal fun RoomEntity.addStateEvents(stateEvents: List<Event>,
|
||||||
stateIndex: Int = Int.MIN_VALUE,
|
stateIndex: Int = Int.MIN_VALUE,
|
||||||
|
filterDuplicates: Boolean = false,
|
||||||
isUnlinked: Boolean = false) {
|
isUnlinked: Boolean = false) {
|
||||||
if (!isManaged) {
|
if (!isManaged) {
|
||||||
throw IllegalStateException("Chunk entity should be managed to use fast contains")
|
throw IllegalStateException("Chunk entity should be managed to use fast contains")
|
||||||
}
|
}
|
||||||
stateEvents.forEach { event ->
|
stateEvents.forEach { event ->
|
||||||
if (event.eventId == null) {
|
if (event.eventId == null || (filterDuplicates && fastContains(event.eventId))) {
|
||||||
return@forEach
|
return@forEach
|
||||||
}
|
}
|
||||||
val eventEntity = event.toEntity(roomId)
|
val eventEntity = event.toEntity(roomId)
|
||||||
eventEntity.updateWith(stateIndex, isUnlinked)
|
eventEntity.updateWith(stateIndex, isUnlinked)
|
||||||
untimelinedStateEvents.add(eventEntity)
|
untimelinedStateEvents.add(eventEntity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.matrix.android.internal.database.model
|
||||||
|
|
||||||
|
import io.realm.RealmObject
|
||||||
|
import io.realm.annotations.PrimaryKey
|
||||||
|
|
||||||
|
internal open class UserEntity(@PrimaryKey var userId: String = "",
|
||||||
|
var displayName: String = "",
|
||||||
|
var avatarUrl: String = ""
|
||||||
|
) : RealmObject() {
|
||||||
|
|
||||||
|
companion object
|
||||||
|
}
|
||||||
|
|
|
@ -27,8 +27,7 @@ import io.realm.Sort
|
||||||
import io.realm.kotlin.where
|
import io.realm.kotlin.where
|
||||||
|
|
||||||
internal fun EventEntity.Companion.where(realm: Realm, eventId: String): RealmQuery<EventEntity> {
|
internal fun EventEntity.Companion.where(realm: Realm, eventId: String): RealmQuery<EventEntity> {
|
||||||
return realm.where<EventEntity>()
|
return realm.where<EventEntity>().equalTo(EventEntityFields.EVENT_ID, eventId)
|
||||||
.equalTo(EventEntityFields.EVENT_ID, eventId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun EventEntity.Companion.where(realm: Realm,
|
internal fun EventEntity.Companion.where(realm: Realm,
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.matrix.android.internal.database.query
|
package im.vector.matrix.android.internal.database.query
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.room.model.MyMembership
|
import im.vector.matrix.android.api.session.room.model.MyMembership
|
||||||
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomEntityFields
|
import im.vector.matrix.android.internal.database.model.RoomEntityFields
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
|
@ -34,3 +35,7 @@ internal fun RoomEntity.Companion.where(realm: Realm, membership: MyMembership?
|
||||||
}
|
}
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun RoomEntity.fastContains(eventId: String): Boolean {
|
||||||
|
return EventEntity.where(realm, eventId = eventId).findFirst() != null
|
||||||
|
}
|
||||||
|
|
|
@ -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.matrix.android.internal.database.query
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.database.model.UserEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.UserEntityFields
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmQuery
|
||||||
|
import io.realm.kotlin.where
|
||||||
|
|
||||||
|
internal fun UserEntity.Companion.where(realm: Realm, userId: String): RealmQuery<UserEntity> {
|
||||||
|
return realm
|
||||||
|
.where<UserEntity>()
|
||||||
|
.equalTo(UserEntityFields.USER_ID, userId)
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import androidx.annotation.MainThread
|
import androidx.annotation.MainThread
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||||
|
@ -28,6 +29,8 @@ import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
import im.vector.matrix.android.api.session.room.Room
|
import im.vector.matrix.android.api.session.room.Room
|
||||||
import im.vector.matrix.android.api.session.room.RoomService
|
import im.vector.matrix.android.api.session.room.RoomService
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
|
import im.vector.matrix.android.api.session.user.UserService
|
||||||
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
import im.vector.matrix.android.internal.database.LiveEntityObserver
|
import im.vector.matrix.android.internal.database.LiveEntityObserver
|
||||||
import im.vector.matrix.android.internal.di.MatrixKoinComponent
|
import im.vector.matrix.android.internal.di.MatrixKoinComponent
|
||||||
import im.vector.matrix.android.internal.di.MatrixKoinHolder
|
import im.vector.matrix.android.internal.di.MatrixKoinHolder
|
||||||
|
@ -35,11 +38,12 @@ import im.vector.matrix.android.internal.session.group.GroupModule
|
||||||
import im.vector.matrix.android.internal.session.room.RoomModule
|
import im.vector.matrix.android.internal.session.room.RoomModule
|
||||||
import im.vector.matrix.android.internal.session.sync.SyncModule
|
import im.vector.matrix.android.internal.session.sync.SyncModule
|
||||||
import im.vector.matrix.android.internal.session.sync.job.SyncThread
|
import im.vector.matrix.android.internal.session.sync.job.SyncThread
|
||||||
|
import im.vector.matrix.android.internal.session.user.UserModule
|
||||||
import org.koin.core.scope.Scope
|
import org.koin.core.scope.Scope
|
||||||
import org.koin.standalone.inject
|
import org.koin.standalone.inject
|
||||||
|
|
||||||
|
|
||||||
internal class DefaultSession(override val sessionParams: SessionParams) : Session, MatrixKoinComponent, RoomService {
|
internal class DefaultSession(override val sessionParams: SessionParams) : Session, MatrixKoinComponent {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val SCOPE: String = "session"
|
const val SCOPE: String = "session"
|
||||||
|
@ -47,10 +51,12 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
|
||||||
|
|
||||||
private lateinit var scope: Scope
|
private lateinit var scope: Scope
|
||||||
|
|
||||||
|
private val monarchy by inject<Monarchy>()
|
||||||
private val liveEntityUpdaters by inject<List<LiveEntityObserver>>()
|
private val liveEntityUpdaters by inject<List<LiveEntityObserver>>()
|
||||||
private val sessionListeners by inject<SessionListeners>()
|
private val sessionListeners by inject<SessionListeners>()
|
||||||
private val roomService by inject<RoomService>()
|
private val roomService by inject<RoomService>()
|
||||||
private val groupService by inject<GroupService>()
|
private val groupService by inject<GroupService>()
|
||||||
|
private val userService by inject<UserService>()
|
||||||
private val syncThread by inject<SyncThread>()
|
private val syncThread by inject<SyncThread>()
|
||||||
private val contentUrlResolver by inject<ContentUrlResolver>()
|
private val contentUrlResolver by inject<ContentUrlResolver>()
|
||||||
private var isOpen = false
|
private var isOpen = false
|
||||||
|
@ -64,8 +70,12 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
|
||||||
val syncModule = SyncModule().definition
|
val syncModule = SyncModule().definition
|
||||||
val roomModule = RoomModule().definition
|
val roomModule = RoomModule().definition
|
||||||
val groupModule = GroupModule().definition
|
val groupModule = GroupModule().definition
|
||||||
MatrixKoinHolder.instance.loadModules(listOf(sessionModule, syncModule, roomModule, groupModule))
|
val userModule = UserModule().definition
|
||||||
|
MatrixKoinHolder.instance.loadModules(listOf(sessionModule, syncModule, roomModule, groupModule, userModule))
|
||||||
scope = getKoin().getOrCreateScope(SCOPE)
|
scope = getKoin().getOrCreateScope(SCOPE)
|
||||||
|
if (!monarchy.isMonarchyThreadOpen) {
|
||||||
|
monarchy.openManually()
|
||||||
|
}
|
||||||
liveEntityUpdaters.forEach { it.start() }
|
liveEntityUpdaters.forEach { it.start() }
|
||||||
syncThread.start()
|
syncThread.start()
|
||||||
}
|
}
|
||||||
|
@ -77,6 +87,9 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
|
||||||
assert(isOpen)
|
assert(isOpen)
|
||||||
syncThread.kill()
|
syncThread.kill()
|
||||||
liveEntityUpdaters.forEach { it.dispose() }
|
liveEntityUpdaters.forEach { it.dispose() }
|
||||||
|
if (monarchy.isMonarchyThreadOpen) {
|
||||||
|
monarchy.closeManually()
|
||||||
|
}
|
||||||
scope.close()
|
scope.close()
|
||||||
isOpen = false
|
isOpen = false
|
||||||
}
|
}
|
||||||
|
@ -118,6 +131,13 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
|
||||||
return groupService.liveGroupSummaries()
|
return groupService.liveGroupSummaries()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// USER SERVICE
|
||||||
|
|
||||||
|
override fun getUser(userId: String): User? {
|
||||||
|
assert(isOpen)
|
||||||
|
return userService.getUser(userId)
|
||||||
|
}
|
||||||
|
|
||||||
// Private methods *****************************************************************************
|
// Private methods *****************************************************************************
|
||||||
|
|
||||||
private fun assertMainThread() {
|
private fun assertMainThread() {
|
||||||
|
|
|
@ -22,17 +22,20 @@ import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||||
import im.vector.matrix.android.api.session.group.GroupService
|
import im.vector.matrix.android.api.session.group.GroupService
|
||||||
import im.vector.matrix.android.api.session.room.RoomService
|
import im.vector.matrix.android.api.session.room.RoomService
|
||||||
|
import im.vector.matrix.android.api.session.user.UserService
|
||||||
import im.vector.matrix.android.internal.database.LiveEntityObserver
|
import im.vector.matrix.android.internal.database.LiveEntityObserver
|
||||||
import im.vector.matrix.android.internal.session.content.DefaultContentUrlResolver
|
import im.vector.matrix.android.internal.session.content.DefaultContentUrlResolver
|
||||||
import im.vector.matrix.android.internal.session.group.DefaultGroupService
|
import im.vector.matrix.android.internal.session.group.DefaultGroupService
|
||||||
import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater
|
import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater
|
||||||
import im.vector.matrix.android.internal.session.room.DefaultRoomService
|
import im.vector.matrix.android.internal.session.room.DefaultRoomService
|
||||||
import im.vector.matrix.android.internal.session.room.RoomAvatarResolver
|
import im.vector.matrix.android.internal.session.room.RoomAvatarResolver
|
||||||
import im.vector.matrix.android.internal.session.room.RoomFactory
|
|
||||||
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
|
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
|
||||||
import im.vector.matrix.android.internal.session.room.members.RoomDisplayNameResolver
|
import im.vector.matrix.android.internal.session.room.members.RoomDisplayNameResolver
|
||||||
import im.vector.matrix.android.internal.session.room.members.RoomMemberDisplayNameResolver
|
import im.vector.matrix.android.internal.session.room.members.RoomMemberDisplayNameResolver
|
||||||
import im.vector.matrix.android.internal.session.room.prune.EventsPruner
|
import im.vector.matrix.android.internal.session.room.prune.EventsPruner
|
||||||
|
import im.vector.matrix.android.internal.session.user.DefaultUserService
|
||||||
|
import im.vector.matrix.android.internal.session.user.UserEntityUpdater
|
||||||
|
import im.vector.matrix.android.internal.session.user.UserModule
|
||||||
import im.vector.matrix.android.internal.util.md5
|
import im.vector.matrix.android.internal.util.md5
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import org.koin.dsl.module.module
|
import org.koin.dsl.module.module
|
||||||
|
@ -96,6 +99,10 @@ internal class SessionModule(private val sessionParams: SessionParams) {
|
||||||
DefaultGroupService(get()) as GroupService
|
DefaultGroupService(get()) as GroupService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scope(DefaultSession.SCOPE) {
|
||||||
|
DefaultUserService(get()) as UserService
|
||||||
|
}
|
||||||
|
|
||||||
scope(DefaultSession.SCOPE) {
|
scope(DefaultSession.SCOPE) {
|
||||||
SessionListeners()
|
SessionListeners()
|
||||||
}
|
}
|
||||||
|
@ -108,7 +115,8 @@ internal class SessionModule(private val sessionParams: SessionParams) {
|
||||||
val roomSummaryUpdater = RoomSummaryUpdater(get(), get(), get(), get(), sessionParams.credentials)
|
val roomSummaryUpdater = RoomSummaryUpdater(get(), get(), get(), get(), sessionParams.credentials)
|
||||||
val groupSummaryUpdater = GroupSummaryUpdater(get())
|
val groupSummaryUpdater = GroupSummaryUpdater(get())
|
||||||
val eventsPruner = EventsPruner(get())
|
val eventsPruner = EventsPruner(get())
|
||||||
listOf<LiveEntityObserver>(roomSummaryUpdater, groupSummaryUpdater, eventsPruner)
|
val userEntityUpdater = UserEntityUpdater(get(), get(), get())
|
||||||
|
listOf<LiveEntityObserver>(roomSummaryUpdater, groupSummaryUpdater, eventsPruner, userEntityUpdater)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTas
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
|
|
||||||
internal data class DefaultRoom(
|
internal class DefaultRoom(
|
||||||
override val roomId: String,
|
override val roomId: String,
|
||||||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||||
private val monarchy: Monarchy,
|
private val monarchy: Monarchy,
|
||||||
|
|
|
@ -26,13 +26,13 @@ import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.util.fetchCopied
|
import im.vector.matrix.android.internal.util.fetchManaged
|
||||||
|
|
||||||
internal class DefaultRoomService(private val monarchy: Monarchy,
|
internal class DefaultRoomService(private val monarchy: Monarchy,
|
||||||
private val roomFactory: RoomFactory) : RoomService {
|
private val roomFactory: RoomFactory) : RoomService {
|
||||||
|
|
||||||
override fun getRoom(roomId: String): Room? {
|
override fun getRoom(roomId: String): Room? {
|
||||||
monarchy.fetchCopied { RoomEntity.where(it, roomId).findFirst() } ?: return null
|
monarchy.fetchManaged { RoomEntity.where(it, roomId).findFirst() } ?: return null
|
||||||
return roomFactory.instantiate(roomId)
|
return roomFactory.instantiate(roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,22 +34,23 @@ internal class RoomMemberExtractor(private val monarchy: Monarchy,
|
||||||
private val cached = HashMap<String, RoomMember?>()
|
private val cached = HashMap<String, RoomMember?>()
|
||||||
|
|
||||||
fun extractFrom(event: EventEntity): RoomMember? {
|
fun extractFrom(event: EventEntity): RoomMember? {
|
||||||
if (cached.containsKey(event.eventId)) {
|
|
||||||
return cached[event.eventId]
|
|
||||||
}
|
|
||||||
val sender = event.sender ?: return null
|
val sender = event.sender ?: return null
|
||||||
|
val cacheKey = sender + event.stateIndex
|
||||||
|
if (cached.containsKey(cacheKey)) {
|
||||||
|
return cached[cacheKey]
|
||||||
|
}
|
||||||
// If the event is unlinked we want to fetch unlinked state events
|
// If the event is unlinked we want to fetch unlinked state events
|
||||||
val unlinked = event.isUnlinked
|
val unlinked = event.isUnlinked
|
||||||
// When stateIndex is negative, we try to get the next stateEvent prevContent()
|
// When stateIndex is negative, we try to get the next stateEvent prevContent()
|
||||||
// If prevContent is null we fallback to the Int.MIN state events content()
|
// If prevContent is null we fallback to the Int.MIN state events content()
|
||||||
val content = if (event.stateIndex <= 0) {
|
val content = if (event.stateIndex <= 0) {
|
||||||
baseQuery(monarchy, roomId, sender, unlinked).next(from = event.stateIndex)?.prevContent
|
baseQuery(monarchy, roomId, sender, unlinked).next(from = event.stateIndex)?.prevContent
|
||||||
?: baseQuery(monarchy, roomId, sender, unlinked).last(since = event.stateIndex)?.content
|
?: baseQuery(monarchy, roomId, sender, unlinked).last(since = event.stateIndex)?.content
|
||||||
} else {
|
} else {
|
||||||
baseQuery(monarchy, roomId, sender, unlinked).last(since = event.stateIndex)?.content
|
baseQuery(monarchy, roomId, sender, unlinked).last(since = event.stateIndex)?.content
|
||||||
}
|
}
|
||||||
val roomMember: RoomMember? = ContentMapper.map(content).toModel()
|
val roomMember: RoomMember? = ContentMapper.map(content).toModel()
|
||||||
cached[event.eventId] = roomMember
|
cached[cacheKey] = roomMember
|
||||||
return roomMember
|
return roomMember
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ import im.vector.matrix.android.internal.database.model.EventEntityFields
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
|
import io.realm.Sort
|
||||||
|
|
||||||
internal class RoomMembers(private val realm: Realm,
|
internal class RoomMembers(private val realm: Realm,
|
||||||
private val roomId: String
|
private val roomId: String
|
||||||
|
@ -35,6 +36,17 @@ internal class RoomMembers(private val realm: Realm,
|
||||||
RoomSummaryEntity.where(realm, roomId).findFirst()
|
RoomSummaryEntity.where(realm, roomId).findFirst()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun get(userId: String): RoomMember? {
|
||||||
|
return EventEntity
|
||||||
|
.where(realm, roomId, EventType.STATE_ROOM_MEMBER)
|
||||||
|
.sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING)
|
||||||
|
.equalTo(EventEntityFields.STATE_KEY, userId)
|
||||||
|
.findFirst()
|
||||||
|
?.let {
|
||||||
|
it.asDomain().content?.toModel<RoomMember>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getLoaded(): Map<String, RoomMember> {
|
fun getLoaded(): Map<String, RoomMember> {
|
||||||
return EventEntity
|
return EventEntity
|
||||||
.where(realm, roomId, EventType.STATE_ROOM_MEMBER)
|
.where(realm, roomId, EventType.STATE_ROOM_MEMBER)
|
||||||
|
@ -45,7 +57,6 @@ internal class RoomMembers(private val realm: Realm,
|
||||||
.mapValues { it.value.content.toModel<RoomMember>()!! }
|
.mapValues { it.value.content.toModel<RoomMember>()!! }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun getNumberOfJoinedMembers(): Int {
|
fun getNumberOfJoinedMembers(): Int {
|
||||||
return roomSummary?.joinedMembersCount
|
return roomSummary?.joinedMembersCount
|
||||||
?: getLoaded().filterValues { it.membership == Membership.JOIN }.size
|
?: getLoaded().filterValues { it.membership == Membership.JOIN }.size
|
||||||
|
|
|
@ -92,7 +92,7 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
|
||||||
|
|
||||||
if (roomSync.state != null && roomSync.state.events.isNotEmpty()) {
|
if (roomSync.state != null && roomSync.state.events.isNotEmpty()) {
|
||||||
val untimelinedStateIndex = if (isInitialSync) Int.MIN_VALUE else stateIndexOffset
|
val untimelinedStateIndex = if (isInitialSync) Int.MIN_VALUE else stateIndexOffset
|
||||||
roomEntity.addStateEvents(roomSync.state.events, stateIndex = untimelinedStateIndex)
|
roomEntity.addStateEvents(roomSync.state.events, filterDuplicates = true, stateIndex = untimelinedStateIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (roomSync.timeline != null && roomSync.timeline.events.isNotEmpty()) {
|
if (roomSync.timeline != null && roomSync.timeline.events.isNotEmpty()) {
|
||||||
|
|
|
@ -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.matrix.android.internal.session.user
|
||||||
|
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import im.vector.matrix.android.api.session.user.UserService
|
||||||
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
import im.vector.matrix.android.internal.database.model.UserEntity
|
||||||
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
|
import im.vector.matrix.android.internal.util.fetchCopied
|
||||||
|
|
||||||
|
internal class DefaultUserService(private val monarchy: Monarchy) : UserService {
|
||||||
|
|
||||||
|
override fun getUser(userId: String): User? {
|
||||||
|
val userEntity = monarchy.fetchCopied { UserEntity.where(it, userId).findFirst() }
|
||||||
|
?: return null
|
||||||
|
|
||||||
|
return User(
|
||||||
|
userEntity.userId,
|
||||||
|
userEntity.displayName,
|
||||||
|
userEntity.avatarUrl
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.internal.session.user
|
||||||
|
|
||||||
|
import arrow.core.Try
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
|
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||||
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.UserEntity
|
||||||
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
|
import im.vector.matrix.android.internal.session.room.members.RoomMembers
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import im.vector.matrix.android.internal.util.tryTransactionSync
|
||||||
|
|
||||||
|
internal interface UpdateUserTask : Task<UpdateUserTask.Params, Unit> {
|
||||||
|
|
||||||
|
data class Params(val eventIds: List<String>)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultUpdateUserTask(private val monarchy: Monarchy) : UpdateUserTask {
|
||||||
|
|
||||||
|
override fun execute(params: UpdateUserTask.Params): Try<Unit> {
|
||||||
|
return monarchy.tryTransactionSync { realm ->
|
||||||
|
params.eventIds.forEach { eventId ->
|
||||||
|
val event = EventEntity.where(realm, eventId).findFirst()?.asDomain()
|
||||||
|
?: return@forEach
|
||||||
|
val roomId = event.roomId ?: return@forEach
|
||||||
|
val userId = event.stateKey ?: return@forEach
|
||||||
|
val roomMember = RoomMembers(realm, roomId).get(userId) ?: return@forEach
|
||||||
|
if (roomMember.membership != Membership.JOIN) return@forEach
|
||||||
|
|
||||||
|
val userEntity = UserEntity.where(realm, userId).findFirst()
|
||||||
|
?: realm.createObject(UserEntity::class.java, userId)
|
||||||
|
userEntity.displayName = roomMember.displayName ?: ""
|
||||||
|
userEntity.avatarUrl = roomMember.avatarUrl ?: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* * 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.matrix.android.internal.session.user
|
||||||
|
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
|
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
|
||||||
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
||||||
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
|
import im.vector.matrix.android.internal.task.TaskThread
|
||||||
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
|
import io.realm.Sort
|
||||||
|
|
||||||
|
internal class UserEntityUpdater(monarchy: Monarchy,
|
||||||
|
private val updateUserTask: UpdateUserTask,
|
||||||
|
private val taskExecutor: TaskExecutor)
|
||||||
|
: RealmLiveEntityObserver<EventEntity>(monarchy) {
|
||||||
|
|
||||||
|
override val query = Monarchy.Query<EventEntity> {
|
||||||
|
EventEntity
|
||||||
|
.where(it, type = EventType.STATE_ROOM_MEMBER)
|
||||||
|
.sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING)
|
||||||
|
.distinct(EventEntityFields.STATE_KEY)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun process(inserted: List<EventEntity>, updated: List<EventEntity>, deleted: List<EventEntity>) {
|
||||||
|
val roomMembersEvents = inserted.map { it.eventId }
|
||||||
|
val taskParams = UpdateUserTask.Params(roomMembersEvents)
|
||||||
|
updateUserTask
|
||||||
|
.configureWith(taskParams)
|
||||||
|
.executeOn(TaskThread.IO)
|
||||||
|
.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,9 +14,18 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.riotredesign.core.extensions
|
package im.vector.matrix.android.internal.session.user
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.session.DefaultSession
|
||||||
|
import org.koin.dsl.module.module
|
||||||
|
|
||||||
fun CharSequence.firstCharAsString(): String {
|
internal class UserModule {
|
||||||
return if (isNotEmpty()) this[0].toString() else ""
|
|
||||||
}
|
val definition = module(override = true) {
|
||||||
|
|
||||||
|
scope(DefaultSession.SCOPE) {
|
||||||
|
DefaultUpdateUserTask(get()) as UpdateUserTask
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,11 +34,25 @@ internal fun Monarchy.tryTransactionAsync(transaction: (realm: Realm) -> Unit):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T : RealmModel> Monarchy.fetchManaged(query: (Realm) -> T?): T? {
|
||||||
|
return fetch(query, false)
|
||||||
|
}
|
||||||
|
|
||||||
fun <T : RealmModel> Monarchy.fetchCopied(query: (Realm) -> T?): T? {
|
fun <T : RealmModel> Monarchy.fetchCopied(query: (Realm) -> T?): T? {
|
||||||
|
return fetch(query, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T : RealmModel> Monarchy.fetch(query: (Realm) -> T?, copyFromRealm: Boolean): T? {
|
||||||
val ref = AtomicReference<T>()
|
val ref = AtomicReference<T>()
|
||||||
doWithRealm { realm ->
|
doWithRealm { realm ->
|
||||||
val result = query.invoke(realm)?.let { realm.copyFromRealm(it) }
|
val result = query.invoke(realm)?.let {
|
||||||
|
if (copyFromRealm) {
|
||||||
|
realm.copyFromRealm(it)
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
ref.set(result)
|
ref.set(result)
|
||||||
}
|
}
|
||||||
return ref.get()
|
return ref.get()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue