Compare commits

..

3 Commits

Author SHA1 Message Date
e70049f1f5 feat: Round avatars 2025-02-25 00:39:22 +03:00
c0944ec0a8 wip: Get room info serially 2025-02-22 21:18:16 +03:00
5fbffd8700 wip: Room list 2025-02-22 21:18:16 +03:00
15 changed files with 405 additions and 79 deletions

View File

@ -1,7 +1,3 @@
<component name="CopyrightManager"> <component name="CopyrightManager">
<settings default="My"> <settings default="My" />
<module2copyright>
<element module="All" copyright="My" />
</module2copyright>
</settings>
</component> </component>

View File

@ -4,6 +4,14 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-02-22T11:46:39.159466074Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=22163a3c" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState> </SelectionState>
</selectionStates> </selectionStates>
</component> </component>

2
.idea/kotlinc.xml generated
View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="KotlinJpsPluginSettings"> <component name="KotlinJpsPluginSettings">
<option name="version" value="2.0.0" /> <option name="version" value="2.0.21" />
</component> </component>
</project> </project>

View File

@ -1,13 +1,14 @@
/* /*
* Created by sweetbread on 21.02.2025, 12:01 * Created by sweetbread on 22.02.2025, 15:45
* Copyright (c) 2025. All rights reserved. * Copyright (c) 2025. All rights reserved.
* Last modified 21.02.2025, 12:01 * Last modified 22.02.2025, 14:56
*/ */
plugins { plugins {
alias(libs.plugins.android.application) alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose) alias(libs.plugins.kotlin.compose)
id("com.google.devtools.ksp")
} }
android { android {
@ -34,11 +35,11 @@ android {
} }
} }
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_17
} }
kotlinOptions { kotlinOptions {
jvmTarget = "1.8" jvmTarget = "17"
} }
buildFeatures { buildFeatures {
compose = true compose = true
@ -64,12 +65,19 @@ dependencies {
// Ktor - web client // Ktor - web client
implementation(libs.ktor.client.core) implementation(libs.ktor.client.core)
implementation(libs.ktor.client.cio) implementation(libs.ktor.client.okhttp)
implementation(libs.ktor.client.logging)
// Coil - image loader // Coil - image loader
implementation(libs.coil.compose) implementation(libs.coil.compose)
implementation(libs.coil.network.okhttp) implementation(libs.coil.network.okhttp)
// Room - database
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.lifecycle.viewmodel.ktx)
implementation(libs.androidx.room.ktx)
ksp(libs.androidx.room.compiler)
// Others // Others
implementation(libs.splitties.base) // Syntax sugar implementation(libs.splitties.base) // Syntax sugar
} }

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- <!--
~ Created by sweetbread on 21.02.2025, 12:08 ~ Created by sweetbread on 22.02.2025, 15:45
~ Copyright (c) 2025. All rights reserved. ~ Copyright (c) 2025. All rights reserved.
~ Last modified 21.02.2025, 12:07 ~ Last modified 22.02.2025, 14:00
--> -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
@ -22,13 +22,7 @@
tools:targetApi="31"> tools:targetApi="31">
<activity <activity
android:name=".ui.activity.Login" android:name=".ui.activity.Login"
android:exported="false"
android:label="@string/title_activity_login"
android:theme="@style/Theme.PixelDragon" />
<activity
android:name=".ui.activity.MainActivity"
android:exported="true" android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.PixelDragon"> android:theme="@style/Theme.PixelDragon">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -36,6 +30,11 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".ui.activity.MainActivity"
android:exported="false"
android:theme="@style/Theme.PixelDragon" />
</application> </application>
</manifest> </manifest>

View File

