diff --git a/MykSuite/build.gradle.kts b/MykSuite/build.gradle.kts index af2955ee..5070ae57 100644 --- a/MykSuite/build.gradle.kts +++ b/MykSuite/build.gradle.kts @@ -2,7 +2,9 @@ plugins { id("com.android.library") alias(core.plugins.kotlin.android) alias(core.plugins.compose.compiler) + alias(core.plugins.ksp) kotlin("plugin.parcelize") + kotlin("plugin.serialization") version core.versions.kotlin id("androidx.navigation.safeargs.kotlin") } @@ -19,6 +21,8 @@ android { minSdk = legacyMinSdk consumerProguardFiles("consumer-rules.pro") + + ksp { arg("room.schemaLocation", "$projectDir/schemas") } } buildTypes { @@ -47,6 +51,12 @@ dependencies { implementation(core.androidx.core.ktx) implementation(core.material) implementation(core.navigation.fragment.ktx) + implementation(core.kotlinx.serialization.json) + + // Room + implementation(core.room.runtime) + implementation(core.room.ktx) + ksp(core.room.compiler) // Compose implementation(core.coil.compose) diff --git a/MykSuite/schemas/com.infomaniak.core.myksuite.ui.data.MyKSuiteDatabase/1.json b/MykSuite/schemas/com.infomaniak.core.myksuite.ui.data.MyKSuiteDatabase/1.json new file mode 100644 index 00000000..21cabad0 --- /dev/null +++ b/MykSuite/schemas/com.infomaniak.core.myksuite.ui.data.MyKSuiteDatabase/1.json @@ -0,0 +1,172 @@ +{ + "formatVersion": 1, + "database": { + "version": 1, + "identityHash": "7b5e43bd9cc17b034cf90891e63090d7", + "entities": [ + { + "tableName": "MyKSuiteData", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `status` TEXT NOT NULL, `pack_id` INTEGER NOT NULL, `trialExpiryAt` INTEGER, `is_free` INTEGER NOT NULL, `has_auto_renew` INTEGER NOT NULL, `canTrial` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `k_suite_pack_id` INTEGER NOT NULL, `k_suite_pack_name` TEXT NOT NULL, `k_suite_pack_drive_storage` INTEGER NOT NULL, `k_suite_pack_mail_storage` INTEGER NOT NULL, `k_suite_pack_mail_daily_limit_send` INTEGER NOT NULL, `k_suite_pack_is_max_storage_offer` INTEGER NOT NULL, `drive_id` INTEGER NOT NULL, `drive_name` TEXT NOT NULL, `drive_size` INTEGER NOT NULL, `drive_used_size` INTEGER NOT NULL, `mail_id` INTEGER NOT NULL, `mail_email` TEXT NOT NULL, `mail_daily_limit_sent` INTEGER NOT NULL, `mail_storage_size_limit` INTEGER NOT NULL, `mail_used_size` INTEGER NOT NULL, `mail_mailboxId` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "kSuitePackId", + "columnName": "pack_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "trialExpiryAt", + "columnName": "trialExpiryAt", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "isFree", + "columnName": "is_free", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAutoRenew", + "columnName": "has_auto_renew", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canTrial", + "columnName": "canTrial", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "kSuitePack.id", + "columnName": "k_suite_pack_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "kSuitePack.name", + "columnName": "k_suite_pack_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "kSuitePack.driveStorage", + "columnName": "k_suite_pack_drive_storage", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "kSuitePack.mailStorage", + "columnName": "k_suite_pack_mail_storage", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "kSuitePack.mailDailyLimitSend", + "columnName": "k_suite_pack_mail_daily_limit_send", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "kSuitePack.isMaxStorageOffer", + "columnName": "k_suite_pack_is_max_storage_offer", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "drive.id", + "columnName": "drive_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "drive.name", + "columnName": "drive_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "drive.size", + "columnName": "drive_size", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "drive.usedSize", + "columnName": "drive_used_size", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "mail.id", + "columnName": "mail_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "mail.email", + "columnName": "mail_email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mail.dailyLimitSent", + "columnName": "mail_daily_limit_sent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "mail.storageSizeLimit", + "columnName": "mail_storage_size_limit", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "mail.usedSize", + "columnName": "mail_used_size", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "mail.mailboxId", + "columnName": "mail_mailboxId", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "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, '7b5e43bd9cc17b034cf90891e63090d7')" + ] + } +} \ No newline at end of file diff --git a/MykSuite/src/main/java/com/infomaniak/core/myksuite/ui/data/KSuitePack.kt b/MykSuite/src/main/java/com/infomaniak/core/myksuite/ui/data/KSuitePack.kt new file mode 100644 index 00000000..36447910 --- /dev/null +++ b/MykSuite/src/main/java/com/infomaniak/core/myksuite/ui/data/KSuitePack.kt @@ -0,0 +1,51 @@ +/* + * Infomaniak Core - Android + * Copyright (C) 2025 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.core.myksuite.ui.data + +import androidx.annotation.StringRes +import androidx.room.ColumnInfo +import com.infomaniak.core.myksuite.R +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class KSuitePack( + val id: Int, + val name: String = "", + @SerialName("drive_storage") + @ColumnInfo("drive_storage") + val driveStorage: Long = 0, + @SerialName("mail_storage") + @ColumnInfo("mail_storage") + val mailStorage: Long = 0, + @SerialName("mail_daily_limit_send") + @ColumnInfo("mail_daily_limit_send") + val mailDailyLimitSend: Int = 0, + @SerialName("is_max_storage_offer") + @ColumnInfo("is_max_storage_offer") + val isMaxStorageOffer: Boolean = false, +) { + + val type get() = runCatching { enumValueOf(name.uppercase()) }.getOrNull() + + enum class KSuitePackType(@StringRes val displayNameRes: Int) { + MY_KSUITE(R.string.myKSuiteName), + MY_KSUITE_PLUS(R.string.myKSuitePlusName), + MY_KSUITE_PLUS_DRIVE_SOLO(R.string.myKSuitePlusName), + } +} diff --git a/MykSuite/src/main/java/com/infomaniak/core/myksuite/ui/data/MyKSuiteData.kt b/MykSuite/src/main/java/com/infomaniak/core/myksuite/ui/data/MyKSuiteData.kt new file mode 100644 index 00000000..9d50bb6e --- /dev/null +++ b/MykSuite/src/main/java/com/infomaniak/core/myksuite/ui/data/MyKSuiteData.kt @@ -0,0 +1,65 @@ +/* + * Infomaniak Core - Android + * Copyright (C) 2025 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.core.myksuite.ui.data + +import androidx.room.ColumnInfo +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.PrimaryKey +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient + +@Serializable +@Entity +data class MyKSuiteData( + @PrimaryKey val id: Int, + val status: String, + @SerialName("pack_id") + @ColumnInfo("pack_id") + val kSuitePackId: Int, + @SerialName("pack") + @Embedded("k_suite_pack_") + val kSuitePack: KSuitePack, + @SerialName("trial_expiry_at") + @ColumnInfo("trial_expiry_at") + val trialExpiryAt: Long? = null, + @SerialName("is_free") + @ColumnInfo("is_free") + val isFree: Boolean, + @Embedded("drive_") + val drive: KSuiteDrive, + @Embedded("mail_") + val mail: KSuiteMail, + @SerialName("has_auto_renew") + @ColumnInfo("has_auto_renew") + val hasAutoRenew: Boolean, + @SerialName("can_trial") + @ColumnInfo("can_trial") + val canTrial: Boolean, +) { + + @Transient + @ColumnInfo("user_id") + var userId: Int = 0 + + val isMyKSuite get() = kSuitePack.type == KSuitePack.KSuitePackType.MY_KSUITE + val isMyKSuitePlus + get() = kSuitePack.type == KSuitePack.KSuitePackType.MY_KSUITE_PLUS || + kSuitePack.type == KSuitePack.KSuitePackType.MY_KSUITE_PLUS_DRIVE_SOLO +} diff --git a/MykSuite/src/main/java/com/infomaniak/core/myksuite/ui/data/MyKSuiteDataDao.kt b/MykSuite/src/main/java/com/infomaniak/core/myksuite/ui/data/MyKSuiteDataDao.kt new file mode 100644 index 00000000..2e3b2c30 --- /dev/null +++ b/MykSuite/src/main/java/com/infomaniak/core/myksuite/ui/data/MyKSuiteDataDao.kt @@ -0,0 +1,42 @@ +/* + * Infomaniak Core - Android + * Copyright (C) 2025 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.core.myksuite.ui.data + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Query +import androidx.room.Upsert + +@Dao +interface MyKSuiteDataDao { + + @Query("SELECT * FROM MyKSuiteData") + suspend fun getAll(): List + + @Query("SELECT * FROM MyKSuiteData WHERE id = :id LIMIT 1") + suspend fun findById(id: Int): MyKSuiteData? + + @Query("SELECT * FROM MyKSuiteData WHERE user_id = :userId LIMIT 1") + suspend fun findByUserId(userId: Int): MyKSuiteData? + + @Upsert + suspend fun upsert(data: MyKSuiteData) + + @Delete + suspend fun delete(data: MyKSuiteData) +} diff --git a/MykSuite/src/main/java/com/infomaniak/core/myksuite/ui/data/MyKSuiteDataManager.kt b/MykSuite/src/main/java/com/infomaniak/core/myksuite/ui/data/MyKSuiteDataManager.kt new file mode 100644 index 00000000..b4d5e9b1 --- /dev/null +++ b/MykSuite/src/main/java/com/infomaniak/core/myksuite/ui/data/MyKSuiteDataManager.kt @@ -0,0 +1,51 @@ +/* + * Infomaniak Core - Android + * Copyright (C) 2025 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.core.myksuite.ui.data + +import android.content.Context + +abstract class MyKSuiteDataManager { + + abstract var myKSuiteId: Int + abstract var myKSuite: MyKSuiteData? + abstract val userId: Int + + private var myKSuiteDatabase: MyKSuiteDatabase? = null + + fun initDatabase(appContext: Context) { + myKSuiteDatabase = MyKSuiteDatabase.getDatabase(appContext) + } + + suspend fun requestKSuiteData(id: Int? = null) { + myKSuite = id?.let { getKSuiteData(it) } ?: getKSuiteDataByUser() + } + + suspend fun upsertKSuiteData(kSuiteData: MyKSuiteData) { + myKSuite = kSuiteData + myKSuiteDatabase?.myKSuiteDataDao()?.upsert(kSuiteData.apply { userId = this@MyKSuiteDataManager.userId }) + } + + suspend fun deleteKSuiteData(kSuiteData: MyKSuiteData) { + myKSuite = null + myKSuiteDatabase?.myKSuiteDataDao()?.delete(kSuiteData) + } + + private suspend fun getKSuiteData(id: Int) = myKSuiteDatabase?.myKSuiteDataDao()?.findById(id) + + private suspend fun getKSuiteDataByUser() = myKSuiteDatabase?.myKSuiteDataDao()?.findByUserId(userId) +} diff --git a/MykSuite/src/main/java/com/infomaniak/core/myksuite/ui/data/MyKSuiteDatabase.kt b/MykSuite/src/main/java/com/infomaniak/core/myksuite/ui/data/MyKSuiteDatabase.kt new file mode 100644 index 00000000..47ce2f68 --- /dev/null +++ b/MykSuite/src/main/java/com/infomaniak/core/myksuite/ui/data/MyKSuiteDatabase.kt @@ -0,0 +1,54 @@ +/* + * Infomaniak Core - Android + * Copyright (C) 2025 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.core.myksuite.ui.data + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase + +@Database( + entities = [MyKSuiteData::class], + version = 1, + exportSchema = true, +) +abstract class MyKSuiteDatabase : RoomDatabase() { + + abstract fun myKSuiteDataDao(): MyKSuiteDataDao + + companion object { + + @Volatile + private var INSTANCE: MyKSuiteDatabase? = null + + fun getDatabase(context: Context): MyKSuiteDatabase { + return INSTANCE ?: synchronized(this) { + val instance = Room.databaseBuilder( + context = context.applicationContext, + klass = MyKSuiteDatabase::class.java, + name = "my_ksuite_database", + ).apply { + fallbackToDestructiveMigration() + }.build() + + INSTANCE = instance + instance + } + } + } +} diff --git a/MykSuite/src/main/java/com/infomaniak/core/myksuite/ui/data/kSuiteDrive.kt b/MykSuite/src/main/java/com/infomaniak/core/myksuite/ui/data/kSuiteDrive.kt new file mode 100644 index 00000000..c31690bc --- /dev/null +++ b/MykSuite/src/main/java/com/infomaniak/core/myksuite/ui/data/kSuiteDrive.kt @@ -0,0 +1,32 @@ +/* + * Infomaniak Core - Android + * Copyright (C) 2025 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.core.myksuite.ui.data + +import androidx.room.ColumnInfo +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class KSuiteDrive( + val id: Int, + val name: String, + val size: Long, + @SerialName("used_size") + @ColumnInfo("used_size") + val usedSize: Long, +) diff --git a/MykSuite/src/main/java/com/infomaniak/core/myksuite/ui/data/kSuiteMail.kt b/MykSuite/src/main/java/com/infomaniak/core/myksuite/ui/data/kSuiteMail.kt new file mode 100644 index 00000000..fc72c841 --- /dev/null +++ b/MykSuite/src/main/java/com/infomaniak/core/myksuite/ui/data/kSuiteMail.kt @@ -0,0 +1,39 @@ +/* + * Infomaniak Core - Android + * Copyright (C) 2025 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.core.myksuite.ui.data + +import androidx.room.ColumnInfo +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class KSuiteMail( + val id: Int, + val email: String, + @SerialName("daily_limit_sent") + @ColumnInfo("daily_limit_sent") + val dailyLimitSent: Int, + @SerialName("storage_size_limit") + @ColumnInfo("storage_size_limit") + val storageSizeLimit: Long, + @SerialName("used_size") + @ColumnInfo("used_size") + val usedSize: Long, + @SerialName("mailbox_id") + val mailboxId: Int, +) diff --git a/MykSuite/src/main/java/com/infomaniak/core/myksuite/ui/network/ApiRoutes.kt b/MykSuite/src/main/java/com/infomaniak/core/myksuite/ui/network/ApiRoutes.kt new file mode 100644 index 00000000..1d892f11 --- /dev/null +++ b/MykSuite/src/main/java/com/infomaniak/core/myksuite/ui/network/ApiRoutes.kt @@ -0,0 +1,25 @@ +/* + * Infomaniak Core - Android + * Copyright (C) 2025 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.core.myksuite.ui.network + +object ApiRoutes { + + private const val BASE_URL = "https://api.staging-myksuite.dev.infomaniak.ch" + + val myKSuiteData = "$BASE_URL/1/my_ksuite/current?with=*" +} diff --git a/gradle/core.versions.toml b/gradle/core.versions.toml index cef8f8aa..60455827 100644 --- a/gradle/core.versions.toml +++ b/gradle/core.versions.toml @@ -7,6 +7,7 @@ integrity = "1.4.0" junit = "4.13.2" junitAndroidx = "1.2.1" kotlin = "2.0.21" +ksp = "2.0.21-1.0.28" kotlinxCoroutines = "1.9.0" kotlinxSerializationJson = "1.7.3" ktor = "3.0.1" @@ -15,6 +16,7 @@ material = "1.12.0" matomo = "4.1.4" navigation = "2.8.5" playReview = "2.0.2" +room = "2.6.1" sentryAndroid = "7.15.0" splitties = "3.0.0" @@ -46,6 +48,9 @@ navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fr navigation-safeargs = { module = "androidx.navigation:navigation-safe-args-gradle-plugin", version.ref = "navigation" } play-review = { module = "com.google.android.play:review", version.ref = "playReview" } play-review-ktx = { module = "com.google.android.play:review-ktx", version.ref = "playReview" } +room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" } +room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" } +room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" } sentry-android = { module = "io.sentry:sentry-android", version.ref = "sentryAndroid" } splitties-appctx = { module = "com.louiscad.splitties:splitties-appctx", version.ref = "splitties" } splitties-collections = { module = "com.louiscad.splitties:splitties-collections", version.ref = "splitties" } @@ -61,3 +66,4 @@ junit = { group = "junit", name = "junit", version.ref = "junit" } [plugins] compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }