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

DROID-3662 Chats | Enhancement | Introduce system widget "Chat" + navigation changes (#2420)

This commit is contained in:
Evgenii Kozlov 2025-05-21 13:17:07 +02:00 committed by GitHub
parent d7946bd54d
commit 5bddcd640c
Signed by: github
GPG key ID: B5690EEEBB952194
21 changed files with 175 additions and 82 deletions

View file

@ -1666,6 +1666,12 @@ fun CoroutineScope.sendChangeWidgetSourceEvent(
BundledWidgetSourceView.AllObjects -> {
put(WidgetAnalytics.TYPE, WidgetAnalytics.WIDGET_SOURCE_ALL_OBJECTS)
}
BundledWidgetSourceView.AllObjects -> {
put(WidgetAnalytics.TYPE, WidgetAnalytics.WIDGET_SOURCE_CHAT)
}
BundledWidgetSourceView.Chat -> {
put(WidgetAnalytics.TYPE, WidgetAnalytics.WIDGET_SOURCE_CHAT)
}
}
if (isForNewWidget)
put(WidgetAnalytics.ROUTE, WidgetAnalytics.ROUTE_ADD_WIDGET)
@ -1769,6 +1775,9 @@ fun CoroutineScope.sendDeleteWidgetEvent(
Widget.Source.Bundled.AllObjects -> {
put(WidgetAnalytics.TYPE, WidgetAnalytics.WIDGET_SOURCE_ALL_OBJECTS)
}
Widget.Source.Bundled.Chat -> {
put(WidgetAnalytics.TYPE, WidgetAnalytics.WIDGET_SOURCE_CHAT)
}
}
if (isInEditMode)
put(WidgetAnalytics.CONTEXT, WidgetAnalytics.CONTEXT_EDITOR)
@ -1814,6 +1823,9 @@ fun CoroutineScope.sendClickWidgetTitleEvent(
Widget.Source.Bundled.AllObjects -> {
put(WidgetAnalytics.TAB, WidgetAnalytics.WIDGET_SOURCE_ALL_OBJECTS)
}
Widget.Source.Bundled.Chat -> {
put(WidgetAnalytics.TAB, WidgetAnalytics.WIDGET_SOURCE_CHAT)
}
}
isAutoCreated?.let {
@ -1902,6 +1914,9 @@ fun CoroutineScope.sendReorderWidgetEvent(
Widget.Source.Bundled.AllObjects -> {
put(WidgetAnalytics.TYPE, WidgetAnalytics.WIDGET_SOURCE_ALL_OBJECTS)
}
Widget.Source.Bundled.Chat -> {
put(WidgetAnalytics.TYPE, WidgetAnalytics.WIDGET_SOURCE_CHAT)
}
}
isAutoCreated?.let {

View file

@ -136,6 +136,7 @@ import com.anytypeio.anytype.presentation.widgets.WidgetId
import com.anytypeio.anytype.presentation.widgets.WidgetSessionStateHolder
import com.anytypeio.anytype.presentation.widgets.WidgetView
import com.anytypeio.anytype.presentation.widgets.collection.Subscription
import com.anytypeio.anytype.presentation.widgets.forceChatPosition
import com.anytypeio.anytype.presentation.widgets.hasValidLayout
import com.anytypeio.anytype.presentation.widgets.parseActiveViews
import com.anytypeio.anytype.presentation.widgets.parseWidgets
@ -503,7 +504,8 @@ class HomeScreenViewModel(
viewModelScope.launch {
widgets.filterNotNull().map { widgets ->
val currentlyDisplayedViews = views.value
widgets.filter { widget -> widget.hasValidLayout() }.map { widget ->
widgets.forceChatPosition().filter { widget -> widget.hasValidLayout() }.map { widget ->
when (widget) {
is Widget.Link -> LinkWidgetContainer(
widget = widget,
@ -738,6 +740,7 @@ class HomeScreenViewModel(
if (
dispatch.source == BundledWidgetSourceView.AllObjects.id
|| dispatch.source == BundledWidgetSourceView.Bin.id
|| dispatch.source == BundledWidgetSourceView.Chat.id
) {
// Applying link layout automatically to all-objects widget
proceedWithCreatingWidget(
@ -1105,6 +1108,26 @@ class HomeScreenViewModel(
)
}
}
is Widget.Source.Bundled.Chat -> {
viewModelScope.launch {
if (mode.value == InteractionMode.Edit) {
return@launch
}
val space = spaceManager.get()
val view = spaceViewSubscriptionContainer.get(SpaceId(space))
val chat = view?.chatId
if (chat != null) {
navigation(
Navigation.OpenChat(
ctx = chat,
space = space
)
)
} else {
Timber.w("Failed to open chat from widget: chat not found")
}
}
}
}
}
@ -2487,41 +2510,46 @@ class HomeScreenViewModel(
viewModelScope.launch {
val source = view.source
if (source is Widget.Source.Default) {
if (source.obj.layout == ObjectType.Layout.OBJECT_TYPE) {
val wrapper = ObjectWrapper.Type(source.obj.map)
val space = SpaceId(spaceManager.get())
val startTime = System.currentTimeMillis()
createObject.async(
params = CreateObject.Param(
space = space,
type = TypeKey(wrapper.uniqueKey),
prefilled = mapOf(Relations.IS_FAVORITE to true)
)
).onSuccess { result ->
sendAnalyticsObjectCreateEvent(
objType = wrapper.uniqueKey,
analytics = analytics,
route = EventsDictionary.Routes.widget,
startTime = startTime,
view = null,
spaceParams = provideParams(space.id)
)
proceedWithNavigation(result.obj.navigation())
when (source.obj.layout) {
ObjectType.Layout.OBJECT_TYPE -> {
val wrapper = ObjectWrapper.Type(source.obj.map)
val space = SpaceId(spaceManager.get())
val startTime = System.currentTimeMillis()
createObject.async(
params = CreateObject.Param(
space = space,
type = TypeKey(wrapper.uniqueKey),
prefilled = mapOf(Relations.IS_FAVORITE to true)
)
).onSuccess { result ->
sendAnalyticsObjectCreateEvent(
objType = wrapper.uniqueKey,
analytics = analytics,
route = EventsDictionary.Routes.widget,
startTime = startTime,
view = null,
spaceParams = provideParams(space.id)
)
proceedWithNavigation(result.obj.navigation())
}
}
ObjectType.Layout.COLLECTION -> {
onCreateDataViewObject(
widget = view.id,
view = null,
navigate = true
)
}
ObjectType.Layout.SET -> {
onCreateDataViewObject(
widget = view.id,
view = null,
navigate = true
)
}
else -> {
Timber.w("Unexpected source layout: ${source.obj.layout}")
}
} else if (source.obj.layout == ObjectType.Layout.COLLECTION) {
onCreateDataViewObject(
widget = view.id,
view = null,
navigate = true
)
} else if (source.obj.layout == ObjectType.Layout.SET) {
onCreateDataViewObject(
widget = view.id,
view = null,
navigate = true
)
} else {
Timber.w("Unexpected source layout: ${source.obj.layout}")
}
}
}

View file

@ -17,7 +17,6 @@ interface AppNavigation {
)
fun openChat(target: Id, space: Id)
fun openDocument(target: Id, space: Id)
fun openDiscussion(target: Id, space: Id)
fun openModalTemplateSelect(
template: Id,
templateTypeId: Id,
@ -98,7 +97,7 @@ interface AppNavigation {
val space: Id
) : Command()
object OpenSettings : Command()
data object OpenSettings : Command()
data class OpenShareScreen(
val space: SpaceId
@ -132,7 +131,7 @@ interface AppNavigation {
data class LaunchObjectSet(val target: Id, val space: Id) : Command()
object OpenUpdateAppScreen : Command()
data object OpenUpdateAppScreen : Command()
data class DeletedAccountScreen(val deadline: Long) : Command()

View file

@ -18,6 +18,7 @@ import com.anytypeio.anytype.core_models.ObjectTypeIds.COLLECTION
import com.anytypeio.anytype.core_models.SupportedLayouts
import com.anytypeio.anytype.core_models.exceptions.AccountMigrationNeededException
import com.anytypeio.anytype.core_models.exceptions.NeedToUpdateApplicationException
import com.anytypeio.anytype.core_models.multiplayer.SpaceType
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_models.primitives.TypeKey
import com.anytypeio.anytype.core_models.restrictions.SpaceStatus
@ -257,7 +258,7 @@ class SplashViewModel(
Command.NavigateToObjectSet(
id = target,
space = spaceId,
chat = view.chatId
chat = if (view.spaceType == SpaceType.CHAT) view.chatId else null
)
)
} else {
@ -265,7 +266,7 @@ class SplashViewModel(
Command.NavigateToObject(
id = target,
space = spaceId,
chat = view.chatId
chat = if (view.spaceType == SpaceType.CHAT) view.chatId else null
)
)
}
@ -317,7 +318,7 @@ class SplashViewModel(
Command.NavigateToObjectSet(
id = id,
space = space,
chat = view.chatId
chat = if (view.spaceType == SpaceType.CHAT) view.chatId else null
)
)
ObjectType.Layout.DATE -> {
@ -325,7 +326,7 @@ class SplashViewModel(
Command.NavigateToDateObject(
id = id,
space = space,
chat = view.chatId
chat = if (view.spaceType == SpaceType.CHAT) view.chatId else null
)
)
}
@ -334,7 +335,7 @@ class SplashViewModel(
Command.NavigateToObjectType(
id = id,
space = space,
chat = view.chatId
chat = if (view.spaceType == SpaceType.CHAT) view.chatId else null
)
)
}
@ -343,7 +344,7 @@ class SplashViewModel(
Command.NavigateToObject(
id = id,
space = space,
chat = view.chatId
chat = if (view.spaceType == SpaceType.CHAT) view.chatId else null
)
)
}
@ -398,7 +399,7 @@ class SplashViewModel(
deeplink = deeplink
)
)
} else {
} else if (view.spaceType == SpaceType.CHAT) {
commands.emit(
Command.NavigateToSpaceLevelChat(
space = space.id,

View file

@ -12,6 +12,7 @@ import com.anytypeio.anytype.core_models.Event
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Wallpaper
import com.anytypeio.anytype.core_models.multiplayer.SpaceType
import com.anytypeio.anytype.core_models.primitives.Space
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.domain.base.fold
@ -147,7 +148,8 @@ class VaultViewModel(
onSuccess = {
proceedWithSavingCurrentSpace(
targetSpace = targetSpace,
chat = view.space.chatId?.ifEmpty { null }
chat = view.space.chatId?.ifEmpty { null },
spaceType = view.space.spaceType
)
}
)
@ -252,7 +254,8 @@ class VaultViewModel(
private suspend fun proceedWithSavingCurrentSpace(
targetSpace: String,
chat: Id?
chat: Id?,
spaceType: SpaceType?
) {
saveCurrentSpace.async(
SaveCurrentSpace.Params(SpaceId(targetSpace))
@ -261,7 +264,7 @@ class VaultViewModel(
Timber.e(it, "Error while saving current space on vault screen")
},
onSuccess = {
if (chat != null && ChatConfig.isChatAllowed(space = targetSpace)) {
if (spaceType == SpaceType.CHAT && chat != null && ChatConfig.isChatAllowed(space = targetSpace)) {
commands.emit(
Command.EnterSpaceLevelChat(
space = Space(targetSpace),

View file

@ -130,6 +130,9 @@ class SelectWidgetSourceViewModel(
if (contains(BundledWidgetSourceIds.ALL_OBJECTS)) {
add(BundledWidgetSourceView.AllObjects)
}
if (contains(BundledWidgetSourceIds.CHAT)) {
add(BundledWidgetSourceView.Chat)
}
if (contains(BundledWidgetSourceIds.RECENT)) {
add(BundledWidgetSourceView.Recent)
}
@ -270,6 +273,7 @@ class SelectWidgetSourceViewModel(
if (
view is BundledWidgetSourceView.AllObjects
|| view is BundledWidgetSourceView.Bin
|| view is BundledWidgetSourceView.Chat
) {
isDismissed.value = true
}

View file

@ -109,10 +109,24 @@ sealed class Widget {
override val id: Id = BundledWidgetSourceIds.ALL_OBJECTS
override val type: Id? = null
}
data object Chat : Bundled() {
override val id: Id = BundledWidgetSourceIds.CHAT
override val type: Id? = null
}
}
}
}
fun List<Widget>.forceChatPosition(): List<Widget> {
// Partition the list into chat widgets and the rest
val (chatWidgets, otherWidgets) = partition { widget ->
widget.source is Widget.Source.Bundled.Chat
}
// Place chat widgets first, followed by the others
return chatWidgets + otherWidgets
}
fun Widget.hasValidLayout(): Boolean = when (val widgetSource = source) {
is Widget.Source.Default -> isSupportedForWidgets(widgetSource.obj.layout)
is Widget.Source.Bundled -> true
@ -246,6 +260,7 @@ fun Id.bundled() : Widget.Source.Bundled = when (this) {
BundledWidgetSourceIds.FAVORITE -> Widget.Source.Bundled.Favorites
BundledWidgetSourceIds.BIN -> Widget.Source.Bundled.Bin
BundledWidgetSourceIds.ALL_OBJECTS -> Widget.Source.Bundled.AllObjects
BundledWidgetSourceIds.CHAT -> Widget.Source.Bundled.Chat
else -> throw IllegalStateException("Widget bundled id can't be $this")
}

View file

@ -78,6 +78,7 @@ sealed class WidgetView {
val canCreateObjectOfType : Boolean get() {
return when(source) {
Widget.Source.Bundled.AllObjects -> false
Widget.Source.Bundled.Chat -> false
Widget.Source.Bundled.Bin -> false
Widget.Source.Bundled.Favorites -> true
Widget.Source.Bundled.Recent -> false

View file

@ -29,6 +29,10 @@ sealed class BundledWidgetSourceView : DefaultSearchItem {
data object AllObjects : BundledWidgetSourceView() {
override val id: Id get() = BundledWidgetSourceIds.ALL_OBJECTS
}
data object Chat : BundledWidgetSourceView() {
override val id: Id get() = BundledWidgetSourceIds.CHAT
}
}
data class SuggestWidgetObjectType(