Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,30 @@ import AnytypeCore

struct SpaceHubDropDelegate: DropDelegate {

let destinationItem: ParticipantSpaceViewDataWithPreview
let destinationSpaceViewId: String?
@Binding var allSpaces: [ParticipantSpaceViewDataWithPreview]?
@Binding var draggedItem: ParticipantSpaceViewDataWithPreview?
@Binding var draggedSpaceViewId: String?
@Binding var initialIndex: Int?

func dropUpdated(info: DropInfo) -> DropProposal? {
return DropProposal(operation: .move)
}

func performDrop(info: DropInfo) -> Bool {
guard let allSpaces, draggedItem.isNotNil, let initialIndex else { return false }
guard let allSpaces, draggedSpaceViewId.isNotNil, let initialIndex else { return false }

guard let finalIndex = allSpaces.firstIndex(of: destinationItem) else { return false }
guard let finalIndex = allSpaces.firstIndex(where: { $0.spaceView.id == destinationSpaceViewId }) else { return false }
guard finalIndex != initialIndex else { return false }

self.draggedItem = nil
self.draggedSpaceViewId = nil
self.initialIndex = nil
return true
}

func dropEntered(info: DropInfo) {
guard var allSpaces, let draggedItem else { return }
guard let fromIndex = allSpaces.firstIndex(where: { $0.space.id == draggedItem.space.id } ) else { return }
guard let toIndex = allSpaces.firstIndex(where: { $0.space.id == destinationItem.space.id } ) else { return }
guard var allSpaces, let draggedSpaceViewId else { return }
guard let fromIndex = allSpaces.firstIndex(where: { $0.space.spaceView.id == draggedSpaceViewId } ) else { return }
guard let toIndex = allSpaces.firstIndex(where: { $0.space.spaceView.id == destinationSpaceViewId } ) else { return }

guard fromIndex != toIndex else { return }

Expand All @@ -52,7 +52,7 @@ struct SpaceHubDropDelegate: DropDelegate {
let spaceOrderService = Container.shared.spaceOrderService()

try await spaceOrderService.setOrder(
spaceViewIdMoved: draggedItem.spaceView.id, newOrder: newOrder
spaceViewIdMoved: draggedSpaceViewId, newOrder: newOrder
)
AnytypeAnalytics.instance().logReorderSpace()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ import DesignKit

struct SpaceHubView: View {
@State private var model: SpaceHubViewModel
@State private var draggedSpace: ParticipantSpaceViewDataWithPreview?
@State private var draggedInitialIndex: Int?
@State private var vaultBackToRootsToggle = FeatureFlags.vaultBackToRoots

private var namespace: Namespace.ID

Expand All @@ -34,53 +31,30 @@ struct SpaceHubView: View {
@ViewBuilder
private var content: some View {
Group {
if let spaces = model.spaces {
spacesView(spaces)
if model.dataLoaded {
spacesView()
} else {
EmptyView() // Do not show empty state view if we do not receive data yet
}

Spacer()
}
.ignoresSafeArea(edges: .bottom)
.animation(.default, value: model.spaces)
}

private func spacesView(_ spaces: [ParticipantSpaceViewDataWithPreview]) -> some View {
private func spacesView() -> some View {
NavigationStack {
Group {
if spaces.isEmpty {
emptyStateView
} else if model.filteredSpaces.isNotEmpty {
scrollView
} else {
SpaceHubSearchEmptySpaceView()
SpaceHubList(model: model)
.navigationTitle(Loc.myChannels)
.navigationBarTitleDisplayMode(.inline)
.toolbar { toolbarItems }
.searchable(text: $model.searchText)
.onChange(of: model.searchText) {
model.searchTextUpdated()
}
}
.navigationTitle(Loc.myChannels)
.navigationBarTitleDisplayMode(.inline)
.toolbar { toolbarItems }
.searchable(text: $model.searchText)
.onChange(of: model.searchText) {
model.searchTextUpdated()
}
}.tint(Color.Text.secondary)
}

private var scrollView: some View {
ScrollView {
VStack(spacing: vaultBackToRootsToggle ? 8 : 0) {
HomeUpdateSubmoduleView().padding(8)

ForEach(model.filteredSpaces) {
spaceCard($0)
}

Spacer.fixedHeight(40)
}
}
}

private var toolbarItems: some ToolbarContent {
SpaceHubToolbar(
showLoading: model.showLoading,
Expand All @@ -95,54 +69,6 @@ struct SpaceHubView: View {
}
)
}

private var emptyStateView: some View {
SpaceHubEmptyStateView {
model.onTapCreateSpace()
}
}

private func spaceCard(_ space: ParticipantSpaceViewDataWithPreview) -> some View {
SpaceCard(
spaceData: space,
wallpaper: model.wallpapers[space.spaceView.targetSpaceId] ?? .default,
draggedSpace: $draggedSpace,
onTap: {
model.onSpaceTap(spaceId: space.spaceView.targetSpaceId)
},
onTapCopy: {
model.copySpaceInfo(spaceView: space.spaceView)
},
onTapMute: {
model.muteSpace(spaceView: space.spaceView)
},
onTapPin: {
try await model.pin(spaceView: space.spaceView)
},
onTapUnpin: {
try await model.unpin(spaceView: space.spaceView)
},
onTapSettings: {
model.openSpaceSettings(spaceId: space.spaceView.targetSpaceId)
},
onTapDelete: {
model.onDeleteSpace(spaceId: space.spaceView.targetSpaceId)
}
)
.equatable()
.padding(.horizontal, vaultBackToRootsToggle ? 16 : 0)
.if(space.spaceView.isPinned) {
$0.onDrop(
of: [.text],
delegate: SpaceHubDropDelegate(
destinationItem: space,
allSpaces: $model.spaces,
draggedItem: $draggedSpace,
initialIndex: $draggedInitialIndex
)
)
}
}
}

#Preview {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import Loc
@MainActor
@Observable
final class SpaceHubViewModel {

var spaces: [ParticipantSpaceViewDataWithPreview]?
var dataLoaded = false
var searchText: String = ""

var filteredSpaces: [ParticipantSpaceViewDataWithPreview] = []
var filteredSpaces: [SpaceCardModel] = []

var wallpapers: [String: SpaceWallpaperType] = [:]

Expand All @@ -23,7 +24,6 @@ final class SpaceHubViewModel {

@ObservationIgnored
private weak var output: (any SpaceHubModuleOutput)?


@Injected(\.userDefaultsStorage) @ObservationIgnored
private var userDefaults: any UserDefaultsStorageProtocol
Expand All @@ -39,6 +39,8 @@ final class SpaceHubViewModel {
private var pushNotificationsSystemSettingsBroadcaster: any PushNotificationsSystemSettingsBroadcasterProtocol
@Injected(\.workspaceService) @ObservationIgnored
private var workspaceService: any WorkspaceServiceProtocol
@Injected(\.spaceCardModelBuilder)@ObservationIgnored
private var spaceCardModelBuilder: any SpaceCardModelBuilderProtocol

init(output: (any SpaceHubModuleOutput)?) {
self.output = output
Expand All @@ -62,31 +64,33 @@ final class SpaceHubViewModel {
}


func copySpaceInfo(spaceView: SpaceView) {
func copySpaceInfo(spaceViewId: String) {
guard let spaceView = spaces?.first(where: { $0.spaceView.id == spaceViewId })?.spaceView else { return }
UIPasteboard.general.string = String(describing: spaceView)
}

func muteSpace(spaceView: SpaceView) {
func muteSpace(spaceViewId: String) {
guard let spaceView = spaces?.first(where: { $0.spaceView.id == spaceViewId })?.spaceView else { return }
let isUnmutedAll = spaceView.pushNotificationMode.isUnmutedAll
spaceMuteData = SpaceMuteData(
spaceId: spaceView.targetSpaceId,
mode: isUnmutedAll ? .mentions : .all
)
}

func pin(spaceView: SpaceView) async throws {
func pin(spaceViewId: String) async throws {
guard let spaces else { return }
let pinnedSpaces = spaces.filter { $0.spaceView.isPinned }

var newOrder = pinnedSpaces.filter { $0.spaceView.id != spaceViewId }.map(\.spaceView.id)
newOrder.insert(spaceViewId, at: 0)

var newOrder = pinnedSpaces.filter { $0.spaceView.id != spaceView.id }.map(\.spaceView.id)
newOrder.insert(spaceView.id, at: 0)

try await spaceOrderService.setOrder(spaceViewIdMoved: spaceView.id, newOrder: newOrder)
try await spaceOrderService.setOrder(spaceViewIdMoved: spaceViewId, newOrder: newOrder)
AnytypeAnalytics.instance().logPinSpace()
}

func unpin(spaceView: SpaceView) async throws {
try await spaceOrderService.unsetOrder(spaceViewId: spaceView.id)
func unpin(spaceViewId: String) async throws {
try await spaceOrderService.unsetOrder(spaceViewId: spaceViewId)
AnytypeAnalytics.instance().logUnpinSpace()
}

Expand Down Expand Up @@ -120,15 +124,18 @@ final class SpaceHubViewModel {
}

func searchTextUpdated() {
updateFilteredSpaces()
Task {
await updateFilteredSpaces()
}
}

// MARK: - Private
private func subscribeOnSpaces() async {
for await spaces in await spaceHubSpacesStorage.spacesStream {
self.spaces = spaces.sorted(by: sortSpacesForPinnedFeature)
showLoading = spaces.contains { $0.spaceView.isLoading }
updateFilteredSpaces()
await updateFilteredSpaces()
self.dataLoaded = spaces.isNotEmpty
}
}

Expand Down Expand Up @@ -207,20 +214,21 @@ final class SpaceHubViewModel {
}
}

private func updateFilteredSpaces() {

private func updateFilteredSpaces() async {
guard let spaces else {
filteredSpaces = []
return
}

guard !searchText.isEmpty else {
filteredSpaces = spaces
return
let spacesToFilter: [ParticipantSpaceViewDataWithPreview]
if searchText.isEmpty {
spacesToFilter = spaces
} else {
spacesToFilter = spaces.filter { space in
space.spaceView.name.localizedCaseInsensitiveContains(searchText)
}
}

filteredSpaces = spaces.filter { space in
space.spaceView.name.localizedCaseInsensitiveContains(searchText)
}
self.filteredSpaces = await spaceCardModelBuilder.build(from: spacesToFilter, wallpapers: wallpapers)
}
}
Loading