diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0cd53c1..4c448fb 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,12 @@ 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.messenger) + // Ktor - web client implementation(libs.ktor.client.core) implementation(libs.ktor.client.okhttp) @@ -75,7 +81,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 +93,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 97990e1..73ab520 100755 --- a/app/src/main/java/ru/risdeveau/pixeldragon/Common.kt +++ b/app/src/main/java/ru/risdeveau/pixeldragon/Common.kt @@ -1,6 +1,6 @@ /* * Created by sweetbread - * Copyright (c) 2025. All rights reserved. + * Copyright (c) 2026. All rights reserved. */ package ru.risdeveau.pixeldragon @@ -11,11 +11,12 @@ 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.MatrixSyncService -import ru.risdeveau.pixeldragon.api.getMe +import net.folivo.trixnity.client.MatrixClient +//import ru.risdeveau.pixeldragon.api.MatrixSyncService +//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) { @@ -28,9 +29,7 @@ val client = HttpClient { install(HttpCache) } -lateinit var homeserver: String -lateinit var baseUrl: String -lateinit var token: String +var client: MatrixClient? = null object AccountData : Preferences("system_parameters") { var token by stringOrNullPref("token", null) @@ -40,15 +39,15 @@ object AccountData : Preferences("system_parameters") { var filter by stringOrNullPref("filter", null) } -val syncService = MatrixSyncService() +//val syncService = MatrixSyncService() -suspend fun initCheck(): Boolean { - Log.d("initCheck", "checking...") - - token = AccountData.token ?: return false - homeserver = AccountData.homeserver ?: return false - - baseUrl = "$homeserver/_matrix/client/v3" - - return getMe() != null -} +//suspend fun initCheck(): Boolean { +// Log.d("initCheck", "checking...") +// +// token = AccountData.token ?: return false +// homeserver = AccountData.homeserver ?: return false +// +// baseUrl = "$homeserver/_matrix/client/v3" +// +// return getMe() != null +//} 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 2cda807..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,19 +1,11 @@ /* * 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 ( 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..2471da0 100755 --- a/app/src/main/java/ru/risdeveau/pixeldragon/api/Server.kt +++ b/app/src/main/java/ru/risdeveau/pixeldragon/api/Server.kt @@ -1,6 +1,6 @@ /* * Created by sweetbread - * Copyright (c) 2025. All rights reserved. + * Copyright (c) 2026. All rights reserved. */ package ru.risdeveau.pixeldragon.api @@ -9,11 +9,11 @@ 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") } + val r = try { webClient.get("https://$url/.well-known/matrix/client") } catch (_: Exception) { return null } val json = try { JSONObject(r.bodyAsText()) } @@ -27,11 +27,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..0b277e2 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,73 @@ /* * 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 io.ktor.http.Url +import net.folivo.trixnity.client.MatrixClient +import net.folivo.trixnity.client.login +import net.folivo.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")) -} +//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 = webClient.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.login( + baseUrl = hs, + identifier = IdentifierType.User(login), + password = pass, + initialDeviceDisplayName = "PixelDragon Android v${pinfo.versionName}", + repositoriesModule = getRoomStore(appCtx), + mediaStore = getMediaStore() + ).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() + return false } -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) -} \ No newline at end of file +//suspend fun getAccountData(user: String, state: String): JSONObject? { +// val r = webClient.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 = webClient.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) +//} diff --git a/app/src/main/java/ru/risdeveau/pixeldragon/api/sync.kt b/app/src/main/java/ru/risdeveau/pixeldragon/api/sync.kt index 73af08b..8828b5a 100755 --- a/app/src/main/java/ru/risdeveau/pixeldragon/api/sync.kt +++ b/app/src/main/java/ru/risdeveau/pixeldragon/api/sync.kt @@ -1,242 +1,4 @@ /* * Created by sweetbread - * Copyright (c) 2025. 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.header -import io.ktor.client.request.parameter -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.contentLength -import io.ktor.http.contentType -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import org.json.JSONObject -import ru.risdeveau.pixeldragon.AccountData -import ru.risdeveau.pixeldragon.AccountData.syncLastBatch -import ru.risdeveau.pixeldragon.baseUrl -import ru.risdeveau.pixeldragon.client -import ru.risdeveau.pixeldragon.db.isConnected -import ru.risdeveau.pixeldragon.token -import ru.risdeveau.pixeldragon.ui.activity.ME -import splitties.init.appCtx -import java.io.File - - -class MatrixSyncService { - private val _syncState = MutableStateFlow(SyncState.Idle) - val syncState: StateFlow = _syncState.asStateFlow() - - private var syncJob: Job? = null -// private var isInitialized = false -// -// fun initialize() { -// isInitialized = true -// } - - fun startSync() { - if (AccountData.token == null) - Log.wtf("MatrixSyncService", "Token is null") - if (AccountData.userId == null) - Log.wtf("MatrixSyncService", "User ID is null") - if (AccountData.homeserver == null) - Log.wtf("MatrixSyncService", "Homeserver is null") - - Log.i("MatrixSyncService", "Start syncing") -// if (!isInitialized) -// throw IllegalStateException("Sync service not initialized") - - if (syncJob?.isActive == true) return - - syncJob = CoroutineScope(Dispatchers.IO).launch { - if (syncLastBatch == null) { - Log.i("MatrixSyncService", "Init syncing") - _syncState.value = SyncState.Syncing - try { - initialSync() - _syncState.value = SyncState.Idle - } catch (e: Exception) { - Log.e("MatrixSyncService", "Initial sync error", e) - _syncState.value = SyncState.Error(e.message) - } - } - -// while (isActive) { -// try { -// val response = sync ( -// timeout = 30000, -// filter = getFilter() -// ) -// -// processSyncResponse(response) -// -// } catch (e: Exception) { -// Log.w("sync", e.message.toString()) -// delay(5000) // Wait before retry -// } -// } - } - } - - - fun stopSync() { - syncJob?.cancel() - _syncState.value = SyncState.Idle - } - - fun pauseSync() { - // Called when app goes to background - syncJob?.cancel() - } - - fun resumeSync() { - // Called when app comes to foreground - if (!isSyncActive()) { - startSync() - } - } - - fun isSyncActive(): Boolean = syncJob?.isActive == true - - private suspend fun parseSyncResponse(response: JSONObject) { - Log.v("syncResponse", response.toString(2)) - val newMessages = mutableListOf() - val roomUpdates = mutableListOf() - - response.rooms?.join?.forEach { (roomId, roomData) -> - // Process timeline events (messages) - roomData.timeline?.events?.forEach { event -> - val message = event.toMessage(roomId) - database.messageDao().insertMessage(message) - newMessages.add(message) - } - - // Process room state updates - roomData.state?.events?.forEach { event -> - when (event.type) { - "m.room.name", "m.room.avatar" -> { - roomUpdates.add(RoomUpdate(roomId, event.type, event.content)) - } - } - } - - // Process ephemeral events (typing, receipts) - roomData.ephemeral?.events?.forEach { event -> - when (event.type) { - "m.typing" -> handleTypingEvent(roomId, event) - "m.receipt" -> handleReceiptEvent(roomId, event) - } - } - } - - // Notify the app about new data - if (newMessages.isNotEmpty()) { - _newMessages.emit(newMessages) - } - if (roomUpdates.isNotEmpty()) { - _roomUpdates.emit(roomUpdates) - } - } -} - -sealed class SyncState { - object Idle : SyncState() - object Syncing : SyncState() - object Success : SyncState() - data class Error(val message: String?) : SyncState() -} - -suspend fun sync(): JSONObject { - - return JSONObject() - -} - -suspend fun initialSync(): JSONObject { - fetchRoomMeta() -} - -/** - * Fetch rooms metadata during initial sync - */ -suspend fun fetchRoomMeta() { - val filterId = getFilterId(""" - { - "room": { - "state": { - "types": [ - "m.room.name", - "m.room.avatar", - "m.room.canonical_alias", - "m.room.member" - ], - "lazy_load_members": true - }, - "timeline": { - "limit": 0, - "types": [] - }, - "ephemeral": { - "types": [] - }, - "include_leave": false - }, - "presence": { - "types": [] - } - } - """.trimIndent()) -// val filterId = "vmNk" - - val r = client.get("$baseUrl/sync") { - bearerAuth(token) - url { - parameter("filter", filterId) - } - } - - if (r.status != HttpStatusCode.OK) - throw IllegalStateException("Failed to sync") - Log.v("initialSync", "Response size: ${r.contentLength()}") - - r.contentLength()?.let { - if (it >= 50*1024*1024) - Log.w("initialSync", "Response size is too large") - } - val json = JSONObject(r.bodyAsText()) - syncLastBatch = json.getString("next_batch") -} - -/** - * Send a filter to the server and get its id - * @param filter JSON Filter - * @return Filter ID - */ -suspend fun getFilterId(filter: String): String { - val userId = AccountData.userId - if (userId == null) - Log.wtf("getFilter", "user_id is not defined") - - val r = client.post("$baseUrl/user/$userId/filter") { - setBody(filter) - contentType(ContentType.Application.Json) - bearerAuth(token) - } - - return JSONObject(r.bodyAsText()).getString("filter_id") -} \ 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/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 75441ca..ed37a2d 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,16 +1,18 @@ /* * 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.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold @@ -18,23 +20,24 @@ 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 kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import ru.risdeveau.pixeldragon.AccountData -import ru.risdeveau.pixeldragon.api.Me -import ru.risdeveau.pixeldragon.api.getMe -import ru.risdeveau.pixeldragon.initCheck -import ru.risdeveau.pixeldragon.syncService -//import ru.risdeveau.pixeldragon.ui.layout.Room -import ru.risdeveau.pixeldragon.ui.layout.RoomList +import net.folivo.trixnity.client.MatrixClient +import net.folivo.trixnity.client.fromStore +import ru.risdeveau.pixeldragon.client 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 class MainActivity : ComponentActivity() { @@ -42,50 +45,72 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - if (AccountData.token == null) { - start() - finish() - } else { - syncService.startSync() - } 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) } - 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 -> + if (!isClientReady) { + CircularProgressIndicator() + } else { + 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() + + 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" + ) + } } - 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.fromStore( + getRoomStore(appCtx), + getMediaStore() + ).getOrNull() + + if (client == null) { + start() + finish() + return@LaunchedEffect + } + } + + Log.i("MainActivity", "Log in as ${client!!.userId}") + isClientReady = true + } } } } 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..8828b5a 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,81 +1,4 @@ /* * Created by sweetbread - * Copyright (c) 2025. All rights reserved. - */ - -package ru.risdeveau.pixeldragon.ui.item - -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material.icons.Icons -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.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.ContentScale -import coil3.compose.AsyncImagePainter -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 - - -enum class ImageLoadState { - Loading, Success, Error -} - -@Composable -fun MXCImage( - mxcUrl: String, - modifier: Modifier = Modifier, - contentScale: ContentScale = ContentScale.Fit, - contentDescription: String = "" -) { - 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 - } - } - ) - - Box( - modifier = modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - 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 - } - } - } -} \ 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/layout/Rooms.kt b/app/src/main/java/ru/risdeveau/pixeldragon/ui/layout/Rooms.kt index 7dfdc52..8828b5a 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,118 +1,4 @@ /* * Created by sweetbread - * Copyright (c) 2025. All rights reserved. - */ - -package ru.risdeveau.pixeldragon.ui.layout - -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -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.MaterialTheme -import androidx.compose.material3.Text -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.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 ru.risdeveau.pixeldragon.ui.item.MXCImage - -@Composable -fun RoomList(modifier: Modifier = Modifier, navController: NavController) { - var list by remember { mutableStateOf(listOf()) } - val listState = rememberLazyListState() - -// if (itemState.scrollToTop) { -// LaunchedEffect(coroutineScope) { -// Log.e("TAG", "TopCoinsScreen: scrollToTop" ) -// listState.scrollToItem(0) -// } -// } - -// LaunchedEffect(Unit) { -// list = withContext(Dispatchers.IO) { Room.getJoined() } -// } - - LazyColumn(modifier = modifier, state = listState) { - items(list) { room -> - RoomItem(room = room, navController = navController ) - } - - item { - if (list.isEmpty()) { - Text("You have no rooms") - } - } - } -} - -@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") - - Row( - modifier - .padding(8.dp) - .height((52 + 8 * 2).dp) - .fillMaxWidth() - .background( - color = - if (room.type == "m.space") - MaterialTheme.colorScheme.tertiary - else - MaterialTheme.colorScheme.background - ) - .clip(RoundedCornerShape(12.dp)) - .clickable { - if (room.type == "m.space") - navController.navigate("space/${room.id}") - else - navController.navigate("room/${room.id}") - } - .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 - ) - } - } -} \ 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/util/TrixnityStores.kt b/app/src/main/java/ru/risdeveau/pixeldragon/util/TrixnityStores.kt new file mode 100755 index 0000000..9d67bfb --- /dev/null +++ b/app/src/main/java/ru/risdeveau/pixeldragon/util/TrixnityStores.kt @@ -0,0 +1,27 @@ +/* + * Created by sweetbread + * Copyright (c) 2026. All rights reserved. + */ + +package ru.risdeveau.pixeldragon.util + +import android.content.Context +import androidx.room.Room +import net.folivo.trixnity.client.media.okio.OkioMediaStore +import net.folivo.trixnity.client.store.repository.room.TrixnityRoomDatabase +import net.folivo.trixnity.client.store.repository.room.createRoomRepositoriesModule +import okio.Path.Companion.toPath +import org.koin.dsl.module +import splitties.init.appCtx + +fun getMediaStore() = OkioMediaStore(appCtx.filesDir.resolve("media").absolutePath.toPath()) +fun getRoomStore(context: Context) = module { + includes( + createRoomRepositoriesModule( + 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..b23bd0a 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,9 @@ composeBom = "2025.02.00" navigationCompose = "2.8.8" room = "2.6.1" splittiesFunPackAndroidBase = "3.0.0" +trixnityMessenger = "3.8.11" +trixnityClient = "4.15.0" +ksp = "2.0.21-1.0.27" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -48,9 +51,12 @@ ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "kto 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 = "net.folivo:trixnity-client", version.ref = "trixnityClient" } +trixnity-client-media-okio = { module = "net.folivo:trixnity-client-media-okio", version.ref = "trixnityClient" } +trixnity-client-repository-room = { module = "net.folivo:trixnity-client-repository-room", version.ref = "trixnityClient" } +trixnity-messenger = { module = "de.connect2x:trixnity-messenger", version.ref = "trixnityMessenger" } [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") } }