diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml
new file mode 100755
index 0000000..4a53bee
--- /dev/null
+++ b/.idea/AndroidProjectSystem.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index cde3e19..763e424 100755
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -49,6 +49,9 @@
+
+
+
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
index 931b96c..16660f1 100755
--- a/.idea/runConfigurations.xml
+++ b/.idea/runConfigurations.xml
@@ -5,8 +5,12 @@
+
+
+
+
diff --git a/.idea/studiobot.xml b/.idea/studiobot.xml
new file mode 100755
index 0000000..539e3b8
--- /dev/null
+++ b/.idea/studiobot.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 0cd53c1..e70d1b8 100755
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,13 +1,13 @@
/*
* Created by sweetbread
- * Copyright (c) 2025. All rights reserved.
+ * Copyright (c) 2026. All rights reserved.
*/
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
- id("com.google.devtools.ksp")
+// alias(libs.plugins.ksp)
}
android {
@@ -16,7 +16,7 @@ android {
defaultConfig {
applicationId = "ru.risdeveau.pixeldragon"
- minSdk = 26
+ minSdk = 28
targetSdk = 35
versionCode = 1
versionName = "1.0"
@@ -62,6 +62,13 @@ dependencies {
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
+ // Trixnity - Matrix wrapper
+ implementation(libs.trixnity.client)
+ implementation(libs.trixnity.client.media.okio)
+ implementation(libs.trixnity.client.repository.room)
+ implementation(libs.trixnity.client.cryptodriver.vodozemac)
+// implementation(libs.trixnity.messenger)
+
// Ktor - web client
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.okhttp)
@@ -75,7 +82,7 @@ dependencies {
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.lifecycle.viewmodel.ktx)
implementation(libs.androidx.room.ktx)
- ksp(libs.androidx.room.compiler)
+// ksp(libs.androidx.room.compiler)
// Navigation Compose
implementation(libs.androidx.navigation.compose)
@@ -87,4 +94,5 @@ dependencies {
// Others
implementation(libs.splitties.base) // Syntax sugar
implementation(libs.jsoup) // HTML parser
+
}
\ No newline at end of file
diff --git a/app/src/main/java/ru/risdeveau/pixeldragon/Common.kt b/app/src/main/java/ru/risdeveau/pixeldragon/Common.kt
index f72d1e0..f776ec0 100755
--- a/app/src/main/java/ru/risdeveau/pixeldragon/Common.kt
+++ b/app/src/main/java/ru/risdeveau/pixeldragon/Common.kt
@@ -1,20 +1,19 @@
/*
* Created by sweetbread
- * Copyright (c) 2025. All rights reserved.
+ * Copyright (c) 2026. All rights reserved.
*/
package ru.risdeveau.pixeldragon
import android.util.Log
+import de.connect2x.trixnity.client.MatrixClient
import io.ktor.client.HttpClient
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 splitties.preferences.Preferences
-val client = HttpClient {
+val webClient = HttpClient {
install(Logging) {
logger = object : Logger {
override fun log(message: String) {
@@ -27,24 +26,4 @@ val client = HttpClient {
install(HttpCache)
}
-lateinit var homeserver: String
-lateinit var baseUrl: String
-lateinit var token: String
-
-object AccountData : Preferences("settings") {
- var token by stringPref("token", "")
- var homeserver by stringPref("homeserver", "")
-}
-
-suspend fun initCheck(): Boolean {
- Log.d("initCheck", "checking...")
-
- token = AccountData.token
- homeserver = AccountData.homeserver
-
- if (token.isEmpty() or homeserver.isEmpty()) return false
-
- baseUrl = "$homeserver/_matrix/client/v3"
-
- return getMe() != null
-}
\ No newline at end of file
+var client: MatrixClient? = null
\ No newline at end of file
diff --git a/app/src/main/java/ru/risdeveau/pixeldragon/api/Event.kt b/app/src/main/java/ru/risdeveau/pixeldragon/api/Event.kt
index 441a01d..de0e5e7 100755
--- a/app/src/main/java/ru/risdeveau/pixeldragon/api/Event.kt
+++ b/app/src/main/java/ru/risdeveau/pixeldragon/api/Event.kt
@@ -1,56 +1,50 @@
/*
* Created by sweetbread
- * Copyright (c) 2025. All rights reserved.
- * Last modified 03.03.2025, 20:21
+ * Copyright (c) 2026. All rights reserved.
*/
package ru.risdeveau.pixeldragon.api
-import io.ktor.client.request.bearerAuth
-import io.ktor.client.request.get
-import io.ktor.client.request.parameter
-import io.ktor.client.statement.bodyAsText
import org.json.JSONObject
-import ru.risdeveau.pixeldragon.baseUrl
-import ru.risdeveau.pixeldragon.client
-import ru.risdeveau.pixeldragon.token
+import java.time.Instant
class Event (
val id: String,
val rid: String,
- val sender: String,
val type: String,
- val content: JSONObject
+ val content: JSONObject,
+ val time: Instant,
+ val sender: String
) {
- constructor(json: JSONObject) : this(
- json.getString("event_id"),
- json.getString("room_id"),
- json.getString("sender"),
- json.getString("type"),
- json.getJSONObject("content")
- )
+// constructor(json: JSONObject) : this(
+// json.getString("event_id"),
+// json.getString("room_id"),
+// json.getString("sender"),
+// json.getString("type"),
+// json.getJSONObject("content")
+// )
}
-data class EventsAround (
- val base: Event,
- val before: List,
- val after: List
-)
-
-suspend fun getEventsAround(room: String, event: String): EventsAround {
- val r = client.get("$baseUrl/rooms/$room/context/$event") {
- bearerAuth(token)
- parameter("limit", "50")
- }
- val json = JSONObject(r.bodyAsText())
-
- return EventsAround(
- Event(json.getJSONObject("event")),
- if (json.has("events_before")) json.getJSONArray("events_before").let {
- List(it.length()) { i -> Event(it.getJSONObject(i))}.reversed()
- } else listOf(),
- if (json.has("events_after")) json.getJSONArray("events_after").let {
- List(it.length()) { i -> Event(it.getJSONObject(i))}
- } else listOf()
- )
-}
\ No newline at end of file
+//data class EventsAround (
+// val base: Event,
+// val before: List,
+// val after: List
+//)
+//
+//suspend fun getEventsAround(room: String, event: String): EventsAround {
+// val r = client.get("$baseUrl/rooms/$room/context/$event") {
+// bearerAuth(token)
+// parameter("limit", "50")
+// }
+// val json = JSONObject(r.bodyAsText())
+//
+// return EventsAround(
+// Event(json.getJSONObject("event")),
+// if (json.has("events_before")) json.getJSONArray("events_before").let {
+// List(it.length()) { i -> Event(it.getJSONObject(i))}.reversed()
+// } else listOf(),
+// if (json.has("events_after")) json.getJSONArray("events_after").let {
+// List(it.length()) { i -> Event(it.getJSONObject(i))}
+// } else listOf()
+// )
+//}
\ No newline at end of file
diff --git a/app/src/main/java/ru/risdeveau/pixeldragon/api/Room.kt b/app/src/main/java/ru/risdeveau/pixeldragon/api/Room.kt
index c7d165f..46e6edb 100755
--- a/app/src/main/java/ru/risdeveau/pixeldragon/api/Room.kt
+++ b/app/src/main/java/ru/risdeveau/pixeldragon/api/Room.kt
@@ -1,97 +1,4 @@
/*
* Created by sweetbread
- * Copyright (c) 2025. All rights reserved.
+ * Copyright (c) 2026. All rights reserved.
*/
-
-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 kotlinx.coroutines.async
-import kotlinx.coroutines.coroutineScope
-import org.json.JSONObject
-import ru.risdeveau.pixeldragon.baseUrl
-import ru.risdeveau.pixeldragon.client
-import ru.risdeveau.pixeldragon.repo.Room
-import ru.risdeveau.pixeldragon.repo.User
-import ru.risdeveau.pixeldragon.token
-
-//fun getRooms(): List {
-// return db.roomDoa().getAllJoined()
-//}
-//
-//fun updateRooms(): List {
-//
-//}
-
-suspend fun getJoinedRooms(): List {
- val r = client.get("$baseUrl/joined_rooms")
- { bearerAuth(token) }
- val rooms = JSONObject(r.bodyAsText()).getJSONArray("joined_rooms")
- return List(
- rooms.length()
- ) { i -> getRoom(rooms.getString(i), true) }
-}
-
-suspend fun isJoined(id: String): Boolean {
- val r = client.get("$baseUrl/joined_rooms")
- { bearerAuth(token) }
- val rooms = JSONObject(r.bodyAsText()).getJSONArray("joined_rooms")
- return List(
- rooms.length()
- ) { i -> rooms.getString(i) }.contains(id)
-}
-
-suspend fun getRoom(rid: String, joined: Boolean? = null): Room {
- val direct = getAccountData(getMe()!!.userId, "m.direct")
- var directWith = ""
- direct?.let {
- for (user in direct.keys()) {
- val roomsWithUser = direct.getJSONArray(user)
- for (i in 0 until roomsWithUser.length()) {
- if (rid == roomsWithUser.getString(i)) {
- directWith = user
- break
- }
- }
- if (directWith.isNotEmpty()) break
- }
- }
-
- return coroutineScope {
- val name = async { getState(rid, "m.room.name", "name") }
- val type = async { getState(rid, "m.room.create", "type") ?: "m.room" }
- val creator = async { getState(rid, "m.room.create", "creator") }
- val avatar = async { getState(rid, "m.room.avatar", "url") }
- val joined = async { joined ?: isJoined(rid) }
- val direct = async { if (directWith.isNotEmpty()) User.getById(directWith) else null }
-
- Room(
- rid,
- name.await(),
- type.await(),
- creator.await(),
- null,
- avatar.await(),
- null,
- joined.await(),
- direct.await()
- )
- }
-}
-
-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)
-}
-
-suspend fun getAccountData(user: String, room: String, state: String): JSONObject? {
- val r = client.get("$baseUrl/user/$user/rooms/$room/account_data/$state") { bearerAuth(token) }
- if (r.status != HttpStatusCode.OK) return null
- return JSONObject(r.bodyAsText())
-}
diff --git a/app/src/main/java/ru/risdeveau/pixeldragon/api/Server.kt b/app/src/main/java/ru/risdeveau/pixeldragon/api/Server.kt
index 99bda0b..de16a64 100755
--- a/app/src/main/java/ru/risdeveau/pixeldragon/api/Server.kt
+++ b/app/src/main/java/ru/risdeveau/pixeldragon/api/Server.kt
@@ -1,23 +1,30 @@
/*
* Created by sweetbread
- * Copyright (c) 2025. All rights reserved.
+ * Copyright (c) 2026. All rights reserved.
*/
package ru.risdeveau.pixeldragon.api
+import android.util.Log
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import org.json.JSONException
import org.json.JSONObject
-import ru.risdeveau.pixeldragon.client
-import ru.risdeveau.pixeldragon.homeserver
+import ru.risdeveau.pixeldragon.webClient
+//import ru.risdeveau.pixeldragon.homeserver
suspend fun getHomeserver(url: String): String? {
- val r = try { client.get("https://$url/.well-known/matrix/client") }
- catch (_: Exception) { return null }
+ val r = try { webClient.get("https://$url/.well-known/matrix/client") }
+ catch (e: Exception) {
+ Log.w("getHomeserver", "Fail sending the request", e)
+ return null
+ }
val json = try { JSONObject(r.bodyAsText()) }
- catch (_: JSONException) { return null }
+ catch (e: JSONException) {
+ Log.w("getHomeserver", "Fail parsing the JSON", e)
+ return null
+ }
if (!json.has("m.homeserver")) return null
@@ -27,11 +34,11 @@ suspend fun getHomeserver(url: String): String? {
return homeserver
}
-fun mxcToUrl(mxc: String): String? {
- val pattern = Regex("mxc://([-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6})/([^#?]+)")
- val match = pattern.find(mxc)
-
- if ((match?.groupValues[1] == null) or (match?.groupValues[2] == null)) return null
-
- return "$homeserver/_matrix/client/v1/media/download/${match?.groupValues[1]}/${match?.groupValues[2]}"
-}
\ No newline at end of file
+//fun mxcToUrl(mxc: String): String? {
+// val pattern = Regex("mxc://([-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6})/([^#?]+)")
+// val match = pattern.find(mxc)
+//
+// if ((match?.groupValues[1] == null) or (match?.groupValues[2] == null)) return null
+//
+// return "$homeserver/_matrix/client/v1/media/download/${match?.groupValues[1]}/${match?.groupValues[2]}"
+//}
\ No newline at end of file
diff --git a/app/src/main/java/ru/risdeveau/pixeldragon/api/User.kt b/app/src/main/java/ru/risdeveau/pixeldragon/api/User.kt
index e83bf2b..65e72cd 100755
--- a/app/src/main/java/ru/risdeveau/pixeldragon/api/User.kt
+++ b/app/src/main/java/ru/risdeveau/pixeldragon/api/User.kt
@@ -1,100 +1,49 @@
/*
* Created by sweetbread
- * Copyright (c) 2025. All rights reserved.
+ * Copyright (c) 2026. All rights reserved.
*/
package ru.risdeveau.pixeldragon.api
import android.util.Log
-import io.ktor.client.request.bearerAuth
-import io.ktor.client.request.get
-import io.ktor.client.request.post
-import io.ktor.client.request.setBody
-import io.ktor.client.statement.bodyAsText
-import io.ktor.http.ContentType
-import io.ktor.http.HttpStatusCode
-import io.ktor.http.contentType
-import org.json.JSONObject
-import ru.risdeveau.pixeldragon.AccountData
-import ru.risdeveau.pixeldragon.baseUrl
+import de.connect2x.trixnity.client.CryptoDriverModule
+import io.ktor.http.Url
+import de.connect2x.trixnity.client.MatrixClient
+import de.connect2x.trixnity.client.create
+import de.connect2x.trixnity.client.cryptodriver.vodozemac.vodozemac
+import de.connect2x.trixnity.clientserverapi.client.MatrixClientAuthProviderData
+import de.connect2x.trixnity.clientserverapi.client.classicLogin
+import de.connect2x.trixnity.clientserverapi.model.authentication.IdentifierType
import ru.risdeveau.pixeldragon.client
-import ru.risdeveau.pixeldragon.initCheck
-import ru.risdeveau.pixeldragon.token
+import ru.risdeveau.pixeldragon.util.getMediaStore
+import ru.risdeveau.pixeldragon.util.getRoomStore
import splitties.experimental.ExperimentalSplittiesApi
import splitties.init.appCtx
-import splitties.preferences.edit
-data class Me (val userId: String, val deviceId: String)
-data class UserProfile (val displayName: String, val avatarUrl: String, val other: JSONObject)
-
-/**
- * This func is to validate the token
- */
-suspend fun getMe(): Me? {
- val r = client.get("$baseUrl/account/whoami") { bearerAuth(token) }
- if (r.status != HttpStatusCode.OK) {
- Log.e("getMe", r.bodyAsText())
- return null
- }
-
- val json = JSONObject(r.bodyAsText())
- return Me(json.getString("user_id"), json.getString("device_id"))
-}
@OptIn(ExperimentalSplittiesApi::class)
suspend fun login(server: String, login: String, pass: String): Boolean {
- val hs = getHomeserver(server)!!
+ val hs = Url(getHomeserver(server)!!)
val pinfo = appCtx.packageManager.getPackageInfo(appCtx.packageName, 0)
- val pattern = """
- {
- "type": "m.login.password",
- "identifier": {
- "type": "m.id.user"
- },
- "initial_device_display_name": "PixelDragon Android v${pinfo.versionName}"
- }
- """.trimIndent()
- val json = JSONObject(pattern)
- json.getJSONObject("identifier").put("user", login)
- json.put("password", pass)
+ try {
+ client = MatrixClient.create(
+ repositoriesModule = getRoomStore(appCtx),
+ mediaStoreModule = getMediaStore(),
+ cryptoDriverModule = CryptoDriverModule.vodozemac(),
+ authProviderData = MatrixClientAuthProviderData.classicLogin(
+ baseUrl = hs,
+ identifier = IdentifierType.User(login),
+ password = pass,
+ initialDeviceDisplayName = "PixelDragon Android v${pinfo.versionName}",
+ ).getOrThrow()
+ ).getOrThrow()
- val r = try {
- client.post("$hs/_matrix/client/v3/login") {
- setBody(json.toString())
- contentType(ContentType.Application.Json)
- }
+ return true
} catch (e: Exception) {
- Log.e("login", e.toString())
- return false
+ Log.i("Login", "Failed to login", e)
}
- if (r.status != HttpStatusCode.OK) {
- Log.e("login", r.bodyAsText())
- return false // TODO: Inform a user of error code
- }
-
- val res = JSONObject(r.bodyAsText())
- AccountData.edit {
- token = res.getString("access_token")
- homeserver = hs
- }
-
- return initCheck()
-}
-
-suspend fun getAccountData(user: String, state: String): JSONObject? {
- val r = client.get("$baseUrl/user/$user/account_data/$state") { bearerAuth(token) }
- if (r.status != HttpStatusCode.OK) return null
- return JSONObject(r.bodyAsText())
-}
-
-suspend fun getUserProfile(userId: String): UserProfile? {
- val r = client.get("$baseUrl/profile/$userId") { bearerAuth(token) }
- if (r.status != HttpStatusCode.OK) return null
- val json = JSONObject(r.bodyAsText())
- val name = json.optString("displayname", ""); json.remove("displayname")
- val avatar = json.optString("avatar_url", ""); json.remove("avatar_url")
- return UserProfile(name, avatar, json)
+ return false
}
\ No newline at end of file
diff --git a/app/src/main/java/ru/risdeveau/pixeldragon/api/sync.kt b/app/src/main/java/ru/risdeveau/pixeldragon/api/sync.kt
new file mode 100755
index 0000000..8828b5a
--- /dev/null
+++ b/app/src/main/java/ru/risdeveau/pixeldragon/api/sync.kt
@@ -0,0 +1,4 @@
+/*
+ * Created by sweetbread
+ * Copyright (c) 2026. All rights reserved.
+ */
\ No newline at end of file
diff --git a/app/src/main/java/ru/risdeveau/pixeldragon/db/Common.kt b/app/src/main/java/ru/risdeveau/pixeldragon/db/Common.kt
index c2d8165..8828b5a 100755
--- a/app/src/main/java/ru/risdeveau/pixeldragon/db/Common.kt
+++ b/app/src/main/java/ru/risdeveau/pixeldragon/db/Common.kt
@@ -1,41 +1,4 @@
/*
* Created by sweetbread
- * Copyright (c) 2025. All rights reserved.
- */
-
-package ru.risdeveau.pixeldragon.db
-
-import androidx.room.Database
-import androidx.room.Room
-import androidx.room.RoomDatabase
-import androidx.room.TypeConverter
-import splitties.init.appCtx
-import splitties.systemservices.connectivityManager
-import java.time.Instant
-
-@Database(entities = [RoomDB::class, SpaceToRoom::class, UserDB::class], version = 1)
-abstract class AppDatabase : RoomDatabase() {
- abstract fun roomDoa(): RoomDao
- abstract fun userDoa(): UserDao
-}
-
-class Converters {
- @TypeConverter
- fun fromInstant(value: Instant?): String? {
- return value?.toString()
- }
-
- @TypeConverter
- fun toInstant(value: String?): Instant? {
- return value?.let { Instant.parse(it) }
- }
-}
-
-fun isConnected(): Boolean = connectivityManager.isDefaultNetworkActive
-
-val cacheDb = Room.databaseBuilder(
- appCtx,
- AppDatabase::class.java, "cache"
-)
- .fallbackToDestructiveMigration()
- .build()
\ No newline at end of file
+ * Copyright (c) 2026. All rights reserved.
+ */
\ No newline at end of file
diff --git a/app/src/main/java/ru/risdeveau/pixeldragon/db/Room.kt b/app/src/main/java/ru/risdeveau/pixeldragon/db/Room.kt
index 3056b3d..8828b5a 100755
--- a/app/src/main/java/ru/risdeveau/pixeldragon/db/Room.kt
+++ b/app/src/main/java/ru/risdeveau/pixeldragon/db/Room.kt
@@ -1,101 +1,4 @@
/*
* Created by sweetbread
- * Copyright (c) 2025. All rights reserved.
- */
-
-package ru.risdeveau.pixeldragon.db
-
-import androidx.room.Dao
-import androidx.room.Delete
-import androidx.room.Embedded
-import androidx.room.Entity
-import androidx.room.Insert
-import androidx.room.Junction
-import androidx.room.OnConflictStrategy
-import androidx.room.PrimaryKey
-import androidx.room.Query
-import androidx.room.Relation
-import androidx.room.TypeConverters
-import ru.risdeveau.pixeldragon.repo.Room
-import ru.risdeveau.pixeldragon.repo.User
-import java.time.Instant
-
-@Entity(tableName = "room")
-@TypeConverters(Converters::class)
-data class RoomDB (
- @PrimaryKey val id: String,
- val updatedAt: Instant,
- val name: String?,
- val type: String,
- val creatorId: String?,
- val createTime: Long?,
- val avatarUrl: String?,
- val members: Int?,
- val joined: Boolean,
- val direct: String?
-)
-
-fun RoomDB.isExpired(cacheDuration: Long = 60 * 60): Boolean {
- return Instant.now().minusSeconds(cacheDuration) > updatedAt
-}
-
-suspend fun RoomDB.toDomain(): Room = Room(
- id = id,
- name = name,
- type = type,
- creatorId = creatorId,
- createTime = createTime,
- avatarUrl = avatarUrl,
- members = members,
- joined = joined,
- direct = direct?.let { User.getById(it) }
-)
-
-fun Room.toEntity(): RoomDB = RoomDB(
- id = id,
- updatedAt = Instant.now(),
- name = name,
- type = type,
- creatorId = creatorId,
- createTime = createTime,
- avatarUrl = avatarUrl,
- members = members,
- joined = joined,
- direct = direct?.id
-)
-
-
-@Dao
-interface RoomDao {
- @Query("SELECT * FROM room WHERE id LIKE :id LIMIT 1")
- fun getById(id: String): RoomDB?
-
-// @Transaction
-// @Query("SELECT * FROM room WHERE ")
-// fun getSpace(rid: String): Space
-
- @Query("SELECT * FROM room WHERE joined = 1 AND id NOT IN (SELECT id FROM SpaceToRoom)")
- fun getAllJoined(): List
-
- @Insert(onConflict = OnConflictStrategy.REPLACE)
- fun insert(vararg rooms: RoomDB)
-
- @Delete
- fun delete(room: RoomDB)
-}
-
-@Entity(primaryKeys = ["spaceId", "roomId"])
-data class SpaceToRoom(
- val spaceId: String,
- val roomId: String
-)
-
-data class Space(
- @Embedded val space: RoomDB,
- @Relation(
- parentColumn = "spaceId",
- entityColumn = "roomId",
- associateBy = Junction(SpaceToRoom::class)
- )
- val children: List
-)
\ No newline at end of file
+ * Copyright (c) 2026. All rights reserved.
+ */
\ No newline at end of file
diff --git a/app/src/main/java/ru/risdeveau/pixeldragon/db/User.kt b/app/src/main/java/ru/risdeveau/pixeldragon/db/User.kt
index e2bbe19..8828b5a 100755
--- a/app/src/main/java/ru/risdeveau/pixeldragon/db/User.kt
+++ b/app/src/main/java/ru/risdeveau/pixeldragon/db/User.kt
@@ -1,60 +1,4 @@
/*
* Created by sweetbread
- * Copyright (c) 2025. All rights reserved.
- */
-
-package ru.risdeveau.pixeldragon.db
-
-import androidx.room.Dao
-import androidx.room.Delete
-import androidx.room.Entity
-import androidx.room.Insert
-import androidx.room.OnConflictStrategy
-import androidx.room.PrimaryKey
-import androidx.room.Query
-import androidx.room.TypeConverters
-import org.json.JSONObject
-import ru.risdeveau.pixeldragon.repo.User
-import java.time.Instant
-
-@Entity(tableName = "user")
-@TypeConverters(Converters::class)
-data class UserDB (
- @PrimaryKey val id: String,
- val updatedAt: Instant,
- val name: String?,
- val avatar: String?,
- val attrs: String,
-)
-
-fun UserDB.isExpired(cacheDuration: Long = 60 * 60): Boolean {
- return Instant.now().minusSeconds(cacheDuration) > updatedAt
-}
-
-fun UserDB.toDomain(): User = User(
- id = id,
- name = name,
- avatarUrl = avatar,
- attrs = JSONObject(attrs),
-)
-
-fun User.toEntity(): UserDB = UserDB(
- id = id,
- updatedAt = Instant.now(),
- name = name,
- avatar = avatarUrl,
- attrs = attrs.toString().trim(),
-)
-
-
-@Dao
-interface UserDao {
- @Query("SELECT * FROM user WHERE id LIKE :id LIMIT 1")
- fun getById(id: String): UserDB?
-
- @Insert(onConflict = OnConflictStrategy.REPLACE)
- fun insert(vararg users: UserDB)
-
- @Delete
- fun delete(user: UserDB)
-}
\ No newline at end of file
+ * Copyright (c) 2026. All rights reserved.
+ */
\ No newline at end of file
diff --git a/app/src/main/java/ru/risdeveau/pixeldragon/repo/Room.kt b/app/src/main/java/ru/risdeveau/pixeldragon/repo/Room.kt
index fe1c96b..8828b5a 100755
--- a/app/src/main/java/ru/risdeveau/pixeldragon/repo/Room.kt
+++ b/app/src/main/java/ru/risdeveau/pixeldragon/repo/Room.kt
@@ -1,62 +1,4 @@
/*
* Created by sweetbread
- * Copyright (c) 2025. All rights reserved.
- */
-
-package ru.risdeveau.pixeldragon.repo
-
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import ru.risdeveau.pixeldragon.api.getJoinedRooms
-import ru.risdeveau.pixeldragon.api.getRoom
-import ru.risdeveau.pixeldragon.db.cacheDb
-import ru.risdeveau.pixeldragon.db.isConnected
-import ru.risdeveau.pixeldragon.db.isExpired
-import ru.risdeveau.pixeldragon.db.toDomain
-import ru.risdeveau.pixeldragon.db.toEntity
-
-class Room (
- val id: String,
- val name: String?,
- val type: String,
- val creatorId: String?,
- val createTime: Long?,
- val avatarUrl: String?,
- val members: Int?,
- val joined: Boolean,
- val direct: User?,
-
-) {
- companion object {
- suspend fun getById(id: String, cached: Boolean = true): Room {
- val cachedRoom = cacheDb.roomDoa().getById(id)
- if (!isConnected() and
- (!cached or (cachedRoom == null) or (cachedRoom?.isExpired() == true))
- ) {
- val room = getRoom(id)
- CoroutineScope(Dispatchers.IO).launch {
- val cacheRoom = room.toEntity()
- cacheDb.roomDoa().insert(cacheRoom)
- }
- return room
- }
- return cachedRoom!!.toDomain()
- }
-
- suspend fun getJoined(cached: Boolean = true): List {
- val cacheJoined = cacheDb.roomDoa().getAllJoined()
- if (isConnected() and
- (!cached or cacheJoined.isEmpty() or (cacheJoined.any { it.isExpired() }))
- ) {
- val rooms = getJoinedRooms()
- CoroutineScope(Dispatchers.IO).launch {
- val roomsDb = List(rooms.size) { i -> rooms[i].toEntity() }
- cacheDb.roomDoa().insert(*roomsDb.toTypedArray())
- }
- return rooms
- }
- return List(cacheJoined.size) { i -> cacheJoined[i].toDomain() }
- }
- }
-}
\ No newline at end of file
+ * Copyright (c) 2026. All rights reserved.
+ */
\ No newline at end of file
diff --git a/app/src/main/java/ru/risdeveau/pixeldragon/repo/User.kt b/app/src/main/java/ru/risdeveau/pixeldragon/repo/User.kt
index c45bf20..8828b5a 100755
--- a/app/src/main/java/ru/risdeveau/pixeldragon/repo/User.kt
+++ b/app/src/main/java/ru/risdeveau/pixeldragon/repo/User.kt
@@ -1,51 +1,4 @@
/*
* Created by sweetbread
- * Copyright (c) 2025. All rights reserved.
- */
-
-package ru.risdeveau.pixeldragon.repo
-
-import android.util.Log
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import org.json.JSONObject
-import ru.risdeveau.pixeldragon.api.getUserProfile
-import ru.risdeveau.pixeldragon.db.cacheDb
-import ru.risdeveau.pixeldragon.db.isConnected
-import ru.risdeveau.pixeldragon.db.isExpired
-import ru.risdeveau.pixeldragon.db.toDomain
-import ru.risdeveau.pixeldragon.db.toEntity
-
-class User (
- val id: String,
- val name: String?,
- val avatarUrl: String?,
- val attrs: JSONObject
-) {
- companion object {
- suspend fun getById(id: String, cached: Boolean = true): User? {
- val cachedUser = cacheDb.userDoa().getById(id)
- if (isConnected() and
- (!cached or (cachedUser == null) or (cachedUser?.isExpired() == true))
- ) {
- val userProfile = getUserProfile(id)
- if (userProfile == null) {
- Log.i("User.getById", "User $id not found")
- return null
- }
- val user = User(
- id,
- userProfile.displayName,
- userProfile.avatarUrl,
- userProfile.other
- )
- CoroutineScope(Dispatchers.IO).launch {
- cacheDb.userDoa().insert(user.toEntity())
- }
- return user
- }
- return cachedUser!!.toDomain()
- }
- }
-}
\ No newline at end of file
+ * Copyright (c) 2026. All rights reserved.
+ */
\ No newline at end of file
diff --git a/app/src/main/java/ru/risdeveau/pixeldragon/ui/activity/MainActivity.kt b/app/src/main/java/ru/risdeveau/pixeldragon/ui/activity/MainActivity.kt
index 04c37a7..7f39915 100755
--- a/app/src/main/java/ru/risdeveau/pixeldragon/ui/activity/MainActivity.kt
+++ b/app/src/main/java/ru/risdeveau/pixeldragon/ui/activity/MainActivity.kt
@@ -1,40 +1,53 @@
/*
* Created by sweetbread
- * Copyright (c) 2025. All rights reserved.
+ * Copyright (c) 2026. All rights reserved.
*/
package ru.risdeveau.pixeldragon.ui.activity
import android.os.Bundle
+import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.CenterAlignedTopAppBar
+import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
-import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults.topAppBarColors
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
+import de.connect2x.trixnity.client.CryptoDriverModule
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-import ru.risdeveau.pixeldragon.api.Me
-import ru.risdeveau.pixeldragon.api.getMe
-import ru.risdeveau.pixeldragon.initCheck
-import ru.risdeveau.pixeldragon.ui.layout.Room
+import kotlinx.coroutines.launch
+import de.connect2x.trixnity.client.MatrixClient
+import de.connect2x.trixnity.client.create
+import de.connect2x.trixnity.client.cryptodriver.vodozemac.vodozemac
+import de.connect2x.trixnity.clientserverapi.client.SyncState
+import ru.risdeveau.pixeldragon.R
+import ru.risdeveau.pixeldragon.client
import ru.risdeveau.pixeldragon.ui.layout.RoomList
import ru.risdeveau.pixeldragon.ui.theme.PixelDragonTheme
+import ru.risdeveau.pixeldragon.util.getMediaStore
+import ru.risdeveau.pixeldragon.util.getRoomStore
import splitties.activities.start
+import splitties.init.appCtx
+import splitties.resources.str
-var ME: Me? = null
class MainActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3Api::class)
@@ -44,56 +57,87 @@ class MainActivity : ComponentActivity() {
enableEdgeToEdge()
setContent {
PixelDragonTheme {
- Scaffold(
- modifier = Modifier.fillMaxSize(),
- topBar = {
- TopAppBar(
- colors = topAppBarColors(
- containerColor = MaterialTheme.colorScheme.primaryContainer,
- titleContentColor = MaterialTheme.colorScheme.primary,
- ),
- title = {
- Text("Top app bar")
- }
- )
- },
- ) { innerPadding ->
- val navController = rememberNavController()
+ var isClientReady by remember { mutableStateOf(false) }
+ val syncState by remember { mutableStateOf(SyncState.STOPPED) }
- LaunchedEffect(Unit) {
- if (initCheck()) {
- ME = withContext(Dispatchers.IO) { getMe() }
- if (ME != null) {
- navController.navigate("rooms")
- } else {
- start()
- finish()
+ if (!isClientReady) {
+ CircularProgressIndicator()
+ } else {
+ Scaffold(
+ modifier = Modifier.fillMaxSize(),
+ topBar = {
+ CenterAlignedTopAppBar(
+ colors = topAppBarColors(
+ containerColor = MaterialTheme.colorScheme.primaryContainer,
+ titleContentColor = MaterialTheme.colorScheme.primary,
+ ),
+ title = {
+ when (syncState) {
+ SyncState.STARTED -> Text("Syncing...")
+ SyncState.INITIAL_SYNC -> Text("Initial sync...")
+ SyncState.STOPPED,
+ SyncState.RUNNING -> Text(str(R.string.app_name))
+ SyncState.TIMEOUT -> Text("No network connection")
+ SyncState.ERROR -> Text("Error syncing")
+ }
+ }
+ )
+ },
+ ) { innerPadding ->
+ val navController = rememberNavController()
+
+ NavHost(navController = navController, startDestination = "rooms") {
+ composable("rooms") {
+ RoomList(Modifier.padding(innerPadding), navController)
}
- } else {
+ composable(
+ "room/{rid}",
+ arguments = listOf(navArgument("rid") { type = NavType.StringType })
+ ) { navBackStackEntry ->
+// Room(Modifier
+// .padding(innerPadding)
+// .fillMaxSize(), navBackStackEntry.arguments!!.getString("rid")!!)
+ }
+ composable(
+ "space/{rid}",
+ arguments = listOf(navArgument("rid") { type = NavType.StringType })
+ ) { navBackStackEntry ->
+ Text(
+ modifier = Modifier.padding(innerPadding),
+ text = "Not implemented"
+ )
+ }
+ }
+ }
+ }
+
+ LaunchedEffect(Unit) {
+ if (client == null) {
+ client = MatrixClient.create(
+ repositoriesModule = getRoomStore(appCtx),
+ mediaStoreModule = getMediaStore(),
+ cryptoDriverModule = CryptoDriverModule.vodozemac()
+ ).getOrNull()
+
+ if (client == null) {
start()
finish()
+ return@LaunchedEffect
}
}
- NavHost(navController = navController, startDestination = "none") {
- composable("none") { }
- composable("rooms") { RoomList(Modifier.padding(innerPadding), navController) }
- composable(
- "room/{rid}",
- arguments = listOf(navArgument("rid") { type = NavType.StringType })
- ) { navBackStackEntry ->
- Room(Modifier
- .padding(innerPadding)
- .fillMaxSize(), navBackStackEntry.arguments!!.getString("rid")!!)
- }
- composable(
- "space/{rid}",
- arguments = listOf(navArgument("rid") { type = NavType.StringType })
- ) { navBackStackEntry ->
- Text(modifier = Modifier.padding(innerPadding), text = "Not implemented") }
- }
+ Log.i("MainActivity", "Log in as ${client!!.userId}")
+ client!!.startSync()
+ isClientReady = true
}
}
}
}
+
+ override fun onDestroy() {
+ super.onDestroy()
+ CoroutineScope(Dispatchers.Main).launch {
+ client?.stopSync()
+ }
+ }
}
diff --git a/app/src/main/java/ru/risdeveau/pixeldragon/ui/item/Image.kt b/app/src/main/java/ru/risdeveau/pixeldragon/ui/item/Image.kt
index d32e3a8..5d868c8 100755
--- a/app/src/main/java/ru/risdeveau/pixeldragon/ui/item/Image.kt
+++ b/app/src/main/java/ru/risdeveau/pixeldragon/ui/item/Image.kt
@@ -1,6 +1,6 @@
/*
* Created by sweetbread
- * Copyright (c) 2025. All rights reserved.
+ * Copyright (c) 2026. All rights reserved.
*/
package ru.risdeveau.pixeldragon.ui.item
@@ -13,20 +13,22 @@ import androidx.compose.material.icons.outlined.Warning
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
-import coil3.compose.AsyncImagePainter
+import androidx.compose.ui.platform.LocalContext
import coil3.compose.rememberAsyncImagePainter
-import coil3.network.NetworkHeaders
-import coil3.network.httpHeaders
import coil3.request.ImageRequest
-import ru.risdeveau.pixeldragon.api.mxcToUrl
-import ru.risdeveau.pixeldragon.token
-import splitties.init.appCtx
-
+import kotlinx.coroutines.flow.MutableStateFlow
+import de.connect2x.trixnity.client.media
+import de.connect2x.trixnity.clientserverapi.model.media.FileTransferProgress
+import ru.risdeveau.pixeldragon.client
enum class ImageLoadState {
Loading, Success, Error
@@ -37,44 +39,90 @@ fun MXCImage(
mxcUrl: String,
modifier: Modifier = Modifier,
contentScale: ContentScale = ContentScale.Fit,
- contentDescription: String = ""
+ contentDescription: String = "",
+ showProgress: Boolean = true
) {
- mxcToUrl(mxcUrl)?.let { url ->
- val loadState = remember { mutableStateOf(ImageLoadState.Loading) }
- val painter = rememberAsyncImagePainter(
- model = ImageRequest.Builder(appCtx)
- .data(url)
- .httpHeaders(
- NetworkHeaders.Builder()
- .set("Authorization", "Bearer $token")
- .set("Cache-Control", "max-age=86400")
- .build()
- )
- .build(),
- onState = { state ->
- loadState.value = when (state) {
- is AsyncImagePainter.State.Loading -> ImageLoadState.Loading
- is AsyncImagePainter.State.Success -> ImageLoadState.Success
- else -> ImageLoadState.Error
- }
- }
+ val context = LocalContext.current
+ var imageLoadState by remember { mutableStateOf(ImageLoadState.Loading) }
+ var imageBytes by remember { mutableStateOf(null) }
+
+ val progressFlow = remember { MutableStateFlow(null) }
+
+ LaunchedEffect(mxcUrl) {
+ if (mxcUrl.isBlank()) {
+ imageLoadState = ImageLoadState.Error
+ return@LaunchedEffect
+ }
+ imageLoadState = ImageLoadState.Loading
+ progressFlow.value = null
+
+ val result = client!!.media.getMedia(
+ uri = mxcUrl,
+ progress = progressFlow.takeIf { showProgress },
+ saveToCache = true
)
- Box(
- modifier = modifier.fillMaxSize(),
- contentAlignment = Alignment.Center
- ) {
+ imageLoadState = result.fold(
+ onSuccess = { media ->
+ val bytes = media.toByteArray()
+ if (bytes != null) {
+ imageBytes = bytes
+ ImageLoadState.Success
+ } else {
+ ImageLoadState.Error
+ }
+ },
+ onFailure = {
+ ImageLoadState.Error
+ }
+ )
+ }
+
+ val progress by progressFlow.collectAsState()
+
+ val showProgressIndicator = showProgress &&
+ imageLoadState == ImageLoadState.Loading &&
+ progress != null
+
+ val painter = rememberAsyncImagePainter(
+ model = ImageRequest.Builder(context)
+ .data(imageBytes)
+ .build()
+ )
+
+ Box(modifier = modifier, contentAlignment = Alignment.Center) {
+ if (imageBytes != null) {
Image(
painter = painter,
contentDescription = contentDescription,
contentScale = contentScale,
modifier = Modifier.fillMaxSize()
)
+ }
- when (loadState.value) {
- ImageLoadState.Loading -> CircularProgressIndicator()
- ImageLoadState.Error -> Icon(Icons.Outlined.Warning, "Error")
- ImageLoadState.Success -> Unit
+ when {
+ showProgressIndicator -> {
+ progress?.let { p ->
+ val percent = p.total?.let { p.transferred.toFloat() / it }
+ if (percent == null) {
+ CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
+ } else {
+ CircularProgressIndicator(
+ progress = percent,
+ modifier = Modifier.align(Alignment.Center)
+ )
+ }
+ } ?: CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
+ }
+ imageLoadState == ImageLoadState.Loading -> {
+ CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
+ }
+ imageLoadState == ImageLoadState.Error -> {
+ Icon(
+ Icons.Outlined.Warning,
+ contentDescription = "Error",
+ modifier = Modifier.align(Alignment.Center)
+ )
}
}
}
diff --git a/app/src/main/java/ru/risdeveau/pixeldragon/ui/layout/Room.kt b/app/src/main/java/ru/risdeveau/pixeldragon/ui/layout/Room.kt
index a5259be..dba9067 100755
--- a/app/src/main/java/ru/risdeveau/pixeldragon/ui/layout/Room.kt
+++ b/app/src/main/java/ru/risdeveau/pixeldragon/ui/layout/Room.kt
@@ -1,342 +1,342 @@
-/*
- * Created by sweetbread
- * Copyright (c) 2025. All rights reserved.
- */
-
-package ru.risdeveau.pixeldragon.ui.layout
-
-import android.content.Context
-import android.content.Intent
-import android.net.Uri
-import android.webkit.WebResourceRequest
-import android.webkit.WebView
-import android.webkit.WebViewClient
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.foundation.lazy.rememberLazyListState
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.toArgb
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.viewinterop.AndroidView
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-import org.jsoup.Jsoup
-import org.jsoup.safety.Safelist
-import ru.risdeveau.pixeldragon.api.Event
-import ru.risdeveau.pixeldragon.api.getAccountData
-import ru.risdeveau.pixeldragon.api.getEventsAround
-import ru.risdeveau.pixeldragon.ui.activity.ME
-import ru.risdeveau.pixeldragon.ui.item.MXCImage
-
-@Composable
-fun Room(modifier: Modifier = Modifier, rid: String) {
- var eventsId by remember { mutableStateOf(listOf()) }
- val listState = rememberLazyListState()
-
- LaunchedEffect(Unit) {
- withContext(Dispatchers.IO) {
- val readMark = getAccountData(ME!!.userId, rid, "m.fully_read")
- val eventsAround = getEventsAround(rid, readMark!!.getString("event_id")) //FIXME: Null check
- eventsId = eventsAround.let {
- it.before + listOf(it.base) + it.after
- }
- }
- }
-
- LazyColumn(modifier = modifier, state = listState, reverseLayout = true) {
- items(eventsId.reversed()) { event ->
- EventItem(event)
- }
-
- item {
- if (eventsId.isEmpty()) {
- Text("Empty room")
- }
- }
- }
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun EventItem(event: Event) {
- Box (Modifier.fillMaxWidth()) {
- when (event.type) {
- "m.room.message" -> Column(
- Modifier
- .fillMaxSize()
- .then(
- if (event.sender != ME!!.userId)
- Modifier.padding(end = 16.dp)
- else
- Modifier.padding(start = 16.dp)
- )
- .padding(4.dp)
- .background(
- if (event.sender != ME?.userId)
- MaterialTheme.colorScheme.surfaceContainer
- else
- MaterialTheme.colorScheme.primaryContainer
- )
- .clip(RoundedCornerShape(16.dp))
- .padding(4.dp)
- ) {
- Text(event.sender, maxLines = 1, fontWeight = FontWeight.Bold)
-
- when (val msgtype = event.content.optString("msgtype", null)) {
- "m.text" -> when (event.content.optString("format")) {
- "org.matrix.custom.html" -> {
- if (event.content.getString("body") == event.content.getString("formatted_body"))
- Text(event.content.getString("body"))
- HtmlRenderer(event.content.getString("formatted_body"))
- }
-
- else -> Text(event.content.getString("body"))
- }
-
- "m.image" ->
- MXCImage(event.content.getString("url"), Modifier.fillMaxWidth(.9f))
-
- null -> Text(event.content.toString(2))
-
- else -> Text("Unknown type: $msgtype", color = MaterialTheme.colorScheme.error)
- }
-
- }
-
- else -> Text(event.type,
- Modifier
- .fillMaxHeight()
- .padding(4.dp)
- .background(MaterialTheme.colorScheme.errorContainer)
- .padding(4.dp)
- )
- }
- }
-}
-
-private fun String.sanitizeHTML(): String {
- val matrixSafelist = Safelist()
- .addTags(
- "h1", "h2", "h3", "h4", "h5", "h6",
- "b", "i", "u", "strong", "s", "del",
- "sup", "sub", "code",
- "table", "thead", "tbody",
- "tr", "th", "td", "ul", "ol", "li",
- "blockquote", "details", "summary",
- "em", "code", "div", "pre", "span", "img"
- )
-// .addAttributes("span",
-// "data-mx-bg-color", "data-mx-color",
-// "data-mx-spoiler", "data-mx-maths"
+///*
+// * Created by sweetbread
+// * Copyright (c) 2025. All rights reserved.
+// */
+//
+//package ru.risdeveau.pixeldragon.ui.layout
+//
+//import android.content.Context
+//import android.content.Intent
+//import android.net.Uri
+//import android.webkit.WebResourceRequest
+//import android.webkit.WebView
+//import android.webkit.WebViewClient
+//import androidx.compose.foundation.background
+//import androidx.compose.foundation.layout.Box
+//import androidx.compose.foundation.layout.Column
+//import androidx.compose.foundation.layout.fillMaxHeight
+//import androidx.compose.foundation.layout.fillMaxSize
+//import androidx.compose.foundation.layout.fillMaxWidth
+//import androidx.compose.foundation.layout.padding
+//import androidx.compose.foundation.lazy.LazyColumn
+//import androidx.compose.foundation.lazy.items
+//import androidx.compose.foundation.lazy.rememberLazyListState
+//import androidx.compose.foundation.shape.RoundedCornerShape
+//import androidx.compose.material3.ExperimentalMaterial3Api
+//import androidx.compose.material3.MaterialTheme
+//import androidx.compose.material3.Text
+//import androidx.compose.runtime.Composable
+//import androidx.compose.runtime.DisposableEffect
+//import androidx.compose.runtime.LaunchedEffect
+//import androidx.compose.runtime.getValue
+//import androidx.compose.runtime.mutableStateOf
+//import androidx.compose.runtime.remember
+//import androidx.compose.runtime.setValue
+//import androidx.compose.ui.Modifier
+//import androidx.compose.ui.draw.clip
+//import androidx.compose.ui.graphics.Color
+//import androidx.compose.ui.graphics.toArgb
+//import androidx.compose.ui.platform.LocalContext
+//import androidx.compose.ui.text.font.FontWeight
+//import androidx.compose.ui.unit.dp
+//import androidx.compose.ui.viewinterop.AndroidView
+//import kotlinx.coroutines.Dispatchers
+//import kotlinx.coroutines.withContext
+//import org.jsoup.Jsoup
+//import org.jsoup.safety.Safelist
+//import ru.risdeveau.pixeldragon.api.Event
+//import ru.risdeveau.pixeldragon.api.getAccountData
+//import ru.risdeveau.pixeldragon.api.getEventsAround
+//import ru.risdeveau.pixeldragon.ui.activity.ME
+//import ru.risdeveau.pixeldragon.ui.item.MXCImage
+//
+//@Composable
+//fun Room(modifier: Modifier = Modifier, rid: String) {
+// var eventsId by remember { mutableStateOf(listOf()) }
+// val listState = rememberLazyListState()
+//
+// LaunchedEffect(Unit) {
+// withContext(Dispatchers.IO) {
+// val readMark = getAccountData(ME!!.userId, rid, "m.fully_read")
+// val eventsAround = getEventsAround(rid, readMark!!.getString("event_id")) //FIXME: Null check
+// eventsId = eventsAround.let {
+// it.before + listOf(it.base) + it.after
+// }
+// }
+// }
+//
+// LazyColumn(modifier = modifier, state = listState, reverseLayout = true) {
+// items(eventsId.reversed()) { event ->
+// EventItem(event)
+// }
+//
+// item {
+// if (eventsId.isEmpty()) {
+// Text("Empty room")
+// }
+// }
+// }
+//}
+//
+//@OptIn(ExperimentalMaterial3Api::class)
+//@Composable
+//fun EventItem(event: Event) {
+// Box (Modifier.fillMaxWidth()) {
+// when (event.type) {
+// "m.room.message" -> Column(
+// Modifier
+// .fillMaxSize()
+// .then(
+// if (event.sender != ME!!.userId)
+// Modifier.padding(end = 16.dp)
+// else
+// Modifier.padding(start = 16.dp)
+// )
+// .padding(4.dp)
+// .background(
+// if (event.sender != ME?.userId)
+// MaterialTheme.colorScheme.surfaceContainer
+// else
+// MaterialTheme.colorScheme.primaryContainer
+// )
+// .clip(RoundedCornerShape(16.dp))
+// .padding(4.dp)
+// ) {
+// Text(event.sender, maxLines = 1, fontWeight = FontWeight.Bold)
+//
+// when (val msgtype = event.content.optString("msgtype", null)) {
+// "m.text" -> when (event.content.optString("format")) {
+// "org.matrix.custom.html" -> {
+// if (event.content.getString("body") == event.content.getString("formatted_body"))
+// Text(event.content.getString("body"))
+// HtmlRenderer(event.content.getString("formatted_body"))
+// }
+//
+// else -> Text(event.content.getString("body"))
+// }
+//
+// "m.image" ->
+// MXCImage(event.content.getString("url"), Modifier.fillMaxWidth(.9f))
+//
+// null -> Text(event.content.toString(2))
+//
+// else -> Text("Unknown type: $msgtype", color = MaterialTheme.colorScheme.error)
+// }
+//
+// }
+//
+// else -> Text(event.type,
+// Modifier
+// .fillMaxHeight()
+// .padding(4.dp)
+// .background(MaterialTheme.colorScheme.errorContainer)
+// .padding(4.dp)
+// )
+// }
+// }
+//}
+//
+//private fun String.sanitizeHTML(): String {
+// val matrixSafelist = Safelist()
+// .addTags(
+// "h1", "h2", "h3", "h4", "h5", "h6",
+// "b", "i", "u", "strong", "s", "del",
+// "sup", "sub", "code",
+// "table", "thead", "tbody",
+// "tr", "th", "td", "ul", "ol", "li",
+// "blockquote", "details", "summary",
+// "em", "code", "div", "pre", "span", "img"
// )
- .addAttributes("a",
- "target", "href"
- )
- .addAttributes("img",
- "width", "height", "alt", "title", "src"
- )
- .addAttributes("ol", "start")
- .addAttributes("code", "class")
- .addAttributes("div", "data-mx-maths")
-
- val doc = Jsoup.parse(this)
- doc.select("mx-reply").remove()
-
- val out = Jsoup.clean(doc.body().toString(), matrixSafelist)
- return out
-}
-
-
-@Composable
-fun HtmlRenderer(
- htmlContent: String,
- modifier: Modifier = Modifier
-) {
- val context = LocalContext.current
- val webView = remember { WebView(context).apply {
- settings.apply {
- javaScriptEnabled = false
- loadWithOverviewMode = true
- useWideViewPort = true
- }
- isVerticalScrollBarEnabled = false
- setBackgroundColor(Color.Transparent.toArgb())
- } }
-
- val css = """
- body {
- font-family: -apple-system, sans-serif;
- font-size: 16px;
- line-height: 1.6;
- color: ${colorToCss(MaterialTheme.colorScheme.onBackground)};
- margin: 0;
- padding: 0;
- }
- h1, h2, h3, h4, h5, h6 {
- font-weight: bold;
- padding: 0;
- }
- h1 { font-size: 24px; }
- h2 { font-size: 22px; }
- h3 { font-size: 20px; }
- h4 { font-size: 18px; }
- h5 { font-size: 16px; }
- h6 { font-size: 14px; }
- a {
- color: ${colorToCss(MaterialTheme.colorScheme.primary)};
- text-decoration: none;
- }
- img {
- max-width: 100%;
- height: auto;
- display: block;
- margin: 12px 0;
- }
- table {
- width: 100%;
- border-collapse: collapse;
- margin: 16px 0;
- }
- th, td {
- border: 1px solid ${colorToCss(MaterialTheme.colorScheme.outline)};
- padding: 12px;
- text-align: left;
- }
- th {
- background-color: ${colorToCss(MaterialTheme.colorScheme.surfaceVariant)};
- font-weight: bold;
- }
- blockquote {
- border-left: 4px solid ${colorToCss(MaterialTheme.colorScheme.primary)};
- padding-left: 16px;
- margin-left: 0;
- color: ${colorToCss(MaterialTheme.colorScheme.onSurfaceVariant)};
- }
- pre {
- background-color: ${colorToCss(MaterialTheme.colorScheme.surfaceVariant)};
- padding: 16px;
- overflow: auto;
- border-radius: 4px;
- }
- code {
- font-family: monospace;
- background-color: ${colorToCss(MaterialTheme.colorScheme.surfaceVariant)};
- padding: 2px 4px;
- border-radius: 4px;
- }
- hr {
- border: 0;
- height: 1px;
- background-color: ${colorToCss(MaterialTheme.colorScheme.outline)};
- margin: 24px 0;
- }
- ul, ol {
- padding-left: 24px;
- margin: 12px 0;
- }
- li {
- margin-bottom: 8px;
- }
- details {
- margin: 12px 0;
- }
- summary {
- font-weight: bold;
- cursor: pointer;
- }
- @media (prefers-color-scheme: dark) {
- :root {
- --border-color: ${colorToCss(MaterialTheme.colorScheme.outline)};
- }
- }
- """.trimIndent()
-
- LaunchedEffect(htmlContent) {
- webView.loadDataWithBaseURL(
- null,
- wrapHtml(htmlContent, css),
- "text/html",
- "UTF-8",
- null
- )
- }
-
- DisposableEffect(webView) {
- onDispose {
- webView.destroy()
- }
- }
-
- AndroidView(
- factory = { webView },
- modifier = modifier,
- update = { view ->
- view.webViewClient = SafeWebViewClient(context)
- }
- )
-}
-
-private class SafeWebViewClient(
- private val context: Context
-) : WebViewClient() {
- override fun shouldOverrideUrlLoading(
- view: WebView,
- request: WebResourceRequest
- ): Boolean {
- val url = request.url.toString()
- try {
- // Открываем ссылки во внешнем браузере
- context.startActivity(
- Intent(Intent.ACTION_VIEW, Uri.parse(url))
- )
- return true
- } catch (e: Exception) {
- // Обработка ошибок открытия ссылки
- return false
- }
- }
-}
-
-private fun wrapHtml(content: String, css: String): String {
- return """
-
-
-
-
-
-
-
- ${content.sanitizeHTML()}
-
-
- """.trimIndent()
-}
-
-private fun colorToCss(color: Color): String {
- val argb = color.toArgb()
- return String.format("#%06X", 0xFFFFFF and argb)
-}
\ No newline at end of file
+//// .addAttributes("span",
+//// "data-mx-bg-color", "data-mx-color",
+//// "data-mx-spoiler", "data-mx-maths"
+//// )
+// .addAttributes("a",
+// "target", "href"
+// )
+// .addAttributes("img",
+// "width", "height", "alt", "title", "src"
+// )
+// .addAttributes("ol", "start")
+// .addAttributes("code", "class")
+// .addAttributes("div", "data-mx-maths")
+//
+// val doc = Jsoup.parse(this)
+// doc.select("mx-reply").remove()
+//
+// val out = Jsoup.clean(doc.body().toString(), matrixSafelist)
+// return out
+//}
+//
+//
+//@Composable
+//fun HtmlRenderer(
+// htmlContent: String,
+// modifier: Modifier = Modifier
+//) {
+// val context = LocalContext.current
+// val webView = remember { WebView(context).apply {
+// settings.apply {
+// javaScriptEnabled = false
+// loadWithOverviewMode = true
+// useWideViewPort = true
+// }
+// isVerticalScrollBarEnabled = false
+// setBackgroundColor(Color.Transparent.toArgb())
+// } }
+//
+// val css = """
+// body {
+// font-family: -apple-system, sans-serif;
+// font-size: 16px;
+// line-height: 1.6;
+// color: ${colorToCss(MaterialTheme.colorScheme.onBackground)};
+// margin: 0;
+// padding: 0;
+// }
+// h1, h2, h3, h4, h5, h6 {
+// font-weight: bold;
+// padding: 0;
+// }
+// h1 { font-size: 24px; }
+// h2 { font-size: 22px; }
+// h3 { font-size: 20px; }
+// h4 { font-size: 18px; }
+// h5 { font-size: 16px; }
+// h6 { font-size: 14px; }
+// a {
+// color: ${colorToCss(MaterialTheme.colorScheme.primary)};
+// text-decoration: none;
+// }
+// img {
+// max-width: 100%;
+// height: auto;
+// display: block;
+// margin: 12px 0;
+// }
+// table {
+// width: 100%;
+// border-collapse: collapse;
+// margin: 16px 0;
+// }
+// th, td {
+// border: 1px solid ${colorToCss(MaterialTheme.colorScheme.outline)};
+// padding: 12px;
+// text-align: left;
+// }
+// th {
+// background-color: ${colorToCss(MaterialTheme.colorScheme.surfaceVariant)};
+// font-weight: bold;
+// }
+// blockquote {
+// border-left: 4px solid ${colorToCss(MaterialTheme.colorScheme.primary)};
+// padding-left: 16px;
+// margin-left: 0;
+// color: ${colorToCss(MaterialTheme.colorScheme.onSurfaceVariant)};
+// }
+// pre {
+// background-color: ${colorToCss(MaterialTheme.colorScheme.surfaceVariant)};
+// padding: 16px;
+// overflow: auto;
+// border-radius: 4px;
+// }
+// code {
+// font-family: monospace;
+// background-color: ${colorToCss(MaterialTheme.colorScheme.surfaceVariant)};
+// padding: 2px 4px;
+// border-radius: 4px;
+// }
+// hr {
+// border: 0;
+// height: 1px;
+// background-color: ${colorToCss(MaterialTheme.colorScheme.outline)};
+// margin: 24px 0;
+// }
+// ul, ol {
+// padding-left: 24px;
+// margin: 12px 0;
+// }
+// li {
+// margin-bottom: 8px;
+// }
+// details {
+// margin: 12px 0;
+// }
+// summary {
+// font-weight: bold;
+// cursor: pointer;
+// }
+// @media (prefers-color-scheme: dark) {
+// :root {
+// --border-color: ${colorToCss(MaterialTheme.colorScheme.outline)};
+// }
+// }
+// """.trimIndent()
+//
+// LaunchedEffect(htmlContent) {
+// webView.loadDataWithBaseURL(
+// null,
+// wrapHtml(htmlContent, css),
+// "text/html",
+// "UTF-8",
+// null
+// )
+// }
+//
+// DisposableEffect(webView) {
+// onDispose {
+// webView.destroy()
+// }
+// }
+//
+// AndroidView(
+// factory = { webView },
+// modifier = modifier,
+// update = { view ->
+// view.webViewClient = SafeWebViewClient(context)
+// }
+// )
+//}
+//
+//private class SafeWebViewClient(
+// private val context: Context
+//) : WebViewClient() {
+// override fun shouldOverrideUrlLoading(
+// view: WebView,
+// request: WebResourceRequest
+// ): Boolean {
+// val url = request.url.toString()
+// try {
+// // Открываем ссылки во внешнем браузере
+// context.startActivity(
+// Intent(Intent.ACTION_VIEW, Uri.parse(url))
+// )
+// return true
+// } catch (e: Exception) {
+// // Обработка ошибок открытия ссылки
+// return false
+// }
+// }
+//}
+//
+//private fun wrapHtml(content: String, css: String): String {
+// return """
+//
+//
+//
+//
+//
+//
+//
+// ${content.sanitizeHTML()}
+//
+//
+// """.trimIndent()
+//}
+//
+//private fun colorToCss(color: Color): String {
+// val argb = color.toArgb()
+// return String.format("#%06X", 0xFFFFFF and argb)
+//}
\ No newline at end of file
diff --git a/app/src/main/java/ru/risdeveau/pixeldragon/ui/layout/Rooms.kt b/app/src/main/java/ru/risdeveau/pixeldragon/ui/layout/Rooms.kt
index 5ef7c2f..d6c3e1a 100755
--- a/app/src/main/java/ru/risdeveau/pixeldragon/ui/layout/Rooms.kt
+++ b/app/src/main/java/ru/risdeveau/pixeldragon/ui/layout/Rooms.kt
@@ -1,10 +1,12 @@
/*
* Created by sweetbread
- * Copyright (c) 2025. All rights reserved.
+ * Copyright (c) 2026. All rights reserved.
*/
package ru.risdeveau.pixeldragon.ui.layout
+import android.annotation.SuppressLint
+import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
@@ -21,24 +23,27 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-import ru.risdeveau.pixeldragon.repo.Room
+import kotlinx.coroutines.flow.map
+import de.connect2x.trixnity.client.flattenValues
+import de.connect2x.trixnity.client.room
+import de.connect2x.trixnity.client.store.Room
+import de.connect2x.trixnity.client.store.type
+import de.connect2x.trixnity.core.model.events.m.room.CreateEventContent
+import ru.risdeveau.pixeldragon.client
import ru.risdeveau.pixeldragon.ui.item.MXCImage
+@SuppressLint("FlowOperatorInvokedInComposition")
@Composable
fun RoomList(modifier: Modifier = Modifier, navController: NavController) {
- var list by remember { mutableStateOf(listOf()) }
+ val rooms by client!!.room.getAll().flattenValues().map { it.toList() }.collectAsState(initial = emptyList())
+
val listState = rememberLazyListState()
// if (itemState.scrollToTop) {
@@ -48,17 +53,17 @@ fun RoomList(modifier: Modifier = Modifier, navController: NavController) {
// }
// }
- LaunchedEffect(Unit) {
- list = withContext(Dispatchers.IO) { Room.getJoined() }
- }
+// LaunchedEffect(Unit) {
+// list = withContext(Dispatchers.IO) { Room.getJoined() }
+// }
LazyColumn(modifier = modifier, state = listState) {
- items(list) { room ->
+ items(rooms) { room ->
RoomItem(room = room, navController = navController )
}
item {
- if (list.isEmpty()) {
+ if (rooms.isEmpty()) {
Text("You have no rooms")
}
}
@@ -67,8 +72,9 @@ fun RoomList(modifier: Modifier = Modifier, navController: NavController) {
@Composable
fun RoomItem(modifier: Modifier = Modifier, room: Room, navController: NavController) {
- val avatarUrl = room.avatarUrl ?: (room.direct?.avatarUrl ?: "")
- val name = room.name ?: (room.direct?.name ?: "Unnamed")
+ val name = room.name
+ val isSpace = room.type == CreateEventContent.RoomType.Space
+
Row(
modifier
@@ -77,42 +83,46 @@ fun RoomItem(modifier: Modifier = Modifier, room: Room, navController: NavContro
.fillMaxWidth()
.background(
color =
- if (room.type == "m.space")
+ if (isSpace)
MaterialTheme.colorScheme.tertiary
else
MaterialTheme.colorScheme.background
)
.clip(RoundedCornerShape(12.dp))
.clickable {
- if (room.type == "m.space")
- navController.navigate("space/${room.id}")
+ if (isSpace)
+ navController.navigate("space/${room.roomId}")
else
- navController.navigate("room/${room.id}")
+ navController.navigate("room/${room.roomId}")
}
.padding(8.dp)
) {
- MXCImage(
- avatarUrl,
- modifier = Modifier
- .padding(end = 4.dp)
- .height(52.dp)
- .width(52.dp)
- .let {
- if (room.type == "m.space")
- it.clip(RoundedCornerShape(12.dp))
- else
- it.clip(CircleShape)
- },
- ContentScale.Crop
- )
- Column {
- Text(room.type)
- Text(
- name,
- maxLines = 1,
- color = MaterialTheme.colorScheme.primary,
- fontSize = MaterialTheme.typography.titleLarge.fontSize
+ Log.v("RoomItem", room.avatarUrl.toString())
+ room.avatarUrl?.let { mxc ->
+ MXCImage(
+ mxc,
+ modifier = Modifier
+ .padding(end = 4.dp)
+ .height(52.dp)
+ .width(52.dp)
+ .let {
+ if (isSpace)
+ it.clip(RoundedCornerShape(12.dp))
+ else
+ it.clip(CircleShape)
+ },
+ ContentScale.Crop
)
}
+ Column {
+ (name?.explicitName ?: name?.heroes?.firstNotNullOf {it.localpart})?.let {
+ Text(
+ it,
+ maxLines = 1,
+ color = MaterialTheme.colorScheme.primary,
+ fontSize = MaterialTheme.typography.titleLarge.fontSize
+ )
+ }
+ }
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/ru/risdeveau/pixeldragon/util/TrixnityStores.kt b/app/src/main/java/ru/risdeveau/pixeldragon/util/TrixnityStores.kt
new file mode 100755
index 0000000..f91bc2e
--- /dev/null
+++ b/app/src/main/java/ru/risdeveau/pixeldragon/util/TrixnityStores.kt
@@ -0,0 +1,24 @@
+/*
+ * Created by sweetbread
+ * Copyright (c) 2026. All rights reserved.
+ */
+
+package ru.risdeveau.pixeldragon.util
+
+import android.content.Context
+import androidx.room.Room
+import de.connect2x.trixnity.client.MediaStoreModule
+import de.connect2x.trixnity.client.RepositoriesModule
+import de.connect2x.trixnity.client.media.okio.okio
+import de.connect2x.trixnity.client.store.repository.room.TrixnityRoomDatabase
+import de.connect2x.trixnity.client.store.repository.room.room
+import okio.Path.Companion.toPath
+import splitties.init.appCtx
+
+fun getMediaStore() = MediaStoreModule.okio(appCtx.filesDir.resolve("media").absolutePath.toPath())
+fun getRoomStore(context: Context) = RepositoriesModule.room(
+ databaseBuilder = Room.databaseBuilder(
+ context,
+ "trixnity.db"
+ )
+)
diff --git a/build.gradle.kts b/build.gradle.kts
index b55b582..015c4bf 100755
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,7 +1,6 @@
/*
- * Created by sweetbread on 22.02.2025, 15:45
- * Copyright (c) 2025. All rights reserved.
- * Last modified 21.02.2025, 12:21
+ * Created by sweetbread
+ * Copyright (c) 2026. All rights reserved.
*/
// Top-level build file where you can add configuration options common to all sub-projects/modules.
@@ -9,5 +8,4 @@ plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose) apply false
- id("com.google.devtools.ksp") version "2.0.21-1.0.27" apply false
-}
\ No newline at end of file
+}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 2a27e1b..e341f1d 100755
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -2,7 +2,7 @@
agp = "8.13.0"
coil = "3.1.0"
jsoup = "1.20.1"
-kotlin = "2.0.21"
+kotlin = "2.2.21"
coreKtx = "1.15.0"
junit = "4.13.2"
junitVersion = "1.2.1"
@@ -15,6 +15,7 @@ composeBom = "2025.02.00"
navigationCompose = "2.8.8"
room = "2.6.1"
splittiesFunPackAndroidBase = "3.0.0"
+trixnityClient = "5.2.0"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -24,7 +25,6 @@ androidx-navigation-dynamic-features-fragment = { module = "androidx.navigation:
androidx-navigation-fragment = { module = "androidx.navigation:navigation-fragment", version.ref = "navigationCompose" }
androidx-navigation-testing = { module = "androidx.navigation:navigation-testing", version.ref = "navigationCompose" }
androidx-navigation-ui = { module = "androidx.navigation:navigation-ui", version.ref = "navigationCompose" }
-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" }
@@ -43,14 +43,16 @@ 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-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
-kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
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-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
splitties-base = { module = "com.louiscad.splitties:splitties-fun-pack-android-base", version.ref = "splittiesFunPackAndroidBase" }
+trixnity-client = { module = "de.connect2x.trixnity:trixnity-client", version.ref = "trixnityClient" }
+trixnity-client-media-okio = { module = "de.connect2x.trixnity:trixnity-client-media-okio", version.ref = "trixnityClient" }
+trixnity-client-repository-room = { module = "de.connect2x.trixnity:trixnity-client-repository-room", version.ref = "trixnityClient" }
+trixnity-client-cryptodriver-vodozemac = { module = "de.connect2x.trixnity:trixnity-client-cryptodriver-vodozemac", version.ref = "trixnityClient" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
-
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 4e0a134..72d62b7 100755
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,7 +1,6 @@
/*
- * Created by sweetbread on 21.02.2025, 12:00
- * Copyright (c) 2025. All rights reserved.
- * Last modified 21.02.2025, 12:00
+ * Created by sweetbread
+ * Copyright (c) 2026. All rights reserved.
*/
pluginManagement {
@@ -22,6 +21,7 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
+ maven("https://gitlab.com/api/v4/projects/47538655/packages/maven")
}
}