diff --git a/clientapp/android/.idea/kotlinc.xml b/clientapp/android/.idea/kotlinc.xml
index fdf8d994..6d0ee1c2 100644
--- a/clientapp/android/.idea/kotlinc.xml
+++ b/clientapp/android/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/clientapp/android/.idea/other.xml b/clientapp/android/.idea/other.xml
new file mode 100644
index 00000000..0d3a1fbb
--- /dev/null
+++ b/clientapp/android/.idea/other.xml
@@ -0,0 +1,263 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/clientapp/android/app/build.gradle.kts b/clientapp/android/app/build.gradle.kts
index 9dc16992..e706caa1 100644
--- a/clientapp/android/app/build.gradle.kts
+++ b/clientapp/android/app/build.gradle.kts
@@ -1,8 +1,9 @@
-import java.util.Properties
-
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
+ alias(libs.plugins.androidx.room)
+ alias(libs.plugins.com.google.devtools.ksp)
+ alias(libs.plugins.com.google.dagger.hilt.android)
}
android {
@@ -59,6 +60,13 @@ android {
excludes += "META-INF/LICENSE-notice.md"
}
}
+ room {
+ schemaDirectory("$projectDir/schemas")
+ }
+}
+
+ksp {
+ arg("room.generateKotlin", "true")
}
dependencies {
@@ -73,9 +81,14 @@ dependencies {
implementation(libs.androidx.preference.ktx)
implementation(libs.androidx.fragment.ktx)
implementation(libs.androidx.window)
+ implementation(libs.androidx.room.runtime)
+ implementation(libs.androidx.room.ktx)
+ implementation(libs.hilt)
- testImplementation(libs.junit)
+ ksp(libs.androidx.room.compiler)
+ ksp(libs.hilt.compiler)
+ testImplementation(libs.junit)
androidTestImplementation(libs.junit)
androidTestImplementation(libs.junit.jupiter)
androidTestImplementation(libs.androidx.espresso.core)
diff --git a/clientapp/android/app/schemas/com.royna.tgbotclient.datastore.ChatIDDatabase/1.json b/clientapp/android/app/schemas/com.royna.tgbotclient.datastore.ChatIDDatabase/1.json
new file mode 100644
index 00000000..f41bbd8c
--- /dev/null
+++ b/clientapp/android/app/schemas/com.royna.tgbotclient.datastore.ChatIDDatabase/1.json
@@ -0,0 +1,50 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 1,
+ "identityHash": "6780e511c5a2371a71169f6bd0de9e12",
+ "entities": [
+ {
+ "tableName": "ChatIDEntry",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`chat_id` INTEGER NOT NULL, `chat_name` TEXT NOT NULL, PRIMARY KEY(`chat_id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "chat_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "chat_name",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "chat_id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_ChatIDEntry_chat_name",
+ "unique": true,
+ "columnNames": [
+ "chat_name"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_ChatIDEntry_chat_name` ON `${TABLE_NAME}` (`chat_name`)"
+ }
+ ],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6780e511c5a2371a71169f6bd0de9e12')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/clientapp/android/app/src/main/java/com/royna/tgbotclient/ClientApplication.kt b/clientapp/android/app/src/main/java/com/royna/tgbotclient/ClientApplication.kt
index 342b9289..d63e1578 100644
--- a/clientapp/android/app/src/main/java/com/royna/tgbotclient/ClientApplication.kt
+++ b/clientapp/android/app/src/main/java/com/royna/tgbotclient/ClientApplication.kt
@@ -2,9 +2,10 @@ package com.royna.tgbotclient
import android.app.Application
import com.royna.tgbotclient.ui.settings.TgClientSettings
-import com.royna.tgbotclient.util.DeviceUtils
import com.royna.tgbotclient.util.Logging
+import dagger.hilt.android.HiltAndroidApp
+@HiltAndroidApp
class ClientApplication : Application() {
override fun onCreate() {
super.onCreate()
diff --git a/clientapp/android/app/src/main/java/com/royna/tgbotclient/MainActivity.kt b/clientapp/android/app/src/main/java/com/royna/tgbotclient/MainActivity.kt
index 50332064..fd6256f0 100644
--- a/clientapp/android/app/src/main/java/com/royna/tgbotclient/MainActivity.kt
+++ b/clientapp/android/app/src/main/java/com/royna/tgbotclient/MainActivity.kt
@@ -21,7 +21,9 @@ import com.royna.tgbotclient.pm.IStoragePermission
import com.royna.tgbotclient.pm.StoragePermissionPreR
import com.royna.tgbotclient.pm.StoragePermissionR
import com.royna.tgbotclient.ui.settings.SettingsActivity
+import dagger.hilt.android.AndroidEntryPoint
+@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var binding: ActivityMainBinding
diff --git a/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/ChatIDDatabase.kt b/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/ChatIDDatabase.kt
new file mode 100644
index 00000000..626a577d
--- /dev/null
+++ b/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/ChatIDDatabase.kt
@@ -0,0 +1,9 @@
+package com.royna.tgbotclient.datastore
+
+import androidx.room.Database
+import androidx.room.RoomDatabase
+
+@Database(entities = [ChatIDEntry::class], version = 1)
+abstract class ChatIDDatabase : RoomDatabase() {
+ abstract fun impl(): IChatIDOperations
+}
\ No newline at end of file
diff --git a/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/ChatIDEntry.kt b/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/ChatIDEntry.kt
new file mode 100644
index 00000000..d863e3a4
--- /dev/null
+++ b/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/ChatIDEntry.kt
@@ -0,0 +1,18 @@
+package com.royna.tgbotclient.datastore
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.Index
+import androidx.room.PrimaryKey
+
+@Entity(indices = [Index(value = ["chat_name"], unique = true)])
+data class ChatIDEntry (
+ @PrimaryKey
+ @ColumnInfo(name="chat_id")
+ val id: ChatID,
+
+ @ColumnInfo(name="chat_name")
+ val name: String
+)
+
+typealias ChatID = Long
\ No newline at end of file
diff --git a/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/ChatIDModule.kt b/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/ChatIDModule.kt
new file mode 100644
index 00000000..cd12e23f
--- /dev/null
+++ b/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/ChatIDModule.kt
@@ -0,0 +1,24 @@
+package com.royna.tgbotclient.datastore
+
+import android.content.Context
+import androidx.room.Room
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+
+@Module
+@InstallIn(SingletonComponent::class)
+class ChatIDModule {
+ @Provides
+ fun provideChatIDOperations(database: ChatIDDatabase): IChatIDOperations
+ = database.impl()
+
+ @Provides
+ fun provideChatIDDatabase(@ApplicationContext context: Context): ChatIDDatabase = Room.databaseBuilder(
+ context,
+ ChatIDDatabase::class.java,
+ "chatid.db"
+ ).build()
+}
\ No newline at end of file
diff --git a/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/IChatIDOperations.kt b/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/IChatIDOperations.kt
new file mode 100644
index 00000000..3b9ad87c
--- /dev/null
+++ b/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/IChatIDOperations.kt
@@ -0,0 +1,27 @@
+package com.royna.tgbotclient.datastore
+
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.Query
+
+@Dao
+interface IChatIDOperations {
+ @Insert
+ fun add(vararg entries: ChatIDEntry)
+
+ @Query("SELECT * FROM ChatIDEntry")
+ fun getAll(): List
+
+ @Delete
+ fun remove(entry: ChatIDEntry)
+
+ @Query("SELECT chat_id FROM ChatIDEntry WHERE chat_name = :name")
+ fun getChatID(name: String): ChatID
+
+ @Query("SELECT chat_name FROM ChatIDEntry WHERE chat_id = :id")
+ fun getChatName(id: ChatID): String
+
+ @Query("DELETE FROM ChatIDEntry")
+ fun clearAll()
+}
\ No newline at end of file
diff --git a/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/IDataStore.kt b/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/IDataStore.kt
deleted file mode 100644
index 205f0828..00000000
--- a/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/IDataStore.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-package com.royna.tgbotclient.datastore
-
-/**
- * IDataStore interface defines the contract for a key-value data store.
- *
- * @param K the type of keys maintained by this data store.
- * @param V the type of mapped values.
- */
-interface IDataStore {
-
- /**
- * Creates the data store.
- *
- * @return `true` if the data store is successfully created, `false` otherwise.
- */
- fun create() : Boolean
-
- /**
- * Writes the specified value with the specified key in the data store.
- *
- * @param key the key with which the specified value is to be associated.
- * @param value the value to be associated with the specified key.
- * @return `true` if the value is successfully written, `false` otherwise.
- */
- fun write(key: K, value: V) : Boolean
-
- /**
- * Deletes the value associated with the specified key in the data store.
- *
- * @param key the key whose associated value is to be deleted.
- * @return `true` if the value is successfully deleted, `false` otherwise.
- */
- fun delete(key: K) : Boolean
-
- /**
- * Reads all key-value pairs from the data store.
- *
- * @return a map containing all key-value pairs in the data store.
- */
- fun readAll() : Map
-
- /**
- * Clears all key-value pairs from the data store.
- *
- * @return `true` if the data store is successfully cleared, `false` otherwise.
- */
- fun clearAll() : Boolean
-
- /**
- * Reads the value associated with the specified key from the data store.
- *
- * @param key the key whose associated value is to be read.
- * @return the value associated with the specified key, or `null` if the key does not exist.
- */
- fun read(key: K) : V? {
- readAll().forEach {
- if (it.key == key) {
- return it.value
- }
- }
- return null
- }
-
- /**
- * Finds the key associated with the specified value in the data store.
- *
- * @param value the value whose associated key is to be found.
- * @return the key associated with the specified value, or `null`
- * if the value does not exist in the data store.
- */
- fun findKey(value: V) : K? {
- readAll().forEach {
- if (it.value == value) {
- return it.key
- }
- }
- return null
- }
-}
diff --git a/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/chat/SQLiteChatDatastore.kt b/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/chat/SQLiteChatDatastore.kt
deleted file mode 100644
index 229846f0..00000000
--- a/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/chat/SQLiteChatDatastore.kt
+++ /dev/null
@@ -1,132 +0,0 @@
-package com.royna.tgbotclient.datastore.chat
-
-import android.content.ContentValues
-import android.content.Context
-import android.database.sqlite.SQLiteDatabase
-import android.database.sqlite.SQLiteOpenHelper
-import com.royna.tgbotclient.datastore.IDataStore
-import com.royna.tgbotclient.util.Logging
-
-class SQLiteChatDatastore(context: Context) : IDataStore,
- SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
- override fun create(): Boolean = true
- override fun readAll(): Map {
- Logging.verbose("Reading all")
- val result = mutableMapOf()
- readableDatabase.use { db ->
- db.query(
- TABLE_NAME,
- arrayOf(COLUMN_NAME, COLUMN_ID),
- null,
- null,
- null,
- null,
- null
- ).use {
- while (it.moveToNext()) {
- result[it.getString(0)] = it.getLong(1)
- }
- }
- Logging.verbose("Read all: result $result")
- }
- return result
- }
-
- override fun delete(key: String): Boolean {
- val success : Boolean
-
- Logging.verbose("Deleting $key")
- readableDatabase.use {
- success = it.delete(TABLE_NAME, "$COLUMN_NAME = ?", arrayOf(key)) == 1
- }
- Logging.verbose("Deleted $key: result $success")
- return success
- }
-
- override fun write(key: String, value: ChatId): Boolean {
- Logging.verbose("Writing $key:$value")
- writableDatabase.use { wdb ->
- val cntv = ContentValues().apply {
- put(COLUMN_NAME, key)
- put(COLUMN_ID, value)
- }
- return runCatching {
- wdb.insertOrThrow(TABLE_NAME, null, cntv) > 0
- }.getOrElse {
- Logging.error("Failed to write $key:$value", it)
- false
- }
- }
- }
-
- override fun read(key: String): ChatId? {
- var chatId : ChatId? = null
-
- Logging.verbose("Reading $key")
- readableDatabase.use { db ->
- db.query(
- TABLE_NAME,
- arrayOf(COLUMN_ID),
- "$COLUMN_NAME = ?",
- arrayOf(key),
- null,
- null,
- null
- ).use {
- if (it.moveToFirst()) {
- chatId = it.getLong(0)
- }
- }
- }
- Logging.verbose("Read $key: result $chatId")
- return chatId
- }
-
- override fun clearAll(): Boolean {
- Logging.verbose("Clearing all")
- val success = writableDatabase.delete(TABLE_NAME, null, null) > 0
- Logging.verbose("Cleared all: result $success")
- return success
- }
-
- override fun findKey(value: ChatId): String? {
- Logging.verbose("Finding $value")
- readableDatabase.use { db ->
- db.query(
- TABLE_NAME,
- arrayOf(COLUMN_NAME),
- "$COLUMN_ID = ?",
- arrayOf(value.toString()),
- null,
- null,
- null).use {
- if (it.moveToFirst()) {
- return it.getString(0)
- }
- }
- }
- return null
- }
-
- override fun onCreate(db: SQLiteDatabase?) {
- db?.execSQL("CREATE TABLE $TABLE_NAME ($COLUMN_ID BIGINT UNIQUE," +
- "$COLUMN_NAME TEXT UNIQUE NOT NULL)")
- }
-
- override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
- if (oldVersion < newVersion) {
- db?.execSQL("DROP TABLE IF EXISTS $TABLE_NAME")
- onCreate(db)
- }
- }
-
- companion object {
- const val DATABASE_NAME = "chat_ids.db"
- const val DATABASE_VERSION = 1
- const val TABLE_NAME = "chat_ids"
- const val COLUMN_ID = "id"
- const val COLUMN_NAME = "name"
- }
-}
-
-typealias ChatId = Long
\ No newline at end of file
diff --git a/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/chat/ui/ChatDatastoreViewModel.kt b/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/chat/ui/ChatDatastoreViewModel.kt
deleted file mode 100644
index 00aa6cf6..00000000
--- a/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/chat/ui/ChatDatastoreViewModel.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.royna.tgbotclient.datastore.chat.ui
-
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-import com.royna.tgbotclient.datastore.chat.ChatId
-
-class ChatDatastoreViewModel : ViewModel() {
- private var _chatId = MutableLiveData()
- private var _chatName = MutableLiveData()
- val chatId: LiveData = _chatId
- val chatName: LiveData = _chatName
- fun setChatId(chatId: ChatId) {
- _chatId.value = chatId
- }
- fun setChatName(chatName: String) {
- _chatName.value = chatName
- }
-}
\ No newline at end of file
diff --git a/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/chat/ui/ChatTableViewItem.kt b/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/chat/ui/ChatTableViewItem.kt
deleted file mode 100644
index 5a9e75a2..00000000
--- a/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/chat/ui/ChatTableViewItem.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.royna.tgbotclient.datastore.chat.ui
-
-import com.royna.tgbotclient.datastore.chat.ChatId
-
-data class ChatTableViewItem(var index: Int = 0, val chatName: String, val chatId: ChatId)
diff --git a/clientapp/android/app/src/main/java/com/royna/tgbotclient/ui/SingleViewModelBase.kt b/clientapp/android/app/src/main/java/com/royna/tgbotclient/ui/SingleViewModelBase.kt
index 3f05ec2f..304bec18 100644
--- a/clientapp/android/app/src/main/java/com/royna/tgbotclient/ui/SingleViewModelBase.kt
+++ b/clientapp/android/app/src/main/java/com/royna/tgbotclient/ui/SingleViewModelBase.kt
@@ -4,10 +4,8 @@ import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
import com.royna.tgbotclient.SocketCommandNative
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.cancel
abstract class SingleViewModelBase : ViewModel() {
protected var _liveData = MutableLiveData()
@@ -16,12 +14,7 @@ abstract class SingleViewModelBase : ViewModel() {
_liveData.value = inval
}
- protected val gMainScope = CoroutineScope(Dispatchers.Main)
+ protected val gMainScope = viewModelScope
abstract suspend fun coroutineFunction(activity: FragmentActivity) : V
abstract fun execute(activity: FragmentActivity, callback: SocketCommandNative.ICommandCallback)
-
- override fun onCleared() {
- super.onCleared()
- gMainScope.cancel()
- }
}
\ No newline at end of file
diff --git a/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/chat/ui/ChatDatastoreFragment.kt b/clientapp/android/app/src/main/java/com/royna/tgbotclient/ui/chatid/ChatDatastoreFragment.kt
similarity index 72%
rename from clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/chat/ui/ChatDatastoreFragment.kt
rename to clientapp/android/app/src/main/java/com/royna/tgbotclient/ui/chatid/ChatDatastoreFragment.kt
index b028aec0..6870464c 100644
--- a/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/chat/ui/ChatDatastoreFragment.kt
+++ b/clientapp/android/app/src/main/java/com/royna/tgbotclient/ui/chatid/ChatDatastoreFragment.kt
@@ -1,4 +1,4 @@
-package com.royna.tgbotclient.datastore.chat.ui
+package com.royna.tgbotclient.ui.chatid
import android.os.Bundle
import android.view.LayoutInflater
@@ -6,14 +6,20 @@ import android.view.View
import android.view.ViewGroup
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment
-import androidx.lifecycle.ViewModelProvider
+import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.snackbar.Snackbar
import com.royna.tgbotclient.databinding.FragmentDatastoreChatBinding
-import com.royna.tgbotclient.datastore.chat.SQLiteChatDatastore
+import com.royna.tgbotclient.datastore.ChatIDEntry
import com.royna.tgbotclient.util.DeviceUtils
import com.royna.tgbotclient.util.Logging
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+@AndroidEntryPoint
class ChatDatastoreFragment : Fragment() {
private var _binding: FragmentDatastoreChatBinding? = null
@@ -21,24 +27,25 @@ class ChatDatastoreFragment : Fragment() {
// onDestroyView.
private val binding get() = _binding!!
+ private val vm : ChatDatastoreViewModel by viewModels()
+
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
- val vm = ViewModelProvider(this)[ChatDatastoreViewModel::class.java]
_binding = FragmentDatastoreChatBinding.inflate(inflater, container, false)
- kDatastore = SQLiteChatDatastore(requireContext())
binding.datastoreSaveButton.setOnClickListener {
if (vm.chatId.value == null || vm.chatName.value == null) {
Snackbar.make(it, "Fill all fields", Snackbar.LENGTH_SHORT).show()
return@setOnClickListener
}
- if (kDatastore.write(vm.chatName.value!!, vm.chatId.value!!)) {
+ val id = vm.chatId.value!!
+ val name = vm.chatName.value!!
+ vm.add(ChatIDEntry(id, name)).invokeOnCompletion { ex ->
+ assert(ex == null)
Snackbar.make(it, "Saved", Snackbar.LENGTH_SHORT).show()
- kAdapter.addItem(ChatTableViewItem(0, vm.chatName.value!!, vm.chatId.value!!))
- } else {
- Snackbar.make(it, "Failed", Snackbar.LENGTH_SHORT).show()
+ kAdapter.addItem(ChatTableViewItem(0, name, id))
}
}
binding.datastoreChatEdit.doOnTextChanged { text, _, _, _ ->
@@ -52,7 +59,7 @@ class ChatDatastoreFragment : Fragment() {
vm.setChatName(text.toString())
}
binding.datastoreClearButton.setOnClickListener {
- kDatastore.clearAll()
+ vm.clearAll().start()
kAdapter.clearAll()
}
kAdapter = ChatTableViewAdapter()
@@ -71,9 +78,14 @@ class ChatDatastoreFragment : Fragment() {
}
}
kAdapter.clearAll()
- kDatastore.readAll().forEach {
- Logging.debug("Read: $it")
- kAdapter.addItem(ChatTableViewItem(chatName = it.key, chatId = it.value))
+ CoroutineScope(Dispatchers.IO).launch {
+ vm.getAll().await().forEach {
+ Logging.debug("Read: $it")
+ withContext(Dispatchers.Main) {
+ kAdapter.addItem(ChatTableViewItem(chatName = it.name, chatId = it.id))
+
+ }
+ }
}
}
@@ -82,6 +94,5 @@ class ChatDatastoreFragment : Fragment() {
_binding = null
}
- private lateinit var kDatastore : SQLiteChatDatastore
private lateinit var kAdapter : ChatTableViewAdapter
}
\ No newline at end of file
diff --git a/clientapp/android/app/src/main/java/com/royna/tgbotclient/ui/chatid/ChatDatastoreViewModel.kt b/clientapp/android/app/src/main/java/com/royna/tgbotclient/ui/chatid/ChatDatastoreViewModel.kt
new file mode 100644
index 00000000..8357ea63
--- /dev/null
+++ b/clientapp/android/app/src/main/java/com/royna/tgbotclient/ui/chatid/ChatDatastoreViewModel.kt
@@ -0,0 +1,44 @@
+package com.royna.tgbotclient.ui.chatid
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.royna.tgbotclient.datastore.ChatID
+import com.royna.tgbotclient.datastore.ChatIDEntry
+import com.royna.tgbotclient.datastore.IChatIDOperations
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.async
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import javax.inject.Inject
+
+@HiltViewModel
+class ChatDatastoreViewModel @Inject constructor(private val operation: IChatIDOperations) : ViewModel() {
+ private var _chatId = MutableLiveData()
+ private var _chatName = MutableLiveData()
+ val chatId: LiveData = _chatId
+ val chatName: LiveData = _chatName
+ fun setChatId(chatId: ChatID) {
+ _chatId.value = chatId
+ }
+ fun setChatName(chatName: String) {
+ _chatName.value = chatName
+ }
+ fun clearAll() = viewModelScope.launch {
+ withContext(Dispatchers.IO) {
+ operation.clearAll()
+ }
+ }
+ fun add(entry: ChatIDEntry) = viewModelScope.launch {
+ withContext(Dispatchers.IO) {
+ operation.add(entry)
+ }
+ }
+ fun getAll() = viewModelScope.async {
+ withContext(Dispatchers.IO) {
+ operation.getAll()
+ }
+ }
+}
\ No newline at end of file
diff --git a/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/chat/ui/ChatTableViewAdapter.kt b/clientapp/android/app/src/main/java/com/royna/tgbotclient/ui/chatid/ChatTableViewAdapter.kt
similarity index 98%
rename from clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/chat/ui/ChatTableViewAdapter.kt
rename to clientapp/android/app/src/main/java/com/royna/tgbotclient/ui/chatid/ChatTableViewAdapter.kt
index b4d2022c..620c64da 100644
--- a/clientapp/android/app/src/main/java/com/royna/tgbotclient/datastore/chat/ui/ChatTableViewAdapter.kt
+++ b/clientapp/android/app/src/main/java/com/royna/tgbotclient/ui/chatid/ChatTableViewAdapter.kt
@@ -1,4 +1,4 @@
-package com.royna.tgbotclient.datastore.chat.ui
+package com.royna.tgbotclient.ui.chatid
import android.annotation.SuppressLint
import android.view.LayoutInflater
diff --git a/clientapp/android/app/src/main/java/com/royna/tgbotclient/ui/chatid/ChatTableViewItem.kt b/clientapp/android/app/src/main/java/com/royna/tgbotclient/ui/chatid/ChatTableViewItem.kt
new file mode 100644
index 00000000..129c4662
--- /dev/null
+++ b/clientapp/android/app/src/main/java/com/royna/tgbotclient/ui/chatid/ChatTableViewItem.kt
@@ -0,0 +1,5 @@
+package com.royna.tgbotclient.ui.chatid
+
+import com.royna.tgbotclient.datastore.ChatID
+
+data class ChatTableViewItem(var index: Int = 0, val chatName: String, val chatId: ChatID)
diff --git a/clientapp/android/app/src/main/java/com/royna/tgbotclient/ui/commands/sendmsg/TextToChatFragment.kt b/clientapp/android/app/src/main/java/com/royna/tgbotclient/ui/commands/sendmsg/TextToChatFragment.kt
index 04466dbd..b5819fed 100644
--- a/clientapp/android/app/src/main/java/com/royna/tgbotclient/ui/commands/sendmsg/TextToChatFragment.kt
+++ b/clientapp/android/app/src/main/java/com/royna/tgbotclient/ui/commands/sendmsg/TextToChatFragment.kt
@@ -10,12 +10,17 @@ import androidx.core.widget.doAfterTextChanged
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment
import androidx.fragment.app.commit
-import androidx.lifecycle.ViewModelProvider
+import androidx.fragment.app.viewModels
import com.royna.tgbotclient.R
import com.royna.tgbotclient.databinding.FragmentSendMessageBinding
-import com.royna.tgbotclient.datastore.chat.SQLiteChatDatastore
+import com.royna.tgbotclient.datastore.ChatIDEntry
import com.royna.tgbotclient.ui.CurrentSettingFragment
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+@AndroidEntryPoint
class TextToChatFragment : Fragment() {
private var _binding: FragmentSendMessageBinding? = null
@@ -23,13 +28,13 @@ class TextToChatFragment : Fragment() {
// onDestroyView.
private val binding get() = _binding!!
+ private val textToChatViewModel : TextToChatViewModel by viewModels()
+
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
- val textToChatViewModel =
- ViewModelProvider(this)[TextToChatViewModel::class.java]
_binding = FragmentSendMessageBinding.inflate(inflater, container, false)
val root: View = binding.root
@@ -46,10 +51,11 @@ class TextToChatFragment : Fragment() {
}
textToChatViewModel.chatId.observe(viewLifecycleOwner) {
- binding.showChatIdText.text = if (mChatIdMap.containsValue(it)) {
- getString(R.string.destination_chat_fmt, mChatIdMap.filterValues {
- v-> v == it
- }.keys.first(), it)
+ val chatName = mChatIdMap.find { ent ->
+ ent.id == it
+ }?.name
+ binding.showChatIdText.text = if (chatName != null) {
+ getString(R.string.destination_chat_fmt, chatName, it)
} else if (it != InvalidChatId) {
getString(R.string.destination_chat_id_fmt, it)
} else {
@@ -69,22 +75,16 @@ class TextToChatFragment : Fragment() {
computeSendButtonState()
}.onFailure {
// This is not a number, query it on DB
- mChatIdMap[editor.toString()].let {
- if (it != null) {
+ mChatIdMap.filter {
+ it.name.lowercase() == editor.toString().lowercase()
+ }.let { map ->
+ if (map.isNotEmpty()) {
// Found
- textToChatViewModel.setChatId(it)
+ assert(map.size == 1)
+ textToChatViewModel.setChatId(map.first().id)
} else {
// Not found
- mChatIdMap.filterKeys { key ->
- key.lowercase() == editor.toString().lowercase()
- }.also { map ->
- assert(map.size == 1 || map.isEmpty())
- if (map.isEmpty()) {
- textToChatViewModel.setChatId(InvalidChatId)
- }
- }.forEach { entry ->
- textToChatViewModel.setChatId(entry.value)
- }
+ textToChatViewModel.setChatId(InvalidChatId)
}
computeSendButtonState()
}
@@ -116,7 +116,9 @@ class TextToChatFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- mChatIdMap = SQLiteChatDatastore(requireContext()).readAll()
+ CoroutineScope(Dispatchers.IO).launch {
+ mChatIdMap = textToChatViewModel.getAll().await()
+ }
childFragmentManager.commit {
replace(R.id.current_setting_container, CurrentSettingFragment())
}
@@ -127,7 +129,7 @@ class TextToChatFragment : Fragment() {
_binding = null
}
- private var mChatIdMap: Map = mapOf()
+ private var mChatIdMap: List = listOf()
companion object {
private const val InvalidChatId = 0L
}
diff --git a/clientapp/android/app/src/main/java/com/royna/tgbotclient/ui/commands/sendmsg/TextToChatViewModel.kt b/clientapp/android/app/src/main/java/com/royna/tgbotclient/ui/commands/sendmsg/TextToChatViewModel.kt
index 0c11c508..bc3073e0 100644
--- a/clientapp/android/app/src/main/java/com/royna/tgbotclient/ui/commands/sendmsg/TextToChatViewModel.kt
+++ b/clientapp/android/app/src/main/java/com/royna/tgbotclient/ui/commands/sendmsg/TextToChatViewModel.kt
@@ -3,15 +3,21 @@ package com.royna.tgbotclient.ui.commands.sendmsg
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
import com.royna.tgbotclient.SocketCommandNative
+import com.royna.tgbotclient.datastore.IChatIDOperations
+import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
+import javax.inject.Inject
-class TextToChatViewModel : ViewModel() {
+@HiltViewModel
+class TextToChatViewModel @Inject constructor(private val operation: IChatIDOperations) : ViewModel() {
// Private MutableLiveData
private val _messageText = MutableLiveData()
private val _chatId = MutableLiveData()
@@ -66,4 +72,10 @@ class TextToChatViewModel : ViewModel() {
super.onCleared()
gMainScope.cancel()
}
+
+ fun getAll() = viewModelScope.async {
+ withContext(Dispatchers.IO) {
+ operation.getAll()
+ }
+ }
}
\ No newline at end of file
diff --git a/clientapp/android/app/src/main/res/layout/fragment_datastore_chat.xml b/clientapp/android/app/src/main/res/layout/fragment_datastore_chat.xml
index bf4d887c..08d5db28 100644
--- a/clientapp/android/app/src/main/res/layout/fragment_datastore_chat.xml
+++ b/clientapp/android/app/src/main/res/layout/fragment_datastore_chat.xml
@@ -4,7 +4,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
- tools:context=".datastore.chat.ui.ChatDatastoreFragment">
+ tools:context=".ui.chatid.ChatDatastoreFragment">
\ No newline at end of file
diff --git a/clientapp/android/build.gradle.kts b/clientapp/android/build.gradle.kts
index f74b04bf..74fda1c8 100644
--- a/clientapp/android/build.gradle.kts
+++ b/clientapp/android/build.gradle.kts
@@ -2,4 +2,7 @@
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.jetbrains.kotlin.android) apply false
+ alias(libs.plugins.androidx.room) apply false
+ alias(libs.plugins.com.google.devtools.ksp) apply false
+ alias(libs.plugins.com.google.dagger.hilt.android) apply false
}
\ No newline at end of file
diff --git a/clientapp/android/gradle/libs.versions.toml b/clientapp/android/gradle/libs.versions.toml
index 53d0d318..e3b02d89 100644
--- a/clientapp/android/gradle/libs.versions.toml
+++ b/clientapp/android/gradle/libs.versions.toml
@@ -1,8 +1,9 @@
[versions]
-agp = "8.4.2"
-espressoCoreVersion = "3.5.1"
+agp = "8.5.0"
+hilt = "2.48"
+espressoCoreVersion = "3.6.1"
junitJupiter = "5.8.1"
-kotlin = "1.9.0"
+kotlin = "2.0.0"
coreKtx = "1.13.1"
junit = "4.13.2"
appcompat = "1.7.0"
@@ -13,13 +14,21 @@ lifecycleViewmodelKtx = "2.8.2"
navigationFragmentKtx = "2.7.7"
navigationUiKtx = "2.7.7"
preferenceKtx = "1.2.1"
-fragmentKtx = "1.8.0"
+fragmentKtx = "1.8.1"
+roomCompiler = "2.6.1"
window = "1.3.0"
+room = "2.6.1"
+ksp = "2.0.0-1.0.22"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCoreVersion" }
+androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomCompiler" }
+androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomCompiler" }
+androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomCompiler" }
androidx-window = { module = "androidx.window:window", version.ref = "window" }
+hilt-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" }
+hilt = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junitJupiter" }
@@ -35,4 +44,6 @@ androidx-fragment-ktx = { group = "androidx.fragment", name = "fragment-ktx", ve
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
-
+androidx-room = { id = "androidx.room", version.ref="room" }
+com-google-devtools-ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
+com-google-dagger-hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
\ No newline at end of file
diff --git a/clientapp/android/gradle/wrapper/gradle-wrapper.properties b/clientapp/android/gradle/wrapper/gradle-wrapper.properties
index c6669bce..d0999e0f 100644
--- a/clientapp/android/gradle/wrapper/gradle-wrapper.properties
+++ b/clientapp/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Fri May 17 22:50:33 KST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists