diff --git a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Common/WidgetSeeAllRow.swift b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Common/WidgetSeeAllRow.swift new file mode 100644 index 0000000000..784183490d --- /dev/null +++ b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Common/WidgetSeeAllRow.swift @@ -0,0 +1,17 @@ +import SwiftUI +import DesignKit + +struct WidgetSeeAllRow: View { + + let onTap: () -> Void + + var body: some View { + Button { + onTap() + } label: { + AnytypeText(Loc.seeAll, style: .caption1Medium) + .foregroundColor(Color.Text.secondary) + .frame(height: 40) + } + } +} diff --git a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Gallery/Common/GalleryWidgetView.swift b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Gallery/Common/GalleryWidgetView.swift index 3da1072303..448075ab5c 100644 --- a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Gallery/Common/GalleryWidgetView.swift +++ b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Gallery/Common/GalleryWidgetView.swift @@ -1,11 +1,15 @@ import Foundation import SwiftUI +import AnytypeCore struct GalleryWidgetView: View { let rows: [GalleryWidgetRowModel]? + let showAllObjects: Bool let onShowAllObjects: () -> Void + @State private var showAllButtonInWidgets = FeatureFlags.showAllButtonInWidgets + var body: some View { WidgetContainerWithEmptyState(showEmpty: (rows?.isEmpty ?? false)) { content @@ -20,7 +24,9 @@ struct GalleryWidgetView: View { ForEach(rows, id: \.objectId) { row in GalleryWidgetRow(model: row) } - GalleryWidgetShowAllView(onTap: onShowAllObjects) + if showAllButtonInWidgets, showAllObjects { + GalleryWidgetShowAllView(onTap: onShowAllObjects) + } } } .padding(.horizontal, 16) diff --git a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/List/Common/Content/ListWidgetContentView.swift b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/List/Common/Content/ListWidgetContentView.swift index 82fe6e186b..51f60701eb 100644 --- a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/List/Common/Content/ListWidgetContentView.swift +++ b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/List/Common/Content/ListWidgetContentView.swift @@ -1,10 +1,15 @@ import Foundation import SwiftUI +import AnytypeCore struct ListWidgetContentView: View { let style: ListWidgetStyle let rows: [ListWidgetRowModel]? + let showAllObjects: Bool + let allObjectsTap: () -> Void + + @State private var showAllButtonInWidgets = FeatureFlags.showAllButtonInWidgets var body: some View { WidgetContainerWithEmptyState(showEmpty: rows?.isEmpty ?? false) { @@ -20,6 +25,9 @@ struct ListWidgetContentView: View { ForEach(rows) { rowView(row: $0, showDivider: $0.id != rows.last?.id) } + if showAllButtonInWidgets, showAllObjects { + WidgetSeeAllRow(onTap: allObjectsTap) + } Spacer.fixedHeight(8) } } diff --git a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/List/Common/ListWidgetView.swift b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/List/Common/ListWidgetView.swift index c14db72c89..b7b86e922d 100644 --- a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/List/Common/ListWidgetView.swift +++ b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/List/Common/ListWidgetView.swift @@ -67,7 +67,7 @@ private struct ListWidgetInternalView: View { VStack(spacing: 0) { // TODO: Delete this header with galleryWidget toggle. Header implemented in View widget for set. ViewWidgetTabsView(items: model.headerItems) - ListWidgetContentView(style: model.style, rows: model.rows) + ListWidgetContentView(style: model.style, rows: model.rows, showAllObjects: false, allObjectsTap: {}) } } diff --git a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/ObjectType/Helpers/ObjectTypeRowsBuilder.swift b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/ObjectType/Helpers/ObjectTypeRowsBuilder.swift index d97c06924d..0cce636228 100644 --- a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/ObjectType/Helpers/ObjectTypeRowsBuilder.swift +++ b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/ObjectType/Helpers/ObjectTypeRowsBuilder.swift @@ -98,13 +98,13 @@ actor ObjectTypeRowsBuilder: ObjectTypeRowsBuilderProtocol { } } ) - await updateRows(rowDetails: rowDetails) + await updateRows(rowDetails: rowDetails, availableMoreObjects: state.total > state.items.count) } } catch {} } - private func updateRows(rowDetails: [SetContentViewItemConfiguration]) async { + private func updateRows(rowDetails: [SetContentViewItemConfiguration], availableMoreObjects: Bool) async { let rows: ObjectTypeWidgetRowType if isImageType { let galleryRows = rowDetails.map { details in @@ -116,15 +116,15 @@ actor ObjectTypeRowsBuilder: ObjectTypeRowsBuilderProtocol { onTap: details.onItemTap ) } - rows = .gallery(rows: galleryRows) + rows = .gallery(rows: galleryRows, availableMoreObjects: availableMoreObjects) } else { switch setDocument.activeView.type { case .table, .list, .kanban, .calendar, .graph: let listRows = rowDetails.map { ListWidgetRowModel(details: $0) } - rows = .compactList(rows: listRows) + rows = .compactList(rows: listRows, availableMoreObjects: availableMoreObjects) case .gallery: let galleryRows = rowDetails.map { GalleryWidgetRowModel(details: $0) } - rows = .gallery(rows: galleryRows) + rows = .gallery(rows: galleryRows, availableMoreObjects: availableMoreObjects) } } diff --git a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/ObjectType/ObjectTypeWidgetRowType.swift b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/ObjectType/ObjectTypeWidgetRowType.swift index ba0874b21b..30bb52d6e6 100644 --- a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/ObjectType/ObjectTypeWidgetRowType.swift +++ b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/ObjectType/ObjectTypeWidgetRowType.swift @@ -1,6 +1,6 @@ import Foundation enum ObjectTypeWidgetRowType { - case compactList(rows: [ListWidgetRowModel]) - case gallery(rows: [GalleryWidgetRowModel]) + case compactList(rows: [ListWidgetRowModel], availableMoreObjects: Bool) + case gallery(rows: [GalleryWidgetRowModel], availableMoreObjects: Bool) } diff --git a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/ObjectType/ObjectTypeWidgetView.swift b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/ObjectType/ObjectTypeWidgetView.swift index 83d13fcbd3..ed7faad6f3 100644 --- a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/ObjectType/ObjectTypeWidgetView.swift +++ b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/ObjectType/ObjectTypeWidgetView.swift @@ -61,12 +61,14 @@ private struct ObjectTypeWidgetInternalView: View { @ViewBuilder private var content: some View { switch model.rows { - case .compactList(let rows): - ListWidgetContentView(style: .compactList, rows: rows) - case .gallery(let rows): - GalleryWidgetView(rows: rows, onShowAllObjects: { + case .compactList(let rows, let availableMoreObjects): + ListWidgetContentView(style: .compactList, rows: rows, showAllObjects: availableMoreObjects) { model.onShowAllTap() - }) + } + case .gallery(let rows, let availableMoreObjects): + GalleryWidgetView(rows: rows, showAllObjects: availableMoreObjects) { + model.onShowAllTap() + } case .none: EmptyView() } diff --git a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/SetCommon/SetObjecWidgetSubmoduleView.swift b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/SetCommon/SetObjecWidgetSubmoduleView.swift index 5284870a97..7e6e52eae7 100644 --- a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/SetCommon/SetObjecWidgetSubmoduleView.swift +++ b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/SetCommon/SetObjecWidgetSubmoduleView.swift @@ -60,14 +60,19 @@ private struct SetObjectWidgetSubmoduleInternalView: View { private var rows: some View { switch model.rows { case .list(let rows, let id): - ListWidgetContentView(style: .list, rows: rows) - .id(id) + ListWidgetContentView(style: .list, rows: rows, showAllObjects: model.availableMoreObjects) { + model.onOpenObjectTap() + } + .id(id) case .compactList(let rows, let id): - ListWidgetContentView(style: .compactList, rows: rows) - .id(id) + ListWidgetContentView(style: .compactList, rows: rows, showAllObjects: model.availableMoreObjects) { + model.onOpenObjectTap() + } + .id(id) case .gallery(let rows, let id): GalleryWidgetView( rows: rows, + showAllObjects: model.availableMoreObjects, onShowAllObjects: { model.onOpenObjectTap() } diff --git a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/SpecificInternalModels/ObjectWidgetInternalViewModel.swift b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/SpecificInternalModels/ObjectWidgetInternalViewModel.swift index c01cffdccc..b70aeeedae 100644 --- a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/SpecificInternalModels/ObjectWidgetInternalViewModel.swift +++ b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/SpecificInternalModels/ObjectWidgetInternalViewModel.swift @@ -25,13 +25,16 @@ final class ObjectWidgetInternalViewModel: ObservableObject, WidgetInternalViewM private var linkedObjectDetails: ObjectDetails? private var limit: Int = 0 + private var allDetails: [ObjectDetails] = [] @Published private var details: [ObjectDetails]? @Published private var name: String = "" @Published private var icon: Icon? + @Published private var availableMore: Bool = false var detailsPublisher: AnyPublisher<[ObjectDetails]?, Never> { $details.eraseToAnyPublisher() } var namePublisher: AnyPublisher { $name.eraseToAnyPublisher() } var iconPublisher: AnyPublisher { $icon.eraseToAnyPublisher() } + var availableMoreObjects: AnyPublisher { $availableMore.eraseToAnyPublisher() } @Published var allowCreateObject = true init(data: WidgetSubmoduleData) { @@ -56,7 +59,7 @@ final class ObjectWidgetInternalViewModel: ObservableObject, WidgetInternalViewM func startTreeSubscription() async { for await details in subscriptionManager.detailsPublisher.values { guard let links = linkedObjectDetails?.links else { continue } - self.details = details.sorted { a, b in + self.allDetails = details.sorted { a, b in return links.firstIndex(of: a.id) ?? 0 < links.firstIndex(of: b.id) ?? 0 } limitDetails() @@ -112,7 +115,8 @@ final class ObjectWidgetInternalViewModel: ObservableObject, WidgetInternalViewM } private func limitDetails() { - guard let details else { return } - self.details = Array(details.prefix(limit)) + let details = Array(allDetails.prefix(limit)) + availableMore = allDetails.count > details.count + self.details = details } } diff --git a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/SpecificInternalModels/SetObjectWidgetInternalViewModel.swift b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/SpecificInternalModels/SetObjectWidgetInternalViewModel.swift index 271403ecba..c2f8f30a71 100644 --- a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/SpecificInternalModels/SetObjectWidgetInternalViewModel.swift +++ b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/SpecificInternalModels/SetObjectWidgetInternalViewModel.swift @@ -50,6 +50,7 @@ final class SetObjectWidgetInternalViewModel { var rows: SetObjectViewWidgetRows = .list(rows: nil, id: "") var allowCreateObject = true var showUnsupportedBanner = false + var availableMoreObjects = false init(data: WidgetSubmoduleData, style: SetObjecWidgetStyle) { self.widgetBlockId = data.widgetBlockId @@ -220,7 +221,7 @@ final class SetObjectWidgetInternalViewModel { ) try? await subscriptionStorage.startOrUpdateSubscription(data: subscriptionData) { [weak self] data in - await self?.updateRowDetails(details: data.items) + await self?.updateRowDetails(data: data) } } @@ -273,12 +274,15 @@ final class SetObjectWidgetInternalViewModel { await updateViewSubscription() } - private func updateRowDetails(details: [ObjectDetails]) { + private func updateRowDetails(data: SubscriptionStorageState) { guard let setDocument else { return } + + availableMoreObjects = data.total > data.items.count + let rowDetails = setObjectWidgetOrderHelper.reorder( setDocument: setDocument, subscriptionStorage: subscriptionStorage, - details: details, + details: data.items, onItemTap: { [weak self] details, sortedDetails in self?.handleTapOnObject(details: details, allDetails: sortedDetails) } diff --git a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/SpecificInternalModels/WidgetInternalViewModelProtocol.swift b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/SpecificInternalModels/WidgetInternalViewModelProtocol.swift index 31f3361526..b046b00f24 100644 --- a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/SpecificInternalModels/WidgetInternalViewModelProtocol.swift +++ b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/SpecificInternalModels/WidgetInternalViewModelProtocol.swift @@ -7,6 +7,7 @@ protocol WidgetInternalViewModelProtocol: AnyObject { var detailsPublisher: AnyPublisher<[ObjectDetails]?, Never> { get } var namePublisher: AnyPublisher { get } var iconPublisher: AnyPublisher { get } + var availableMoreObjects: AnyPublisher { get } var allowCreateObject: Bool { get } func startHeaderSubscription() @@ -19,5 +20,6 @@ protocol WidgetInternalViewModelProtocol: AnyObject { extension WidgetInternalViewModelProtocol { var allowCreateObject: Bool { false } var iconPublisher: AnyPublisher { Just(nil).eraseToAnyPublisher() } + var availableMoreObjects: AnyPublisher { Just(false).eraseToAnyPublisher() } func onCreateObjectTap() {} } diff --git a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Tree/Common/TreeWidgetView.swift b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Tree/Common/TreeWidgetView.swift index de6a508b32..f0d8c7274e 100644 --- a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Tree/Common/TreeWidgetView.swift +++ b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Tree/Common/TreeWidgetView.swift @@ -1,11 +1,13 @@ import Foundation import SwiftUI +import AnytypeCore struct TreeWidgetView: View { let data: WidgetSubmoduleData @StateObject private var model: TreeWidgetViewModel + @State private var showAllButtonInWidgets = FeatureFlags.showAllButtonInWidgets init( data: WidgetSubmoduleData, @@ -53,6 +55,11 @@ struct TreeWidgetView: View { ForEach(rows, id: \.rowId) { TreeWidgetRowView(model: $0, showDivider: $0.rowId != rows.last?.rowId) } + if showAllButtonInWidgets, model.availableMore { + WidgetSeeAllRow { + model.onSeeAllTap() + } + } Spacer.fixedHeight(8) } } diff --git a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Tree/Common/TreeWidgetViewModel.swift b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Tree/Common/TreeWidgetViewModel.swift index b079b5a118..5c4e3292d6 100644 --- a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Tree/Common/TreeWidgetViewModel.swift +++ b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Tree/Common/TreeWidgetViewModel.swift @@ -38,6 +38,7 @@ final class TreeWidgetViewModel: ObservableObject { @Published private(set) var name: String = "" @Published private(set) var icon: Icon? + @Published private(set) var availableMore: Bool = false var dragId: String? { widgetBlockId } var allowCreateObject: Bool { internalModel.allowCreateObject } @@ -62,6 +63,11 @@ final class TreeWidgetViewModel: ObservableObject { output?.onObjectSelected(screenData: screenData) } + func onSeeAllTap() { + guard let screenData = internalModel.screenData() else { return } + output?.onObjectSelected(screenData: screenData) + } + func onCreateObjectTap() { internalModel.onCreateObjectTap() } @@ -109,6 +115,10 @@ final class TreeWidgetViewModel: ObservableObject { .receiveOnMain() .assign(to: &$icon) + internalModel.availableMoreObjects + .receiveOnMain() + .assign(to: &$availableMore) + internalModel.detailsPublisher .receiveOnMain() .sink { [weak self] details in diff --git a/Modules/AnytypeCore/AnytypeCore/Generated/FeatureFlags+Flags.swift b/Modules/AnytypeCore/AnytypeCore/Generated/FeatureFlags+Flags.swift index 238d3efa32..63ae3cbebf 100644 --- a/Modules/AnytypeCore/AnytypeCore/Generated/FeatureFlags+Flags.swift +++ b/Modules/AnytypeCore/AnytypeCore/Generated/FeatureFlags+Flags.swift @@ -46,6 +46,10 @@ public extension FeatureFlags { value(for: .brandNewAuthFlow) } + static var showAllButtonInWidgets: Bool { + value(for: .showAllButtonInWidgets) + } + static var turnOffAutomaticWidgetOpening: Bool { value(for: .turnOffAutomaticWidgetOpening) } @@ -142,6 +146,7 @@ public extension FeatureFlags { .loadAttachmentsOnHomePlusMenu, .vaultBackToRoots, .brandNewAuthFlow, + .showAllButtonInWidgets, .turnOffAutomaticWidgetOpening, .setKanbanView, .fullInlineSetImpl, diff --git a/Modules/AnytypeCore/AnytypeCore/Utils/FeatureFlags/FeatureDescription+Flags.swift b/Modules/AnytypeCore/AnytypeCore/Utils/FeatureFlags/FeatureDescription+Flags.swift index fd98a10aba..7f9600d166 100644 --- a/Modules/AnytypeCore/AnytypeCore/Utils/FeatureFlags/FeatureDescription+Flags.swift +++ b/Modules/AnytypeCore/AnytypeCore/Utils/FeatureFlags/FeatureDescription+Flags.swift @@ -64,6 +64,11 @@ public extension FeatureDescription { defaultValue: true ) + static let showAllButtonInWidgets = FeatureDescription( + title: "See all as the last point in the widget - IOS-4945", + type: .feature(author: "m@anytype.io", releaseVersion: "13.5"), + defaultValue: true + ) static let turnOffAutomaticWidgetOpening = FeatureDescription( title: "Turn off automatic object opening - IOS-5309", diff --git a/Modules/Loc/Sources/Loc/Generated/Strings.swift b/Modules/Loc/Sources/Loc/Generated/Strings.swift index ca393d864c..bf411846b3 100644 --- a/Modules/Loc/Sources/Loc/Generated/Strings.swift +++ b/Modules/Loc/Sources/Loc/Generated/Strings.swift @@ -572,6 +572,7 @@ public enum Loc { public static let search = Loc.tr("UI", "Search", fallback: "Search...") public static let searchForLanguage = Loc.tr("UI", "Search for language", fallback: "Search for language") public static let searchOrCreateNew = Loc.tr("UI", "Search or create new", fallback: "Search or create new") + public static let seeAll = Loc.tr("UI", "SeeAll", fallback: "See all") public static let selectAll = Loc.tr("UI", "Select all", fallback: "Select all") public static let selectDate = Loc.tr("UI", "Select date", fallback: "Select date") public static let selectFile = Loc.tr("UI", "Select file", fallback: "Select file") diff --git a/Modules/Loc/Sources/Loc/Resources/UI.xcstrings b/Modules/Loc/Sources/Loc/Resources/UI.xcstrings index 69ba2d8d32..fe2e3f7710 100644 --- a/Modules/Loc/Sources/Loc/Resources/UI.xcstrings +++ b/Modules/Loc/Sources/Loc/Resources/UI.xcstrings @@ -76768,6 +76768,17 @@ } } }, + "SeeAll" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "See all" + } + } + } + }, "Select all" : { "extractionState" : "manual", "localizations" : {