Compare commits
19
Commits
0384626d83
...
feat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a91bbaf129 | ||
|
|
f8b10ebb34 | ||
|
|
a090ff614a | ||
|
|
dd3b31d0b2 | ||
|
|
c7b5f20c06 | ||
|
|
8d6a76ccb5 | ||
|
|
1cfad2ca4e | ||
|
|
b6e8c73758 | ||
|
|
28337b1306 | ||
|
|
05d3d739e2 | ||
|
|
5c6cd29a05 | ||
|
|
70d9db6cbf | ||
|
|
7026acc229 | ||
|
|
fa53e0ae86 | ||
|
|
ebbc9a4b1f | ||
|
|
05fbfbac07 | ||
|
|
5897d31a51 | ||
|
|
a0bfba23cf | ||
|
|
00f273c866 |
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AndroidProjectSystem">
|
||||||
|
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
+3
@@ -49,6 +49,9 @@
|
|||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
<option name="previewFile" value="true" />
|
<option name="previewFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
<option name="previewFile" value="true" />
|
<option name="previewFile" value="true" />
|
||||||
|
|||||||
Generated
+4
@@ -5,8 +5,12 @@
|
|||||||
<set>
|
<set>
|
||||||
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||||
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||||
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||||
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="StudioBotProjectSettings">
|
||||||
|
<option name="shareContext" value="OptedIn" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
+12
-4
@@ -1,13 +1,13 @@
|
|||||||
/*
|
/*
|
||||||
* Created by sweetbread
|
* Created by sweetbread
|
||||||
* Copyright (c) 2025. All rights reserved.
|
* Copyright (c) 2026. All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
alias(libs.plugins.kotlin.compose)
|
alias(libs.plugins.kotlin.compose)
|
||||||
id("com.google.devtools.ksp")
|
// alias(libs.plugins.ksp)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -16,7 +16,7 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "ru.risdeveau.pixeldragon"
|
applicationId = "ru.risdeveau.pixeldragon"
|
||||||
minSdk = 24
|
minSdk = 28
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = "1.0"
|
versionName = "1.0"
|
||||||
@@ -62,6 +62,13 @@ dependencies {
|
|||||||
debugImplementation(libs.androidx.ui.tooling)
|
debugImplementation(libs.androidx.ui.tooling)
|
||||||
debugImplementation(libs.androidx.ui.test.manifest)
|
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
|
// Ktor - web client
|
||||||
implementation(libs.ktor.client.core)
|
implementation(libs.ktor.client.core)
|
||||||
implementation(libs.ktor.client.okhttp)
|
implementation(libs.ktor.client.okhttp)
|
||||||
@@ -75,7 +82,7 @@ dependencies {
|
|||||||
implementation(libs.androidx.room.runtime)
|
implementation(libs.androidx.room.runtime)
|
||||||
implementation(libs.androidx.lifecycle.viewmodel.ktx)
|
implementation(libs.androidx.lifecycle.viewmodel.ktx)
|
||||||
implementation(libs.androidx.room.ktx)
|
implementation(libs.androidx.room.ktx)
|
||||||
ksp(libs.androidx.room.compiler)
|
// ksp(libs.androidx.room.compiler)
|
||||||
|
|
||||||
// Navigation Compose
|
// Navigation Compose
|
||||||
implementation(libs.androidx.navigation.compose)
|
implementation(libs.androidx.navigation.compose)
|
||||||
@@ -87,4 +94,5 @@ dependencies {
|
|||||||
// Others
|
// Others
|
||||||
implementation(libs.splitties.base) // Syntax sugar
|
implementation(libs.splitties.base) // Syntax sugar
|
||||||
implementation(libs.jsoup) // HTML parser
|
implementation(libs.jsoup) // HTML parser
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!--
|
<!--
|
||||||
~ Created by sweetbread on 22.02.2025, 15:45
|
~ Created by sweetbread
|
||||||
~ Copyright (c) 2025. All rights reserved.
|
~ Copyright (c) 2025. All rights reserved.
|
||||||
~ Last modified 22.02.2025, 14:00
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
@@ -21,7 +20,7 @@
|
|||||||
android:theme="@style/Theme.PixelDragon"
|
android:theme="@style/Theme.PixelDragon"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.activity.Login"
|
android:name=".ui.activity.MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:theme="@style/Theme.PixelDragon">
|
android:theme="@style/Theme.PixelDragon">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@@ -32,7 +31,7 @@
|
|||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.activity.MainActivity"
|
android:name=".ui.activity.Login"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:theme="@style/Theme.PixelDragon" />
|
android:theme="@style/Theme.PixelDragon" />
|
||||||
</application>
|
</application>
|
||||||
|
|||||||
@@ -1,24 +1,19 @@
|
|||||||
/*
|
/*
|
||||||
* Created by sweetbread
|
* Created by sweetbread
|
||||||
* Copyright (c) 2025. All rights reserved.
|
* Copyright (c) 2026. All rights reserved.
|
||||||
* Last modified 03.03.2025, 15:32
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.risdeveau.pixeldragon
|
package ru.risdeveau.pixeldragon
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.room.Room
|
import de.connect2x.trixnity.client.MatrixClient
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.plugins.cache.HttpCache
|
import io.ktor.client.plugins.cache.HttpCache
|
||||||
import io.ktor.client.plugins.logging.LogLevel
|
import io.ktor.client.plugins.logging.LogLevel
|
||||||
import io.ktor.client.plugins.logging.Logger
|
import io.ktor.client.plugins.logging.Logger
|
||||||
import io.ktor.client.plugins.logging.Logging
|
import io.ktor.client.plugins.logging.Logging
|
||||||
import ru.risdeveau.pixeldragon.api.getMe
|
|
||||||
import ru.risdeveau.pixeldragon.db.AppDatabase
|
|
||||||
import splitties.init.appCtx
|
|
||||||
|
|
||||||
val client = HttpClient {
|
val webClient = HttpClient {
|
||||||
install(Logging) {
|
install(Logging) {
|
||||||
logger = object : Logger {
|
logger = object : Logger {
|
||||||
override fun log(message: String) {
|
override fun log(message: String) {
|
||||||
@@ -31,28 +26,4 @@ val client = HttpClient {
|
|||||||
install(HttpCache)
|
install(HttpCache)
|
||||||
}
|
}
|
||||||
|
|
||||||
val accountData = appCtx.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
var client: MatrixClient? = null
|
||||||
lateinit var homeserver: String
|
|
||||||
lateinit var baseUrl: String
|
|
||||||
lateinit var token: String
|
|
||||||
|
|
||||||
suspend fun initCheck(): Boolean {
|
|
||||||
Log.d("initCheck", "checking...")
|
|
||||||
|
|
||||||
if (!accountData.contains("token")) return false
|
|
||||||
if (!accountData.contains("homeserver")) return false
|
|
||||||
|
|
||||||
|
|
||||||
token = accountData.getString("token", "").toString()
|
|
||||||
homeserver = accountData.getString("homeserver", "").toString()
|
|
||||||
baseUrl = "$homeserver/_matrix/client/v3"
|
|
||||||
|
|
||||||
Log.d("initCheck", "homeserver: $homeserver")
|
|
||||||
|
|
||||||
return getMe() != null
|
|
||||||
}
|
|
||||||
|
|
||||||
val db = Room.databaseBuilder(
|
|
||||||
appCtx,
|
|
||||||
AppDatabase::class.java, "database"
|
|
||||||
).build()
|
|
||||||
@@ -1,56 +1,50 @@
|
|||||||
/*
|
/*
|
||||||
* Created by sweetbread
|
* Created by sweetbread
|
||||||
* Copyright (c) 2025. All rights reserved.
|
* Copyright (c) 2026. All rights reserved.
|
||||||
* Last modified 03.03.2025, 20:21
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.risdeveau.pixeldragon.api
|
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 org.json.JSONObject
|
||||||
import ru.risdeveau.pixeldragon.baseUrl
|
import java.time.Instant
|
||||||
import ru.risdeveau.pixeldragon.client
|
|
||||||
import ru.risdeveau.pixeldragon.token
|
|
||||||
|
|
||||||
class Event (
|
class Event (
|
||||||
val id: String,
|
val id: String,
|
||||||
val rid: String,
|
val rid: String,
|
||||||
val sender: String,
|
|
||||||
val type: String,
|
val type: String,
|
||||||
val content: JSONObject
|
val content: JSONObject,
|
||||||
|
val time: Instant,
|
||||||
|
val sender: String
|
||||||
) {
|
) {
|
||||||
constructor(json: JSONObject) : this(
|
// constructor(json: JSONObject) : this(
|
||||||
json.getString("event_id"),
|
// json.getString("event_id"),
|
||||||
json.getString("room_id"),
|
// json.getString("room_id"),
|
||||||
json.getString("sender"),
|
// json.getString("sender"),
|
||||||
json.getString("type"),
|
// json.getString("type"),
|
||||||
json.getJSONObject("content")
|
// json.getJSONObject("content")
|
||||||
)
|
// )
|
||||||
}
|
}
|
||||||
|
|
||||||
data class EventsAround (
|
//data class EventsAround (
|
||||||
val base: Event,
|
// val base: Event,
|
||||||
val before: List<Event>,
|
// val before: List<Event>,
|
||||||
val after: List<Event>
|
// val after: List<Event>
|
||||||
)
|
//)
|
||||||
|
//
|
||||||
suspend fun getEventsAround(room: String, event: String): EventsAround {
|
//suspend fun getEventsAround(room: String, event: String): EventsAround {
|
||||||
val r = client.get("$baseUrl/rooms/$room/context/$event") {
|
// val r = client.get("$baseUrl/rooms/$room/context/$event") {
|
||||||
bearerAuth(token)
|
// bearerAuth(token)
|
||||||
parameter("limit", "50")
|
// parameter("limit", "50")
|
||||||
}
|
// }
|
||||||
val json = JSONObject(r.bodyAsText())
|
// val json = JSONObject(r.bodyAsText())
|
||||||
|
//
|
||||||
return EventsAround(
|
// return EventsAround(
|
||||||
Event(json.getJSONObject("event")),
|
// Event(json.getJSONObject("event")),
|
||||||
if (json.has("events_before")) json.getJSONArray("events_before").let {
|
// if (json.has("events_before")) json.getJSONArray("events_before").let {
|
||||||
List<Event>(it.length()) { i -> Event(it.getJSONObject(i))}.reversed()
|
// List<Event>(it.length()) { i -> Event(it.getJSONObject(i))}.reversed()
|
||||||
} else listOf(),
|
// } else listOf(),
|
||||||
if (json.has("events_after")) json.getJSONArray("events_after").let {
|
// if (json.has("events_after")) json.getJSONArray("events_after").let {
|
||||||
List<Event>(it.length()) { i -> Event(it.getJSONObject(i))}
|
// List<Event>(it.length()) { i -> Event(it.getJSONObject(i))}
|
||||||
} else listOf()
|
// } else listOf()
|
||||||
)
|
// )
|
||||||
}
|
//}
|
||||||
@@ -1,63 +1,4 @@
|
|||||||
/*
|
/*
|
||||||
* Created by sweetbread
|
* Created by sweetbread
|
||||||
* Copyright (c) 2025. All rights reserved.
|
* Copyright (c) 2026. All rights reserved.
|
||||||
* Last modified 03.03.2025, 18:28
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.risdeveau.pixeldragon.api
|
|
||||||
|
|
||||||
import io.ktor.client.request.bearerAuth
|
|
||||||
import io.ktor.client.request.get
|
|
||||||
import io.ktor.client.statement.bodyAsText
|
|
||||||
import io.ktor.http.HttpStatusCode
|
|
||||||
import org.json.JSONObject
|
|
||||||
import ru.risdeveau.pixeldragon.baseUrl
|
|
||||||
import ru.risdeveau.pixeldragon.client
|
|
||||||
import ru.risdeveau.pixeldragon.db
|
|
||||||
import ru.risdeveau.pixeldragon.db.Room
|
|
||||||
import ru.risdeveau.pixeldragon.token
|
|
||||||
|
|
||||||
//fun getRooms(): List<Room> {
|
|
||||||
// return db.roomDoa().getAllJoined()
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//fun updateRooms(): List<Room> {
|
|
||||||
//
|
|
||||||
//}
|
|
||||||
|
|
||||||
suspend fun getRooms(): List<String> {
|
|
||||||
val r = client.get("$baseUrl/joined_rooms")
|
|
||||||
{ bearerAuth(token) }
|
|
||||||
val rooms = JSONObject(r.bodyAsText()).getJSONArray("joined_rooms")
|
|
||||||
return List<String>(
|
|
||||||
rooms.length()
|
|
||||||
) { i -> rooms.getString(i) }
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getRoom(rid: String): Room {
|
|
||||||
var room = db.roomDoa().getById(rid)
|
|
||||||
if (room == null) {
|
|
||||||
val name = getState(rid, "m.room.name", "name")
|
|
||||||
val type = getState(rid, "m.room.create", "type") ?: "m.room"
|
|
||||||
val creator = getState(rid, "m.room.create", "creator")
|
|
||||||
val avatar = getState(rid, "m.room.avatar", "url")
|
|
||||||
room = Room(rid, name, type, creator, null, avatar, null, true)
|
|
||||||
db.roomDoa().insert(room)
|
|
||||||
}
|
|
||||||
|
|
||||||
return room
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun getState(rid: String, state: String, key: String): String? {
|
|
||||||
val r = client.get("$baseUrl/rooms/$rid/state/$state") { bearerAuth(token) }
|
|
||||||
if (r.status != HttpStatusCode.OK) return null
|
|
||||||
val json = JSONObject(r.bodyAsText())
|
|
||||||
if (!json.has(key)) return null
|
|
||||||
return json.getString(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,24 +1,30 @@
|
|||||||
/*
|
/*
|
||||||
* Created by sweetbread
|
* Created by sweetbread
|
||||||
* Copyright (c) 2025. All rights reserved.
|
* Copyright (c) 2026. All rights reserved.
|
||||||
* Last modified 03.03.2025, 15:40
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.risdeveau.pixeldragon.api
|
package ru.risdeveau.pixeldragon.api
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import io.ktor.client.request.get
|
import io.ktor.client.request.get
|
||||||
import io.ktor.client.statement.bodyAsText
|
import io.ktor.client.statement.bodyAsText
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import ru.risdeveau.pixeldragon.client
|
import ru.risdeveau.pixeldragon.webClient
|
||||||
import ru.risdeveau.pixeldragon.homeserver
|
//import ru.risdeveau.pixeldragon.homeserver
|
||||||
|
|
||||||
suspend fun getHomeserver(url: String): String? {
|
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 }
|
catch (e: Exception) {
|
||||||
|
Log.w("getHomeserver", "Fail sending the request", e)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
val json = try { JSONObject(r.bodyAsText()) }
|
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
|
if (!json.has("m.homeserver")) return null
|
||||||
|
|
||||||
@@ -28,9 +34,11 @@ suspend fun getHomeserver(url: String): String? {
|
|||||||
return homeserver
|
return homeserver
|
||||||
}
|
}
|
||||||
|
|
||||||
fun mxcToUrl(mxc: String): String {
|
//fun mxcToUrl(mxc: String): String? {
|
||||||
val pattern = Regex("mxc://([-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6})/([a-zA-Z]+)")
|
// val pattern = Regex("mxc://([-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6})/([^#?]+)")
|
||||||
val match = pattern.find(mxc)
|
// val match = pattern.find(mxc)
|
||||||
|
//
|
||||||
return "$homeserver/_matrix/client/v1/media/download/${match?.groupValues[1]}/${match?.groupValues[2]}"
|
// 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]}"
|
||||||
|
//}
|
||||||
@@ -1,84 +1,49 @@
|
|||||||
/*
|
/*
|
||||||
* Created by sweetbread
|
* Created by sweetbread
|
||||||
* Copyright (c) 2025. All rights reserved.
|
* Copyright (c) 2026. All rights reserved.
|
||||||
* Last modified 03.03.2025, 15:43
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.risdeveau.pixeldragon.api
|
package ru.risdeveau.pixeldragon.api
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import io.ktor.client.request.bearerAuth
|
import de.connect2x.trixnity.client.CryptoDriverModule
|
||||||
import io.ktor.client.request.get
|
import io.ktor.http.Url
|
||||||
import io.ktor.client.request.post
|
import de.connect2x.trixnity.client.MatrixClient
|
||||||
import io.ktor.client.request.setBody
|
import de.connect2x.trixnity.client.create
|
||||||
import io.ktor.client.statement.bodyAsText
|
import de.connect2x.trixnity.client.cryptodriver.vodozemac.vodozemac
|
||||||
import io.ktor.http.ContentType
|
import de.connect2x.trixnity.clientserverapi.client.MatrixClientAuthProviderData
|
||||||
import io.ktor.http.HttpStatusCode
|
import de.connect2x.trixnity.clientserverapi.client.classicLogin
|
||||||
import io.ktor.http.contentType
|
import de.connect2x.trixnity.clientserverapi.model.authentication.IdentifierType
|
||||||
import org.json.JSONObject
|
|
||||||
import ru.risdeveau.pixeldragon.accountData
|
|
||||||
import ru.risdeveau.pixeldragon.baseUrl
|
|
||||||
import ru.risdeveau.pixeldragon.client
|
import ru.risdeveau.pixeldragon.client
|
||||||
import ru.risdeveau.pixeldragon.initCheck
|
import ru.risdeveau.pixeldragon.util.getMediaStore
|
||||||
import ru.risdeveau.pixeldragon.token
|
import ru.risdeveau.pixeldragon.util.getRoomStore
|
||||||
|
import splitties.experimental.ExperimentalSplittiesApi
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
|
|
||||||
data class Me (val userId: String, val deviceId: String)
|
|
||||||
|
|
||||||
/**
|
@OptIn(ExperimentalSplittiesApi::class)
|
||||||
* 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"))
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("ApplySharedPref")
|
|
||||||
suspend fun login(server: String, login: String, pass: String): Boolean {
|
suspend fun login(server: String, login: String, pass: String): Boolean {
|
||||||
val homeserver = getHomeserver(server)!!
|
val hs = Url(getHomeserver(server)!!)
|
||||||
|
|
||||||
val pinfo = appCtx.packageManager.getPackageInfo(appCtx.packageName, 0)
|
val pinfo = appCtx.packageManager.getPackageInfo(appCtx.packageName, 0)
|
||||||
|
|
||||||
val pattern = """
|
try {
|
||||||
{
|
client = MatrixClient.create(
|
||||||
"type": "m.login.password",
|
repositoriesModule = getRoomStore(appCtx),
|
||||||
"identifier": {
|
mediaStoreModule = getMediaStore(),
|
||||||
"type": "m.id.user"
|
cryptoDriverModule = CryptoDriverModule.vodozemac(),
|
||||||
},
|
authProviderData = MatrixClientAuthProviderData.classicLogin(
|
||||||
"initial_device_display_name": "PixelDragon Android v${pinfo.versionName}"
|
baseUrl = hs,
|
||||||
}
|
identifier = IdentifierType.User(login),
|
||||||
""".trimIndent()
|
password = pass,
|
||||||
val json = JSONObject(pattern)
|
initialDeviceDisplayName = "PixelDragon Android v${pinfo.versionName}",
|
||||||
json.getJSONObject("identifier").put("user", login)
|
).getOrThrow()
|
||||||
json.put("password", pass)
|
).getOrThrow()
|
||||||
|
|
||||||
val r = try {
|
return true
|
||||||
client.post("$homeserver/_matrix/client/v3/login") {
|
|
||||||
setBody(json.toString())
|
|
||||||
contentType(ContentType.Application.Json)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("login", e.toString())
|
Log.i("Login", "Failed to login", e)
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
|
||||||
|
|
||||||
if (r.status != HttpStatusCode.OK) {
|
|
||||||
Log.e("login", r.bodyAsText())
|
|
||||||
return false // TODO: Inform a user of error code
|
|
||||||
}
|
|
||||||
|
|
||||||
val res = JSONObject(r.bodyAsText())
|
|
||||||
val editor = accountData.edit()
|
|
||||||
editor.putString("token", res.getString("access_token"))
|
|
||||||
editor.putString("homeserver", homeserver)
|
|
||||||
editor.commit()
|
|
||||||
|
|
||||||
return initCheck()
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/*
|
||||||
|
* Created by sweetbread
|
||||||
|
* Copyright (c) 2026. All rights reserved.
|
||||||
|
*/
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/*
|
||||||
|
* Created by sweetbread
|
||||||
|
* Copyright (c) 2026. All rights reserved.
|
||||||
|
*/
|
||||||
@@ -1,81 +1,4 @@
|
|||||||
/*
|
/*
|
||||||
* Created by sweetbread
|
* Created by sweetbread
|
||||||
* Copyright (c) 2025. All rights reserved.
|
* Copyright (c) 2026. All rights reserved.
|
||||||
* Last modified 22.02.2025, 19:49
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.risdeveau.pixeldragon.db
|
|
||||||
|
|
||||||
import androidx.room.Dao
|
|
||||||
import androidx.room.Database
|
|
||||||
import androidx.room.Delete
|
|
||||||
import androidx.room.Embedded
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.Insert
|
|
||||||
import androidx.room.Junction
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
import androidx.room.Query
|
|
||||||
import androidx.room.Relation
|
|
||||||
import androidx.room.RoomDatabase
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
data class Room(
|
|
||||||
@PrimaryKey val roomId: String,
|
|
||||||
val name: String?,
|
|
||||||
val type: String,
|
|
||||||
val creatorId: String?,
|
|
||||||
val createTime: Long?,
|
|
||||||
val avatarUrl: String?,
|
|
||||||
val members: Int?,
|
|
||||||
val joined: Boolean
|
|
||||||
)
|
|
||||||
|
|
||||||
@Dao
|
|
||||||
interface RoomDao {
|
|
||||||
// @Query("SELECT * FROM room")
|
|
||||||
// fun getAll(): List<User>
|
|
||||||
|
|
||||||
// @Query("SELECT * FROM user WHERE uid IN (:userIds)")
|
|
||||||
// fun loadAllByIds(userIds: IntArray): List<User>
|
|
||||||
|
|
||||||
// @Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
|
|
||||||
// "last_name LIKE :last LIMIT 1")
|
|
||||||
// fun findByName(first: String, last: String): User
|
|
||||||
|
|
||||||
@Query("SELECT * FROM room WHERE roomId LIKE :rid LIMIT 1")
|
|
||||||
fun getById(rid: String): Room?
|
|
||||||
|
|
||||||
// @Transaction
|
|
||||||
// @Query("SELECT * FROM room WHERE ")
|
|
||||||
// fun getSpace(rid: String): Space
|
|
||||||
|
|
||||||
@Query("SELECT * FROM room WHERE joined = 1 AND roomId NOT IN (SELECT roomId FROM SpaceToRoom)")
|
|
||||||
fun getAllJoined(): List<Room>
|
|
||||||
|
|
||||||
@Insert
|
|
||||||
fun insert(vararg rooms: Room)
|
|
||||||
|
|
||||||
@Delete
|
|
||||||
fun delete(room: Room)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Entity(primaryKeys = ["spaceId", "roomId"])
|
|
||||||
data class SpaceToRoom(
|
|
||||||
val spaceId: String,
|
|
||||||
val roomId: String
|
|
||||||
)
|
|
||||||
|
|
||||||
data class Space(
|
|
||||||
@Embedded val space: Room,
|
|
||||||
@Relation(
|
|
||||||
parentColumn = "spaceId",
|
|
||||||
entityColumn = "roomId",
|
|
||||||
associateBy = Junction(SpaceToRoom::class)
|
|
||||||
)
|
|
||||||
val children: List<Room>
|
|
||||||
)
|
|
||||||
|
|
||||||
@Database(entities = [Room::class, SpaceToRoom::class, User::class], version = 1)
|
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
|
||||||
abstract fun roomDoa(): RoomDao
|
|
||||||
}
|
|
||||||
@@ -1,18 +1,4 @@
|
|||||||
/*
|
/*
|
||||||
* Created by sweetbread on 22.02.2025, 15:45
|
* Created by sweetbread
|
||||||
* Copyright (c) 2025. All rights reserved.
|
* Copyright (c) 2026. All rights reserved.
|
||||||
* Last modified 21.02.2025, 13:38
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.risdeveau.pixeldragon.db
|
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
data class User(
|
|
||||||
@PrimaryKey val uid: String,
|
|
||||||
@ColumnInfo(name = "name") val name: String?,
|
|
||||||
@ColumnInfo(name = "avatar") val avatar: String?
|
|
||||||
)
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/*
|
||||||
|
* Created by sweetbread
|
||||||
|
* Copyright (c) 2026. All rights reserved.
|
||||||
|
*/
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/*
|
||||||
|
* Created by sweetbread
|
||||||
|
* Copyright (c) 2026. All rights reserved.
|
||||||
|
*/
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Created by sweetbread
|
* Created by sweetbread
|
||||||
* Copyright (c) 2025. All rights reserved.
|
* Copyright (c) 2025. All rights reserved.
|
||||||
* Last modified 03.03.2025, 16:08
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.risdeveau.pixeldragon.ui.activity
|
package ru.risdeveau.pixeldragon.ui.activity
|
||||||
@@ -36,11 +35,9 @@ import androidx.compose.ui.text.input.KeyboardType
|
|||||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import ru.risdeveau.pixeldragon.api.getHomeserver
|
import ru.risdeveau.pixeldragon.api.getHomeserver
|
||||||
import ru.risdeveau.pixeldragon.api.login
|
import ru.risdeveau.pixeldragon.api.login
|
||||||
import ru.risdeveau.pixeldragon.initCheck
|
|
||||||
import ru.risdeveau.pixeldragon.ui.theme.PixelDragonTheme
|
import ru.risdeveau.pixeldragon.ui.theme.PixelDragonTheme
|
||||||
import splitties.activities.start
|
import splitties.activities.start
|
||||||
|
|
||||||
@@ -49,14 +46,6 @@ class Login : ComponentActivity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
|
||||||
GlobalScope.launch {
|
|
||||||
if (initCheck()) {
|
|
||||||
start<MainActivity>()
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
setContent {
|
setContent {
|
||||||
PixelDragonTheme {
|
PixelDragonTheme {
|
||||||
@@ -75,7 +64,10 @@ class Login : ComponentActivity() {
|
|||||||
.padding(innerPadding)) {
|
.padding(innerPadding)) {
|
||||||
LoginField(
|
LoginField(
|
||||||
Modifier.align(Alignment.Center),
|
Modifier.align(Alignment.Center),
|
||||||
{ start<MainActivity>() },
|
{
|
||||||
|
start<MainActivity>()
|
||||||
|
finish()
|
||||||
|
},
|
||||||
{
|
{
|
||||||
scope.launch {
|
scope.launch {
|
||||||
snackbarHostState
|
snackbarHostState
|
||||||
|
|||||||
@@ -1,38 +1,53 @@
|
|||||||
/*
|
/*
|
||||||
* Created by sweetbread
|
* Created by sweetbread
|
||||||
* Copyright (c) 2025. All rights reserved.
|
* Copyright (c) 2026. All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.risdeveau.pixeldragon.ui.activity
|
package ru.risdeveau.pixeldragon.ui.activity
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
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.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.material3.TopAppBarDefaults.topAppBarColors
|
import androidx.compose.material3.TopAppBarDefaults.topAppBarColors
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
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.Modifier
|
||||||
import androidx.navigation.NavType
|
import androidx.navigation.NavType
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import androidx.navigation.navArgument
|
import androidx.navigation.navArgument
|
||||||
|
import de.connect2x.trixnity.client.CryptoDriverModule
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.launch
|
||||||
import ru.risdeveau.pixeldragon.api.Me
|
import de.connect2x.trixnity.client.MatrixClient
|
||||||
import ru.risdeveau.pixeldragon.api.getMe
|
import de.connect2x.trixnity.client.create
|
||||||
import ru.risdeveau.pixeldragon.ui.layout.Room
|
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.layout.RoomList
|
||||||
import ru.risdeveau.pixeldragon.ui.theme.PixelDragonTheme
|
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() {
|
class MainActivity : ComponentActivity() {
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -42,44 +57,87 @@ class MainActivity : ComponentActivity() {
|
|||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
setContent {
|
setContent {
|
||||||
PixelDragonTheme {
|
PixelDragonTheme {
|
||||||
|
var isClientReady by remember { mutableStateOf(false) }
|
||||||
|
val syncState by remember { mutableStateOf(SyncState.STOPPED) }
|
||||||
|
|
||||||
|
if (!isClientReady) {
|
||||||
|
CircularProgressIndicator()
|
||||||
|
} else {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
CenterAlignedTopAppBar(
|
||||||
colors = topAppBarColors(
|
colors = topAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||||
titleContentColor = MaterialTheme.colorScheme.primary,
|
titleContentColor = MaterialTheme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
title = {
|
title = {
|
||||||
Text("Top app bar")
|
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 ->
|
) { innerPadding ->
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
ME = getMe()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NavHost(navController = navController, startDestination = "rooms") {
|
NavHost(navController = navController, startDestination = "rooms") {
|
||||||
composable("rooms") { RoomList(Modifier.padding(innerPadding), navController) }
|
composable("rooms") {
|
||||||
|
RoomList(Modifier.padding(innerPadding), navController)
|
||||||
|
}
|
||||||
composable(
|
composable(
|
||||||
"room/{rid}",
|
"room/{rid}",
|
||||||
arguments = listOf(navArgument("rid") { type = NavType.StringType })
|
arguments = listOf(navArgument("rid") { type = NavType.StringType })
|
||||||
) { navBackStackEntry ->
|
) { navBackStackEntry ->
|
||||||
Room(Modifier.padding(innerPadding).fillMaxSize(), navBackStackEntry.arguments!!.getString("rid")!!)
|
// Room(Modifier
|
||||||
|
// .padding(innerPadding)
|
||||||
|
// .fillMaxSize(), navBackStackEntry.arguments!!.getString("rid")!!)
|
||||||
}
|
}
|
||||||
composable(
|
composable(
|
||||||
"space/{rid}",
|
"space/{rid}",
|
||||||
arguments = listOf(navArgument("rid") { type = NavType.StringType })
|
arguments = listOf(navArgument("rid") { type = NavType.StringType })
|
||||||
) { navBackStackEntry ->
|
) { navBackStackEntry ->
|
||||||
Text(modifier = Modifier.padding(innerPadding), text = "Not implemented") }
|
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<Login>()
|
||||||
|
finish()
|
||||||
|
return@LaunchedEffect
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i("MainActivity", "Log in as ${client!!.userId}")
|
||||||
|
client!!.startSync()
|
||||||
|
isClientReady = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
client?.stopSync()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Created by sweetbread
|
* Created by sweetbread
|
||||||
* Copyright (c) 2025. All rights reserved.
|
* Copyright (c) 2026. All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.risdeveau.pixeldragon.ui.item
|
package ru.risdeveau.pixeldragon.ui.item
|
||||||
@@ -13,19 +13,22 @@ import androidx.compose.material.icons.outlined.Warning
|
|||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.runtime.Composable
|
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.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import coil3.compose.AsyncImagePainter
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import coil3.compose.rememberAsyncImagePainter
|
import coil3.compose.rememberAsyncImagePainter
|
||||||
import coil3.network.NetworkHeaders
|
|
||||||
import coil3.network.httpHeaders
|
|
||||||
import coil3.request.ImageRequest
|
import coil3.request.ImageRequest
|
||||||
import ru.risdeveau.pixeldragon.api.mxcToUrl
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import ru.risdeveau.pixeldragon.token
|
import de.connect2x.trixnity.client.media
|
||||||
import splitties.init.appCtx
|
import de.connect2x.trixnity.clientserverapi.model.media.FileTransferProgress
|
||||||
|
import ru.risdeveau.pixeldragon.client
|
||||||
|
|
||||||
enum class ImageLoadState {
|
enum class ImageLoadState {
|
||||||
Loading, Success, Error
|
Loading, Success, Error
|
||||||
@@ -36,42 +39,91 @@ fun MXCImage(
|
|||||||
mxcUrl: String,
|
mxcUrl: String,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
contentScale: ContentScale = ContentScale.Fit,
|
contentScale: ContentScale = ContentScale.Fit,
|
||||||
contentDescription: String = ""
|
contentDescription: String = "",
|
||||||
|
showProgress: Boolean = true
|
||||||
) {
|
) {
|
||||||
val loadState = remember { mutableStateOf(ImageLoadState.Loading) }
|
val context = LocalContext.current
|
||||||
val painter = rememberAsyncImagePainter(
|
var imageLoadState by remember { mutableStateOf(ImageLoadState.Loading) }
|
||||||
model = ImageRequest.Builder(appCtx)
|
var imageBytes by remember { mutableStateOf<ByteArray?>(null) }
|
||||||
.data(mxcToUrl(mxcUrl))
|
|
||||||
.httpHeaders(
|
val progressFlow = remember { MutableStateFlow<FileTransferProgress?>(null) }
|
||||||
NetworkHeaders.Builder()
|
|
||||||
.set("Authorization", "Bearer $token")
|
LaunchedEffect(mxcUrl) {
|
||||||
.set("Cache-Control", "max-age=86400")
|
if (mxcUrl.isBlank()) {
|
||||||
.build()
|
imageLoadState = ImageLoadState.Error
|
||||||
)
|
return@LaunchedEffect
|
||||||
.build(),
|
|
||||||
onState = { state ->
|
|
||||||
loadState.value = when (state) {
|
|
||||||
is AsyncImagePainter.State.Loading -> ImageLoadState.Loading
|
|
||||||
is AsyncImagePainter.State.Success -> ImageLoadState.Success
|
|
||||||
else -> ImageLoadState.Error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
imageLoadState = ImageLoadState.Loading
|
||||||
|
progressFlow.value = null
|
||||||
|
|
||||||
|
val result = client!!.media.getMedia(
|
||||||
|
uri = mxcUrl,
|
||||||
|
progress = progressFlow.takeIf { showProgress },
|
||||||
|
saveToCache = true
|
||||||
)
|
)
|
||||||
|
|
||||||
Box(
|
imageLoadState = result.fold(
|
||||||
modifier = modifier.fillMaxSize()
|
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(
|
Image(
|
||||||
painter = painter,
|
painter = painter,
|
||||||
contentDescription = contentDescription,
|
contentDescription = contentDescription,
|
||||||
contentScale = contentScale,
|
contentScale = contentScale,
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
when (loadState.value) {
|
when {
|
||||||
ImageLoadState.Loading -> CircularProgressIndicator()
|
showProgressIndicator -> {
|
||||||
ImageLoadState.Error -> Icon(Icons.Outlined.Warning, "Error")
|
progress?.let { p ->
|
||||||
ImageLoadState.Success -> Unit
|
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)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
package ru.risdeveau.pixeldragon.ui.layout
|
package ru.risdeveau.pixeldragon.ui.layout
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
@@ -28,10 +29,11 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.produceState
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
@@ -41,43 +43,49 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
|
import de.connect2x.trixnity.client.room
|
||||||
|
import de.connect2x.trixnity.client.room.toFlowList
|
||||||
|
import de.connect2x.trixnity.client.store.TimelineEvent
|
||||||
|
import de.connect2x.trixnity.client.user
|
||||||
|
import de.connect2x.trixnity.core.model.RoomId
|
||||||
|
import de.connect2x.trixnity.core.model.events.ClientEvent
|
||||||
|
import de.connect2x.trixnity.core.model.events.m.room.RoomMessageEventContent
|
||||||
|
import io.ktor.util.reflect.instanceOf
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.flatMapConcat
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
import org.jsoup.safety.Safelist
|
import org.jsoup.safety.Safelist
|
||||||
import ru.risdeveau.pixeldragon.api.Event
|
import ru.risdeveau.pixeldragon.client
|
||||||
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
|
import ru.risdeveau.pixeldragon.ui.item.MXCImage
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Room(modifier: Modifier = Modifier, rid: String) {
|
fun Room(modifier: Modifier = Modifier, rid: String) {
|
||||||
var eventsId by remember { mutableStateOf(listOf<Event>()) }
|
val roomId = remember(rid) { RoomId(rid) }
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val limit = remember { MutableStateFlow(50) }
|
||||||
|
val eventsFlow = remember(roomId) {
|
||||||
|
client!!.room
|
||||||
|
.getLastTimelineEvents(roomId)
|
||||||
|
.toFlowList(limit)
|
||||||
|
}
|
||||||
|
val eventFlows by eventsFlow.collectAsState(initial = emptyList())
|
||||||
|
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
LaunchedEffect(true) {
|
|
||||||
coroutineScope.launch {
|
|
||||||
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) {
|
LazyColumn(modifier = modifier, state = listState, reverseLayout = true) {
|
||||||
items(eventsId.reversed()) { event ->
|
items(eventFlows, key = { it.hashCode() }) { eventFlow ->
|
||||||
EventItem(event)
|
EventItem(eventFlow)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (eventFlows.isEmpty()) {
|
||||||
item {
|
item {
|
||||||
if (eventsId.isEmpty()) {
|
Text("Empty room", modifier = Modifier.padding(16.dp))
|
||||||
Text("Empty room")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,13 +93,15 @@ fun Room(modifier: Modifier = Modifier, rid: String) {
|
|||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun EventItem(event: Event) {
|
fun EventItem(eventFlow: Flow<TimelineEvent>) {
|
||||||
Box (Modifier.fillMaxWidth()) {
|
val event by eventFlow.collectAsState(null)
|
||||||
when (event.type) {
|
event?.let { event ->
|
||||||
|
Box(Modifier.fillMaxWidth()) {
|
||||||
|
/*when (event.type) {
|
||||||
"m.room.message" -> Column(
|
"m.room.message" -> Column(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.then (
|
.then(
|
||||||
if (event.sender != ME!!.userId)
|
if (event.sender != ME!!.userId)
|
||||||
Modifier.padding(end = 16.dp)
|
Modifier.padding(end = 16.dp)
|
||||||
else
|
else
|
||||||
@@ -111,8 +121,11 @@ fun EventItem(event: Event) {
|
|||||||
|
|
||||||
when (val msgtype = event.content.optString("msgtype", null)) {
|
when (val msgtype = event.content.optString("msgtype", null)) {
|
||||||
"m.text" -> when (event.content.optString("format")) {
|
"m.text" -> when (event.content.optString("format")) {
|
||||||
"org.matrix.custom.html" ->
|
"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"))
|
HtmlRenderer(event.content.getString("formatted_body"))
|
||||||
|
}
|
||||||
|
|
||||||
else -> Text(event.content.getString("body"))
|
else -> Text(event.content.getString("body"))
|
||||||
}
|
}
|
||||||
@@ -127,13 +140,14 @@ fun EventItem(event: Event) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> Text(event.type,
|
else -> Text(event.content?.getOrNull().toString(),
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
.padding(4.dp)
|
.padding(4.dp)
|
||||||
.background(MaterialTheme.colorScheme.errorContainer)
|
.background(MaterialTheme.colorScheme.errorContainer)
|
||||||
.padding(4.dp)
|
.padding(4.dp)
|
||||||
)
|
)*/
|
||||||
|
Text(event.content?.getOrNull().toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -342,3 +356,12 @@ private fun colorToCss(color: Color): String {
|
|||||||
val argb = color.toArgb()
|
val argb = color.toArgb()
|
||||||
return String.format("#%06X", 0xFFFFFF and argb)
|
return String.format("#%06X", 0xFFFFFF and argb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
fun Flow<Flow<Flow<TimelineEvent>>?>.flattenTimelineEvents(): Flow<TimelineEvent> = this
|
||||||
|
.filterNotNull()
|
||||||
|
.flatMapConcat { middleFlow ->
|
||||||
|
middleFlow.flatMapConcat { innerFlow ->
|
||||||
|
innerFlow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Created by sweetbread
|
* Created by sweetbread
|
||||||
* Copyright (c) 2025. All rights reserved.
|
* Copyright (c) 2026. All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ru.risdeveau.pixeldragon.ui.layout
|
package ru.risdeveau.pixeldragon.ui.layout
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.util.Log
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -18,32 +20,30 @@ import androidx.compose.foundation.lazy.items
|
|||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import de.connect2x.trixnity.client.flattenValues
|
||||||
import kotlinx.coroutines.withContext
|
import de.connect2x.trixnity.client.room
|
||||||
import ru.risdeveau.pixeldragon.api.getRoom
|
import de.connect2x.trixnity.client.store.Room
|
||||||
import ru.risdeveau.pixeldragon.api.getRooms
|
import de.connect2x.trixnity.client.store.type
|
||||||
import ru.risdeveau.pixeldragon.db.Room
|
import de.connect2x.trixnity.core.model.events.m.room.CreateEventContent
|
||||||
|
import ru.risdeveau.pixeldragon.client
|
||||||
import ru.risdeveau.pixeldragon.ui.item.MXCImage
|
import ru.risdeveau.pixeldragon.ui.item.MXCImage
|
||||||
|
|
||||||
|
@SuppressLint("FlowOperatorInvokedInComposition")
|
||||||
@Composable
|
@Composable
|
||||||
fun RoomList(modifier: Modifier = Modifier, navController: NavController) {
|
fun RoomList(modifier: Modifier = Modifier, navController: NavController) {
|
||||||
var list by remember { mutableStateOf(listOf<String>()) }
|
val rooms by client!!.room.getAll().flattenValues().map { it.toList() }.collectAsState(initial = emptyList())
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
// if (itemState.scrollToTop) {
|
// if (itemState.scrollToTop) {
|
||||||
@@ -53,21 +53,17 @@ fun RoomList(modifier: Modifier = Modifier, navController: NavController) {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
LaunchedEffect(true) {
|
// LaunchedEffect(Unit) {
|
||||||
coroutineScope.launch {
|
// list = withContext(Dispatchers.IO) { Room.getJoined() }
|
||||||
withContext(Dispatchers.IO) {
|
// }
|
||||||
list = getRooms()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LazyColumn(modifier = modifier, state = listState) {
|
LazyColumn(modifier = modifier, state = listState) {
|
||||||
items(list) { rid ->
|
items(rooms) { room ->
|
||||||
RoomItem(rid = rid, navController = navController )
|
RoomItem(room = room, navController = navController )
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
if (list.isEmpty()) {
|
if (rooms.isEmpty()) {
|
||||||
Text("You have no rooms")
|
Text("You have no rooms")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,67 +71,58 @@ fun RoomList(modifier: Modifier = Modifier, navController: NavController) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RoomItem(modifier: Modifier = Modifier, rid: String, navController: NavController) {
|
fun RoomItem(modifier: Modifier = Modifier, room: Room, navController: NavController) {
|
||||||
var room by remember { mutableStateOf<Room?>(null) }
|
val name = room.name
|
||||||
val scope = rememberCoroutineScope()
|
val isSpace = room.type == CreateEventContent.RoomType.Space
|
||||||
|
|
||||||
LaunchedEffect(true) {
|
|
||||||
scope.launch {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
room = getRoom(rid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (room != null) {
|
|
||||||
Row(
|
Row(
|
||||||
modifier
|
modifier
|
||||||
.padding(8.dp)
|
.padding(8.dp)
|
||||||
.height((52 + 8 * 2).dp)
|
.height((52 + 8 * 2).dp)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(MaterialTheme.colorScheme.background)
|
.background(
|
||||||
|
color =
|
||||||
|
if (isSpace)
|
||||||
|
MaterialTheme.colorScheme.tertiary
|
||||||
|
else
|
||||||
|
MaterialTheme.colorScheme.background
|
||||||
|
)
|
||||||
.clip(RoundedCornerShape(12.dp))
|
.clip(RoundedCornerShape(12.dp))
|
||||||
.clickable {
|
.clickable {
|
||||||
if (room!!.type == "m.space")
|
if (isSpace)
|
||||||
navController.navigate("space/$rid")
|
navController.navigate("space/${room.roomId}")
|
||||||
else
|
else
|
||||||
navController.navigate("room/$rid")
|
navController.navigate("room/${room.roomId}")
|
||||||
}
|
}
|
||||||
.padding(8.dp)
|
.padding(8.dp)
|
||||||
) {
|
) {
|
||||||
|
Log.v("RoomItem", room.avatarUrl.toString())
|
||||||
|
room.avatarUrl?.let { mxc ->
|
||||||
MXCImage(
|
MXCImage(
|
||||||
room!!.avatarUrl ?: "",
|
mxc,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(end = 4.dp)
|
.padding(end = 4.dp)
|
||||||
.height(52.dp)
|
.height(52.dp)
|
||||||
.width(52.dp)
|
.width(52.dp)
|
||||||
.let {
|
.let {
|
||||||
if (room!!.type == "m.space")
|
if (isSpace)
|
||||||
it.clip(RoundedCornerShape(12.dp))
|
it.clip(RoundedCornerShape(12.dp))
|
||||||
else
|
else
|
||||||
it.clip(CircleShape)
|
it.clip(CircleShape)
|
||||||
}
|
},
|
||||||
|
ContentScale.Crop
|
||||||
)
|
)
|
||||||
|
}
|
||||||
Column {
|
Column {
|
||||||
|
(name?.explicitName ?: name?.heroes?.firstNotNullOf {it.localpart})?.let {
|
||||||
Text(
|
Text(
|
||||||
room!!.name ?: "Unnamed",
|
it,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
fontSize = MaterialTheme.typography.titleLarge.fontSize
|
fontSize = MaterialTheme.typography.titleLarge.fontSize
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Row(
|
|
||||||
modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
.height(80.dp)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.background(MaterialTheme.colorScheme.background)
|
|
||||||
.clip(RoundedCornerShape(16.dp))
|
|
||||||
.padding(8.dp)
|
|
||||||
) {
|
|
||||||
LinearProgressIndicator(Modifier.fillMaxWidth())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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<TrixnityRoomDatabase>(
|
||||||
|
context,
|
||||||
|
"trixnity.db"
|
||||||
|
)
|
||||||
|
)
|
||||||
+2
-4
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Created by sweetbread on 22.02.2025, 15:45
|
* Created by sweetbread
|
||||||
* Copyright (c) 2025. All rights reserved.
|
* Copyright (c) 2026. All rights reserved.
|
||||||
* Last modified 21.02.2025, 12:21
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
@@ -9,5 +8,4 @@ plugins {
|
|||||||
alias(libs.plugins.android.application) apply false
|
alias(libs.plugins.android.application) apply false
|
||||||
alias(libs.plugins.kotlin.android) apply false
|
alias(libs.plugins.kotlin.android) apply false
|
||||||
alias(libs.plugins.kotlin.compose) apply false
|
alias(libs.plugins.kotlin.compose) apply false
|
||||||
id("com.google.devtools.ksp") version "2.0.21-1.0.27" apply false
|
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "8.7.3"
|
agp = "8.13.0"
|
||||||
coil = "3.1.0"
|
coil = "3.1.0"
|
||||||
jsoup = "1.20.1"
|
jsoup = "1.20.1"
|
||||||
kotlin = "2.0.21"
|
kotlin = "2.2.21"
|
||||||
coreKtx = "1.15.0"
|
coreKtx = "1.15.0"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
junitVersion = "1.2.1"
|
junitVersion = "1.2.1"
|
||||||
@@ -15,6 +15,7 @@ composeBom = "2025.02.00"
|
|||||||
navigationCompose = "2.8.8"
|
navigationCompose = "2.8.8"
|
||||||
room = "2.6.1"
|
room = "2.6.1"
|
||||||
splittiesFunPackAndroidBase = "3.0.0"
|
splittiesFunPackAndroidBase = "3.0.0"
|
||||||
|
trixnityClient = "5.2.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
@@ -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-fragment = { module = "androidx.navigation:navigation-fragment", version.ref = "navigationCompose" }
|
||||||
androidx-navigation-testing = { module = "androidx.navigation:navigation-testing", 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-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-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
|
||||||
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
|
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
|
||||||
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
|
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
|
||||||
@@ -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-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
|
||||||
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||||
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||||
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-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
|
||||||
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
|
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
|
||||||
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
|
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
|
||||||
splitties-base = { module = "com.louiscad.splitties:splitties-fun-pack-android-base", version.ref = "splittiesFunPackAndroidBase" }
|
splitties-base = { module = "com.louiscad.splitties:splitties-fun-pack-android-base", version.ref = "splittiesFunPackAndroidBase" }
|
||||||
|
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]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||||
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||||
|
|
||||||
|
|||||||
+2
-3
@@ -1,12 +1,11 @@
|
|||||||
#
|
#
|
||||||
# Created by sweetbread on 21.02.2025, 12:00
|
# Created by sweetbread
|
||||||
# Copyright (c) 2025. All rights reserved.
|
# Copyright (c) 2025. All rights reserved.
|
||||||
# Last modified 21.02.2025, 12:00
|
|
||||||
#
|
#
|
||||||
|
|
||||||
#Thu Feb 20 10:45:47 GMT 2025
|
#Thu Feb 20 10:45:47 GMT 2025
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
+6
-3
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Created by sweetbread on 21.02.2025, 12:00
|
* Created by sweetbread
|
||||||
* Copyright (c) 2025. All rights reserved.
|
* Copyright (c) 2026. All rights reserved.
|
||||||
* Last modified 21.02.2025, 12:00
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pluginManagement {
|
pluginManagement {
|
||||||
@@ -17,11 +16,15 @@ pluginManagement {
|
|||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
plugins {
|
||||||
|
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
|
||||||
|
}
|
||||||
dependencyResolutionManagement {
|
dependencyResolutionManagement {
|
||||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven("https://gitlab.com/api/v4/projects/47538655/packages/maven")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user