mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-22 01:15:54 +03:00
Clean code
This commit is contained in:
parent
88755a79b4
commit
f454078c6b
32 changed files with 430 additions and 489 deletions
|
@ -1,5 +1,6 @@
|
||||||
<component name="ProjectCodeStyleConfiguration">
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
<code_scheme name="Project" version="173">
|
<code_scheme name="Project" version="173">
|
||||||
|
<option name="RIGHT_MARGIN" value="160" />
|
||||||
<AndroidXmlCodeStyleSettings>
|
<AndroidXmlCodeStyleSettings>
|
||||||
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
|
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
|
||||||
</AndroidXmlCodeStyleSettings>
|
</AndroidXmlCodeStyleSettings>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<component name="ProjectCodeStyleConfiguration">
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
<state>
|
<state>
|
||||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
|
||||||
</state>
|
</state>
|
||||||
</component>
|
</component>
|
|
@ -118,7 +118,7 @@ class CommonTestHelper(context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNewTimelineEvents(eventIds: List<String>) {
|
override fun onNewTimelineEvents(eventIds: List<String>) {
|
||||||
//noop
|
// noop
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||||
|
|
|
@ -26,7 +26,7 @@ import im.vector.matrix.android.api.util.Optional
|
||||||
*/
|
*/
|
||||||
interface ReadService {
|
interface ReadService {
|
||||||
|
|
||||||
enum class MarkAsReadParams{
|
enum class MarkAsReadParams {
|
||||||
READ_RECEIPT,
|
READ_RECEIPT,
|
||||||
READ_MARKER,
|
READ_MARKER,
|
||||||
BOTH
|
BOTH
|
||||||
|
|
|
@ -70,7 +70,6 @@ import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask
|
||||||
import im.vector.matrix.android.internal.crypto.verification.DefaultVerificationService
|
import im.vector.matrix.android.internal.crypto.verification.DefaultVerificationService
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
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.model.EventEntityFields
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
|
||||||
import im.vector.matrix.android.internal.database.query.whereType
|
import im.vector.matrix.android.internal.database.query.whereType
|
||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
import im.vector.matrix.android.internal.extensions.foldToCallback
|
import im.vector.matrix.android.internal.extensions.foldToCallback
|
||||||
|
|
|
@ -320,7 +320,6 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||||
// al devices =
|
// al devices =
|
||||||
val models = response.deviceKeys?.get(userId)?.mapValues { entry -> CryptoInfoMapper.map(entry.value) }
|
val models = response.deviceKeys?.get(userId)?.mapValues { entry -> CryptoInfoMapper.map(entry.value) }
|
||||||
|
|
||||||
|
|
||||||
Timber.v("## doKeyDownloadForUsers() : Got keys for $userId : $models")
|
Timber.v("## doKeyDownloadForUsers() : Got keys for $userId : $models")
|
||||||
if (!models.isNullOrEmpty()) {
|
if (!models.isNullOrEmpty()) {
|
||||||
val workingCopy = models.toMutableMap()
|
val workingCopy = models.toMutableMap()
|
||||||
|
|
|
@ -36,7 +36,6 @@ import im.vector.matrix.android.internal.session.room.timeline.PaginationDirecti
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.Sort
|
import io.realm.Sort
|
||||||
import io.realm.kotlin.createObject
|
import io.realm.kotlin.createObject
|
||||||
import kotlinx.coroutines.coroutineScope
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
internal fun ChunkEntity.deleteOnCascade() {
|
internal fun ChunkEntity.deleteOnCascade() {
|
||||||
|
@ -110,7 +109,6 @@ internal fun ChunkEntity.addTimelineEvent(roomId: String,
|
||||||
eventEntity: EventEntity,
|
eventEntity: EventEntity,
|
||||||
direction: PaginationDirection,
|
direction: PaginationDirection,
|
||||||
roomMemberContentsByUser: HashMap<String, RoomMemberContent?>) {
|
roomMemberContentsByUser: HashMap<String, RoomMemberContent?>) {
|
||||||
|
|
||||||
val eventId = eventEntity.eventId
|
val eventId = eventEntity.eventId
|
||||||
if (timelineEvents.find(eventId) != null) {
|
if (timelineEvents.find(eventId) != null) {
|
||||||
return
|
return
|
||||||
|
@ -134,8 +132,7 @@ internal fun ChunkEntity.addTimelineEvent(roomId: String,
|
||||||
this.senderName = roomMemberContent?.displayName
|
this.senderName = roomMemberContent?.displayName
|
||||||
if (roomMemberContent?.displayName != null) {
|
if (roomMemberContent?.displayName != null) {
|
||||||
val isHistoricalUnique = roomMemberContentsByUser.values.find {
|
val isHistoricalUnique = roomMemberContentsByUser.values.find {
|
||||||
roomMemberContent != it &&
|
it != roomMemberContent && it?.displayName == roomMemberContent.displayName
|
||||||
it?.displayName == roomMemberContent.displayName
|
|
||||||
} == null
|
} == null
|
||||||
isUniqueDisplayName = if (isLastForward) {
|
isUniqueDisplayName = if (isLastForward) {
|
||||||
val isLiveUnique = RoomMemberSummaryEntity
|
val isLiveUnique = RoomMemberSummaryEntity
|
||||||
|
|
|
@ -19,7 +19,6 @@ package im.vector.matrix.android.internal.database.model
|
||||||
|
|
||||||
import io.realm.RealmObject
|
import io.realm.RealmObject
|
||||||
import io.realm.annotations.Index
|
import io.realm.annotations.Index
|
||||||
import io.realm.annotations.PrimaryKey
|
|
||||||
|
|
||||||
internal open class CurrentStateEventEntity(var eventId: String = "",
|
internal open class CurrentStateEventEntity(var eventId: String = "",
|
||||||
var root: EventEntity? = null,
|
var root: EventEntity? = null,
|
||||||
|
|
|
@ -21,9 +21,7 @@ import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
||||||
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
import io.realm.RealmObject
|
import io.realm.RealmObject
|
||||||
import io.realm.RealmResults
|
|
||||||
import io.realm.annotations.Index
|
import io.realm.annotations.Index
|
||||||
import io.realm.annotations.LinkingObjects
|
|
||||||
import io.realm.annotations.PrimaryKey
|
import io.realm.annotations.PrimaryKey
|
||||||
|
|
||||||
internal open class EventEntity(@PrimaryKey var eventId: String = "",
|
internal open class EventEntity(@PrimaryKey var eventId: String = "",
|
||||||
|
|
|
@ -20,7 +20,6 @@ import io.realm.RealmObject
|
||||||
import io.realm.RealmResults
|
import io.realm.RealmResults
|
||||||
import io.realm.annotations.Index
|
import io.realm.annotations.Index
|
||||||
import io.realm.annotations.LinkingObjects
|
import io.realm.annotations.LinkingObjects
|
||||||
import io.realm.annotations.PrimaryKey
|
|
||||||
|
|
||||||
internal open class TimelineEventEntity(var localId: Long = 0,
|
internal open class TimelineEventEntity(var localId: Long = 0,
|
||||||
@Index var eventId: String = "",
|
@Index var eventId: String = "",
|
||||||
|
|
|
@ -29,7 +29,8 @@ internal fun CurrentStateEventEntity.Companion.where(realm: Realm, roomId: Strin
|
||||||
.equalTo(CurrentStateEventEntityFields.TYPE, type)
|
.equalTo(CurrentStateEventEntityFields.TYPE, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun CurrentStateEventEntity.Companion.whereStateKey(realm: Realm, roomId: String, type: String, stateKey: String): RealmQuery<CurrentStateEventEntity> {
|
internal fun CurrentStateEventEntity.Companion.whereStateKey(realm: Realm, roomId: String, type: String, stateKey: String)
|
||||||
|
: RealmQuery<CurrentStateEventEntity> {
|
||||||
return where(realm = realm, roomId = roomId, type = type)
|
return where(realm = realm, roomId = roomId, type = type)
|
||||||
.equalTo(CurrentStateEventEntityFields.STATE_KEY, stateKey)
|
.equalTo(CurrentStateEventEntityFields.STATE_KEY, stateKey)
|
||||||
}
|
}
|
||||||
|
@ -49,5 +50,3 @@ private fun create(realm: Realm, roomId: String, stateKey: String, type: String)
|
||||||
this.stateKey = stateKey
|
this.stateKey = stateKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,8 @@ internal fun TimelineEventEntity.Companion.where(realm: Realm, roomId: String, e
|
||||||
|
|
||||||
internal fun TimelineEventEntity.Companion.whereRoomId(realm: Realm,
|
internal fun TimelineEventEntity.Companion.whereRoomId(realm: Realm,
|
||||||
roomId: String): RealmQuery<TimelineEventEntity> {
|
roomId: String): RealmQuery<TimelineEventEntity> {
|
||||||
return realm.where<TimelineEventEntity>().equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
|
return realm.where<TimelineEventEntity>()
|
||||||
|
.equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(realm: Realm, senderMembershipEventId: String): List<TimelineEventEntity> {
|
internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(realm: Realm, senderMembershipEventId: String): List<TimelineEventEntity> {
|
||||||
|
|
|
@ -26,7 +26,6 @@ import im.vector.matrix.android.api.auth.AuthenticationService
|
||||||
import im.vector.matrix.android.internal.SessionManager
|
import im.vector.matrix.android.internal.SessionManager
|
||||||
import im.vector.matrix.android.internal.auth.AuthModule
|
import im.vector.matrix.android.internal.auth.AuthModule
|
||||||
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||||
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
|
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
|
|
|
@ -21,6 +21,7 @@ import android.content.Context
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.net.Network
|
import android.net.Network
|
||||||
|
import android.os.Build
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface NetworkCallbackStrategy {
|
internal interface NetworkCallbackStrategy {
|
||||||
|
@ -47,7 +48,7 @@ internal class FallbackNetworkCallbackStrategy @Inject constructor(private val c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(android.os.Build.VERSION_CODES.N)
|
@TargetApi(Build.VERSION_CODES.N)
|
||||||
internal class PreferredNetworkCallbackStrategy @Inject constructor(context: Context) : NetworkCallbackStrategy {
|
internal class PreferredNetworkCallbackStrategy @Inject constructor(context: Context) : NetworkCallbackStrategy {
|
||||||
|
|
||||||
private var hasChangedCallback: (() -> Unit)? = null
|
private var hasChangedCallback: (() -> Unit)? = null
|
||||||
|
|
|
@ -42,10 +42,10 @@ interface NetworkConnectivityChecker {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SessionScope
|
@SessionScope
|
||||||
internal class DefaultNetworkConnectivityChecker @Inject constructor(private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
internal class DefaultNetworkConnectivityChecker @Inject constructor(private val homeServerPinger: HomeServerPinger,
|
||||||
private val homeServerPinger: HomeServerPinger,
|
|
||||||
private val backgroundDetectionObserver: BackgroundDetectionObserver,
|
private val backgroundDetectionObserver: BackgroundDetectionObserver,
|
||||||
private val networkCallbackStrategy: NetworkCallbackStrategy) : NetworkConnectivityChecker {
|
private val networkCallbackStrategy: NetworkCallbackStrategy)
|
||||||
|
: NetworkConnectivityChecker {
|
||||||
|
|
||||||
private val hasInternetAccess = AtomicBoolean(true)
|
private val hasInternetAccess = AtomicBoolean(true)
|
||||||
private val listeners = Collections.synchronizedSet(LinkedHashSet<NetworkConnectivityChecker.Listener>())
|
private val listeners = Collections.synchronizedSet(LinkedHashSet<NetworkConnectivityChecker.Listener>())
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.matrix.android.internal.session
|
package im.vector.matrix.android.internal.session
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
|
@ -194,7 +195,7 @@ internal abstract class SessionModule {
|
||||||
fun providesNetworkCallbackStrategy(fallbackNetworkCallbackStrategy: Provider<FallbackNetworkCallbackStrategy>,
|
fun providesNetworkCallbackStrategy(fallbackNetworkCallbackStrategy: Provider<FallbackNetworkCallbackStrategy>,
|
||||||
preferredNetworkCallbackStrategy: Provider<PreferredNetworkCallbackStrategy>
|
preferredNetworkCallbackStrategy: Provider<PreferredNetworkCallbackStrategy>
|
||||||
): NetworkCallbackStrategy {
|
): NetworkCallbackStrategy {
|
||||||
return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
preferredNetworkCallbackStrategy.get()
|
preferredNetworkCallbackStrategy.get()
|
||||||
} else {
|
} else {
|
||||||
fallbackNetworkCallbackStrategy.get()
|
fallbackNetworkCallbackStrategy.get()
|
||||||
|
@ -241,4 +242,3 @@ internal abstract class SessionModule {
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindHomeServerCapabilitiesService(homeServerCapabilitiesService: DefaultHomeServerCapabilitiesService): HomeServerCapabilitiesService
|
abstract fun bindHomeServerCapabilitiesService(homeServerCapabilitiesService: DefaultHomeServerCapabilitiesService): HomeServerCapabilitiesService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,6 @@ internal interface CapabilitiesAPI {
|
||||||
/**
|
/**
|
||||||
* Request the versions
|
* Request the versions
|
||||||
*/
|
*/
|
||||||
@GET(NetworkConstants.URI_API_PREFIX_PATH_+"versions")
|
@GET(NetworkConstants.URI_API_PREFIX_PATH_ + "versions")
|
||||||
fun getVersions(): Call<Unit>
|
fun getVersions(): Call<Unit>
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ package im.vector.matrix.android.internal.session.homeserver
|
||||||
import im.vector.matrix.android.api.failure.Failure
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,6 @@ import im.vector.matrix.android.api.session.room.model.RoomAliasesContent
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent
|
import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomTopicContent
|
import im.vector.matrix.android.api.session.room.model.RoomTopicContent
|
||||||
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptionEventContent
|
|
||||||
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
||||||
import im.vector.matrix.android.internal.database.model.CurrentStateEventEntity
|
import im.vector.matrix.android.internal.database.model.CurrentStateEventEntity
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
|
@ -36,7 +35,6 @@ import im.vector.matrix.android.internal.database.query.getOrCreate
|
||||||
import im.vector.matrix.android.internal.database.query.getOrNull
|
import im.vector.matrix.android.internal.database.query.getOrNull
|
||||||
import im.vector.matrix.android.internal.database.query.isEventRead
|
import im.vector.matrix.android.internal.database.query.isEventRead
|
||||||
import im.vector.matrix.android.internal.database.query.latestEvent
|
import im.vector.matrix.android.internal.database.query.latestEvent
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
|
||||||
import im.vector.matrix.android.internal.database.query.whereType
|
import im.vector.matrix.android.internal.database.query.whereType
|
||||||
import im.vector.matrix.android.internal.di.UserId
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
import im.vector.matrix.android.internal.session.room.membership.RoomDisplayNameResolver
|
import im.vector.matrix.android.internal.session.room.membership.RoomDisplayNameResolver
|
||||||
|
|
|
@ -19,7 +19,6 @@ package im.vector.matrix.android.internal.session.room.draft
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import com.zhuinden.monarchy.Monarchy
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.room.send.DraftService
|
import im.vector.matrix.android.api.session.room.send.DraftService
|
||||||
import im.vector.matrix.android.api.session.room.send.UserDraft
|
import im.vector.matrix.android.api.session.room.send.UserDraft
|
||||||
|
|
|
@ -122,5 +122,4 @@ internal class DefaultReadService @AssistedInject constructor(
|
||||||
private fun ReadService.MarkAsReadParams.forceReadReceipt(): Boolean {
|
private fun ReadService.MarkAsReadParams.forceReadReceipt(): Boolean {
|
||||||
return this == ReadService.MarkAsReadParams.READ_RECEIPT || this == ReadService.MarkAsReadParams.BOTH
|
return this == ReadService.MarkAsReadParams.READ_RECEIPT || this == ReadService.MarkAsReadParams.BOTH
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,6 @@ import im.vector.matrix.android.internal.database.query.isReadMarkerMoreRecent
|
||||||
import im.vector.matrix.android.internal.database.query.latestEvent
|
import im.vector.matrix.android.internal.database.query.latestEvent
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.di.UserId
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
|
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.session.room.RoomAPI
|
import im.vector.matrix.android.internal.session.room.RoomAPI
|
||||||
import im.vector.matrix.android.internal.session.sync.ReadReceiptHandler
|
import im.vector.matrix.android.internal.session.sync.ReadReceiptHandler
|
||||||
|
|
|
@ -16,12 +16,10 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.session.room.send
|
package im.vector.matrix.android.internal.session.room.send
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.work.BackoffPolicy
|
import androidx.work.BackoffPolicy
|
||||||
import androidx.work.ExistingWorkPolicy
|
import androidx.work.ExistingWorkPolicy
|
||||||
import androidx.work.OneTimeWorkRequest
|
import androidx.work.OneTimeWorkRequest
|
||||||
import androidx.work.Operation
|
import androidx.work.Operation
|
||||||
import androidx.work.WorkManager
|
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
|
|
@ -48,7 +48,6 @@ import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultC
|
||||||
import im.vector.matrix.android.api.session.room.model.relation.ReplyToContent
|
import im.vector.matrix.android.api.session.room.model.relation.ReplyToContent
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
||||||
import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper
|
|
||||||
import im.vector.matrix.android.internal.di.UserId
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
import im.vector.matrix.android.internal.session.content.ThumbnailExtractor
|
import im.vector.matrix.android.internal.session.content.ThumbnailExtractor
|
||||||
import im.vector.matrix.android.internal.session.room.send.pills.TextPillsUtils
|
import im.vector.matrix.android.internal.session.room.send.pills.TextPillsUtils
|
||||||
|
@ -57,7 +56,6 @@ import im.vector.matrix.android.internal.util.StringProvider
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.commonmark.parser.Parser
|
import org.commonmark.parser.Parser
|
||||||
import org.commonmark.renderer.html.HtmlRenderer
|
import org.commonmark.renderer.html.HtmlRenderer
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -27,7 +27,6 @@ import im.vector.matrix.android.api.session.room.send.SendState
|
||||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
||||||
import im.vector.matrix.android.api.session.room.timeline.hasBeenEdited
|
|
||||||
import im.vector.matrix.android.api.util.CancelableBag
|
import im.vector.matrix.android.api.util.CancelableBag
|
||||||
import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper
|
import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper
|
||||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||||
|
@ -665,7 +664,6 @@ internal class DefaultTimeline(
|
||||||
val params = GetContextOfEventTask.Params(roomId, eventId)
|
val params = GetContextOfEventTask.Params(roomId, eventId)
|
||||||
cancelableBag += contextOfEventTask.configureWith(params) {
|
cancelableBag += contextOfEventTask.configureWith(params) {
|
||||||
callback = object : MatrixCallback<TokenChunkEventPersistor.Result> {
|
callback = object : MatrixCallback<TokenChunkEventPersistor.Result> {
|
||||||
|
|
||||||
override fun onSuccess(data: TokenChunkEventPersistor.Result) {
|
override fun onSuccess(data: TokenChunkEventPersistor.Result) {
|
||||||
postSnapshot()
|
postSnapshot()
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,6 @@ import im.vector.matrix.android.internal.database.helper.deleteOnCascade
|
||||||
import im.vector.matrix.android.internal.database.helper.merge
|
import im.vector.matrix.android.internal.database.helper.merge
|
||||||
import im.vector.matrix.android.internal.database.mapper.toEntity
|
import im.vector.matrix.android.internal.database.mapper.toEntity
|
||||||
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.CurrentStateEventEntityFields
|
|
||||||
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.RoomSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||||
|
@ -168,13 +167,19 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
|
||||||
|
|
||||||
private fun handleReachEnd(realm: Realm, roomId: String, direction: PaginationDirection, currentChunk: ChunkEntity) {
|
private fun handleReachEnd(realm: Realm, roomId: String, direction: PaginationDirection, currentChunk: ChunkEntity) {
|
||||||
Timber.v("Reach end of $roomId")
|
Timber.v("Reach end of $roomId")
|
||||||
|
roomId.isBlank()
|
||||||
if (direction == PaginationDirection.FORWARDS) {
|
if (direction == PaginationDirection.FORWARDS) {
|
||||||
val currentLiveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)
|
val currentLiveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)
|
||||||
if (currentChunk != currentLiveChunk) {
|
if (currentChunk != currentLiveChunk) {
|
||||||
currentChunk.isLastForward = true
|
currentChunk.isLastForward = true
|
||||||
currentLiveChunk?.deleteOnCascade()
|
currentLiveChunk?.deleteOnCascade()
|
||||||
RoomSummaryEntity.where(realm, roomId).findFirst()?.apply {
|
RoomSummaryEntity.where(realm, roomId).findFirst()?.apply {
|
||||||
latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true, filterTypes = RoomSummaryUpdater.PREVIEWABLE_TYPES)
|
latestPreviewableEvent = TimelineEventEntity.latestEvent(
|
||||||
|
realm,
|
||||||
|
roomId,
|
||||||
|
includesSending = true,
|
||||||
|
filterTypes = RoomSummaryUpdater.PREVIEWABLE_TYPES
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -237,7 +242,12 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
|
||||||
}
|
}
|
||||||
if (shouldUpdateSummary) {
|
if (shouldUpdateSummary) {
|
||||||
val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId)
|
val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId)
|
||||||
val latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true, filterTypes = RoomSummaryUpdater.PREVIEWABLE_TYPES)
|
val latestPreviewableEvent = TimelineEventEntity.latestEvent(
|
||||||
|
realm,
|
||||||
|
roomId,
|
||||||
|
includesSending = true,
|
||||||
|
filterTypes = RoomSummaryUpdater.PREVIEWABLE_TYPES
|
||||||
|
)
|
||||||
roomSummaryEntity.latestPreviewableEvent = latestPreviewableEvent
|
roomSummaryEntity.latestPreviewableEvent = latestPreviewableEvent
|
||||||
}
|
}
|
||||||
RoomEntity.where(realm, roomId).findFirst()?.addOrUpdate(currentChunk)
|
RoomEntity.where(realm, roomId).findFirst()?.addOrUpdate(currentChunk)
|
||||||
|
|
|
@ -18,7 +18,6 @@ package im.vector.matrix.android.internal.session.sync.job
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import im.vector.matrix.android.BuildConfig
|
|
||||||
import im.vector.matrix.android.api.Matrix
|
import im.vector.matrix.android.api.Matrix
|
||||||
import im.vector.matrix.android.api.failure.isTokenError
|
import im.vector.matrix.android.api.failure.isTokenError
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
|
|
@ -158,4 +158,5 @@ Formatter\.formatFileSize===1
|
||||||
Formatter\.formatShortFileSize===1
|
Formatter\.formatShortFileSize===1
|
||||||
|
|
||||||
### Use kotlin stdlib to test or compare strings
|
### Use kotlin stdlib to test or compare strings
|
||||||
android\.text\.TextUtils
|
# DISABLED
|
||||||
|
# android\.text\.TextUtils
|
||||||
|
|
|
@ -1,445 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2020 New Vector Ltd
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package im.vector.riotx.core.platform;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.TypedArray;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.text.Layout;
|
|
||||||
import android.text.Layout.Alignment;
|
|
||||||
import android.text.Spannable;
|
|
||||||
import android.text.SpannableString;
|
|
||||||
import android.text.SpannableStringBuilder;
|
|
||||||
import android.text.Spanned;
|
|
||||||
import android.text.StaticLayout;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.text.TextUtils.TruncateAt;
|
|
||||||
import android.text.style.ForegroundColorSpan;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.appcompat.widget.AppCompatTextView;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link android.widget.TextView} that ellipsizes more intelligently.
|
|
||||||
* This class supports ellipsizing multiline text through setting {@code android:ellipsize}
|
|
||||||
* and {@code android:maxLines}.
|
|
||||||
* <p/>
|
|
||||||
* Note: {@link android.text.TextUtils.TruncateAt#MARQUEE} ellipsizing type is not supported.
|
|
||||||
* This as to be used to get rid of the StaticLayout issue with maxLines and ellipsize causing some performance issues.
|
|
||||||
*/
|
|
||||||
public class EllipsizingTextView extends AppCompatTextView {
|
|
||||||
public static final int ELLIPSIZE_ALPHA = 0x88;
|
|
||||||
private SpannableString ELLIPSIS = new SpannableString("\u2026");
|
|
||||||
|
|
||||||
private static final Pattern DEFAULT_END_PUNCTUATION
|
|
||||||
= Pattern.compile("[\\.!?,;:\u2026]*$", Pattern.DOTALL);
|
|
||||||
private final List<EllipsizeListener> mEllipsizeListeners = new ArrayList<>();
|
|
||||||
private EllipsizeStrategy mEllipsizeStrategy;
|
|
||||||
private boolean isEllipsized;
|
|
||||||
private boolean isStale;
|
|
||||||
private boolean programmaticChange;
|
|
||||||
private CharSequence mFullText;
|
|
||||||
private int mMaxLines;
|
|
||||||
private float mLineSpacingMult = 1.0f;
|
|
||||||
private float mLineAddVertPad = 0.0f;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The end punctuation which will be removed when appending {@link #ELLIPSIS}.
|
|
||||||
*/
|
|
||||||
private Pattern mEndPunctPattern;
|
|
||||||
|
|
||||||
public EllipsizingTextView(Context context) {
|
|
||||||
this(context, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public EllipsizingTextView(Context context, AttributeSet attrs) {
|
|
||||||
this(context, attrs, android.R.attr.textViewStyle);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public EllipsizingTextView(Context context, AttributeSet attrs, int defStyle) {
|
|
||||||
super(context, attrs, defStyle);
|
|
||||||
TypedArray a = context.obtainStyledAttributes(attrs,
|
|
||||||
new int[]{android.R.attr.maxLines, android.R.attr.ellipsize}, defStyle, 0);
|
|
||||||
setMaxLines(a.getInt(0, Integer.MAX_VALUE));
|
|
||||||
a.recycle();
|
|
||||||
setEndPunctuationPattern(DEFAULT_END_PUNCTUATION);
|
|
||||||
final int currentTextColor = getCurrentTextColor();
|
|
||||||
final int ellipsizeColor = Color.argb(ELLIPSIZE_ALPHA, Color.red(currentTextColor), Color.green(currentTextColor), Color.blue(currentTextColor));
|
|
||||||
ELLIPSIS.setSpan(new ForegroundColorSpan(ellipsizeColor), 0, ELLIPSIS.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEndPunctuationPattern(Pattern pattern) {
|
|
||||||
mEndPunctPattern = pattern;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void addEllipsizeListener(@NonNull EllipsizeListener listener) {
|
|
||||||
mEllipsizeListeners.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void removeEllipsizeListener(@NonNull EllipsizeListener listener) {
|
|
||||||
mEllipsizeListeners.remove(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public boolean isEllipsized() {
|
|
||||||
return isEllipsized;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The maximum number of lines displayed in this {@link android.widget.TextView}.
|
|
||||||
*/
|
|
||||||
public int getMaxLines() {
|
|
||||||
return mMaxLines;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setMaxLines(int maxLines) {
|
|
||||||
super.setMaxLines(maxLines);
|
|
||||||
mMaxLines = maxLines;
|
|
||||||
isStale = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines if the last fully visible line is being ellipsized.
|
|
||||||
*
|
|
||||||
* @return {@code true} if the last fully visible line is being ellipsized;
|
|
||||||
* otherwise, returns {@code false}.
|
|
||||||
*/
|
|
||||||
public boolean ellipsizingLastFullyVisibleLine() {
|
|
||||||
return mMaxLines == Integer.MAX_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setLineSpacing(float add, float mult) {
|
|
||||||
mLineAddVertPad = add;
|
|
||||||
mLineSpacingMult = mult;
|
|
||||||
super.setLineSpacing(add, mult);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setText(CharSequence text, BufferType type) {
|
|
||||||
if (!programmaticChange) {
|
|
||||||
mFullText = text instanceof Spanned ? (Spanned) text : text;
|
|
||||||
isStale = true;
|
|
||||||
}
|
|
||||||
super.setText(text, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
|
||||||
super.onSizeChanged(w, h, oldw, oldh);
|
|
||||||
if (ellipsizingLastFullyVisibleLine()) {
|
|
||||||
isStale = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setPadding(int left, int top, int right, int bottom) {
|
|
||||||
super.setPadding(left, top, right, bottom);
|
|
||||||
if (ellipsizingLastFullyVisibleLine()) {
|
|
||||||
isStale = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDraw(@NonNull Canvas canvas) {
|
|
||||||
if (isStale) {
|
|
||||||
resetText();
|
|
||||||
}
|
|
||||||
super.onDraw(canvas);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the ellipsized text if appropriate.
|
|
||||||
*/
|
|
||||||
private void resetText() {
|
|
||||||
int maxLines = getMaxLines();
|
|
||||||
CharSequence workingText = mFullText;
|
|
||||||
boolean ellipsized = false;
|
|
||||||
|
|
||||||
if (maxLines != -1) {
|
|
||||||
if (mEllipsizeStrategy == null) setEllipsize(null);
|
|
||||||
workingText = mEllipsizeStrategy.processText(mFullText);
|
|
||||||
ellipsized = !mEllipsizeStrategy.isInLayout(mFullText);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!workingText.equals(getText())) {
|
|
||||||
programmaticChange = true;
|
|
||||||
try {
|
|
||||||
setText(workingText);
|
|
||||||
} finally {
|
|
||||||
programmaticChange = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isStale = false;
|
|
||||||
if (ellipsized != isEllipsized) {
|
|
||||||
isEllipsized = ellipsized;
|
|
||||||
for (EllipsizeListener listener : mEllipsizeListeners) {
|
|
||||||
listener.ellipsizeStateChanged(ellipsized);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Causes words in the text that are longer than the view is wide to be ellipsized
|
|
||||||
* instead of broken in the middle. Use {@code null} to turn off ellipsizing.
|
|
||||||
* <p/>
|
|
||||||
* Note: Method does nothing for {@link android.text.TextUtils.TruncateAt#MARQUEE}
|
|
||||||
* ellipsizing type.
|
|
||||||
*
|
|
||||||
* @param where part of text to ellipsize
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void setEllipsize(TruncateAt where) {
|
|
||||||
if (where == null) {
|
|
||||||
mEllipsizeStrategy = new EllipsizeNoneStrategy();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (where) {
|
|
||||||
case END:
|
|
||||||
mEllipsizeStrategy = new EllipsizeEndStrategy();
|
|
||||||
break;
|
|
||||||
case START:
|
|
||||||
mEllipsizeStrategy = new EllipsizeStartStrategy();
|
|
||||||
break;
|
|
||||||
case MIDDLE:
|
|
||||||
mEllipsizeStrategy = new EllipsizeMiddleStrategy();
|
|
||||||
break;
|
|
||||||
case MARQUEE:
|
|
||||||
default:
|
|
||||||
mEllipsizeStrategy = new EllipsizeNoneStrategy();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A listener that notifies when the ellipsize state has changed.
|
|
||||||
*/
|
|
||||||
public interface EllipsizeListener {
|
|
||||||
void ellipsizeStateChanged(boolean ellipsized);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A base class for an ellipsize strategy.
|
|
||||||
*/
|
|
||||||
private abstract class EllipsizeStrategy {
|
|
||||||
/**
|
|
||||||
* Returns ellipsized text if the text does not fit inside of the layout;
|
|
||||||
* otherwise, returns the full text.
|
|
||||||
*
|
|
||||||
* @param text text to process
|
|
||||||
* @return Ellipsized text if the text does not fit inside of the layout;
|
|
||||||
* otherwise, returns the full text.
|
|
||||||
*/
|
|
||||||
public CharSequence processText(CharSequence text) {
|
|
||||||
return !isInLayout(text) ? createEllipsizedText(text) : text;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines if the text fits inside of the layout.
|
|
||||||
*
|
|
||||||
* @param text text to fit
|
|
||||||
* @return {@code true} if the text fits inside of the layout;
|
|
||||||
* otherwise, returns {@code false}.
|
|
||||||
*/
|
|
||||||
public boolean isInLayout(CharSequence text) {
|
|
||||||
Layout layout = createWorkingLayout(text);
|
|
||||||
return layout.getLineCount() <= getLinesCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a working layout with the given text.
|
|
||||||
*
|
|
||||||
* @param workingText text to create layout with
|
|
||||||
* @return {@link android.text.Layout} with the given text.
|
|
||||||
*/
|
|
||||||
protected Layout createWorkingLayout(CharSequence workingText) {
|
|
||||||
return new StaticLayout(workingText, getPaint(),
|
|
||||||
getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight(),
|
|
||||||
Alignment.ALIGN_NORMAL, mLineSpacingMult,
|
|
||||||
mLineAddVertPad, false /* includepad */);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get how many lines of text we are allowed to display.
|
|
||||||
*/
|
|
||||||
protected int getLinesCount() {
|
|
||||||
if (ellipsizingLastFullyVisibleLine()) {
|
|
||||||
int fullyVisibleLinesCount = getFullyVisibleLinesCount();
|
|
||||||
return fullyVisibleLinesCount == -1 ? 1 : fullyVisibleLinesCount;
|
|
||||||
} else {
|
|
||||||
return mMaxLines;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get how many lines of text we can display so their full height is visible.
|
|
||||||
*/
|
|
||||||
protected int getFullyVisibleLinesCount() {
|
|
||||||
Layout layout = createWorkingLayout("");
|
|
||||||
int height = getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
|
|
||||||
int lineHeight = layout.getLineBottom(0);
|
|
||||||
return height / lineHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates ellipsized text from the given text.
|
|
||||||
*
|
|
||||||
* @param fullText text to ellipsize
|
|
||||||
* @return Ellipsized text
|
|
||||||
*/
|
|
||||||
protected abstract CharSequence createEllipsizedText(CharSequence fullText);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An {@link EllipsizingTextView.EllipsizeStrategy} that
|
|
||||||
* does not ellipsize text.
|
|
||||||
*/
|
|
||||||
private class EllipsizeNoneStrategy extends EllipsizeStrategy {
|
|
||||||
@Override
|
|
||||||
protected CharSequence createEllipsizedText(CharSequence fullText) {
|
|
||||||
return fullText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An {@link EllipsizingTextView.EllipsizeStrategy} that
|
|
||||||
* ellipsizes text at the end.
|
|
||||||
*/
|
|
||||||
private class EllipsizeEndStrategy extends EllipsizeStrategy {
|
|
||||||
@Override
|
|
||||||
protected CharSequence createEllipsizedText(CharSequence fullText) {
|
|
||||||
Layout layout = createWorkingLayout(fullText);
|
|
||||||
int cutOffIndex = layout.getLineEnd(mMaxLines - 1);
|
|
||||||
int textLength = fullText.length();
|
|
||||||
int cutOffLength = textLength - cutOffIndex;
|
|
||||||
if (cutOffLength < ELLIPSIS.length()) cutOffLength = ELLIPSIS.length();
|
|
||||||
CharSequence workingText = TextUtils.substring(fullText, 0, textLength - cutOffLength).trim();
|
|
||||||
|
|
||||||
while (!isInLayout(TextUtils.concat(stripEndPunctuation(workingText), ELLIPSIS))) {
|
|
||||||
int lastSpace = TextUtils.lastIndexOf(workingText, ' ');
|
|
||||||
if (lastSpace == -1) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
workingText = TextUtils.substring(workingText, 0, lastSpace).trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
workingText = TextUtils.concat(stripEndPunctuation(workingText), ELLIPSIS);
|
|
||||||
SpannableStringBuilder dest = new SpannableStringBuilder(workingText);
|
|
||||||
|
|
||||||
if (fullText instanceof Spanned) {
|
|
||||||
TextUtils.copySpansFrom((Spanned) fullText, 0, workingText.length(), null, dest, 0);
|
|
||||||
}
|
|
||||||
return dest;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Strips the end punctuation from a given text according to {@link #mEndPunctPattern}.
|
|
||||||
*
|
|
||||||
* @param workingText text to strip end punctuation from
|
|
||||||
* @return Text without end punctuation.
|
|
||||||
*/
|
|
||||||
public String stripEndPunctuation(CharSequence workingText) {
|
|
||||||
return mEndPunctPattern.matcher(workingText).replaceFirst("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An {@link EllipsizingTextView.EllipsizeStrategy} that
|
|
||||||
* ellipsizes text at the start.
|
|
||||||
*/
|
|
||||||
private class EllipsizeStartStrategy extends EllipsizeStrategy {
|
|
||||||
@Override
|
|
||||||
protected CharSequence createEllipsizedText(CharSequence fullText) {
|
|
||||||
Layout layout = createWorkingLayout(fullText);
|
|
||||||
int cutOffIndex = layout.getLineEnd(mMaxLines - 1);
|
|
||||||
int textLength = fullText.length();
|
|
||||||
int cutOffLength = textLength - cutOffIndex;
|
|
||||||
if (cutOffLength < ELLIPSIS.length()) cutOffLength = ELLIPSIS.length();
|
|
||||||
CharSequence workingText = TextUtils.substring(fullText, cutOffLength, textLength).trim();
|
|
||||||
|
|
||||||
while (!isInLayout(TextUtils.concat(ELLIPSIS, workingText))) {
|
|
||||||
int firstSpace = TextUtils.indexOf(workingText, ' ');
|
|
||||||
if (firstSpace == -1) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
workingText = TextUtils.substring(workingText, firstSpace, workingText.length()).trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
workingText = TextUtils.concat(ELLIPSIS, workingText);
|
|
||||||
SpannableStringBuilder dest = new SpannableStringBuilder(workingText);
|
|
||||||
|
|
||||||
if (fullText instanceof Spanned) {
|
|
||||||
TextUtils.copySpansFrom((Spanned) fullText, textLength - workingText.length(),
|
|
||||||
textLength, null, dest, 0);
|
|
||||||
}
|
|
||||||
return dest;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An {@link EllipsizingTextView.EllipsizeStrategy} that
|
|
||||||
* ellipsizes text in the middle.
|
|
||||||
*/
|
|
||||||
private class EllipsizeMiddleStrategy extends EllipsizeStrategy {
|
|
||||||
@Override
|
|
||||||
protected CharSequence createEllipsizedText(CharSequence fullText) {
|
|
||||||
Layout layout = createWorkingLayout(fullText);
|
|
||||||
int cutOffIndex = layout.getLineEnd(mMaxLines - 1);
|
|
||||||
int textLength = fullText.length();
|
|
||||||
int cutOffLength = textLength - cutOffIndex;
|
|
||||||
if (cutOffLength < ELLIPSIS.length()) cutOffLength = ELLIPSIS.length();
|
|
||||||
cutOffLength += cutOffIndex % 2; // Make it even.
|
|
||||||
String firstPart = TextUtils.substring(
|
|
||||||
fullText, 0, textLength / 2 - cutOffLength / 2).trim();
|
|
||||||
String secondPart = TextUtils.substring(
|
|
||||||
fullText, textLength / 2 + cutOffLength / 2, textLength).trim();
|
|
||||||
|
|
||||||
while (!isInLayout(TextUtils.concat(firstPart, ELLIPSIS, secondPart))) {
|
|
||||||
int lastSpaceFirstPart = firstPart.lastIndexOf(' ');
|
|
||||||
int firstSpaceSecondPart = secondPart.indexOf(' ');
|
|
||||||
if (lastSpaceFirstPart == -1 || firstSpaceSecondPart == -1) break;
|
|
||||||
firstPart = firstPart.substring(0, lastSpaceFirstPart).trim();
|
|
||||||
secondPart = secondPart.substring(firstSpaceSecondPart, secondPart.length()).trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
SpannableStringBuilder firstDest = new SpannableStringBuilder(firstPart);
|
|
||||||
SpannableStringBuilder secondDest = new SpannableStringBuilder(secondPart);
|
|
||||||
|
|
||||||
if (fullText instanceof Spanned) {
|
|
||||||
TextUtils.copySpansFrom((Spanned) fullText, 0, firstPart.length(),
|
|
||||||
null, firstDest, 0);
|
|
||||||
TextUtils.copySpansFrom((Spanned) fullText, textLength - secondPart.length(),
|
|
||||||
textLength, null, secondDest, 0);
|
|
||||||
}
|
|
||||||
return TextUtils.concat(firstDest, ELLIPSIS, secondDest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,398 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package im.vector.riotx.core.platform
|
||||||
|
|
||||||
|
import android.R
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.text.Layout
|
||||||
|
import android.text.Spannable
|
||||||
|
import android.text.SpannableString
|
||||||
|
import android.text.SpannableStringBuilder
|
||||||
|
import android.text.Spanned
|
||||||
|
import android.text.StaticLayout
|
||||||
|
import android.text.TextUtils.*
|
||||||
|
import android.text.style.ForegroundColorSpan
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import androidx.appcompat.widget.AppCompatTextView
|
||||||
|
import java.util.ArrayList
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [android.widget.TextView] that ellipsizes more intelligently.
|
||||||
|
* This class supports ellipsizing multiline text through setting `android:ellipsize`
|
||||||
|
* and `android:maxLines`.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Note: [TruncateAt.MARQUEE] ellipsizing type is not supported.
|
||||||
|
* This as to be used to get rid of the StaticLayout issue with maxLines and ellipsize causing some performance issues.
|
||||||
|
*/
|
||||||
|
class EllipsizingTextView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = R.attr.textViewStyle)
|
||||||
|
: AppCompatTextView(context, attrs, defStyle) {
|
||||||
|
|
||||||
|
private val ELLIPSIS = SpannableString("\u2026")
|
||||||
|
private val ellipsizeListeners: MutableList<EllipsizeListener> = ArrayList()
|
||||||
|
private var ellipsizeStrategy: EllipsizeStrategy? = null
|
||||||
|
var isEllipsized = false
|
||||||
|
private set
|
||||||
|
private var isStale = false
|
||||||
|
private var programmaticChange = false
|
||||||
|
private var fullText: CharSequence? = null
|
||||||
|
private var maxLines = 0
|
||||||
|
private var lineSpacingMult = 1.0f
|
||||||
|
private var lineAddVertPad = 0.0f
|
||||||
|
/**
|
||||||
|
* The end punctuation which will be removed when appending [.ELLIPSIS].
|
||||||
|
*/
|
||||||
|
private var mEndPunctPattern: Pattern? = null
|
||||||
|
|
||||||
|
fun setEndPunctuationPattern(pattern: Pattern?) {
|
||||||
|
mEndPunctPattern = pattern
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addEllipsizeListener(listener: EllipsizeListener) {
|
||||||
|
ellipsizeListeners.add(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeEllipsizeListener(listener: EllipsizeListener) {
|
||||||
|
ellipsizeListeners.remove(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The maximum number of lines displayed in this [android.widget.TextView].
|
||||||
|
*/
|
||||||
|
override fun getMaxLines(): Int {
|
||||||
|
return maxLines
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setMaxLines(maxLines: Int) {
|
||||||
|
super.setMaxLines(maxLines)
|
||||||
|
this.maxLines = maxLines
|
||||||
|
isStale = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the last fully visible line is being ellipsized.
|
||||||
|
*
|
||||||
|
* @return `true` if the last fully visible line is being ellipsized;
|
||||||
|
* otherwise, returns `false`.
|
||||||
|
*/
|
||||||
|
fun ellipsizingLastFullyVisibleLine(): Boolean {
|
||||||
|
return maxLines == Int.MAX_VALUE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setLineSpacing(add: Float, mult: Float) {
|
||||||
|
lineAddVertPad = add
|
||||||
|
lineSpacingMult = mult
|
||||||
|
super.setLineSpacing(add, mult)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setText(text: CharSequence, type: BufferType) {
|
||||||
|
if (!programmaticChange) {
|
||||||
|
fullText = if (text is Spanned) text else text
|
||||||
|
isStale = true
|
||||||
|
}
|
||||||
|
super.setText(text, type)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||||
|
super.onSizeChanged(w, h, oldw, oldh)
|
||||||
|
if (ellipsizingLastFullyVisibleLine()) {
|
||||||
|
isStale = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setPadding(left: Int, top: Int, right: Int, bottom: Int) {
|
||||||
|
super.setPadding(left, top, right, bottom)
|
||||||
|
if (ellipsizingLastFullyVisibleLine()) {
|
||||||
|
isStale = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDraw(canvas: Canvas) {
|
||||||
|
if (isStale) {
|
||||||
|
resetText()
|
||||||
|
}
|
||||||
|
super.onDraw(canvas)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the ellipsized text if appropriate.
|
||||||
|
*/
|
||||||
|
private fun resetText() {
|
||||||
|
val maxLines = maxLines
|
||||||
|
var workingText = fullText
|
||||||
|
var ellipsized = false
|
||||||
|
if (maxLines != -1) {
|
||||||
|
if (ellipsizeStrategy == null) setEllipsize(null)
|
||||||
|
workingText = ellipsizeStrategy!!.processText(fullText)
|
||||||
|
ellipsized = !ellipsizeStrategy!!.isInLayout(fullText)
|
||||||
|
}
|
||||||
|
if (workingText != text) {
|
||||||
|
programmaticChange = true
|
||||||
|
text = try {
|
||||||
|
workingText
|
||||||
|
} finally {
|
||||||
|
programmaticChange = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isStale = false
|
||||||
|
if (ellipsized != isEllipsized) {
|
||||||
|
isEllipsized = ellipsized
|
||||||
|
for (listener in ellipsizeListeners) {
|
||||||
|
listener.ellipsizeStateChanged(ellipsized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Causes words in the text that are longer than the view is wide to be ellipsized
|
||||||
|
* instead of broken in the middle. Use `null` to turn off ellipsizing.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Note: Method does nothing for [TruncateAt.MARQUEE]
|
||||||
|
* ellipsizing type.
|
||||||
|
*
|
||||||
|
* @param where part of text to ellipsize
|
||||||
|
*/
|
||||||
|
override fun setEllipsize(where: TruncateAt?) {
|
||||||
|
if (where == null) {
|
||||||
|
ellipsizeStrategy = EllipsizeNoneStrategy()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ellipsizeStrategy = when (where) {
|
||||||
|
TruncateAt.END -> EllipsizeEndStrategy()
|
||||||
|
TruncateAt.START -> EllipsizeStartStrategy()
|
||||||
|
TruncateAt.MIDDLE -> EllipsizeMiddleStrategy()
|
||||||
|
TruncateAt.MARQUEE -> EllipsizeNoneStrategy()
|
||||||
|
else -> EllipsizeNoneStrategy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A listener that notifies when the ellipsize state has changed.
|
||||||
|
*/
|
||||||
|
interface EllipsizeListener {
|
||||||
|
fun ellipsizeStateChanged(ellipsized: Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A base class for an ellipsize strategy.
|
||||||
|
*/
|
||||||
|
private abstract inner class EllipsizeStrategy {
|
||||||
|
/**
|
||||||
|
* Returns ellipsized text if the text does not fit inside of the layout;
|
||||||
|
* otherwise, returns the full text.
|
||||||
|
*
|
||||||
|
* @param text text to process
|
||||||
|
* @return Ellipsized text if the text does not fit inside of the layout;
|
||||||
|
* otherwise, returns the full text.
|
||||||
|
*/
|
||||||
|
fun processText(text: CharSequence?): CharSequence? {
|
||||||
|
return if (!isInLayout(text)) createEllipsizedText(text) else text
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the text fits inside of the layout.
|
||||||
|
*
|
||||||
|
* @param text text to fit
|
||||||
|
* @return `true` if the text fits inside of the layout;
|
||||||
|
* otherwise, returns `false`.
|
||||||
|
*/
|
||||||
|
fun isInLayout(text: CharSequence?): Boolean {
|
||||||
|
val layout = createWorkingLayout(text)
|
||||||
|
return layout.lineCount <= linesCount
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a working layout with the given text.
|
||||||
|
*
|
||||||
|
* @param workingText text to create layout with
|
||||||
|
* @return [android.text.Layout] with the given text.
|
||||||
|
*/
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
protected fun createWorkingLayout(workingText: CharSequence?): Layout {
|
||||||
|
return StaticLayout(
|
||||||
|
workingText,
|
||||||
|
paint,
|
||||||
|
width - compoundPaddingLeft - compoundPaddingRight,
|
||||||
|
Layout.Alignment.ALIGN_NORMAL,
|
||||||
|
lineSpacingMult,
|
||||||
|
lineAddVertPad,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get how many lines of text we are allowed to display.
|
||||||
|
*/
|
||||||
|
protected val linesCount: Int
|
||||||
|
get() = if (ellipsizingLastFullyVisibleLine()) {
|
||||||
|
val fullyVisibleLinesCount = fullyVisibleLinesCount
|
||||||
|
if (fullyVisibleLinesCount == -1) 1 else fullyVisibleLinesCount
|
||||||
|
} else {
|
||||||
|
maxLines
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get how many lines of text we can display so their full height is visible.
|
||||||
|
*/
|
||||||
|
protected val fullyVisibleLinesCount: Int
|
||||||
|
get() {
|
||||||
|
val layout = createWorkingLayout("")
|
||||||
|
val height = height - compoundPaddingTop - compoundPaddingBottom
|
||||||
|
val lineHeight = layout.getLineBottom(0)
|
||||||
|
return height / lineHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates ellipsized text from the given text.
|
||||||
|
*
|
||||||
|
* @param fullText text to ellipsize
|
||||||
|
* @return Ellipsized text
|
||||||
|
*/
|
||||||
|
protected abstract fun createEllipsizedText(fullText: CharSequence?): CharSequence?
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An [EllipsizingTextView.EllipsizeStrategy] that
|
||||||
|
* does not ellipsize text.
|
||||||
|
*/
|
||||||
|
private inner class EllipsizeNoneStrategy : EllipsizeStrategy() {
|
||||||
|
override fun createEllipsizedText(fullText: CharSequence?): CharSequence? {
|
||||||
|
return fullText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An [EllipsizingTextView.EllipsizeStrategy] that
|
||||||
|
* ellipsizes text at the end.
|
||||||
|
*/
|
||||||
|
private inner class EllipsizeEndStrategy : EllipsizeStrategy() {
|
||||||
|
override fun createEllipsizedText(fullText: CharSequence?): CharSequence? {
|
||||||
|
val layout = createWorkingLayout(fullText)
|
||||||
|
val cutOffIndex = layout.getLineEnd(maxLines - 1)
|
||||||
|
val textLength = fullText!!.length
|
||||||
|
var cutOffLength = textLength - cutOffIndex
|
||||||
|
if (cutOffLength < ELLIPSIS.length) cutOffLength = ELLIPSIS.length
|
||||||
|
var workingText: CharSequence = substring(fullText, 0, textLength - cutOffLength).trim()
|
||||||
|
while (!isInLayout(concat(stripEndPunctuation(workingText), ELLIPSIS))) {
|
||||||
|
val lastSpace = lastIndexOf(workingText, ' ')
|
||||||
|
if (lastSpace == -1) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
workingText = substring(workingText, 0, lastSpace).trim()
|
||||||
|
}
|
||||||
|
workingText = concat(stripEndPunctuation(workingText), ELLIPSIS)
|
||||||
|
val dest = SpannableStringBuilder(workingText)
|
||||||
|
if (fullText is Spanned) {
|
||||||
|
copySpansFrom(fullText as Spanned?, 0, workingText.length, null, dest, 0)
|
||||||
|
}
|
||||||
|
return dest
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strips the end punctuation from a given text according to [.mEndPunctPattern].
|
||||||
|
*
|
||||||
|
* @param workingText text to strip end punctuation from
|
||||||
|
* @return Text without end punctuation.
|
||||||
|
*/
|
||||||
|
fun stripEndPunctuation(workingText: CharSequence?): String {
|
||||||
|
return mEndPunctPattern!!.matcher(workingText).replaceFirst("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An [EllipsizingTextView.EllipsizeStrategy] that
|
||||||
|
* ellipsizes text at the start.
|
||||||
|
*/
|
||||||
|
private inner class EllipsizeStartStrategy : EllipsizeStrategy() {
|
||||||
|
override fun createEllipsizedText(fullText: CharSequence?): CharSequence? {
|
||||||
|
val layout = createWorkingLayout(fullText)
|
||||||
|
val cutOffIndex = layout.getLineEnd(maxLines - 1)
|
||||||
|
val textLength = fullText!!.length
|
||||||
|
var cutOffLength = textLength - cutOffIndex
|
||||||
|
if (cutOffLength < ELLIPSIS.length) cutOffLength = ELLIPSIS.length
|
||||||
|
var workingText: CharSequence = substring(fullText, cutOffLength, textLength).trim()
|
||||||
|
while (!isInLayout(concat(ELLIPSIS, workingText))) {
|
||||||
|
val firstSpace = indexOf(workingText, ' ')
|
||||||
|
if (firstSpace == -1) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
workingText = substring(workingText, firstSpace, workingText.length).trim()
|
||||||
|
}
|
||||||
|
workingText = concat(ELLIPSIS, workingText)
|
||||||
|
val dest = SpannableStringBuilder(workingText)
|
||||||
|
if (fullText is Spanned) {
|
||||||
|
copySpansFrom(fullText as Spanned?, textLength - workingText.length,
|
||||||
|
textLength, null, dest, 0)
|
||||||
|
}
|
||||||
|
return dest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An [EllipsizingTextView.EllipsizeStrategy] that
|
||||||
|
* ellipsizes text in the middle.
|
||||||
|
*/
|
||||||
|
private inner class EllipsizeMiddleStrategy : EllipsizeStrategy() {
|
||||||
|
override fun createEllipsizedText(fullText: CharSequence?): CharSequence? {
|
||||||
|
val layout = createWorkingLayout(fullText)
|
||||||
|
val cutOffIndex = layout.getLineEnd(maxLines - 1)
|
||||||
|
val textLength = fullText!!.length
|
||||||
|
var cutOffLength = textLength - cutOffIndex
|
||||||
|
if (cutOffLength < ELLIPSIS.length) cutOffLength = ELLIPSIS.length
|
||||||
|
cutOffLength += cutOffIndex % 2 // Make it even.
|
||||||
|
var firstPart = substring(
|
||||||
|
fullText, 0, textLength / 2 - cutOffLength / 2).trim()
|
||||||
|
var secondPart = substring(
|
||||||
|
fullText, textLength / 2 + cutOffLength / 2, textLength).trim()
|
||||||
|
while (!isInLayout(concat(firstPart, ELLIPSIS, secondPart))) {
|
||||||
|
val lastSpaceFirstPart = firstPart.lastIndexOf(' ')
|
||||||
|
val firstSpaceSecondPart = secondPart.indexOf(' ')
|
||||||
|
if (lastSpaceFirstPart == -1 || firstSpaceSecondPart == -1) break
|
||||||
|
firstPart = firstPart.substring(0, lastSpaceFirstPart).trim()
|
||||||
|
secondPart = secondPart.substring(firstSpaceSecondPart, secondPart.length).trim()
|
||||||
|
}
|
||||||
|
val firstDest = SpannableStringBuilder(firstPart)
|
||||||
|
val secondDest = SpannableStringBuilder(secondPart)
|
||||||
|
if (fullText is Spanned) {
|
||||||
|
copySpansFrom(fullText as Spanned?, 0, firstPart.length,
|
||||||
|
null, firstDest, 0)
|
||||||
|
copySpansFrom(fullText as Spanned?, textLength - secondPart.length,
|
||||||
|
textLength, null, secondDest, 0)
|
||||||
|
}
|
||||||
|
return concat(firstDest, ELLIPSIS, secondDest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ELLIPSIZE_ALPHA = 0x88
|
||||||
|
private val DEFAULT_END_PUNCTUATION = Pattern.compile("[.!?,;:\u2026]*$", Pattern.DOTALL)
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
val a = context.obtainStyledAttributes(attrs, intArrayOf(R.attr.maxLines, R.attr.ellipsize), defStyle, 0)
|
||||||
|
maxLines = a.getInt(0, Int.MAX_VALUE)
|
||||||
|
a.recycle()
|
||||||
|
setEndPunctuationPattern(DEFAULT_END_PUNCTUATION)
|
||||||
|
val currentTextColor = currentTextColor
|
||||||
|
val ellipsizeColor = Color.argb(ELLIPSIZE_ALPHA, Color.red(currentTextColor), Color.green(currentTextColor), Color.blue(currentTextColor))
|
||||||
|
ELLIPSIS.setSpan(ForegroundColorSpan(ellipsizeColor), 0, ELLIPSIS.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,7 +33,6 @@ import androidx.annotation.MainThread
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.ViewModelProviders
|
|
||||||
import butterknife.ButterKnife
|
import butterknife.ButterKnife
|
||||||
import butterknife.Unbinder
|
import butterknife.Unbinder
|
||||||
import com.airbnb.mvrx.BaseMvRxFragment
|
import com.airbnb.mvrx.BaseMvRxFragment
|
||||||
|
|
|
@ -74,7 +74,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
|
||||||
.combineLatest<List<RoomMemberSummary>, PowerLevelsContent, RoomMemberSummaries>(
|
.combineLatest<List<RoomMemberSummary>, PowerLevelsContent, RoomMemberSummaries>(
|
||||||
room.rx(session).liveRoomMembers(roomMemberQueryParams),
|
room.rx(session).liveRoomMembers(roomMemberQueryParams),
|
||||||
room.rx(session)
|
room.rx(session)
|
||||||
.liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS,"")
|
.liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, "")
|
||||||
.mapOptional { it.content.toModel<PowerLevelsContent>() }
|
.mapOptional { it.content.toModel<PowerLevelsContent>() }
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
BiFunction { roomMembers, powerLevelsContent ->
|
BiFunction { roomMembers, powerLevelsContent ->
|
||||||
|
|
Loading…
Reference in a new issue