[Config] Implement (basic) new app config storage.

This commit is contained in:
Kuba Szczodrzyński 2019-11-26 21:55:04 +01:00
parent d1a5d8cba9
commit c2e7931ea6
13 changed files with 342 additions and 7 deletions

View File

@ -68,6 +68,7 @@ import me.leolin.shortcutbadger.ShortcutBadger;
import okhttp3.ConnectionSpec;
import okhttp3.OkHttpClient;
import okhttp3.TlsVersion;
import pl.szczodrzynski.edziennik.config.Config;
import pl.szczodrzynski.edziennik.data.db.AppDb;
import pl.szczodrzynski.edziennik.data.db.modules.debuglog.DebugLog;
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore;
@ -145,6 +146,7 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
//public Register register; // REGISTER for current profile, read from registerStore
public ProfileFull profile;
public Config config;
// other stuff
public Gson gson;
@ -194,6 +196,8 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
gson = new Gson();
networkUtils = new NetworkUtils(this);
config = new Config(db);
Iconics.init(getApplicationContext());
Iconics.registerFont(SzkolnyFont.INSTANCE);
@ -636,6 +640,7 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
MainActivity.Companion.setUseOldMessages(profile.getLoginStoreType() == LOGIN_TYPE_MOBIDZIENNIK && appConfig.mobidziennikOldMessages == 1);
profileId = profile.getId();
appSharedPrefs.edit().putInt("current_profile_id", profile.getId()).apply();
config.setProfile(profileId);
}
else if (!loadedLast) {
profileLoadById(profileLastId(), true);

View File

@ -973,7 +973,7 @@ class MainActivity : AppCompatActivity() {
val item = DrawerPrimaryItem()
.withIdentifier(target.id.toLong())
.withName(target.name)
.withHiddenInMiniDrawer(!app.appConfig.miniDrawerButtonIds.contains(target.id))
.withHiddenInMiniDrawer(!app.config.ui.miniMenuButtons.contains(target.id))
.also { if (target.description != null) it.withDescription(target.description!!) }
.also { if (target.icon != null) it.withIcon(target.icon!!) }
.also { if (target.title != null) it.withAppTitle(getString(target.title!!)) }

View File

@ -0,0 +1,57 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-26.
*/
package pl.szczodrzynski.edziennik.config
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import pl.szczodrzynski.edziennik.data.db.AppDb
import kotlin.coroutines.CoroutineContext
class Config(val db: AppDb) : CoroutineScope {
companion object {
const val DATA_VERSION = 1
}
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Default
private var profileId: Int? = null
val values: HashMap<String, String?> = hashMapOf()
//private val profileValues: HashMap<String, String?> = hashMapOf()
val ui by lazy { ConfigUI(this) }
val sync by lazy { ConfigSync(this) }
val timetable by lazy { ConfigTimetable(this) }
val grades by lazy { ConfigGrades(this) }
private var mDataVersion: Int? = null
var dataVersion: Int
get() { mDataVersion = mDataVersion ?: values.get("dataVersion", 0); return mDataVersion ?: 0 }
set(value) { set(-1, "dataVersion", value); mDataVersion = value }
init {
db.configDao().getAllNow().toHashMap(values)
if (dataVersion < DATA_VERSION)
ConfigMigration(this)
}
fun setProfile(profileId: Int) {
this.profileId = profileId
//profileValues.clear()
//db.configDao().getAllNow(profileId).toHashMap(profileValues)
}
fun set(profileId: Int, key: String, value: String?) {
//if (profileId == -1)
values[key] = value
/*else
profileValues[key] = value*/
launch {
db.configDao().add(ConfigEntry(profileId, key, value))
}
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-26.
*/
package pl.szczodrzynski.edziennik.config
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
@Dao
interface ConfigDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun add(entry: ConfigEntry)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun addAll(list: List<ConfigEntry>)
@Query("SELECT * FROM config WHERE profileId = -1")
fun getAllNow(): List<ConfigEntry>
@Query("SELECT * FROM config WHERE profileId = :profileId")
fun getAllNow(profileId: Int): List<ConfigEntry>
}

View File

@ -0,0 +1,14 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-26.
*/
package pl.szczodrzynski.edziennik.config
import androidx.room.Entity
@Entity(tableName = "config", primaryKeys = ["profileId", "key"])
data class ConfigEntry(
val profileId: Int = -1,
val key: String,
val value: String?
)

View File

@ -0,0 +1,93 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-26.
*/
package pl.szczodrzynski.edziennik.config
import com.google.gson.*
import com.google.gson.reflect.TypeToken
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
private val gson = Gson()
fun Config.set(profileId: Int, key: String, value: Int) {
set(profileId, key, value.toString())
}
fun Config.set(profileId: Int, key: String, value: Boolean) {
set(profileId, key, value.toString())
}
fun Config.set(profileId: Int, key: String, value: Long) {
set(profileId, key, value.toString())
}
fun Config.set(profileId: Int, key: String, value: Float) {
set(profileId, key, value.toString())
}
fun Config.set(profileId: Int, key: String, value: Date?) {
set(profileId, key, value?.stringY_m_d)
}
fun Config.set(profileId: Int, key: String, value: Time?) {
set(profileId, key, value?.stringValue)
}
fun Config.set(profileId: Int, key: String, value: JsonElement?) {
set(profileId, key, value?.toString())
}
fun Config.set(profileId: Int, key: String, value: List<Any>?) {
set(profileId, key, value?.let { gson.toJson(it) })
}
fun Config.setStringList(profileId: Int, key: String, value: List<String>?) {
set(profileId, key, value?.let { gson.toJson(it) })
}
fun Config.setIntList(profileId: Int, key: String, value: List<Int>?) {
set(profileId, key, value?.let { gson.toJson(it) })
}
fun Config.setLongList(profileId: Int, key: String, value: List<Long>?) {
set(profileId, key, value?.let { gson.toJson(it) })
}
fun HashMap<String, String?>.get(key: String, default: String?): String? {
return this[key] ?: default
}
fun HashMap<String, String?>.get(key: String, default: Boolean): Boolean {
return this[key]?.toBoolean() ?: default
}
fun HashMap<String, String?>.get(key: String, default: Int): Int {
return this[key]?.toIntOrNull() ?: default
}
fun HashMap<String, String?>.get(key: String, default: Long): Long {
return this[key]?.toLongOrNull() ?: default
}
fun HashMap<String, String?>.get(key: String, default: Float): Float {
return this[key]?.toFloatOrNull() ?: default
}
fun HashMap<String, String?>.get(key: String, default: Date?): Date? {
return this[key]?.let { Date.fromY_m_d(it) } ?: default
}
fun HashMap<String, String?>.get(key: String, default: Time?): Time? {
return this[key]?.let { Time.fromHms(it) } ?: default
}
fun HashMap<String, String?>.get(key: String, default: JsonObject?): JsonObject? {
return this[key]?.let { JsonParser().parse(it)?.asJsonObject } ?: default
}
fun HashMap<String, String?>.get(key: String, default: JsonArray?): JsonArray? {
return this[key]?.let { JsonParser().parse(it)?.asJsonArray } ?: default
}
fun <T> HashMap<String, String?>.get(key: String, default: List<T>?): List<T>? {
return this[key]?.let { gson.fromJson<List<T>>(it, object: TypeToken<List<T>>(){}.type) } ?: default
}
fun HashMap<String, String?>.getStringList(key: String, default: List<String>?): List<String>? {
return this[key]?.let { gson.fromJson<List<String>>(it, object: TypeToken<List<String>>(){}.type) } ?: default
}
fun HashMap<String, String?>.getIntList(key: String, default: List<Int>?): List<Int>? {
return this[key]?.let { gson.fromJson<List<Int>>(it, object: TypeToken<List<Int>>(){}.type) } ?: default
}
fun HashMap<String, String?>.getLongList(key: String, default: List<Long>?): List<Long>? {
return this[key]?.let { gson.fromJson<List<Long>>(it, object: TypeToken<List<Long>>(){}.type) } ?: default
}
fun List<ConfigEntry>.toHashMap(map: HashMap<String, String?>) {
map.clear()
forEach {
map[it.key] = it.value
}
}

View File

@ -0,0 +1,9 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-26.
*/
package pl.szczodrzynski.edziennik.config
class ConfigGrades(val config: Config) {
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-26.
*/
package pl.szczodrzynski.edziennik.config
import pl.szczodrzynski.edziennik.MainActivity
class ConfigMigration(config: Config) {
init { config.apply {
if (dataVersion < 1) {
ui.theme = 1
sync.enabled = true
sync.interval = 60*60; // seconds
ui.miniMenuButtons = listOf(
MainActivity.DRAWER_ITEM_HOME,
MainActivity.DRAWER_ITEM_TIMETABLE,
MainActivity.DRAWER_ITEM_AGENDA,
MainActivity.DRAWER_ITEM_GRADES,
MainActivity.DRAWER_ITEM_MESSAGES,
MainActivity.DRAWER_ITEM_HOMEWORK,
MainActivity.DRAWER_ITEM_SETTINGS
)
dataVersion = 1
}
}}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-26.
*/
package pl.szczodrzynski.edziennik.config
class ConfigSync(val config: Config) {
private var mSyncEnabled: Boolean? = null
var enabled: Boolean
get() { mSyncEnabled = mSyncEnabled ?: config.values.get("syncEnabled", true); return mSyncEnabled ?: true }
set(value) { config.set(-1, "syncEnabled", value); mSyncEnabled = value }
private var mSyncOnlyWifi: Boolean? = null
var onlyWifi: Boolean
get() { mSyncOnlyWifi = mSyncOnlyWifi ?: config.values.get("syncOnlyWifi", false); return mSyncOnlyWifi ?: notifyAboutUpdates }
set(value) { config.set(-1, "syncOnlyWifi", value); mSyncOnlyWifi = value }
private var mSyncInterval: Int? = null
var interval: Int
get() { mSyncInterval = mSyncInterval ?: config.values.get("syncInterval", 60*60); return mSyncInterval ?: 60*60 }
set(value) { config.set(-1, "syncInterval", value); mSyncInterval = value }
private var mNotifyAboutUpdates: Boolean? = null
var notifyAboutUpdates: Boolean
get() { mNotifyAboutUpdates = mNotifyAboutUpdates ?: config.values.get("notifyAboutUpdates", true); return mNotifyAboutUpdates ?: true }
set(value) { config.set(-1, "notifyAboutUpdates", value); mNotifyAboutUpdates = value }
}

View File

@ -0,0 +1,19 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-26.
*/
package pl.szczodrzynski.edziennik.config
import pl.szczodrzynski.edziennik.utils.models.Time
class ConfigTimetable(val config: Config) {
private var mBellSyncMultiplier: Int? = null
var bellSyncMultiplier: Int
get() { mBellSyncMultiplier = mBellSyncMultiplier ?: config.values.get("bellSyncMultiplier", 0); return mBellSyncMultiplier ?: 0 }
set(value) { config.set(-1, "bellSyncMultiplier", value); mBellSyncMultiplier = value }
private var mBellSyncDiff: Time? = null
var bellSyncDiff: Time?
get() { mBellSyncDiff = mBellSyncDiff ?: config.values.get("bellSyncDiff", null as Time?); return mBellSyncDiff }
set(value) { config.set(-1, "bellSyncDiff", value); mBellSyncDiff = value }
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-26.
*/
package pl.szczodrzynski.edziennik.config
class ConfigUI(val config: Config) {
private var mTheme: Int? = null
var theme: Int
get() { mTheme = mTheme ?: config.values.get("theme", 1); return mTheme ?: 1 }
set(value) { config.set(-1, "theme", value); mTheme = value }
private var mHeaderBackground: String? = null
var headerBackground: String?
get() { mHeaderBackground = mHeaderBackground ?: config.values.get("headerBackground", null as String?); return mHeaderBackground }
set(value) { config.set(-1, "headerBg", value); mHeaderBackground = value }
private var mAppBackground: String? = null
var appBackground: String?
get() { mAppBackground = mAppBackground ?: config.values.get("appBackground", null as String?); return mAppBackground }
set(value) { config.set(-1, "appBg", value); mAppBackground = value }
private var mMiniMenuVisible: Boolean? = null
var miniMenuVisible: Boolean
get() { mMiniMenuVisible = mMiniMenuVisible ?: config.values.get("miniMenuVisible", false); return mMiniMenuVisible ?: false }
set(value) { config.set(-1, "miniMenuVisible", value); mMiniMenuVisible = value }
private var mMiniMenuButtons: List<Int>? = null
var miniMenuButtons: List<Int>
get() { mMiniMenuButtons = mMiniMenuButtons ?: config.values.getIntList("miniMenuButtons", listOf()); return mMiniMenuButtons ?: listOf() }
set(value) { config.set(-1, "miniMenuButtons", value); mMiniMenuButtons = value }
}

View File

@ -10,6 +10,8 @@ import androidx.room.TypeConverters;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
import pl.szczodrzynski.edziennik.config.ConfigDao;
import pl.szczodrzynski.edziennik.config.ConfigEntry;
import pl.szczodrzynski.edziennik.data.db.converters.ConverterDate;
import pl.szczodrzynski.edziennik.data.db.converters.ConverterDateInt;
import pl.szczodrzynski.edziennik.data.db.converters.ConverterJsonObject;
@ -105,7 +107,8 @@ import pl.szczodrzynski.edziennik.utils.models.Date;
NoticeType.class,
AttendanceType.class,
pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson.class,
Metadata.class}, version = 64)
ConfigEntry.class,
Metadata.class}, version = 66)
@TypeConverters({
ConverterTime.class,
ConverterDate.class,
@ -144,6 +147,7 @@ public abstract class AppDb extends RoomDatabase {
public abstract NoticeTypeDao noticeTypeDao();
public abstract AttendanceTypeDao attendanceTypeDao();
public abstract TimetableDao timetableDao();
public abstract ConfigDao configDao();
public abstract MetadataDao metadataDao();
private static volatile AppDb INSTANCE;
@ -763,6 +767,27 @@ public abstract class AppDb extends RoomDatabase {
database.execSQL("CREATE INDEX index_lessons_profileId_type_oldDate ON timetable (profileId, type, oldDate);");
}
};
private static final Migration MIGRATION_64_65 = new Migration(64, 65) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE config (" +
"profileId INTEGER NOT NULL DEFAULT -1," +
"`key` TEXT NOT NULL," +
"value TEXT NOT NULL," +
"PRIMARY KEY(profileId, `key`));");
}
};
private static final Migration MIGRATION_65_66 = new Migration(65, 66) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("DROP TABLE config;");
database.execSQL("CREATE TABLE config (" +
"profileId INTEGER NOT NULL DEFAULT -1," +
"`key` TEXT NOT NULL," +
"value TEXT," +
"PRIMARY KEY(profileId, `key`));");
}
};
public static AppDb getDatabase(final Context context) {
@ -824,7 +849,9 @@ public abstract class AppDb extends RoomDatabase {
MIGRATION_60_61,
MIGRATION_61_62,
MIGRATION_62_63,
MIGRATION_63_64
MIGRATION_63_64,
MIGRATION_64_65,
MIGRATION_65_66
)
.allowMainThreadQueries()
//.fallbackToDestructiveMigration()

View File

@ -372,7 +372,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
buttonCaptions.add(getString(R.string.menu_settings));
//buttonCaptions.add(getString(R.string.title_debugging));
List<Integer> selectedIds = new ArrayList<>();
for (int id: app.appConfig.miniDrawerButtonIds) {
for (int id: app.config.getUi().getMiniMenuButtons()) {
selectedIds.add(buttonIds.indexOf(id));
}
new MaterialDialog.Builder(activity)
@ -380,16 +380,16 @@ public class SettingsNewFragment extends MaterialAboutFragment {
.content(getString(R.string.settings_theme_mini_drawer_buttons_dialog_text))
.items(buttonCaptions)
.itemsCallbackMultiChoice(selectedIds.toArray(new Integer[0]), (dialog, which, text) -> {
app.appConfig.miniDrawerButtonIds.clear();
List<Integer> list = new ArrayList<>();
for (int index: which) {
if (index == -1)
continue;
// wtf
int id = buttonIds.get(index);
app.appConfig.miniDrawerButtonIds.add(id);
list.add(id);
}
app.saveConfig("miniDrawerButtonIds");
app.config.getUi().setMiniMenuButtons(list);
activity.setDrawerItems();
activity.getDrawer().updateBadges();
return true;