@ -1,43 +1,55 @@
/* /*
* Created by sweetbread on 21.02.2025, 12:01 * Created by sweetbread on 22.02.2025, 15:45
* Copyright (c) 2025. All rights reserved. * Copyright (c) 2025. All rights reserved.
* Last modified 21.02.2025, 12:01 * Last modified 22.02.2025, 15:45
*/ */
package ru.risdeveau.pixeldragon package ru.risdeveau.pixeldragon
import android.content.Context import android.content.Context
import android.util.Log
import androidx.room.Room
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.client.engine.cio.endpoint
import io.ktor.client.plugins.cache.HttpCache import io.ktor.client.plugins.cache.HttpCache
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
import ru.risdeveau.pixeldragon.api.getMe import ru.risdeveau.pixeldragon.api.getMe
import ru.risdeveau.pixeldragon.db.AppDatabase
import splitties.init.appCtx import splitties.init.appCtx
val client = HttpClient(CIO) { val client = HttpClient {
engine { install(Logging) {
endpoint { logger = object : Logger {
maxConnectionsPerRoute = 100 override fun log(message: String) {
pipelineMaxSize = 20 Log.i("Ktor", message)
keepAliveTime = 30000
connectTimeout = 15000
connectAttempts = 5
} }
} }
level = LogLevel.ALL
}
install(HttpCache) install(HttpCache)
} }
val accountData = appCtx.getSharedPreferences("settings", Context.MODE_PRIVATE) val accountData = appCtx.getSharedPreferences("settings", Context.MODE_PRIVATE)
lateinit var urlBase: String lateinit var homeserver: String
lateinit var baseUrl: String
lateinit var token: String lateinit var token: String
suspend fun initCheck(): Boolean { suspend fun initCheck(): Boolean {
Log.d("initCheck", "checking...")
if (!accountData.contains("token")) return false if (!accountData.contains("token")) return false
if (!accountData.contains("homeserver")) return false if (!accountData.contains("homeserver")) return false
token = accountData.getString("token", "").toString() token = accountData.getString("token", "").toString()
urlBase = "https://${accountData.getString("homeserver", "")}/_matrix/client/v3" homeserver = accountData.getString("homeserver", "").toString()
baseUrl = "https://$homeserver/_matrix/client/v3"
return getMe() != null return getMe() != null
} }
val db = Room.databaseBuilder(
appCtx,
AppDatabase::class.java, "database"
).build()

View File

@ -0,0 +1,57 @@
/*
* Created by sweetbread
* Copyright (c) 2025. All rights reserved.
* Last modified 22.02.2025, 19:52
*/
package ru.risdeveau.pixeldragon.api
import io.ktor.client.request.bearerAuth
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import io.ktor.http.HttpStatusCode
import org.json.JSONObject
import ru.risdeveau.pixeldragon.baseUrl
import ru.risdeveau.pixeldragon.client
import ru.risdeveau.pixeldragon.db
import ru.risdeveau.pixeldragon.db.Room
import ru.risdeveau.pixeldragon.token
//fun getRooms(): List<Room> {
// return db.roomDoa().getAllJoined()
//}
//
//fun updateRooms(): List<Room> {
//
//}
suspend fun getRooms(): List<String> {
val r = client.get("$baseUrl/joined_rooms")
{ bearerAuth(token) }
val rooms = JSONObject(r.bodyAsText()).getJSONArray("joined_rooms")
return List<String>(
rooms.length()
) { i -> rooms.getString(i) }
}
suspend fun getRoom(rid: String): Room {
var room = db.roomDoa().getById(rid)
if (room == null) {
val name = getState(rid, "m.room.name", "name")
val type = getState(rid, "m.room.create", "type") ?: "m.room"
val creator = getState(rid, "m.room.create", "creator")
val avatar = getState(rid, "m.room.avatar", "url")
room = Room(rid, name, type, creator, null, avatar, null, true)
db.roomDoa().insert(room)
}
return room
}
private suspend fun getState(rid: String, state: String, key: String): String? {
val r = client.get("$baseUrl/rooms/$rid/state/$state") { bearerAuth(token) }
if (r.status != HttpStatusCode.OK) return null
val json = JSONObject(r.bodyAsText())
if (!json.has(key)) return null
return json.getString(key)
}

