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:
parent
490e97589c
commit
5886fc1ae5
319 changed files with 8198 additions and 4258 deletions
|
@ -2,6 +2,7 @@ plugins {
|
|||
id "com.android.library"
|
||||
id "kotlin-android"
|
||||
id "kotlin-kapt"
|
||||
id "kotlinx-serialization"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue