diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml
new file mode 100755
index 0000000..4a53bee
--- /dev/null
+++ b/.idea/AndroidProjectSystem.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
index 931b96c..16660f1 100755
--- a/.idea/runConfigurations.xml
+++ b/.idea/runConfigurations.xml
@@ -5,8 +5,12 @@
+
+
+
+
diff --git a/.idea/studiobot.xml b/.idea/studiobot.xml
new file mode 100755
index 0000000..539e3b8
--- /dev/null
+++ b/.idea/studiobot.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/ru/risdeveau/pixeldragon/Common.kt b/app/src/main/java/ru/risdeveau/pixeldragon/Common.kt
index f72d1e0..57642c7 100755
--- a/app/src/main/java/ru/risdeveau/pixeldragon/Common.kt
+++ b/app/src/main/java/ru/risdeveau/pixeldragon/Common.kt
@@ -11,6 +11,7 @@ import io.ktor.client.plugins.cache.HttpCache
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
+import ru.risdeveau.pixeldragon.api.MatrixSyncService
import ru.risdeveau.pixeldragon.api.getMe
import splitties.preferences.Preferences
@@ -31,20 +32,22 @@ lateinit var homeserver: String
lateinit var baseUrl: String
lateinit var token: String
-object AccountData : Preferences("settings") {
- var token by stringPref("token", "")
- var homeserver by stringPref("homeserver", "")
+object AccountData : Preferences("system_parameters") {
+ var token by stringOrNullPref("token", null)
+ var homeserver by stringOrNullPref("homeserver", null)
+ var syncLastBatch by stringOrNullPref("next_batch", null)
+ var filter by stringOrNullPref("filter", null)
}
+val syncService = MatrixSyncService()
+
suspend fun initCheck(): Boolean {
Log.d("initCheck", "checking...")
- token = AccountData.token
- homeserver = AccountData.homeserver
-
- if (token.isEmpty() or homeserver.isEmpty()) return false
+ token = AccountData.token ?: return false
+ homeserver = AccountData.homeserver ?: return false
baseUrl = "$homeserver/_matrix/client/v3"
return getMe() != null
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/ru/risdeveau/pixeldragon/api/Event.kt b/app/src/main/java/ru/risdeveau/pixeldragon/api/Event.kt
index 441a01d..2cda807 100755
--- a/app/src/main/java/ru/risdeveau/pixeldragon/api/Event.kt
+++ b/app/src/main/java/ru/risdeveau/pixeldragon/api/Event.kt
@@ -14,43 +14,45 @@ import org.json.JSONObject
import ru.risdeveau.pixeldragon.baseUrl
import ru.risdeveau.pixeldragon.client
import ru.risdeveau.pixeldragon.token
+import java.time.Instant
class Event (
val id: String,
val rid: String,
- val sender: String,
val type: String,
- val content: JSONObject
+ val content: JSONObject,
+ val time: Instant,
+ val sender: String
) {
- constructor(json: JSONObject) : this(
- json.getString("event_id"),
- json.getString("room_id"),
- json.getString("sender"),
- json.getString("type"),
- json.getJSONObject("content")
- )
+// constructor(json: JSONObject) : this(
+// json.getString("event_id"),
+// json.getString("room_id"),
+// json.getString("sender"),
+// json.getString("type"),
+// json.getJSONObject("content")
+// )
}
-data class EventsAround (
- val base: Event,
- val before: List,
- val after: List
-)
-
-suspend fun getEventsAround(room: String, event: String): EventsAround {
- val r = client.get("$baseUrl/rooms/$room/context/$event") {
- bearerAuth(token)
- parameter("limit", "50")
- }
- val json = JSONObject(r.bodyAsText())
-
- return EventsAround(
- Event(json.getJSONObject("event")),
- if (json.has("events_before")) json.getJSONArray("events_before").let {
- List(it.length()) { i -> Event(it.getJSONObject(i))}.reversed()
- } else listOf(),
- if (json.has("events_after")) json.getJSONArray("events_after").let {
- List(it.length()) { i -> Event(it.getJSONObject(i))}
- } else listOf()
- )
-}
\ No newline at end of file
+//data class EventsAround (
+// val base: Event,
+// val before: List,
+// val after: List
+//)
+//
+//suspend fun getEventsAround(room: String, event: String): EventsAround {
+// val r = client.get("$baseUrl/rooms/$room/context/$event") {
+// bearerAuth(token)
+// parameter("limit", "50")
+// }
+// val json = JSONObject(r.bodyAsText())
+//
+// return EventsAround(
+// Event(json.getJSONObject("event")),
+// if (json.has("events_before")) json.getJSONArray("events_before").let {
+// List(it.length()) { i -> Event(it.getJSONObject(i))}.reversed()
+// } else listOf(),
+// if (json.has("events_after")) json.getJSONArray("events_after").let {
+// List(it.length()) { i -> Event(it.getJSONObject(i))}
+// } else listOf()
+// )
+//}
\ No newline at end of file
diff --git a/app/src/main/java/ru/risdeveau/pixeldragon/api/sync.kt b/app/src/main/java/ru/risdeveau/pixeldragon/api/sync.kt
new file mode 100755
index 0000000..d3e71d2
--- /dev/null
+++ b/app/src/main/java/ru/risdeveau/pixeldragon/api/sync.kt
@@ -0,0 +1,237 @@
+/*
+ * Created by sweetbread
+ * Copyright (c) 2025. All rights reserved.
+ */
+
+package ru.risdeveau.pixeldragon.api
+
+import android.util.Log
+import io.ktor.client.request.bearerAuth
+import io.ktor.client.request.get
+import io.ktor.client.request.header
+import io.ktor.client.request.parameter
+import io.ktor.client.request.post
+import io.ktor.client.request.setBody
+import io.ktor.client.statement.bodyAsText
+import io.ktor.http.ContentType
+import io.ktor.http.HttpStatusCode
+import io.ktor.http.contentType
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.launch
+import org.json.JSONObject
+import ru.risdeveau.pixeldragon.AccountData.syncLastBatch
+import ru.risdeveau.pixeldragon.baseUrl
+import ru.risdeveau.pixeldragon.client
+import ru.risdeveau.pixeldragon.db.isConnected
+import ru.risdeveau.pixeldragon.token
+import ru.risdeveau.pixeldragon.ui.activity.ME
+import splitties.init.appCtx
+import java.io.File
+
+
+class MatrixSyncService {
+ private val _syncState = MutableStateFlow(SyncState.Idle)
+ val syncState: StateFlow = _syncState.asStateFlow()
+
+ private var syncJob: Job? = null
+// private var isInitialized = false
+//
+// fun initialize() {
+// isInitialized = true
+// }
+
+ fun startSync() {
+// if (!isInitialized)
+// throw IllegalStateException("Sync service not initialized")
+
+ if (syncJob?.isActive == true) return
+
+ syncJob = CoroutineScope(Dispatchers.IO).launch {
+ if (syncLastBatch == "") {
+ _syncState.value = SyncState.Syncing
+ try {
+ processSyncResponse(initialSync())
+ _syncState.value = SyncState.Idle
+ } catch (e: Exception) {
+ _syncState.value = SyncState.Error(e.message)
+ }
+ }
+
+// while (isActive) {
+// try {
+// val response = sync (
+// timeout = 30000,
+// filter = getFilter()
+// )
+//
+// processSyncResponse(response)
+//
+// } catch (e: Exception) {
+// Log.w("sync", e.message.toString())
+// delay(5000) // Wait before retry
+// }
+// }
+ }
+ }
+
+
+ fun stopSync() {
+ syncJob?.cancel()
+ _syncState.value = SyncState.Idle
+ }
+
+ fun pauseSync() {
+ // Called when app goes to background
+ syncJob?.cancel()
+ }
+
+ fun resumeSync() {
+ // Called when app comes to foreground
+ if (!isSyncActive()) {
+ startSync()
+ }
+ }
+
+ fun isSyncActive(): Boolean = syncJob?.isActive == true
+
+ private suspend fun processSyncResponse(response: JSONObject) {
+// Log.d("syncResponse", response.toString(2))
+// val newMessages = mutableListOf()
+// val roomUpdates = mutableListOf()
+//
+// response.rooms?.join?.forEach { (roomId, roomData) ->
+// // Process timeline events (messages)
+// roomData.timeline?.events?.forEach { event ->
+// val message = event.toMessage(roomId)
+// database.messageDao().insertMessage(message)
+// newMessages.add(message)
+// }
+//
+// // Process room state updates
+// roomData.state?.events?.forEach { event ->
+// when (event.type) {
+// "m.room.name", "m.room.avatar" -> {
+// roomUpdates.add(RoomUpdate(roomId, event.type, event.content))
+// }
+// }
+// }
+//
+// // Process ephemeral events (typing, receipts)
+// roomData.ephemeral?.events?.forEach { event ->
+// when (event.type) {
+// "m.typing" -> handleTypingEvent(roomId, event)
+// "m.receipt" -> handleReceiptEvent(roomId, event)
+// }
+// }
+// }
+//
+// // Notify the app about new data
+// if (newMessages.isNotEmpty()) {
+// _newMessages.emit(newMessages)
+// }
+// if (roomUpdates.isNotEmpty()) {
+// _roomUpdates.emit(roomUpdates)
+// }
+ }
+}
+
+sealed class SyncState {
+ object Idle : SyncState()
+ object Syncing : SyncState()
+ object Success : SyncState()
+ data class Error(val message: String?) : SyncState()
+}
+
+suspend fun sync(): JSONObject {
+
+ return JSONObject()
+
+}
+
+suspend fun initialSync(): JSONObject {
+ val initialFilter = """
+ {
+ "room": {
+ "state": {
+ "types": [
+ "m.room.name",
+ "m.room.avatar",
+ "m.room.canonical_alias",
+ "m.room.encryption",
+ "m.room.tombstone",
+ "m.room.power_levels",
+ "m.room.member"
+ ],
+ "lazy_load_members": true,
+ "not_types": []
+ },
+ "timeline": {
+ "limit": 10,
+ "types": ["m.room.message"],
+ "not_types": [
+ "m.room.name",
+ "m.room.avatar",
+ "m.room.canonical_alias",
+ "m.room.encryption",
+ "m.room.tombstone",
+ "m.room.power_levels",
+ "m.room.member",
+ "m.call.*"
+ ]
+ },
+ "ephemeral": {
+ "types": [],
+ "not_types": ["m.typing", "m.receipt"]
+ },
+ "include_leave": false
+ },
+ "presence": {
+ "types": [],
+ "not_types": ["*"]
+ },
+ "event_format": "client",
+ "event_fields": [
+ "type",
+ "content",
+ "sender",
+ "state_key",
+ "room_id",
+ "origin_server_ts"
+ ]
+ }
+ """.trimIndent()
+
+ var r = client.post("$baseUrl/user/${ME!!.userId}/filter") {
+ setBody(initialFilter)
+ contentType(ContentType.Application.Json)
+ bearerAuth(token)
+ }
+
+ if (r.status != HttpStatusCode.OK)
+ throw IllegalStateException("Failed to create a filter")
+ val filterId = JSONObject(r.bodyAsText()).getString("filter_id")
+// val filterId = "vmNk"
+
+ /*val*/ r = client.get("$baseUrl/sync") {
+ bearerAuth(token)
+ url {
+ parameter("filter", filterId)
+ }
+ }
+
+ if (r.status != HttpStatusCode.OK)
+ throw IllegalStateException("Failed to sync")
+ Log.d("initialSync", "Response size: ${r.bodyAsText().length}")
+
+ val json = JSONObject(r.bodyAsText())
+ syncLastBatch = json.getString("next_batch")
+
+ return json
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/risdeveau/pixeldragon/ui/activity/MainActivity.kt b/app/src/main/java/ru/risdeveau/pixeldragon/ui/activity/MainActivity.kt
index 04c37a7..8aa0952 100755
--- a/app/src/main/java/ru/risdeveau/pixeldragon/ui/activity/MainActivity.kt
+++ b/app/src/main/java/ru/risdeveau/pixeldragon/ui/activity/MainActivity.kt
@@ -29,6 +29,7 @@ import kotlinx.coroutines.withContext
import ru.risdeveau.pixeldragon.api.Me
import ru.risdeveau.pixeldragon.api.getMe
import ru.risdeveau.pixeldragon.initCheck
+import ru.risdeveau.pixeldragon.syncService
import ru.risdeveau.pixeldragon.ui.layout.Room
import ru.risdeveau.pixeldragon.ui.layout.RoomList
import ru.risdeveau.pixeldragon.ui.theme.PixelDragonTheme
@@ -64,6 +65,7 @@ class MainActivity : ComponentActivity() {
if (initCheck()) {
ME = withContext(Dispatchers.IO) { getMe() }
if (ME != null) {
+ syncService.startSync()
navController.navigate("rooms")
} else {
start()
diff --git a/app/src/main/java/ru/risdeveau/pixeldragon/ui/layout/Rooms.kt b/app/src/main/java/ru/risdeveau/pixeldragon/ui/layout/Rooms.kt
index 5ef7c2f..7dfdc52 100755
--- a/app/src/main/java/ru/risdeveau/pixeldragon/ui/layout/Rooms.kt
+++ b/app/src/main/java/ru/risdeveau/pixeldragon/ui/layout/Rooms.kt
@@ -48,9 +48,9 @@ fun RoomList(modifier: Modifier = Modifier, navController: NavController) {
// }
// }
- LaunchedEffect(Unit) {
- list = withContext(Dispatchers.IO) { Room.getJoined() }
- }
+// LaunchedEffect(Unit) {
+// list = withContext(Dispatchers.IO) { Room.getJoined() }
+// }
LazyColumn(modifier = modifier, state = listState) {
items(list) { room ->