Initial import of f7cloud-mobile native Android app.

Kotlin/Compose client for F7cloud (mail, files, talk, calendar, contacts, tasks, support).
Current version: 0.5.113 (build 121).
This commit is contained in:
F7cloud Mobile
2026-07-07 12:05:18 +03:00
commit fd17df80a8
1789 changed files with 246889 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
id 'com.google.devtools.ksp'
}
android {
namespace 'ru.forbion.f7cloud.core.database'
compileSdk 36
defaultConfig {
minSdk 26
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '17'
}
}
dependencies {
api 'androidx.room:room-runtime:2.7.2'
implementation 'androidx.room:room-ktx:2.7.2'
ksp 'androidx.room:room-compiler:2.7.2'
}
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />
@@ -0,0 +1,27 @@
package ru.forbion.f7cloud.core.database
import androidx.room.Entity
import androidx.room.Index
@Entity(
tableName = "contacts",
primaryKeys = ["accountKey", "uid"],
indices = [Index("accountKey")],
)
data class ContactEntity(
val accountKey: String,
val uid: String,
val displayName: String,
val email: String,
val phone: String,
val bookName: String,
val photoBase64: String = "",
val photoMimeType: String = "",
val organization: String = "",
val title: String = "",
val address: String = "",
val website: String = "",
val birthday: String = "",
val emails: String = "",
val phones: String = "",
)
@@ -0,0 +1,46 @@
package ru.forbion.f7cloud.core.database
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import kotlinx.coroutines.flow.Flow
@Dao
interface ContactsDao {
@Query(
"""
SELECT * FROM contacts
WHERE accountKey = :accountKey
ORDER BY displayName COLLATE NOCASE
""",
)
fun observeAll(accountKey: String): Flow<List<ContactEntity>>
@Query(
"""
SELECT * FROM contacts
WHERE accountKey = :accountKey
ORDER BY displayName COLLATE NOCASE
""",
)
suspend fun getAll(accountKey: String): List<ContactEntity>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(contacts: List<ContactEntity>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(contact: ContactEntity)
@Query("DELETE FROM contacts WHERE accountKey = :accountKey")
suspend fun deleteAll(accountKey: String)
@Transaction
suspend fun replaceAll(accountKey: String, contacts: List<ContactEntity>) {
deleteAll(accountKey)
if (contacts.isNotEmpty()) {
insertAll(contacts)
}
}
}
@@ -0,0 +1,80 @@
package ru.forbion.f7cloud.core.database
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
@Database(
entities = [FileEntity::class, ContactEntity::class],
version = 4,
exportSchema = false,
)
abstract class F7Database : RoomDatabase() {
abstract fun filesDao(): FilesDao
abstract fun contactsDao(): ContactsDao
companion object {
@Volatile
private var INSTANCE: F7Database? = null
private val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL(
"""
CREATE TABLE IF NOT EXISTS contacts (
accountKey TEXT NOT NULL,
uid TEXT NOT NULL,
displayName TEXT NOT NULL,
email TEXT NOT NULL,
phone TEXT NOT NULL,
bookName TEXT NOT NULL,
PRIMARY KEY(accountKey, uid)
)
""".trimIndent(),
)
db.execSQL(
"CREATE INDEX IF NOT EXISTS index_contacts_accountKey ON contacts(accountKey)",
)
}
}
private val MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL(
"ALTER TABLE contacts ADD COLUMN photoBase64 TEXT NOT NULL DEFAULT ''",
)
db.execSQL(
"ALTER TABLE contacts ADD COLUMN photoMimeType TEXT NOT NULL DEFAULT ''",
)
}
}
private val MIGRATION_3_4 = object : Migration(3, 4) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE contacts ADD COLUMN organization TEXT NOT NULL DEFAULT ''")
db.execSQL("ALTER TABLE contacts ADD COLUMN title TEXT NOT NULL DEFAULT ''")
db.execSQL("ALTER TABLE contacts ADD COLUMN address TEXT NOT NULL DEFAULT ''")
db.execSQL("ALTER TABLE contacts ADD COLUMN website TEXT NOT NULL DEFAULT ''")
db.execSQL("ALTER TABLE contacts ADD COLUMN birthday TEXT NOT NULL DEFAULT ''")
db.execSQL("ALTER TABLE contacts ADD COLUMN emails TEXT NOT NULL DEFAULT ''")
db.execSQL("ALTER TABLE contacts ADD COLUMN phones TEXT NOT NULL DEFAULT ''")
}
}
fun get(context: Context): F7Database {
return INSTANCE ?: synchronized(this) {
INSTANCE ?: Room.databaseBuilder(
context.applicationContext,
F7Database::class.java,
"f7cloud-mobile.db",
)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
.build()
.also { INSTANCE = it }
}
}
}
}
@@ -0,0 +1,14 @@
package ru.forbion.f7cloud.core.database
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "files")
data class FileEntity(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val serverUrl: String,
val username: String,
val name: String,
val isDirectory: Boolean,
)
@@ -0,0 +1,18 @@
package ru.forbion.f7cloud.core.database
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
@Dao
interface FilesDao {
@Query("SELECT * FROM files WHERE serverUrl = :serverUrl AND username = :username ORDER BY isDirectory DESC, name ASC")
suspend fun list(serverUrl: String, username: String): List<FileEntity>
@Query("DELETE FROM files WHERE serverUrl = :serverUrl AND username = :username")
suspend fun clear(serverUrl: String, username: String)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(items: List<FileEntity>)
}