View File

@ -1,7 +1,7 @@
/* /*
* Created by sweetbread on 21.02.2025, 12:01 * Created by sweetbread on 22.02.2025, 17:28
* Copyright (c) 2025. All rights reserved. * Copyright (c) 2025. All rights reserved.
* Last modified 21.02.2025, 12:01 * Last modified 22.02.2025, 17:28
*/ */
package ru.risdeveau.pixeldragon.api package ru.risdeveau.pixeldragon.api
@ -11,6 +11,7 @@ import io.ktor.client.statement.bodyAsText
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
import ru.risdeveau.pixeldragon.client import ru.risdeveau.pixeldragon.client
import ru.risdeveau.pixeldragon.homeserver
suspend fun isMatrixServer(url: String): Boolean { suspend fun isMatrixServer(url: String): Boolean {
val r = try { client.get("https://$url/.well-known/matrix/client") } val r = try { client.get("https://$url/.well-known/matrix/client") }
@ -21,3 +22,10 @@ suspend fun isMatrixServer(url: String): Boolean {
return true return true
} }
fun mxcToUrl(mxc: String): String {
val pattern = Regex("mxc://([-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6})/([a-zA-Z]+)")
val match = pattern.find(mxc)
return "https://$homeserver/_matrix/client/v1/media/download/${match?.groupValues[1]}/${match?.groupValues[2]}"
}

View File

@ -1,7 +1,7 @@
/* /*
* Created by sweetbread on 21.02.2025, 12:09 * Created by sweetbread on 22.02.2025, 15:45
* Copyright (c) 2025. All rights reserved. * Copyright (c) 2025. All rights reserved.
* Last modified 21.02.2025, 12:01 * Last modified 22.02.2025, 15:45
*/ */
package ru.risdeveau.pixeldragon.api package ru.risdeveau.pixeldragon.api
@ -18,10 +18,10 @@ import io.ktor.http.HttpStatusCode
import io.ktor.http.contentType import io.ktor.http.contentType
import org.json.JSONObject import org.json.JSONObject
import ru.risdeveau.pixeldragon.accountData import ru.risdeveau.pixeldragon.accountData
import ru.risdeveau.pixeldragon.baseUrl
import ru.risdeveau.pixeldragon.client import ru.risdeveau.pixeldragon.client
import ru.risdeveau.pixeldragon.initCheck import ru.risdeveau.pixeldragon.initCheck
import ru.risdeveau.pixeldragon.token import ru.risdeveau.pixeldragon.token
import ru.risdeveau.pixeldragon.urlBase
import splitties.init.appCtx import splitties.init.appCtx
data class Me (val userId: String, val deviceId: String) data class Me (val userId: String, val deviceId: String)
@ -30,7 +30,7 @@ data class Me (val userId: String, val deviceId: String)
* This func is to validate the token * This func is to validate the token
*/ */
suspend fun getMe(): Me? { suspend fun getMe(): Me? {
val r = client.get("$urlBase/account/whoami") { bearerAuth(token) } val r = client.get("$baseUrl/account/whoami") { bearerAuth(token) }
if (r.status != HttpStatusCode.OK) { if (r.status != HttpStatusCode.OK) {
Log.e("getMe", r.bodyAsText()) Log.e("getMe", r.bodyAsText())
return null return null

View File

@ -0,0 +1,81 @@
/*
* Created by sweetbread
* Copyright (c) 2025. All rights reserved.
* Last modified 22.02.2025, 19:49
*/
package ru.risdeveau.pixeldragon.db
import androidx.room.Dao
import androidx.room.Database
import androidx.room.Delete
import androidx.room.Embedded
import androidx.room.Entity
import androidx.room.Insert
import androidx.room.Junction
import androidx.room.PrimaryKey
import androidx.room.Query
import androidx.room.Relation
import androidx.room.RoomDatabase
@Entity
data class Room(
@PrimaryKey val roomId: String,
val name: String?,
val type: String,
val creatorId: String?,
val createTime: Long?,
val avatarUrl: String?,
val members: Int?,
val joined: Boolean
)
@Dao
interface RoomDao {
// @Query("SELECT * FROM room")
// fun getAll(): List<User>
// @Query("SELECT * FROM user WHERE uid IN (:userIds)")
// fun loadAllByIds(userIds: IntArray): List<User>
// @Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
// "last_name LIKE :last LIMIT 1")
// fun findByName(first: String, last: String): User
@Query("SELECT * FROM room WHERE roomId LIKE :rid LIMIT 1")
fun getById(rid: String): Room?
// @Transaction
// @Query("SELECT * FROM room WHERE ")
// fun getSpace(rid: String): Space
@Query("SELECT * FROM room WHERE joined = 1 AND roomId NOT IN (SELECT roomId FROM SpaceToRoom)")
fun getAllJoined(): List<Room>
@Insert
fun insert(vararg rooms: Room)
@Delete
fun delete(room: Room)
}
@Entity(primaryKeys = ["spaceId", "roomId"])
data class SpaceToRoom(
val spaceId: String,
val roomId: String
)
data class Space(
@Embedded val space: Room,
@Relation(
parentColumn = "spaceId",
entityColumn = "roomId",
associateBy = Junction(SpaceToRoom::class)
)
val children: List<Room>
)
@Database(entities = [Room::class, SpaceToRoom::class, User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun roomDoa(): RoomDao
}

View File

@ -0,0 +1,18 @@
/*
* Created by sweetbread on 22.02.2025, 15:45
* Copyright (c) 2025. All rights reserved.
* Last modified 21.02.2025, 13:38
*/
package ru.risdeveau.pixeldragon.db
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
data class User(
@PrimaryKey val uid: String,
@ColumnInfo(name = "name") val name: String?,
@ColumnInfo(name = "avatar") val avatar: String?
)

View File

@ -1,7 +1,7 @@
/* /*
* Created by sweetbread on 21.02.2025, 12:08 * Created by sweetbread on 22.02.2025, 15:45
* Copyright (c) 2025. All rights reserved. * Copyright (c) 2025. All rights reserved.
* Last modified 21.02.2025, 12:08 * Last modified 22.02.2025, 15:45
*/ */
package ru.risdeveau.pixeldragon.ui.activity package ru.risdeveau.pixeldragon.ui.activity
@ -35,15 +35,25 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import ru.risdeveau.pixeldragon.api.isMatrixServer import ru.risdeveau.pixeldragon.api.isMatrixServer
import ru.risdeveau.pixeldragon.api.login import ru.risdeveau.pixeldragon.api.login
import ru.risdeveau.pixeldragon.initCheck
import ru.risdeveau.pixeldragon.ui.theme.PixelDragonTheme import ru.risdeveau.pixeldragon.ui.theme.PixelDragonTheme
import splitties.activities.start import splitties.activities.start
class Login : ComponentActivity() { class Login : ComponentActivity() {
@OptIn(DelicateCoroutinesApi::class)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
GlobalScope.launch {
if (initCheck()) start<MainActivity>()
}
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
PixelDragonTheme { PixelDragonTheme {
@ -56,7 +66,10 @@ class Login : ComponentActivity() {
SnackbarHost(hostState = snackbarHostState) SnackbarHost(hostState = snackbarHostState)
} }
) { innerPadding -> ) { innerPadding ->
Box(Modifier.fillMaxSize().padding(innerPadding)) { Box(
Modifier
.fillMaxSize()
.padding(innerPadding)) {
LoginField( LoginField(
Modifier.align(Alignment.Center), Modifier.align(Alignment.Center),
{ start<MainActivity>() }, { start<MainActivity>() },
@ -119,12 +132,11 @@ fun LoginField(modifier: Modifier = Modifier, ok: () -> Unit, err: () -> Unit) {
Button( Button(
enabled = hmsValid && !login.isEmpty() && !pass.isEmpty(), enabled = hmsValid && !login.isEmpty() && !pass.isEmpty(),
onClick = { onClick = {
var loginSuccess = false scope.launch {
scope.launch { loginSuccess = login(homeserver, login, pass) } if (login(homeserver, login, pass)) ok()
if (loginSuccess) ok()
else err() else err()
} }
}
) { ) {
Text("Login") Text("Login")
} }

View File

@ -1,7 +1,7 @@
/* /*
* Created by sweetbread on 21.02.2025, 12:08 * Created by sweetbread
* Copyright (c) 2025. All rights reserved. * Copyright (c) 2025. All rights reserved.
* Last modified 21.02.2025, 12:07 * Last modified 22.02.2025, 20:24
*/ */
package ru.risdeveau.pixeldragon.ui.activity package ru.risdeveau.pixeldragon.ui.activity
@ -10,31 +10,56 @@ import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults.topAppBarColors import androidx.compose.material3.TopAppBarDefaults.topAppBarColors
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.draw.clip
import kotlinx.coroutines.DelicateCoroutinesApi import androidx.compose.ui.layout.ContentScale
import kotlinx.coroutines.GlobalScope import androidx.compose.ui.unit.dp
import coil3.compose.SubcomposeAsyncImage
import coil3.network.NetworkHeaders
import coil3.network.httpHeaders
import coil3.request.ImageRequest
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import ru.risdeveau.pixeldragon.initCheck import kotlinx.coroutines.withContext
import ru.risdeveau.pixeldragon.api.getRoom
import ru.risdeveau.pixeldragon.api.getRooms
import ru.risdeveau.pixeldragon.api.mxcToUrl
import ru.risdeveau.pixeldragon.db.Room
import ru.risdeveau.pixeldragon.token
import ru.risdeveau.pixeldragon.ui.theme.PixelDragonTheme import ru.risdeveau.pixeldragon.ui.theme.PixelDragonTheme
import splitties.activities.start import splitties.init.appCtx
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3Api::class, DelicateCoroutinesApi::class) @OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
GlobalScope.launch {
if (!initCheck()) start<Login>()
}
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
@ -54,10 +79,7 @@ class MainActivity : ComponentActivity() {
) )
}, },
) { innerPadding -> ) { innerPadding ->
Greeting( RoomList(Modifier.padding(innerPadding))
name = "Android",
modifier = Modifier.padding(innerPadding)
)
} }
} }
} }
@ -65,17 +87,115 @@ class MainActivity : ComponentActivity() {
} }
@Composable @Composable
fun Greeting(name: String, modifier: Modifier = Modifier) { fun RoomList(modifier: Modifier = Modifier) {
var list by remember { mutableStateOf(listOf<String>()) }
val coroutineScope = rememberCoroutineScope()
val listState = rememberLazyListState()
// if (itemState.scrollToTop) {
// LaunchedEffect(coroutineScope) {
// Log.e("TAG", "TopCoinsScreen: scrollToTop" )
// listState.scrollToItem(0)
// }
// }
LaunchedEffect(true) {
coroutineScope.launch {
withContext(Dispatchers.IO) {
list = getRooms()
}
}
}
LazyColumn(modifier = modifier, state = listState) {
items(list) { rid ->
RoomItem(rid = rid)
}
item {
if (list.isEmpty()) {
Text("You have no rooms")
}
}
}
}
@Composable
fun RoomItem(modifier: Modifier = Modifier, rid: String) {
var room by remember { mutableStateOf<Room?>(null) }
val scope = rememberCoroutineScope()
LaunchedEffect(true) {
scope.launch {
withContext(Dispatchers.IO) {
room = getRoom(rid)
}
}
}
if (room != null) {
Row(
modifier
.padding(8.dp)
.height(80.dp)
.fillMaxWidth()
.background(MaterialTheme.colorScheme.primaryContainer, RoundedCornerShape(16.dp))
.padding(8.dp)
) {
SubcomposeAsyncImage(
modifier = Modifier
.padding(end = 4.dp)
.height(64.dp)
.width(64.dp)
.let {
if (room!!.type == "m.space")
it.clip(RoundedCornerShape(12.dp))
else
it.clip(CircleShape)
},
model = ImageRequest.Builder(appCtx)
.data(mxcToUrl(room!!.avatarUrl ?: ""))
.httpHeaders(
NetworkHeaders.Builder()
.set("Authorization", "Bearer $token")
.set("Cache-Control", "max-age=86400")
.build()
)
.build(),
contentDescription = room!!.roomId,
contentScale = ContentScale.Crop,
loading = {
CircularProgressIndicator()
}
)
Column {
Text( Text(
text = "Hello $name!", room!!.name ?: "Unnamed",
modifier = modifier maxLines = 1,
color = MaterialTheme.colorScheme.primary,
fontSize = MaterialTheme.typography.titleLarge.fontSize
) )
} }
}
@Preview(showBackground = true) } else {
@Composable Row(
fun GreetingPreview() { modifier
PixelDragonTheme { .padding(8.dp)
Greeting("Android") .height(80.dp)
.fillMaxWidth()
.background(MaterialTheme.colorScheme.primaryContainer, RoundedCornerShape(16.dp))
.padding(8.dp)
) {
LinearProgressIndicator(Modifier.fillMaxWidth())
}
} }
} }
//@Preview(showBackground = true)
//@Composable
//fun GreetingPreview() {
// PixelDragonTheme {
// RoomItem()
// }
//}

View File

@ -1,7 +1,7 @@
/* /*
* Created by sweetbread on 21.02.2025, 12:00 * Created by sweetbread on 22.02.2025, 15:45
* Copyright (c) 2025. All rights reserved. * Copyright (c) 2025. All rights reserved.
* Last modified 21.02.2025, 12:00 * Last modified 21.02.2025, 12:21
*/ */
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
@ -9,4 +9,5 @@ plugins {
alias(libs.plugins.android.application) apply false alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose) apply false alias(libs.plugins.kotlin.compose) apply false
id("com.google.devtools.ksp") version "2.0.21-1.0.27" apply false
} }

View File

@ -1,7 +1,7 @@
[versions] [versions]
agp = "8.7.3" agp = "8.7.3"
coil = "3.1.0" coil = "3.1.0"
kotlin = "2.0.0" kotlin = "2.0.21"
coreKtx = "1.15.0" coreKtx = "1.15.0"
junit = "4.13.2" junit = "4.13.2"
junitVersion = "1.2.1" junitVersion = "1.2.1"
@ -10,10 +10,15 @@ ktor = "3.1.0"
lifecycleRuntimeKtx = "2.8.7" lifecycleRuntimeKtx = "2.8.7"
activityCompose = "1.10.0" activityCompose = "1.10.0"
composeBom = "2025.02.00" composeBom = "2025.02.00"
room = "2.6.1"
splittiesFunPackAndroidBase = "3.0.0" splittiesFunPackAndroidBase = "3.0.0"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "room" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" } coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" }
junit = { group = "junit", name = "junit", version.ref = "junit" } junit = { group = "junit", name = "junit", version.ref = "junit" }
@ -29,8 +34,9 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
splitties-base = { module = "com.louiscad.splitties:splitties-fun-pack-android-base", version.ref = "splittiesFunPackAndroidBase" } splitties-base = { module = "com.louiscad.splitties:splitties-fun-pack-android-base", version.ref = "splittiesFunPackAndroidBase" }
[plugins] [plugins]