Compare commits

...

6 Commits

  1. 20
      app/src/main/java/com/dfsek/feldspar/ui/RoomActivity.kt
  2. 12
      app/src/main/java/com/dfsek/feldspar/ui/SpacesUI.kt
  3. 2
      app/src/main/java/com/dfsek/feldspar/ui/rooms/DirectMessagesActivity.kt
  4. 2
      app/src/main/java/com/dfsek/feldspar/ui/rooms/GroupMessagesActivity.kt
  5. 76
      app/src/main/java/com/dfsek/feldspar/ui/rooms/RoomsUI.kt
  6. 4
      app/src/main/java/com/dfsek/feldspar/ui/settings/SettingsActivity.kt
  7. 5
      app/src/main/java/com/dfsek/feldspar/ui/settings/ThemeActivity.kt
  8. 2
      app/src/main/java/com/dfsek/feldspar/ui/settings/VerificationActivity.kt
  9. 11
      app/src/main/java/com/dfsek/feldspar/util/TimelineEventWrapper.kt
  10. 7
      app/src/main/java/com/dfsek/feldspar/util/Utils.kt

@ -27,6 +27,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
@ -48,6 +49,8 @@ import coil.request.ImageRequest
import com.dfsek.feldspar.AppState
import com.dfsek.feldspar.state.ChatRoomState
import com.dfsek.feldspar.ui.rooms.Avatar
import com.dfsek.feldspar.ui.rooms.LastOnline
import com.dfsek.feldspar.ui.rooms.OnlineIndicator
import com.dfsek.feldspar.util.*
import com.dfsek.feldspar.util.compose_fixes.FixedDefaultFlingBehavior.Companion.fixedFlingBehavior
import com.dfsek.feldspar.util.vector.multipicker.toContentAttachmentData
@ -217,7 +220,22 @@ class RoomActivity : AppCompatActivity() {
Box(modifier = Modifier.fillMaxSize()) {
var roomName by remember { mutableStateOf("") }
Column(modifier = Modifier.fillMaxSize()) {
TopBar(roomName, Modifier.statusBarsPadding(), isSelectionOpen)
DynamicContent(roomState.room, {roomState.room.getRoomSummaryLive()}) {
val summary = it.orElse { roomState.room.roomSummary()!! }
TopBar(modifier = Modifier.statusBarsPadding(), isSelectionOpen = isSelectionOpen) {
Row(modifier = Modifier.align(Alignment.Center)) {
if(summary.isDirect) {
OnlineIndicator(summary, 16.dp, modifier = Modifier.align(Alignment.CenterVertically))
Divider(color = Color.White.copy(alpha = 0f), modifier = Modifier.width(10.dp))
}
Text(roomName, fontSize = 24.sp, color = MaterialTheme.colors.onSurface)
if(summary.isDirect) {
Divider(color = Color.White.copy(alpha = 0f), modifier = Modifier.width(10.dp))
LastOnline(summary, 14.sp, Modifier.align(Alignment.CenterVertically))
}
}
}
}
RoomMessages(roomState, scrollState, Modifier.weight(1f))
UserInput(
onMessageSent = { content ->

@ -27,7 +27,9 @@ import com.dfsek.feldspar.AppState
import com.dfsek.feldspar.ui.rooms.Avatar
import com.dfsek.feldspar.ui.rooms.DirectMessagesActivity
import com.dfsek.feldspar.ui.rooms.GroupMessagesActivity
import com.dfsek.feldspar.ui.rooms.OnlineIndicator
import com.dfsek.feldspar.ui.settings.SettingsDropdown
import com.dfsek.feldspar.util.compose_fixes.FixedDefaultFlingBehavior
import com.dfsek.feldspar.util.getPreviewText
import org.matrix.android.sdk.api.query.SpaceFilter
import org.matrix.android.sdk.api.session.room.Room
@ -81,7 +83,8 @@ fun Activity.RootSpacesSelection(modifier: Modifier = Modifier, isOpen: MutableS
}
}
val avatarSize = AppState.Preferences.spacesAvatarSize
LazyColumn {
val fling = FixedDefaultFlingBehavior.fixedFlingBehavior()
LazyColumn(flingBehavior = fling) {
if(spaceStack.empty()) {
item {
Row(modifier = Modifier.fillMaxWidth().clickable {
@ -163,15 +166,16 @@ fun BackButton(modifier: Modifier = Modifier, isSelectionOpen: MutableState<Bool
@Composable
fun Activity.TopBar(
name: String,
modifier: Modifier,
isSelectionOpen: MutableState<Boolean>
name: String = "",
isSelectionOpen: MutableState<Boolean>,
center: @Composable BoxScope.() -> Unit = {Text(name, fontSize = 24.sp, modifier = Modifier.align(Alignment.Center), color = MaterialTheme.colors.onSurface)}
) {
Box (modifier = modifier
.background(MaterialTheme.colors.surface)
.fillMaxWidth()) {
BackButton(modifier = Modifier.align(Alignment.CenterStart), isSelectionOpen)
Text(name, fontSize = 24.sp, modifier = Modifier.align(Alignment.Center), color = MaterialTheme.colors.onSurface)
center.invoke(this)
SettingsDropdown(modifier = Modifier.align(Alignment.CenterEnd))
}
}

@ -23,7 +23,7 @@ class DirectMessagesActivity : AppCompatActivity() {
Surface(modifier = Modifier.fillMaxSize()) {
val selectionUIOpen = remember { mutableStateOf(false) }
Column {
TopBar("Direct Messages", modifier = Modifier, selectionUIOpen)
TopBar(name = "Direct Messages", modifier = Modifier, isSelectionOpen = selectionUIOpen)
RoomList(remember { RoomSummaryQueryParams.Builder().build() }) {
val reversed = it.sortedBy { it.latestPreviewableEvent?.root?.originServerTs }.reversed()
reversed.filter { it.isDirect }

@ -24,7 +24,7 @@ class GroupMessagesActivity : AppCompatActivity() {
Surface(modifier = Modifier.fillMaxSize()) {
val selectionUIOpen = remember { mutableStateOf(false) }
Column {
TopBar("Group Messages", modifier = Modifier, selectionUIOpen)
TopBar(name = "Group Messages", modifier = Modifier, isSelectionOpen = selectionUIOpen)
RoomList(remember {
RoomSummaryQueryParams.Builder().apply { spaceFilter = SpaceFilter.OrphanRooms }.build()
}) {

@ -9,6 +9,8 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Colors
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
@ -18,8 +20,10 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
@ -27,12 +31,18 @@ import coil.decode.BitmapFactoryDecoder
import coil.request.ImageRequest
import com.dfsek.feldspar.AppState
import com.dfsek.feldspar.ui.RoomActivity
import com.dfsek.feldspar.util.DynamicContent
import com.dfsek.feldspar.util.compose_fixes.FixedDefaultFlingBehavior
import com.dfsek.feldspar.util.getAvatarUrl
import com.dfsek.feldspar.util.getPreviewText
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import kotlin.math.floor
import kotlin.random.Random
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.DurationUnit
import kotlin.time.ExperimentalTime
val colors = listOf(
@ -71,16 +81,47 @@ fun Avatar(mxUrl: String?, size: Int, clip: Shape = CircleShape, name: String? =
contentAlignment = Alignment.Center
) {
name?.let {
Text(it.substring(0, 1),
Text(
it.substring(0, 1),
modifier = Modifier.align(Alignment.Center),
fontSize = (floor(size * 0.75).sp),
textAlign = TextAlign.Center,
color = Color.White)
color = Color.White
)
}
}
}
}
@Composable
fun OnlineIndicator(room: RoomSummary, size: Dp, modifier: Modifier = Modifier) {
val onlineColor = if (room.directUserPresence?.isCurrentlyActive == true) Color.Green else Color.Gray
Box(
modifier = modifier
.size(size)
.clip(CircleShape)
.background(onlineColor),
contentAlignment = Alignment.Center
) {
}
}
@Composable
fun LastOnline(room: RoomSummary, size: TextUnit, modifier: Modifier) {
if(room.directUserPresence?.isCurrentlyActive == true) return
room.directUserPresence?.lastActiveAgo?.let {
it.milliseconds.toComponents { days, hours, minutes, seconds, _ ->
if(days > 0) "$days D"
else if(hours > 0) "$hours H"
else if(minutes > 0) "$minutes M"
else if(seconds > 0) "$seconds S"
else null
}?.let {
Text(it, fontSize = size, color = MaterialTheme.colors.onSurface, modifier = modifier)
}
}
}
@Composable
fun Activity.RoomEntry(room: RoomSummary) {
Row(modifier = Modifier.clickable {
@ -90,7 +131,12 @@ fun Activity.RoomEntry(room: RoomSummary) {
}.fillMaxWidth()) {
val name = remember { room.displayName }
val lastContent = remember { room.latestPreviewableEvent }
Avatar(room.avatarUrl, AppState.Preferences.dmAvatarSize, name = room.displayName)
Box {
Avatar(room.avatarUrl, AppState.Preferences.dmAvatarSize, name = room.displayName)
if (room.isDirect) {
OnlineIndicator(room, (AppState.Preferences.dmAvatarSize * 0.25).dp, modifier = Modifier.align(Alignment.BottomEnd))
}
}
Column {
Text(name, fontSize = 18.sp)
@ -105,20 +151,16 @@ fun Activity.RoomList(
filter: (List<RoomSummary>) -> List<RoomSummary>
) {
AppState.session?.let { session ->
val lifecycleOwner = LocalLifecycleOwner.current
var rooms: List<RoomSummary> by remember { mutableStateOf(emptyList()) }
LaunchedEffect(session) {
session.roomService().getRoomSummariesLive(queryParams)
.observe(lifecycleOwner) {
rooms = filter(it)
DynamicContent(session, { session.roomService().getRoomSummariesLive(queryParams) }) {
val rooms = filter(it)
val fling = FixedDefaultFlingBehavior.fixedFlingBehavior()
LazyColumn(flingBehavior = fling) {
items(rooms, key = {
it.roomId
}) {
Log.d("ROOM", it.roomId)
RoomEntry(it)
}
}
LazyColumn {
items(rooms, key = {
it.roomId
}) {
Log.d("ROOM", it.roomId)
RoomEntry(it)
}
}
}

@ -15,6 +15,7 @@ import androidx.compose.material.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.dfsek.feldspar.AppState
import com.dfsek.feldspar.util.compose_fixes.FixedDefaultFlingBehavior
import kotlin.reflect.KClass
class SettingsActivity : AppCompatActivity() {
@ -34,7 +35,8 @@ class SettingsActivity : AppCompatActivity() {
fun Settings() {
Column {
SettingsTopBar("Settings")
LazyColumn(modifier = Modifier.fillMaxWidth()) {
val fling = FixedDefaultFlingBehavior.fixedFlingBehavior()
LazyColumn(modifier = Modifier.fillMaxWidth(), flingBehavior = fling) {
item {
SettingsItem("General", "General UI configuration.", GeneralActivity::class)
}

@ -24,6 +24,7 @@ import com.dfsek.feldspar.AppState
import com.dfsek.feldspar.AppState.updateTheme
import com.dfsek.feldspar.util.THEME_KEY
import com.dfsek.feldspar.util.THEME_PREFS
import com.dfsek.feldspar.util.compose_fixes.FixedDefaultFlingBehavior
import com.dfsek.feldspar.util.toColor
import com.dfsek.feldspar.util.toHexString
@ -57,8 +58,8 @@ class ThemeActivity : AppCompatActivity() {
currentTheme = theme
updateTheme()
}
LazyColumn {
val fling = FixedDefaultFlingBehavior.fixedFlingBehavior()
LazyColumn(flingBehavior = fling) {
items(themes) {
Row(modifier = Modifier
.fillMaxWidth()

@ -26,7 +26,7 @@ class VerificationActivity : AppCompatActivity() {
AppState.session?.let { session ->
val state = VerificationState(session)
Surface(modifier = Modifier.fillMaxSize()) {
DynamicContent(state.devices) {
DynamicContent(session, { state.devices }) {
Column {
SettingsTopBar("Verification")
Verification(state)

@ -41,6 +41,9 @@ interface TimelineEventWrapper {
}
override val canRedact = false
override val receiptEvent: TimelineEvent
get() = redactionEvent
}
class Replaced(override val event: TimelineEvent, val eventWrapper: TimelineEventWrapper, val replacedBy: TimelineEvent) : TimelineEventWrapper {
@ -55,6 +58,9 @@ interface TimelineEventWrapper {
)
}
}
override val receiptEvent: TimelineEvent
get() = replacedBy
}
class Replied(override val event: TimelineEvent, val repliedTo: String) : TimelineEventWrapper {
@ -96,6 +102,9 @@ interface TimelineEventWrapper {
val event: TimelineEvent
val receiptEvent: TimelineEvent
get() = event
@Composable
fun RenderEvent(modifier: Modifier) {
Column(modifier = modifier.fillMaxWidth()) {
@ -119,7 +128,7 @@ interface TimelineEventWrapper {
@Composable
fun RenderReadReceipts(modifier: Modifier) {
Row(modifier) {
event.readReceipts.forEach {
receiptEvent.readReceipts.forEach {
Avatar(it.roomMember.avatarUrl, 18, name = it.roomMember.displayName)
}
}

@ -47,10 +47,13 @@ internal const val THEME_KEY = "theme"
internal const val GENERAL_PREFS = "general"
@Composable
fun <T> DynamicContent(data: LiveData<T>, consume: @Composable (T) -> Unit) {
fun <T> DynamicContent(key: Any, data: () -> LiveData<T>, consume: @Composable (T) -> Unit) {
val lifecycleOwner = LocalLifecycleOwner.current
var value: T? by remember { mutableStateOf(null) }
data.observe(lifecycleOwner) { value = it }
LaunchedEffect(key) {
val d = data.invoke()
d.observe(lifecycleOwner) { value = it }
}
value?.let { consume(it) }
}

Loading…
Cancel
Save