Compare commits

53 Commits

Author SHA1 Message Date
Sweetbread e10e47d6c0 fixup! refactor: Avatar function 2026-04-23 20:37:09 +03:00
Sweetbread 8f6adf0746 fix: close message field after closing a keyboard,
change design
2026-04-23 20:35:29 +03:00
Sweetbread eae2d4f388 refactor: Avatar function 2026-04-23 20:20:20 +03:00
Sweetbread b312cc50f1 fixup! fixup! Display and send messages 2026-04-23 04:25:07 +03:00
Sweetbread 8b8926f931 fixup! fixup! New TopBar 2026-04-23 03:36:29 +03:00
Sweetbread a35f5196b1 fixup! Sticky avatar 2026-04-21 19:20:07 +03:00
Sweetbread cab1ff93a4 Fix message loading 2026-04-19 17:41:08 +03:00
Sweetbread 769319f308 fixup! New TopBar 2026-04-19 17:40:51 +03:00
Sweetbread d28baa70d4 fixup! fixup! Message bubbles 2026-04-19 02:56:25 +03:00
Sweetbread f29672d1bd fixup! fixup! Sticky date 2026-04-19 02:49:37 +03:00
Sweetbread dd30db2130 New TopBar 2026-04-19 02:48:23 +03:00
Sweetbread 0711ce41d3 fixup! Message bubbles 2026-04-19 01:06:20 +03:00
Sweetbread b1357bd4e7 fixup! Sticky date 2026-04-19 00:42:20 +03:00
Sweetbread 0e107eca0b Sticky avatar 2026-04-18 23:25:19 +03:00
Sweetbread f90a8e3472 Sticky date 2026-04-18 22:53:43 +03:00
Sweetbread aa18e5b0a6 Message bubbles 2026-04-14 01:01:15 +03:00
Sweetbread a21860c21d fixup! Display and send messages 2026-04-14 00:21:30 +03:00
Sweetbread d7d14389fc Display and send messages 2026-04-10 14:26:33 +03:00
Sweetbread 902af5e7b5 wip: show messages 2026-03-26 23:57:28 +03:00
Sweetbread 64de39f0ca update 2026-03-20 01:22:30 +03:00
Sweetbread f8b10ebb34 wip 2026-03-13 17:35:34 +03:00
Sweetbread a090ff614a wip: Migrate to Trixnity 2026-02-22 00:30:11 +03:00
Sweetbread dd3b31d0b2 wip: Migrate to Trixnity 2026-02-21 21:21:57 +03:00
Sweetbread c7b5f20c06 wip: Migrate to Trixnity 2026-02-21 16:48:31 +03:00
Sweetbread 8d6a76ccb5 wip 2026-02-20 17:28:12 +03:00
Sweetbread 1cfad2ca4e wip 2026-02-20 09:41:06 +03:00
Sweetbread b6e8c73758 ref: open Login from MainActivity, not vice versa 2025-11-08 20:40:56 +03:00
Sweetbread 28337b1306 ref: async and thread stuff 2025-11-05 01:23:35 +03:00
Sweetbread 05d3d739e2 feat: check for internet connection before sending API requests 2025-11-05 00:06:04 +03:00
Sweetbread 5c6cd29a05 fix: change mxc regex pattern 2025-11-04 23:32:22 +03:00
Sweetbread 70d9db6cbf ref: caching joined rooms 2025-11-04 23:24:20 +03:00
Sweetbread 7026acc229 fixup! ref: reformat caching data in Rooms 2025-11-04 23:23:52 +03:00
Sweetbread fa53e0ae86 ref: remove coroutineScope 2025-11-04 20:42:11 +03:00
Sweetbread ebbc9a4b1f ref: reformat caching data in Rooms 2025-11-04 20:30:22 +03:00
Sweetbread 05fbfbac07 feat: show room avatar when is DM 2025-11-04 16:01:14 +03:00
Sweetbread 5897d31a51 ref: SharedPref to Splitties 2025-11-04 15:19:50 +03:00
Sweetbread a0bfba23cf dev: Highlight m.spaces 2025-11-03 23:52:43 +03:00
Sweetbread 00f273c866 deps: update AGP 2025-11-03 23:44:59 +03:00
Sweetbread 0384626d83 feat: change message style 2025-06-06 01:23:41 +03:00
Sweetbread ea783c9f27 fixup! feat: add image display 2025-06-06 01:16:50 +03:00
Sweetbread 4236c4342b fixup! wip: feat: add formated text display 2025-06-06 01:09:06 +03:00
Sweetbread c40b13a7ea fixup! ref: add MXCImage 2025-06-05 22:15:07 +03:00
Sweetbread de205b739f feat: add image display 2025-06-05 21:50:39 +03:00
Sweetbread 8ac9ccfaca ref: add MXCImage 2025-06-05 21:50:19 +03:00
Sweetbread 7a2567f019 wip: feat: add formated text display 2025-06-05 19:11:43 +03:00
Sweetbread 26417b8072 fix: add a clip 2025-06-05 00:33:51 +03:00
Sweetbread 23780489f6 feat: Messages 2025-03-04 00:30:01 +03:00
Sweetbread bd87ca2729 fix: Finish LoginActivity 2025-03-03 19:15:04 +03:00
Sweetbread 7ba5876a71 impr: Change room elements style
Make room elements smaller and change background color
2025-03-03 18:55:38 +03:00
Sweetbread cab56d6329 feat: Delegate homeserver
Now if matrix homeserver on matrix.example.com, example.com will be correct too
2025-03-03 18:44:07 +03:00
Sweetbread e70049f1f5 feat: Round avatars 2025-02-25 00:39:22 +03:00
Sweetbread c0944ec0a8 wip: Get room info serially 2025-02-22 21:18:16 +03:00
Sweetbread 5fbffd8700 wip: Room list 2025-02-22 21:18:16 +03:00
32 changed files with 2700 additions and 205 deletions
+6
View File
@@ -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>
+1 -5
View File
@@ -1,7 +1,3 @@
<component name="CopyrightManager">
<settings default="My">
<module2copyright>
<element module="All" copyright="My" />
</module2copyright>
</settings>
<settings default="My" />
</component>
+8
View File
@@ -4,6 +4,14 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-02-22T11:46:39.159466074Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=22163a3c" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
</selectionStates>
</component>
+3
View File
@@ -49,6 +49,9 @@
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</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">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.0.0" />
<option name="version" value="2.0.21" />
</component>
</project>
+4
View File
@@ -5,8 +5,12 @@
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<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.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>
</option>
</component>
Generated Executable
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="StudioBotProjectSettings">
<option name="shareContext" value="OptedIn" />
</component>
</project>
Generated
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="" vcs="Git" />
</component>
</project>
+45 -12
View File
@@ -1,22 +1,28 @@
/*
* Created by sweetbread on 21.02.2025, 12:01
* Copyright (c) 2025. All rights reserved.
* Last modified 21.02.2025, 12:01
* Created by sweetbread
* Copyright (c) 2026. All rights reserved.
*/
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
/*
* Created by sweetbread
* Copyright (c) 2026. All rights reserved.
*/
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
// alias(libs.plugins.ksp)
}
android {
namespace = "ru.risdeveau.pixeldragon"
compileSdk = 35
compileSdk = 36
defaultConfig {
applicationId = "ru.risdeveau.pixeldragon"
minSdk = 24
minSdk = 28
targetSdk = 35
versionCode = 1
versionName = "1.0"
@@ -34,17 +40,20 @@ android {
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
buildFeatures {
compose = true
}
}
kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
@@ -54,6 +63,7 @@ dependencies {
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(libs.androidx.compose.ui.unit)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
@@ -62,14 +72,37 @@ dependencies {
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
// Trixnity - Matrix wrapper
implementation(libs.trixnity.client)
implementation(libs.trixnity.client.media.okio)
implementation(libs.trixnity.client.repository.room)
implementation(libs.trixnity.client.cryptodriver.vodozemac)
// implementation(libs.trixnity.messenger)
// Ktor - web client
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.cio)
implementation(libs.ktor.client.okhttp)
implementation(libs.ktor.client.logging)
// Coil - image loader
implementation(libs.coil.compose)
implementation(libs.coil.network.okhttp)
// Room - database
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.lifecycle.viewmodel.ktx)
implementation(libs.androidx.room.ktx)
// ksp(libs.androidx.room.compiler)
// Navigation Compose
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.navigation.fragment)
implementation(libs.androidx.navigation.ui)
implementation(libs.androidx.navigation.dynamic.features.fragment)
androidTestImplementation(libs.androidx.navigation.testing)
// Others
implementation(libs.splitties.base) // Syntax sugar
implementation(libs.jsoup) // HTML parser
implementation(libs.iconsax.compose) // Material icons
}
+6 -8
View File
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Created by sweetbread on 21.02.2025, 12:08
~ Created by sweetbread
~ Copyright (c) 2025. All rights reserved.
~ Last modified 21.02.2025, 12:07
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
@@ -20,15 +19,9 @@
android:supportsRtl="true"
android:theme="@style/Theme.PixelDragon"
tools:targetApi="31">
<activity
android:name=".ui.activity.Login"
android:exported="false"
android:label="@string/title_activity_login"
android:theme="@style/Theme.PixelDragon" />
<activity
android:name=".ui.activity.MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.PixelDragon">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -36,6 +29,11 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ui.activity.Login"
android:exported="false"
android:theme="@style/Theme.PixelDragon" />
</application>
</manifest>
@@ -1,43 +1,29 @@
/*
* Created by sweetbread on 21.02.2025, 12:01
* Copyright (c) 2025. All rights reserved.
* Last modified 21.02.2025, 12:01
* Created by sweetbread
* Copyright (c) 2026. All rights reserved.
*/
package ru.risdeveau.pixeldragon
import android.content.Context
import android.util.Log
import de.connect2x.trixnity.client.MatrixClient
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.client.engine.cio.endpoint
import io.ktor.client.plugins.cache.HttpCache
import ru.risdeveau.pixeldragon.api.getMe
import splitties.init.appCtx
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
val client = HttpClient(CIO) {
engine {
endpoint {
maxConnectionsPerRoute = 100
pipelineMaxSize = 20
keepAliveTime = 30000
connectTimeout = 15000
connectAttempts = 5
val webClient = HttpClient {
install(Logging) {
logger = object : Logger {
override fun log(message: String) {
Log.i("Ktor", message)
}
}
level = LogLevel.ALL
}
install(HttpCache)
}
val accountData = appCtx.getSharedPreferences("settings", Context.MODE_PRIVATE)
lateinit var urlBase: String
lateinit var token: String
suspend fun initCheck(): Boolean {
if (!accountData.contains("token")) return false
if (!accountData.contains("homeserver")) return false
token = accountData.getString("token", "").toString()
urlBase = "https://${accountData.getString("homeserver", "")}/_matrix/client/v3"
return getMe() != null
}
var client: MatrixClient? = null
+50
View File
@@ -0,0 +1,50 @@
/*
* Created by sweetbread
* Copyright (c) 2026. All rights reserved.
*/
package ru.risdeveau.pixeldragon.api
import org.json.JSONObject
import java.time.Instant
class Event (
val id: String,
val rid: String,
val type: String,
val content: JSONObject,
val time: Instant,
val sender: String
) {
// constructor(json: JSONObject) : this(
// json.getString("event_id"),
// json.getString("room_id"),
// json.getString("sender"),
// json.getString("type"),
// json.getJSONObject("content")
// )
}
//data class EventsAround (
// val base: Event,
// val before: List<Event>,
// val after: List<Event>
//)
//
//suspend fun getEventsAround(room: String, event: String): EventsAround {
// val r = client.get("$baseUrl/rooms/$room/context/$event") {
// bearerAuth(token)
// parameter("limit", "50")
// }
// val json = JSONObject(r.bodyAsText())
//
// return EventsAround(
// Event(json.getJSONObject("event")),
// if (json.has("events_before")) json.getJSONArray("events_before").let {
// List<Event>(it.length()) { i -> Event(it.getJSONObject(i))}.reversed()
// } else listOf(),
// if (json.has("events_after")) json.getJSONArray("events_after").let {
// List<Event>(it.length()) { i -> Event(it.getJSONObject(i))}
// } else listOf()
// )
//}
+4
View File
@@ -0,0 +1,4 @@
/*
* Created by sweetbread
* Copyright (c) 2026. All rights reserved.
*/
@@ -1,23 +1,44 @@
/*
* Created by sweetbread on 21.02.2025, 12:01
* Copyright (c) 2025. All rights reserved.
* Last modified 21.02.2025, 12:01
* Created by sweetbread
* Copyright (c) 2026. All rights reserved.
*/
package ru.risdeveau.pixeldragon.api
import android.util.Log
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import org.json.JSONException
import org.json.JSONObject
import ru.risdeveau.pixeldragon.client
import ru.risdeveau.pixeldragon.webClient
//import ru.risdeveau.pixeldragon.homeserver
suspend fun isMatrixServer(url: String): Boolean {
val r = try { client.get("https://$url/.well-known/matrix/client") }
catch (_: Exception) { return false }
suspend fun getHomeserver(url: String): String? {
val r = try { webClient.get("https://$url/.well-known/matrix/client") }
catch (e: Exception) {
Log.w("getHomeserver", "Fail sending the request", e)
return null
}
try { JSONObject(r.bodyAsText()) }
catch (_: JSONException) { return false }
val json = try { JSONObject(r.bodyAsText()) }
catch (e: JSONException) {
Log.w("getHomeserver", "Fail parsing the JSON", e)
return null
}
return true
if (!json.has("m.homeserver")) return null
var homeserver = json.getJSONObject("m.homeserver").getString("base_url")
if (homeserver.endsWith("/")) homeserver = homeserver.dropLast(1)
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]}"
//}
@@ -1,82 +1,49 @@
/*
* Created by sweetbread on 21.02.2025, 12:09
* Copyright (c) 2025. All rights reserved.
* Last modified 21.02.2025, 12:01
* Created by sweetbread
* Copyright (c) 2026. All rights reserved.
*/
package ru.risdeveau.pixeldragon.api
import android.annotation.SuppressLint
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 de.connect2x.trixnity.client.CryptoDriverModule
import io.ktor.http.Url
import de.connect2x.trixnity.client.MatrixClient
import de.connect2x.trixnity.client.create
import de.connect2x.trixnity.client.cryptodriver.vodozemac.vodozemac
import de.connect2x.trixnity.clientserverapi.client.MatrixClientAuthProviderData
import de.connect2x.trixnity.clientserverapi.client.classicLogin
import de.connect2x.trixnity.clientserverapi.model.authentication.IdentifierType
import ru.risdeveau.pixeldragon.client
import ru.risdeveau.pixeldragon.initCheck
import ru.risdeveau.pixeldragon.token
import ru.risdeveau.pixeldragon.urlBase
import ru.risdeveau.pixeldragon.util.getMediaStore
import ru.risdeveau.pixeldragon.util.getRoomStore
import splitties.experimental.ExperimentalSplittiesApi
import splitties.init.appCtx
data class Me (val userId: String, val deviceId: String)
/**
* This func is to validate the token
*/
suspend fun getMe(): Me? {
val r = client.get("$urlBase/account/whoami") { bearerAuth(token) }
if (r.status != HttpStatusCode.OK) {
Log.e("getMe", r.bodyAsText())
return null
}
@OptIn(ExperimentalSplittiesApi::class)
suspend fun login(server: String, login: String, pass: String): Boolean {
val hs = Url(getHomeserver(server)!!)
val json = JSONObject(r.bodyAsText())
return Me(json.getString("user_id"), json.getString("device_id"))
}
val pinfo = appCtx.packageManager.getPackageInfo(appCtx.packageName, 0)
@SuppressLint("ApplySharedPref")
suspend fun login(homeserver: String, login: String, pass: String): Boolean {
val pinfo = appCtx.packageManager.getPackageInfo(appCtx.packageName, 0);
try {
client = MatrixClient.create(
repositoriesModule = getRoomStore(appCtx),
mediaStoreModule = getMediaStore(),
cryptoDriverModule = CryptoDriverModule.vodozemac(),
authProviderData = MatrixClientAuthProviderData.classicLogin(
baseUrl = hs,
identifier = IdentifierType.User(login),
password = pass,
initialDeviceDisplayName = "PixelDragon Android v${pinfo.versionName}",
).getOrThrow()
).getOrThrow()
val 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)
val r = try {
client.post("https://$homeserver/_matrix/client/v3/login") {
setBody(json.toString())
contentType(ContentType.Application.Json)
}
return true
} catch (e: Exception) {
Log.e("login", e.toString())
Log.i("Login", "Failed to login", e)
}
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", res.getString("home_server"))
editor.commit()
return initCheck()
}
+4
View File
@@ -0,0 +1,4 @@
/*
* Created by sweetbread
* Copyright (c) 2026. All rights reserved.
*/
+4
View File
@@ -0,0 +1,4 @@
/*
* Created by sweetbread
* Copyright (c) 2026. All rights reserved.
*/
+4
View File
@@ -0,0 +1,4 @@
/*
* Created by sweetbread
* Copyright (c) 2026. All rights reserved.
*/
+4
View File
@@ -0,0 +1,4 @@
/*
* Created by sweetbread
* Copyright (c) 2026. All rights reserved.
*/
+4
View File
@@ -0,0 +1,4 @@
/*
* Created by sweetbread
* Copyright (c) 2026. All rights reserved.
*/
+4
View File
@@ -0,0 +1,4 @@
/*
* Created by sweetbread
* Copyright (c) 2026. All rights reserved.
*/
@@ -1,7 +1,6 @@
/*
* Created by sweetbread on 21.02.2025, 12:08
* Created by sweetbread
* Copyright (c) 2025. All rights reserved.
* Last modified 21.02.2025, 12:08
*/
package ru.risdeveau.pixeldragon.ui.activity
@@ -35,15 +34,18 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.launch
import ru.risdeveau.pixeldragon.api.isMatrixServer
import ru.risdeveau.pixeldragon.api.getHomeserver
import ru.risdeveau.pixeldragon.api.login
import ru.risdeveau.pixeldragon.ui.theme.PixelDragonTheme
import splitties.activities.start
class Login : ComponentActivity() {
@OptIn(DelicateCoroutinesApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
PixelDragonTheme {
@@ -56,10 +58,16 @@ class Login : ComponentActivity() {
SnackbarHost(hostState = snackbarHostState)
}
) { innerPadding ->
Box(Modifier.fillMaxSize().padding(innerPadding)) {
Box(
Modifier
.fillMaxSize()
.padding(innerPadding)) {
LoginField(
Modifier.align(Alignment.Center),
{ start<MainActivity>() },
{
start<MainActivity>()
finish()
},
{
scope.launch {
snackbarHostState
@@ -89,7 +97,7 @@ fun LoginField(modifier: Modifier = Modifier, ok: () -> Unit, err: () -> Unit) {
var login by remember { mutableStateOf("") }
var pass by remember { mutableStateOf("") }
var hmsValid by remember { mutableStateOf(false) }
var hmsValid: Boolean by remember { mutableStateOf(false) }
OutlinedTextField(
modifier = Modifier.padding(4.dp),
@@ -119,18 +127,17 @@ fun LoginField(modifier: Modifier = Modifier, ok: () -> Unit, err: () -> Unit) {
Button(
enabled = hmsValid && !login.isEmpty() && !pass.isEmpty(),
onClick = {
var loginSuccess = false
scope.launch { loginSuccess = login(homeserver, login, pass) }
if (loginSuccess) ok()
scope.launch {
if (login(homeserver, login, pass)) ok()
else err()
}
}
) {
Text("Login")
}
LaunchedEffect(homeserver) {
scope.launch { hmsValid = isMatrixServer(homeserver) }
scope.launch { hmsValid = (getHomeserver(homeserver) != null) }
}
}
}
@@ -1,81 +1,319 @@
/*
* Created by sweetbread on 21.02.2025, 12:08
* Copyright (c) 2025. All rights reserved.
* Last modified 21.02.2025, 12:07
* Created by sweetbread
* Copyright (c) 2026. All rights reserved.
*/
package ru.risdeveau.pixeldragon.ui.activity
import android.annotation.SuppressLint
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.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults.topAppBarColors
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import de.connect2x.trixnity.client.CryptoDriverModule
import de.connect2x.trixnity.client.MatrixClient
import de.connect2x.trixnity.client.create
import de.connect2x.trixnity.client.cryptodriver.vodozemac.vodozemac
import de.connect2x.trixnity.client.flattenValues
import de.connect2x.trixnity.client.room
import de.connect2x.trixnity.client.store.type
import de.connect2x.trixnity.clientserverapi.client.SyncState
import de.connect2x.trixnity.clientserverapi.model.user.avatarUrl
import de.connect2x.trixnity.clientserverapi.model.user.displayName
import de.connect2x.trixnity.core.model.events.m.room.CreateEventContent
import io.github.rabehx.iconsax.Iconsax
import io.github.rabehx.iconsax.automirrored.outline.ArrowLeft2
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import ru.risdeveau.pixeldragon.initCheck
import ru.risdeveau.pixeldragon.R
import ru.risdeveau.pixeldragon.client
import ru.risdeveau.pixeldragon.ui.item.Avatar
import ru.risdeveau.pixeldragon.ui.layout.Room
import ru.risdeveau.pixeldragon.ui.layout.RoomList
import ru.risdeveau.pixeldragon.ui.theme.PixelDragonTheme
import ru.risdeveau.pixeldragon.util.getMediaStore
import ru.risdeveau.pixeldragon.util.getRoomStore
import splitties.activities.start
import splitties.init.appCtx
import de.connect2x.trixnity.client.store.Room as TrixnityRoom
class MainActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3Api::class, DelicateCoroutinesApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
GlobalScope.launch {
if (!initCheck()) start<Login>()
}
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
PixelDragonTheme {
var isClientReady by remember { mutableStateOf(false) }
if (!isClientReady || client == null) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator()
}
} else {
val navController = rememberNavController()
val syncState by client!!.api.sync.currentSyncState
.collectAsState(initial = SyncState.STOPPED)
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBar(
PixelDragonTopBar(
navController = navController,
syncState = syncState,
)
},
) { innerPadding ->
NavHost(navController = navController, startDestination = "rooms") {
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 })
) {
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()
}
}
}
@SuppressLint("FlowOperatorInvokedInComposition")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun PixelDragonTopBar(
navController: NavHostController,
syncState: SyncState,
) {
val backStackEntry by navController.currentBackStackEntryAsState()
val route = backStackEntry?.destination?.route
val rid = backStackEntry?.arguments?.getString("rid")
val isRoomLikeScreen = route == "room/{rid}" || route == "space/{rid}"
val roomsFlow = remember(client) {
client!!.room.getAll().flattenValues().map { it.toList() }
}
val rooms by roomsFlow.collectAsState(initial = emptyList())
val currentRoom = remember(isRoomLikeScreen, rid, rooms) {
if (isRoomLikeScreen && rid != null) {
rooms.firstOrNull { it.roomId.toString() == rid }
} else {
null
}
}
if (isRoomLikeScreen) {
RoomTopBar(
room = currentRoom,
fallbackTitle = rid ?: stringResource(R.string.app_name),
onBack = { navController.popBackStack() },
)
} else {
HomeTopBar(syncState = syncState)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun HomeTopBar(syncState: SyncState) {
CenterAlignedTopAppBar(
colors = topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.primary,
),
title = {
Text("Top app bar")
}
)
Text(syncState.toStatusTitle() ?: stringResource(R.string.app_name))
},
) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
actions = {
val client = client!!
var userName by remember { mutableStateOf("?") }
var userAvatar: String? by remember { mutableStateOf(null) }
LaunchedEffect(client, syncState) {
val profile = client.api.user
.getProfile(client.userId)
.getOrNull()
userName = profile?.displayName ?: "?"
userAvatar = profile?.avatarUrl
}
Avatar(
Modifier.size(32.dp).clip(CircleShape),
userAvatar,
userName,
MaterialTheme.colorScheme.background
)
}
}
}
}
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
private fun RoomTopBar(
room: TrixnityRoom?,
fallbackTitle: String,
onBack: () -> Unit,
) {
CenterAlignedTopAppBar(
colors = topAppBarColors(
containerColor = MaterialTheme.colorScheme.surface.copy(.5f),
titleContentColor = MaterialTheme.colorScheme.onSurface,
navigationIconContentColor = MaterialTheme.colorScheme.primary,
),
navigationIcon = {
IconButton(onClick = onBack) {
Icon(
Iconsax.AutoMirrored.Outline.ArrowLeft2,
"To home",
)
}
},
title = {
if (room != null) {
RoomTopBarTitle(room)
} else {
Text(
text = "Hello $name!",
modifier = modifier
text = fallbackTitle,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.titleMedium,
)
}
},
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
PixelDragonTheme {
Greeting("Android")
private fun RoomTopBarTitle(room: TrixnityRoom) {
val title = remember(room) { room.displayName() }
val isSpace = room.type == CreateEventContent.RoomType.Space
val avatarShape = if (isSpace) RoundedCornerShape(8.dp) else CircleShape
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Avatar(
Modifier
.size(36.dp)
.clip(avatarShape),
room.avatarUrl,
title
)
Spacer(Modifier.width(10.dp))
Text(
text = title,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.titleMedium,
)
}
}
private fun TrixnityRoom.displayName(): String {
return name?.explicitName
?: name?.heroes?.firstNotNullOfOrNull { it.localpart }
?: roomId.toString()
}
private fun SyncState.toStatusTitle(): String? {
return when (this) {
SyncState.INITIAL_SYNC -> "Initial sync..."
SyncState.STARTED -> "Syncing..."
SyncState.TIMEOUT -> "No network connection"
SyncState.ERROR -> "Error syncing"
SyncState.RUNNING,
SyncState.STOPPED -> null
}
}
@@ -0,0 +1,65 @@
/*
* Created by sweetbread
* Copyright (c) 2026. All rights reserved.
*/
package ru.risdeveau.pixeldragon.ui.item
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
@Composable
fun Avatar(
modifier: Modifier,
url: String?,
fallbackName: String,
fallbackColor: Color = MaterialTheme.colorScheme.secondaryContainer,
) {
if (url != null) {
MXCImage(
mxcUrl = url,
modifier = modifier,
contentScale = ContentScale.Crop,
contentDescription = fallbackName,
showProgress = false,
)
} else {
AvatarPlaceholder(
modifier,
fallbackName,
fallbackColor,
)
}
}
@Composable
private fun AvatarPlaceholder(
modifier: Modifier = Modifier,
fallbackName: String,
fallbackColor: Color,
) {
val initial = fallbackName
.trim()
.firstOrNull()
?.uppercaseChar()
?.toString()
?: "?"
Box(
modifier = modifier.background(fallbackColor),
contentAlignment = Alignment.Center,
) {
Text(
text = initial,
color = MaterialTheme.colorScheme.onSecondaryContainer,
style = MaterialTheme.typography.titleMedium,
)
}
}
+129
View File
@@ -0,0 +1,129 @@
/*
* Created by sweetbread
* Copyright (c) 2026. 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 io.github.rabehx.iconsax.Iconsax
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import coil3.compose.rememberAsyncImagePainter
import coil3.request.ImageRequest
import kotlinx.coroutines.flow.MutableStateFlow
import de.connect2x.trixnity.client.media
import de.connect2x.trixnity.clientserverapi.model.media.FileTransferProgress
import io.github.rabehx.iconsax.outline.Warning2
import ru.risdeveau.pixeldragon.client
enum class ImageLoadState {
Loading, Success, Error
}
@Composable
fun MXCImage(
mxcUrl: String,
modifier: Modifier = Modifier,
contentScale: ContentScale = ContentScale.Fit,
contentDescription: String = "",
showProgress: Boolean = true
) {
val context = LocalContext.current
var imageLoadState by remember { mutableStateOf(ImageLoadState.Loading) }
var imageBytes by remember { mutableStateOf<ByteArray?>(null) }
val progressFlow = remember { MutableStateFlow<FileTransferProgress?>(null) }
LaunchedEffect(mxcUrl) {
if (mxcUrl.isBlank()) {
imageLoadState = ImageLoadState.Error
return@LaunchedEffect
}
imageLoadState = ImageLoadState.Loading
progressFlow.value = null
val result = client!!.media.getMedia(
uri = mxcUrl,
progress = progressFlow.takeIf { showProgress },
saveToCache = true
)
imageLoadState = result.fold(
onSuccess = { media ->
val bytes = media.toByteArray()
if (bytes != null) {
imageBytes = bytes
ImageLoadState.Success
} else {
ImageLoadState.Error
}
},
onFailure = {
ImageLoadState.Error
}
)
}
val progress by progressFlow.collectAsState()
val showProgressIndicator = showProgress &&
imageLoadState == ImageLoadState.Loading &&
progress != null
val painter = rememberAsyncImagePainter(
model = ImageRequest.Builder(context)
.data(imageBytes)
.build()
)
Box(modifier = modifier, contentAlignment = Alignment.Center) {
if (imageBytes != null) {
Image(
painter = painter,
contentDescription = contentDescription,
contentScale = contentScale,
modifier = Modifier.fillMaxSize()
)
}
when {
showProgressIndicator -> {
progress?.let { p ->
val percent = p.total?.let { p.transferred.toFloat() / it }
if (percent == null) {
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
} else {
CircularProgressIndicator(
progress = percent,
modifier = Modifier.align(Alignment.Center)
)
}
} ?: CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
}
imageLoadState == ImageLoadState.Loading -> {
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
}
imageLoadState == ImageLoadState.Error -> {
Icon(
Iconsax.Outline.Warning2,
contentDescription = "Error",
modifier = Modifier.align(Alignment.Center)
)
}
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,128 @@
/*
* Created by sweetbread
* Copyright (c) 2026. All rights reserved.
*/
package ru.risdeveau.pixeldragon.ui.layout
import android.annotation.SuppressLint
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
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.collectAsState
import androidx.compose.runtime.getValue
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.flow.map
import de.connect2x.trixnity.client.flattenValues
import de.connect2x.trixnity.client.room
import de.connect2x.trixnity.client.store.Room
import de.connect2x.trixnity.client.store.type
import de.connect2x.trixnity.core.model.events.m.room.CreateEventContent
import ru.risdeveau.pixeldragon.client
import ru.risdeveau.pixeldragon.ui.item.MXCImage
@SuppressLint("FlowOperatorInvokedInComposition")
@Composable
fun RoomList(modifier: Modifier = Modifier, navController: NavController) {
val rooms by client!!.room.getAll().flattenValues().map { it.toList() }.collectAsState(initial = emptyList())
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(rooms) { room ->
RoomItem(room = room, navController = navController )
}
item {
if (rooms.isEmpty()) {
Text("You have no rooms")
}
}
}
}
@Composable
fun RoomItem(modifier: Modifier = Modifier, room: Room, navController: NavController) {
val name = room.name
val isSpace = room.type == CreateEventContent.RoomType.Space
Row(
modifier
.padding(8.dp)
.height((52 + 8 * 2).dp)
.fillMaxWidth()
.background(
color =
if (isSpace)
MaterialTheme.colorScheme.tertiary
else
MaterialTheme.colorScheme.background
)
.clip(RoundedCornerShape(12.dp))
.clickable {
if (isSpace)
navController.navigate("space/${room.roomId}")
else
navController.navigate("room/${room.roomId}")
}
.padding(8.dp)
) {
Log.v("RoomItem", room.avatarUrl.toString())
room.avatarUrl?.let { mxc ->
MXCImage(
mxc,
modifier = Modifier
.padding(end = 4.dp)
.height(52.dp)
.width(52.dp)
.let {
if (isSpace)
it.clip(RoundedCornerShape(12.dp))
else
it.clip(CircleShape)
},
ContentScale.Crop
)
}
Column {
(name?.explicitName ?: name?.heroes?.firstNotNullOf {it.localpart})?.let {
Text(
it,
maxLines = 1,
color = MaterialTheme.colorScheme.primary,
fontSize = MaterialTheme.typography.titleLarge.fontSize
)
}
}
}
}
@@ -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 -3
View File
@@ -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.
*/
// Top-level build file where you can add configuration options common to all sub-projects/modules.
+31 -9
View File
@@ -1,21 +1,38 @@
[versions]
agp = "8.7.3"
coil = "3.1.0"
kotlin = "2.0.0"
agp = "9.1.0"
coil = "3.4.0"
iconsaxCompose = "0.0.5"
jsoup = "1.22.1"
kotlin = "2.2.21"
coreKtx = "1.15.0"
junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
ktor = "3.1.0"
junitVersion = "1.3.0"
espressoCore = "3.7.0"
kotlinxSerializationJson = "1.7.3"
ktor = "3.4.1"
lifecycleRuntimeKtx = "2.8.7"
activityCompose = "1.10.0"
composeBom = "2025.02.00"
composeBom = "2026.03.00"
navigationCompose = "2.9.7"
room = "2.6.1"
splittiesFunPackAndroidBase = "3.0.0"
trixnityClient = "5.2.0"
uiUnit = "1.10.6"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "room" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
androidx-navigation-dynamic-features-fragment = { module = "androidx.navigation:navigation-dynamic-features-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-ui = { module = "androidx.navigation:navigation-ui", version.ref = "navigationCompose" }
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" }
iconsax-compose = { module = "io.github.rabehx:iconsax-compose", version.ref = "iconsaxCompose" }
jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
@@ -29,12 +46,17 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
splitties-base = { module = "com.louiscad.splitties:splitties-fun-pack-android-base", version.ref = "splittiesFunPackAndroidBase" }
trixnity-client = { module = "de.connect2x.trixnity:trixnity-client", version.ref = "trixnityClient" }
trixnity-client-media-okio = { module = "de.connect2x.trixnity:trixnity-client-media-okio", version.ref = "trixnityClient" }
trixnity-client-repository-room = { module = "de.connect2x.trixnity:trixnity-client-repository-room", version.ref = "trixnityClient" }
trixnity-client-cryptodriver-vodozemac = { module = "de.connect2x.trixnity:trixnity-client-cryptodriver-vodozemac", version.ref = "trixnityClient" }
androidx-compose-ui-unit = { group = "androidx.compose.ui", name = "ui-unit", version.ref = "uiUnit" }
[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" }
+2 -3
View File
@@ -1,12 +1,11 @@
#
# Created by sweetbread on 21.02.2025, 12:00
# Created by sweetbread
# Copyright (c) 2025. All rights reserved.
# Last modified 21.02.2025, 12:00
#
#Thu Feb 20 10:45:47 GMT 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+6 -3
View File
@@ -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 {
@@ -17,11 +16,15 @@ pluginManagement {
gradlePluginPortal()
}
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven("https://gitlab.com/api/v4/projects/47538655/packages/maven")
}
}