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

DROID-1790 App | Enhancement | Add default object types in app shortcuts (#647)

This commit is contained in:
Evgenii Kozlov 2023-12-06 15:32:42 +01:00 committed by GitHub
parent 4bf2314a41
commit 445425601d
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 196 additions and 60 deletions

View file

@ -20,27 +20,11 @@ class DefaultAppActionManager(val context: Context) : AppActionManager {
try {
when (action) {
is AppActionManager.Action.CreateNew -> {
val name = action.name.ifEmpty {
context.resources.getString(R.string.unknown_type)
}
val label = context.resources.getString(R.string.shortcut_create_new, name)
val shortcut = ShortcutInfoCompat.Builder(context, ACTION_CREATE_NEW_ID)
.setShortLabel(label)
.setLongLabel(label)
.setIcon(IconCompat.createWithResource(context, R.mipmap.ic_launcher))
.setIntent(
Intent(Intent.ACTION_VIEW, null).apply {
setClass(context, MainActivity::class.java)
putExtra(ACTION_CREATE_NEW_TYPE_KEY, action.type.key)
}
)
.build()
ShortcutManagerCompat.pushDynamicShortcut(context, shortcut)
val id = "$ACTION_CREATE_NEW_ID-${action.type}"
setupCreateNewObjectAction(action, id)
}
is AppActionManager.Action.ClearAll -> {
val shortcuts = ShortcutManagerCompat.getDynamicShortcuts(context).map { it.id }
ShortcutManagerCompat.removeLongLivedShortcuts(context, shortcuts)
ShortcutManagerCompat.removeAllDynamicShortcuts(context)
clearShortcuts()
}
}
} catch (e: Exception) {
@ -48,6 +32,42 @@ class DefaultAppActionManager(val context: Context) : AppActionManager {
}
}
override fun setup(actions: List<AppActionManager.Action.CreateNew>) {
clearShortcuts()
actions.forEach { action ->
val id = "$ACTION_CREATE_NEW_ID-${action.type}"
setupCreateNewObjectAction(action, id)
}
}
private fun clearShortcuts() {
val shortcuts = ShortcutManagerCompat.getDynamicShortcuts(context).map { it.id }
ShortcutManagerCompat.removeLongLivedShortcuts(context, shortcuts)
ShortcutManagerCompat.removeAllDynamicShortcuts(context)
}
private fun setupCreateNewObjectAction(
action: AppActionManager.Action.CreateNew,
id: String
) {
val name = action.name.ifEmpty {
context.resources.getString(R.string.unknown_type)
}
val label = context.resources.getString(R.string.shortcut_create_new_object_of_type, name)
val shortcut = ShortcutInfoCompat.Builder(context, id)
.setShortLabel(label)
.setLongLabel(label)
.setIcon(IconCompat.createWithResource(context, R.mipmap.ic_launcher))
.setIntent(
Intent(Intent.ACTION_VIEW, null).apply {
setClass(context, MainActivity::class.java)
putExtra(ACTION_CREATE_NEW_TYPE_KEY, action.type.key)
}
)
.build()
ShortcutManagerCompat.pushDynamicShortcut(context, shortcut)
}
companion object {
const val ACTION_CREATE_NEW_ID = "anytype.app-action.create-new.id"
const val ACTION_CREATE_NEW_TYPE_KEY = "anytype.app-action.create-new.key"

View file

@ -10,6 +10,7 @@ import com.anytypeio.anytype.domain.device.ClearFileCache
import com.anytypeio.anytype.domain.launch.GetDefaultObjectType
import com.anytypeio.anytype.domain.launch.SetDefaultObjectType
import com.anytypeio.anytype.domain.misc.AppActionManager
import com.anytypeio.anytype.domain.search.SearchObjects
import com.anytypeio.anytype.domain.workspace.SpaceManager
import com.anytypeio.anytype.presentation.settings.PersonalizationSettingsViewModel
import com.anytypeio.anytype.ui.settings.PersonalizationSettingsFragment
@ -75,13 +76,15 @@ object PersonalizationSettingsModule {
clearFileCache: ClearFileCache,
appActionManager: AppActionManager,
analytics: Analytics,
spaceManager: SpaceManager
spaceManager: SpaceManager,
searchObjects: SearchObjects
): PersonalizationSettingsViewModel.Factory = PersonalizationSettingsViewModel.Factory(
getDefaultObjectType = getDefaultObjectType,
setDefaultObjectType = setDefaultObjectType,
clearFileCache = clearFileCache,
appActionManager = appActionManager,
analytics = analytics,
spaceManager = spaceManager
spaceManager = spaceManager,
searchObjects = searchObjects
)
}

View file

@ -6,6 +6,8 @@ interface AppActionManager {
fun setup(action: Action)
fun setup(actions: List<Action.CreateNew>)
sealed class Action {
data class CreateNew(val type: TypeKey, val name: String) : Action()
object ClearAll: Action()

View file

@ -6,8 +6,9 @@ import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.domain.base.BaseUseCase
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import javax.inject.Inject
class SearchObjects(
class SearchObjects @Inject constructor(
private val repo: BlockRepository
) : BaseUseCase<List<ObjectWrapper.Basic>, SearchObjects.Params>() {

View file

@ -716,7 +716,6 @@
<string name="go_back">Zurückgehen</string>
<string name="snack_object_set_not_found">Set not found for this type.</string>
<string name="create_new_set">Erstellen</string>
<string name="shortcut_create_new">Create a new %1$s</string>
<string name="close">Schließen</string>
<string name="go_to_settings">Go to settings</string>
<string name="do_not_forget_mnemonic_phrase">Don\'t forget to take and save your recovery phrase</string>

View file

@ -716,7 +716,7 @@
<string name="go_back">Volver</string>
<string name="snack_object_set_not_found">Set no encontrado para este tipo.</string>
<string name="create_new_set">Crear</string>
<string name="shortcut_create_new">Crear un nuevo %1$s</string>
<string name="shortcut_create_new_object_of_type">Un nuevo %1$s</string>
<string name="close">Cerrar</string>
<string name="go_to_settings">Ir a Ajustes</string>
<string name="do_not_forget_mnemonic_phrase">No olvide guardar su frase de recuperación</string>

View file

@ -716,7 +716,6 @@
<string name="go_back">Retour</string>
<string name="snack_object_set_not_found">Set not found for this type.</string>
<string name="create_new_set">Créer</string>
<string name="shortcut_create_new">Create a new %1$s</string>
<string name="close">Fermer</string>
<string name="go_to_settings">Go to settings</string>
<string name="do_not_forget_mnemonic_phrase">N\'oubliez pas d\'enregistrer votre phrase de récupération</string>

View file

@ -715,7 +715,6 @@
<string name="go_back">Kembali</string>
<string name="snack_object_set_not_found">Set not found for this type.</string>
<string name="create_new_set">Buat</string>
<string name="shortcut_create_new">Create a new %1$s</string>
<string name="close">Tutup</string>
<string name="go_to_settings">Go to settings</string>
<string name="do_not_forget_mnemonic_phrase">Jangan lupa menyimpan frasa pemulihanmu</string>

View file

@ -716,7 +716,6 @@
<string name="go_back">Indietro</string>
<string name="snack_object_set_not_found">Set not found for this type.</string>
<string name="create_new_set">Crea</string>
<string name="shortcut_create_new">Create a new %1$s</string>
<string name="close">Chiudi</string>
<string name="go_to_settings">Go to settings</string>
<string name="do_not_forget_mnemonic_phrase">Non dimenticare di prendere e salvare la tua frase di recupero</string>

View file

@ -716,7 +716,6 @@
<string name="go_back">Gå tilbake</string>
<string name="snack_object_set_not_found">Set not found for this type.</string>
<string name="create_new_set">Opprett</string>
<string name="shortcut_create_new">Create a new %1$s</string>
<string name="close">Lukk</string>
<string name="go_to_settings">Go to settings</string>
<string name="do_not_forget_mnemonic_phrase">Don\'t forget to take and save your recovery phrase</string>

View file

@ -716,7 +716,6 @@
<string name="go_back">Voltar</string>
<string name="snack_object_set_not_found">Set not found for this type.</string>
<string name="create_new_set">Criar</string>
<string name="shortcut_create_new">Create a new %1$s</string>
<string name="close">Fechar</string>
<string name="go_to_settings">Go to settings</string>
<string name="do_not_forget_mnemonic_phrase">Don\'t forget to take and save your recovery phrase</string>

View file

@ -718,7 +718,6 @@
<string name="go_back">Вернуться</string>
<string name="snack_object_set_not_found">Set not found for this type.</string>
<string name="create_new_set">Создать</string>
<string name="shortcut_create_new">Create a new %1$s</string>
<string name="close">Закрыть</string>
<string name="go_to_settings">Go to settings</string>
<string name="do_not_forget_mnemonic_phrase">Don\'t forget to take and save your recovery phrase</string>

View file

@ -718,7 +718,6 @@
<string name="go_back">Повернутись</string>
<string name="snack_object_set_not_found">Set not found for this type.</string>
<string name="create_new_set">Створити</string>
<string name="shortcut_create_new">Create a new %1$s</string>
<string name="close">Close</string>
<string name="go_to_settings">Go to settings</string>
<string name="do_not_forget_mnemonic_phrase">Don\'t forget to take and save your recovery phrase</string>

View file

@ -715,7 +715,6 @@
<string name="go_back">回到</string>
<string name="snack_object_set_not_found">未找到此类型的设置。</string>
<string name="create_new_set">创建</string>
<string name="shortcut_create_new">创建新%1$s</string>
<string name="close">关闭</string>
<string name="go_to_settings">前往设置</string>
<string name="do_not_forget_mnemonic_phrase">不要忘记保存您的恢复短语</string>

View file

@ -782,7 +782,7 @@
<string name="snack_object_set_not_found">Set not found for this type.</string>
<string name="create_new_set">Create</string>
<string name="shortcut_create_new">Create a new %1$s</string>
<string name="shortcut_create_new_object_of_type">New %1$s</string>
<string name="close">Close</string>
<string name="go_to_settings">Go to settings</string>
<string name="do_not_forget_mnemonic_phrase">Don\'t forget to take and save your recovery phrase</string>

View file

@ -7,6 +7,8 @@ import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.analytics.base.EventsDictionary
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.Config
import com.anytypeio.anytype.core_models.DVFilter
import com.anytypeio.anytype.core_models.DVFilterCondition
import com.anytypeio.anytype.core_models.Event
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.InternalFlags
@ -23,7 +25,6 @@ import com.anytypeio.anytype.core_models.WidgetSession
import com.anytypeio.anytype.core_models.ext.process
import com.anytypeio.anytype.core_models.primitives.TypeKey
import com.anytypeio.anytype.core_utils.ext.cancel
import com.anytypeio.anytype.core_utils.ext.letNotNull
import com.anytypeio.anytype.core_utils.ext.replace
import com.anytypeio.anytype.core_utils.ext.withLatestFrom
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
@ -46,6 +47,7 @@ import com.anytypeio.anytype.domain.objects.ObjectWatcher
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
import com.anytypeio.anytype.domain.page.CloseBlock
import com.anytypeio.anytype.domain.page.CreateObject
import com.anytypeio.anytype.domain.search.SearchObjects
import com.anytypeio.anytype.domain.spaces.GetSpaceView
import com.anytypeio.anytype.domain.widgets.CreateWidget
import com.anytypeio.anytype.domain.widgets.DeleteWidget
@ -100,6 +102,7 @@ import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
@ -152,6 +155,7 @@ class HomeScreenViewModel(
private val setWidgetActiveView: SetWidgetActiveView,
private val setObjectDetails: SetObjectDetails,
private val getSpaceView: GetSpaceView,
private val searchObjects: SearchObjects
) : NavigationViewModel<HomeScreenViewModel.Navigation>(),
Reducer<ObjectView, Payload>,
WidgetActiveViewStateHolder by widgetActiveViewStateHolder,
@ -1116,23 +1120,69 @@ class HomeScreenViewModel(
}
private fun proceedWithSettingUpShortcuts() {
viewModelScope.launch {
getDefaultObjectType.async(Unit).fold(
onSuccess = {
Pair(it.name, it.type).letNotNull { name, type ->
appActionManager.setup(
AppActionManager.Action.CreateNew(
type = type,
name = name
)
)
spaceManager
.observe()
.onEach { config ->
val defaultObjectType = getDefaultObjectType.async(Unit).getOrNull()
val keys = buildSet {
if (defaultObjectType != null) {
add(defaultObjectType.type.key)
}
},
onFailure = {
Timber.d("Error while setting up app shortcuts")
add(ObjectTypeUniqueKeys.NOTE)
add(ObjectTypeUniqueKeys.PAGE)
add(ObjectTypeUniqueKeys.TASK)
}
)
}
searchObjects(
SearchObjects.Params(
keys = buildList {
add(Relations.ID)
add(Relations.UNIQUE_KEY)
add(Relations.NAME)
},
filters = buildList {
add(
DVFilter(
relation = Relations.SPACE_ID,
value = config.space,
condition = DVFilterCondition.EQUAL
)
)
add(
DVFilter(
relation = Relations.LAYOUT,
value = ObjectType.Layout.OBJECT_TYPE.code.toDouble(),
condition = DVFilterCondition.EQUAL
)
)
add(
DVFilter(
relation = Relations.UNIQUE_KEY,
value = keys.toList(),
condition = DVFilterCondition.IN
)
)
}
)
).process(
success = { wrappers ->
val types = wrappers
.map { ObjectWrapper.Type(it.map) }
.sortedBy { keys.indexOf(it.uniqueKey) }
val actions = types.map { type ->
AppActionManager.Action.CreateNew(
type = TypeKey(type.uniqueKey),
name = type.name.orEmpty()
)
}
appActionManager.setup(actions = actions)
},
failure = {
Timber.e(it, "Error while searching for types")
}
)
}
.launchIn(viewModelScope)
}
private fun dispatchDeleteWidgetAnalyticsEvent(target: Widget?) {
@ -1304,7 +1354,8 @@ class HomeScreenViewModel(
private val spaceManager: SpaceManager,
private val spaceWidgetContainer: SpaceWidgetContainer,
private val setObjectDetails: SetObjectDetails,
private val getSpaceView: GetSpaceView
private val getSpaceView: GetSpaceView,
private val searchObjects: SearchObjects
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T = HomeScreenViewModel(
@ -1339,7 +1390,8 @@ class HomeScreenViewModel(
spaceManager = spaceManager,
spaceWidgetContainer = spaceWidgetContainer,
setObjectDetails = setObjectDetails,
getSpaceView = getSpaceView
getSpaceView = getSpaceView,
searchObjects = searchObjects
) as T
}

View file

@ -8,8 +8,14 @@ import com.anytypeio.anytype.analytics.base.EventsDictionary.defaultTypeChanged
import com.anytypeio.anytype.analytics.base.EventsPropertiesKey
import com.anytypeio.anytype.analytics.event.EventAnalytics
import com.anytypeio.anytype.analytics.props.Props
import com.anytypeio.anytype.core_models.DVFilter
import com.anytypeio.anytype.core_models.DVFilterCondition
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Key
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.ObjectTypeUniqueKeys
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_models.primitives.TypeId
import com.anytypeio.anytype.core_models.primitives.TypeKey
@ -20,7 +26,9 @@ import com.anytypeio.anytype.domain.device.ClearFileCache
import com.anytypeio.anytype.domain.launch.GetDefaultObjectType
import com.anytypeio.anytype.domain.launch.SetDefaultObjectType
import com.anytypeio.anytype.domain.misc.AppActionManager
import com.anytypeio.anytype.domain.search.SearchObjects
import com.anytypeio.anytype.domain.workspace.SpaceManager
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
@ -32,7 +40,8 @@ class PersonalizationSettingsViewModel(
private val clearFileCache: ClearFileCache,
private val appActionManager: AppActionManager,
private val analytics: Analytics,
private val spaceManager: SpaceManager
private val spaceManager: SpaceManager,
private val searchObjects: SearchObjects
) : ViewModel() {
val commands = MutableSharedFlow<Command>(replay = 0)
@ -93,12 +102,63 @@ class PersonalizationSettingsViewModel(
fun proceedWithUpdateType(type: Id, key: Key, name: String?) {
viewModelScope.launch {
appActionManager.setup(
AppActionManager.Action.CreateNew(
type = TypeKey(key),
name = name.orEmpty()
val keys = buildSet {
add(key)
add(ObjectTypeUniqueKeys.NOTE)
add(ObjectTypeUniqueKeys.PAGE)
add(ObjectTypeUniqueKeys.TASK)
}
searchObjects(
SearchObjects.Params(
keys = buildList {
add(Relations.ID)
add(Relations.UNIQUE_KEY)
add(Relations.NAME)
},
filters = buildList {
add(
DVFilter(
relation = Relations.SPACE_ID,
value = spaceManager.get(),
condition = DVFilterCondition.EQUAL
)
)
add(
DVFilter(
relation = Relations.LAYOUT,
value = ObjectType.Layout.OBJECT_TYPE.code.toDouble(),
condition = DVFilterCondition.EQUAL
)
)
add(
DVFilter(
relation = Relations.UNIQUE_KEY,
value = keys.toList(),
condition = DVFilterCondition.IN
)
)
}
)
).process(
success = { wrappers ->
val types = wrappers
.map { ObjectWrapper.Type(it.map) }
.sortedBy { keys.indexOf(it.uniqueKey) }
val actions = types.map { type ->
AppActionManager.Action.CreateNew(
type = TypeKey(type.uniqueKey),
name = type.name.orEmpty()
)
}
appActionManager.setup(actions = actions)
},
failure = {
Timber.e(it, "Error while searching for types")
}
)
}
viewModelScope.launch {
val params = SetDefaultObjectType.Params(
space = SpaceId(spaceManager.get()),
type = TypeId(type)
@ -137,13 +197,14 @@ class PersonalizationSettingsViewModel(
object Exit : Command()
}
class Factory(
class Factory @Inject constructor(
private val getDefaultObjectType: GetDefaultObjectType,
private val setDefaultObjectType: SetDefaultObjectType,
private val clearFileCache: ClearFileCache,
private val appActionManager: AppActionManager,
private val analytics: Analytics,
private val spaceManager: SpaceManager
private val spaceManager: SpaceManager,
private val searchObjects: SearchObjects
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
@ -154,7 +215,8 @@ class PersonalizationSettingsViewModel(
clearFileCache = clearFileCache,
appActionManager = appActionManager,
analytics = analytics,
spaceManager = spaceManager
spaceManager = spaceManager,
searchObjects = searchObjects
) as T
}
}

View file

@ -47,6 +47,7 @@ import com.anytypeio.anytype.domain.objects.ObjectWatcher
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
import com.anytypeio.anytype.domain.page.CloseBlock
import com.anytypeio.anytype.domain.page.CreateObject
import com.anytypeio.anytype.domain.search.SearchObjects
import com.anytypeio.anytype.domain.spaces.GetSpaceView
import com.anytypeio.anytype.domain.widgets.CreateWidget
import com.anytypeio.anytype.domain.widgets.DeleteWidget
@ -189,6 +190,9 @@ class HomeScreenViewModelTest {
@Mock
lateinit var spaceManager: SpaceManager
@Mock
lateinit var searchObjects: SearchObjects
private val objectPayloadDispatcher = Dispatcher.Default<Payload>()
private val widgetEventDispatcher = Dispatcher.Default<WidgetDispatchEvent>()
@ -2806,7 +2810,8 @@ class HomeScreenViewModelTest {
spaceWidgetContainer = spaceWidgetContainer,
spaceManager = spaceManager,
setObjectDetails = setObjectDetails,
getSpaceView = getSpaceView
getSpaceView = getSpaceView,
searchObjects = searchObjects
)
companion object {