1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

DROID-1654 App | Feature | Multispaces + Misc. enhancements (#300)

Co-authored-by: Konstantin Ivanov <54908981+konstantiniiv@users.noreply.github.com>
This commit is contained in:
Evgenii Kozlov 2023-10-24 12:41:32 +02:00 committed by GitHub
parent 490e97589c
commit 5886fc1ae5
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
319 changed files with 8198 additions and 4258 deletions

View file

@ -2,6 +2,7 @@ plugins {
id "com.android.library"
id "kotlin-android"
id "kotlin-kapt"
id "kotlinx-serialization"
}
dependencies {

View file

@ -1,9 +1,12 @@
package com.anytypeio.anytype.persistence.common
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.persistence.model.WallpaperSetting
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.encodeToJsonElement
import timber.log.Timber
typealias JsonString = String
@ -28,4 +31,21 @@ fun JsonString.toStringMap(): Map<String, String> = try {
} catch (e: Exception) {
Timber.e(e, "Error while mapping from json string")
emptyMap()
}
fun Map<Id, WallpaperSetting>.serializeWallpaperSettings(): JsonString = try {
JsonObject(
content = mapValues { entry ->
Json.encodeToJsonElement(entry.value)
}
).toString()
} catch (e: Exception) {
Timber.e(e, "Error while serializing wallpaper setting")
""
}
fun JsonString.deserializeWallpaperSettings() : Map<Id, WallpaperSetting> {
return try { Json.decodeFromString(this) } catch (e: Exception) {
emptyMap()
}
}

View file

@ -0,0 +1,65 @@
package com.anytypeio.anytype.persistence.model
import com.anytypeio.anytype.core_models.Wallpaper
import com.anytypeio.anytype.persistence.model.WallpaperSetting.Companion.WALLPAPER_TYPE_COLOR
import com.anytypeio.anytype.persistence.model.WallpaperSetting.Companion.WALLPAPER_TYPE_GRADIENT
import com.anytypeio.anytype.persistence.model.WallpaperSetting.Companion.WALLPAPER_TYPE_IMAGE
import kotlinx.serialization.Serializable
@Serializable
data class WallpaperSetting(
val type: Int,
val value: String
) {
companion object {
const val WALLPAPER_TYPE_COLOR = 1
const val WALLPAPER_TYPE_GRADIENT = 2
const val WALLPAPER_TYPE_IMAGE = 3
}
}
fun Wallpaper.asSettings(): WallpaperSetting? {
return when (this) {
is Wallpaper.Color -> {
WallpaperSetting(
type = WALLPAPER_TYPE_COLOR,
value = code
)
}
is Wallpaper.Gradient -> {
WallpaperSetting(
type = WALLPAPER_TYPE_GRADIENT,
value = code
)
}
is Wallpaper.Image -> {
WallpaperSetting(
type = WALLPAPER_TYPE_IMAGE,
value = hash
)
}
else -> null
}
}
fun WallpaperSetting.asWallpaper(): Wallpaper {
return when(type) {
WALLPAPER_TYPE_COLOR -> {
Wallpaper.Color(
code = value
)
}
WALLPAPER_TYPE_GRADIENT -> {
Wallpaper.Gradient(
code = value
)
}
WALLPAPER_TYPE_IMAGE -> {
Wallpaper.Image(
hash = value
)
}
else -> Wallpaper.Default
}
}

View file

@ -1,85 +1,105 @@
package com.anytypeio.anytype.persistence.repo
import android.content.SharedPreferences
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.NO_VALUE
import com.anytypeio.anytype.core_models.ThemeMode
import com.anytypeio.anytype.core_models.Wallpaper
import com.anytypeio.anytype.core_models.WidgetSession
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_models.primitives.TypeId
import com.anytypeio.anytype.data.auth.repo.UserSettingsCache
import com.anytypeio.anytype.persistence.common.JsonString
import com.anytypeio.anytype.persistence.common.deserializeWallpaperSettings
import com.anytypeio.anytype.persistence.common.serializeWallpaperSettings
import com.anytypeio.anytype.persistence.common.toJsonString
import com.anytypeio.anytype.persistence.common.toStringMap
import com.anytypeio.anytype.persistence.model.asSettings
import com.anytypeio.anytype.persistence.model.asWallpaper
class DefaultUserSettingsCache(private val prefs: SharedPreferences) : UserSettingsCache {
override suspend fun setDefaultObjectType(type: String, name: String) {
override suspend fun setCurrentSpace(space: SpaceId) {
prefs.edit()
.putString(DEFAULT_OBJECT_TYPE_ID_KEY, type)
.putString(DEFAULT_OBJECT_TYPE_NAME_KEY, name)
.putString(CURRENT_SPACE_KEY, space.id)
.apply()
}
override suspend fun getDefaultObjectType(): Pair<String?, String?> {
val type = prefs.getString(DEFAULT_OBJECT_TYPE_ID_KEY, null)
val name = prefs.getString(DEFAULT_OBJECT_TYPE_NAME_KEY, null)
return Pair(type, name)
override suspend fun getCurrentSpace(): SpaceId? {
val value = prefs.getString(CURRENT_SPACE_KEY, "")
return if (value.isNullOrEmpty())
null
else
SpaceId(value)
}
override suspend fun setWallpaper(wallpaper: Wallpaper) {
when (wallpaper) {
is Wallpaper.Default -> {
prefs.edit()
.remove(WALLPAPER_VALUE_KEY)
.remove(WALLPAPER_TYPE_KEY)
.apply()
override suspend fun setDefaultObjectType(space: SpaceId, type: TypeId) {
val curr = prefs
.getString(DEFAULT_OBJECT_TYPES_KEY, NO_VALUE)
.orEmpty()
.toStringMap()
val updated = buildMap {
putAll(curr)
put(space.id, type.id)
}
prefs
.edit()
.putString(DEFAULT_OBJECT_TYPES_KEY, updated.toJsonString())
.apply()
}
override suspend fun getDefaultObjectType(space: SpaceId): TypeId? {
val curr = prefs
.getString(DEFAULT_OBJECT_TYPES_KEY, "")
.orEmpty()
.toStringMap()
val result = curr[space.id]
return if (result.isNullOrEmpty())
null
else
TypeId(result)
}
override suspend fun setWallpaper(space: Id, wallpaper: Wallpaper) {
if (space.isEmpty()) return
val curr = prefs.getString(WALLPAPER_SETTINGS_KEY, "")
val result: JsonString
val setting = wallpaper.asSettings()
if (!curr.isNullOrEmpty()) {
val map = curr.deserializeWallpaperSettings().toMutableMap()
if (setting != null) {
map[space] = setting
} else {
map.remove(space)
}
is Wallpaper.Color -> {
prefs
.edit()
.putInt(WALLPAPER_TYPE_KEY, WALLPAPER_TYPE_COLOR)
.putString(WALLPAPER_VALUE_KEY, wallpaper.code)
.apply()
}
is Wallpaper.Gradient -> {
prefs
.edit()
.putInt(WALLPAPER_TYPE_KEY, WALLPAPER_TYPE_GRADIENT)
.putString(WALLPAPER_VALUE_KEY, wallpaper.code)
.apply()
}
is Wallpaper.Image -> {
prefs
.edit()
.putInt(WALLPAPER_TYPE_KEY, WALLPAPER_TYPE_IMAGE)
.putString(WALLPAPER_VALUE_KEY, wallpaper.hash)
.apply()
result = map.serializeWallpaperSettings()
} else {
result = if (setting != null) {
mapOf(space to setting).serializeWallpaperSettings()
} else {
""
}
}
prefs
.edit()
.putString(WALLPAPER_SETTINGS_KEY, result)
.apply()
}
override suspend fun getWallpaper(): Wallpaper {
val type = prefs.getInt(WALLPAPER_TYPE_KEY, -1)
if (type != -1) {
val value = prefs.getString(WALLPAPER_VALUE_KEY, null)
if (!value.isNullOrEmpty()) {
return when (type) {
WALLPAPER_TYPE_COLOR -> {
Wallpaper.Color(value)
}
WALLPAPER_TYPE_GRADIENT -> {
Wallpaper.Gradient(value)
}
WALLPAPER_TYPE_IMAGE -> {
Wallpaper.Image(value)
}
else -> {
Wallpaper.Default
}
}
} else {
return Wallpaper.Default
}
override suspend fun getWallpaper(space: Id): Wallpaper {
val rawSettings = prefs.getString(WALLPAPER_SETTINGS_KEY, "")
return if (rawSettings.isNullOrEmpty()) {
Wallpaper.Default
} else {
return Wallpaper.Default
val deserialized = rawSettings.deserializeWallpaperSettings()
val setting = deserialized[space]
setting?.asWallpaper() ?: Wallpaper.Default
}
}
@ -153,19 +173,18 @@ class DefaultUserSettingsCache(private val prefs: SharedPreferences) : UserSetti
.remove(DEFAULT_OBJECT_TYPE_ID_KEY)
.remove(DEFAULT_OBJECT_TYPE_NAME_KEY)
.remove(COLLAPSED_WIDGETS_KEY)
.remove(ACTIVE_WIDGETS_VIEWS_KEY)
.remove(CURRENT_SPACE_KEY)
.apply()
}
companion object {
const val CURRENT_SPACE_KEY = "prefs.user_settings.current_space"
const val DEFAULT_OBJECT_TYPE_ID_KEY = "prefs.user_settings.default_object_type.id"
const val DEFAULT_OBJECT_TYPE_NAME_KEY = "prefs.user_settings.default_object_type.name"
const val WALLPAPER_TYPE_COLOR = 1
const val WALLPAPER_TYPE_GRADIENT = 2
const val WALLPAPER_TYPE_IMAGE = 3
const val WALLPAPER_TYPE_KEY = "prefs.user_settings.wallpaper_type"
const val WALLPAPER_VALUE_KEY = "prefs.user_settings.wallpaper_value"
const val DEFAULT_OBJECT_TYPES_KEY = "prefs.user_settings.default_object_types"
const val WALLPAPER_SETTINGS_KEY = "prefs.user_settings.wallpaper_settings"
const val THEME_KEY = "prefs.user_settings.theme_mode"
const val THEME_TYPE_SYSTEM = 1

View file

@ -0,0 +1,38 @@
package com.anytypeio.anytype.persistence
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.persistence.common.serializeWallpaperSettings
import com.anytypeio.anytype.persistence.model.WallpaperSetting
import com.anytypeio.anytype.test_utils.MockDataFactory
import kotlin.test.assertEquals
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import org.junit.Test
class SerializationTest {
@Test
fun `should serialize-deserialize map with wallpaper settings`() {
val id = MockDataFactory.randomUuid()
val type = MockDataFactory.randomInt()
val value = MockDataFactory.randomString()
val givenMap = mapOf(
id to WallpaperSetting(
type = type,
value = value
)
)
val encoded = givenMap.serializeWallpaperSettings()
val decoded = Json.decodeFromString<Map<Id, WallpaperSetting>>(encoded)
assertEquals(
expected = givenMap,
actual = decoded
)
}
}

View file

@ -0,0 +1,264 @@
package com.anytypeio.anytype.persistence
import android.content.Context
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.anytypeio.anytype.core_models.Wallpaper
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_models.primitives.TypeId
import com.anytypeio.anytype.persistence.repo.DefaultUserSettingsCache
import com.anytypeio.anytype.test_utils.MockDataFactory
import kotlin.test.assertEquals
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE, sdk = [33])
class UserSettingsCacheTest {
@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
private val defaultPrefs = RuntimeEnvironment.getApplication().getSharedPreferences(
"Default prefs",
Context.MODE_PRIVATE
)
@Test
fun `should save and return default wallpaper`() = runTest {
val cache = DefaultUserSettingsCache(
prefs = defaultPrefs
)
val space = MockDataFactory.randomUuid()
cache.setWallpaper(
space = space,
wallpaper = Wallpaper.Default
)
val expected = Wallpaper.Default
val actual = cache.getWallpaper(space)
assertEquals(
expected = expected,
actual = actual
)
}
@Test
fun `should save and return gradient wallpaper`() = runTest {
val cache = DefaultUserSettingsCache(
prefs = defaultPrefs
)
val space = MockDataFactory.randomUuid()
val givenWallpaper = Wallpaper.Gradient(
code = MockDataFactory.randomString()
)
cache.setWallpaper(
space = space,
wallpaper = givenWallpaper
)
val actual = cache.getWallpaper(space)
assertEquals(
expected = givenWallpaper,
actual = actual
)
}
@Test
fun `should not save wallpaper if space id is empty, should return default`() = runTest {
val cache = DefaultUserSettingsCache(
prefs = defaultPrefs
)
val space = ""
val givenWallpaper = Wallpaper.Gradient(
code = MockDataFactory.randomString()
)
cache.setWallpaper(
space = space,
wallpaper = givenWallpaper
)
val expected = Wallpaper.Default
val actual = cache.getWallpaper(space)
assertEquals(
expected = expected,
actual = actual
)
}
@Test
fun `should save and return solid-color wallpaper`() = runTest {
val cache = DefaultUserSettingsCache(
prefs = defaultPrefs
)
val space = MockDataFactory.randomUuid()
val givenWallpaper = Wallpaper.Color(
code = MockDataFactory.randomString()
)
cache.setWallpaper(
space = space,
wallpaper = givenWallpaper
)
val actual = cache.getWallpaper(space)
assertEquals(
expected = givenWallpaper,
actual = actual
)
}
@Test
fun `should save and return image wallpaper`() = runTest {
val cache = DefaultUserSettingsCache(
prefs = defaultPrefs
)
val space = MockDataFactory.randomUuid()
val givenWallpaper = Wallpaper.Image(
hash = MockDataFactory.randomString()
)
cache.setWallpaper(
space = space,
wallpaper = givenWallpaper
)
val actual = cache.getWallpaper(space)
assertEquals(
expected = givenWallpaper,
actual = actual
)
}
@Test
fun `should return default wallpaper`() = runTest {
val cache = DefaultUserSettingsCache(
prefs = defaultPrefs
)
val space = MockDataFactory.randomUuid()
val expected = Wallpaper.Default
val actual = cache.getWallpaper(space)
assertEquals(
expected = expected,
actual = actual
)
}
@Test
fun `should save new default object type for given space`() = runTest {
val cache = DefaultUserSettingsCache(
prefs = defaultPrefs
)
val space = SpaceId(MockDataFactory.randomUuid())
val type = TypeId(MockDataFactory.randomUuid())
// Settings are empty before we save anything
assertEquals(
expected = null,
actual = cache.getDefaultObjectType(space)
)
// Saving object type for given space
cache.setDefaultObjectType(
space = space,
type = type
)
// Making sure default object type is saved
assertEquals(
expected = type,
actual = cache.getDefaultObjectType(space)
)
}
@Test
fun `should save default object type for two given spaces`() = runTest {
val cache = DefaultUserSettingsCache(
prefs = defaultPrefs
)
val space1 = SpaceId(MockDataFactory.randomUuid())
val type1 = TypeId(MockDataFactory.randomUuid())
val space2 = SpaceId(MockDataFactory.randomUuid())
val type2 = TypeId(MockDataFactory.randomUuid())
// Settings are empty before we save anything
assertEquals(
expected = null,
actual = cache.getDefaultObjectType(space1)
)
// Saving the first object type for the first space
cache.setDefaultObjectType(
space = space1,
type = type1
)
// Making sure the first object type is saved
assertEquals(
expected = type1,
actual = cache.getDefaultObjectType(space1)
)
// Making sure the second object type is not saved yet
assertEquals(
expected = null,
actual = cache.getDefaultObjectType(space2)
)
// Saving the second object type for the second space
cache.setDefaultObjectType(
space = space2,
type = type2
)
// Making sure the second object type is saved
assertEquals(
expected = type2,
actual = cache.getDefaultObjectType(space2)
)
}
}