Timeline: Add pills quick access
This commit is contained in:
parent
631707a798
commit
fe66acbd39
|
@ -33,11 +33,13 @@ extension View {
|
||||||
ConversationDetailView(conversation: conversation)
|
ConversationDetailView(conversation: conversation)
|
||||||
case let .hashTag(tag, accountId):
|
case let .hashTag(tag, accountId):
|
||||||
TimelineView(timeline: .constant(.hashtag(tag: tag, accountId: accountId)),
|
TimelineView(timeline: .constant(.hashtag(tag: tag, accountId: accountId)),
|
||||||
|
pinnedFilters: .constant([]),
|
||||||
selectedTagGroup: .constant(nil),
|
selectedTagGroup: .constant(nil),
|
||||||
scrollToTopSignal: .constant(0),
|
scrollToTopSignal: .constant(0),
|
||||||
canFilterTimeline: false)
|
canFilterTimeline: false)
|
||||||
case let .list(list):
|
case let .list(list):
|
||||||
TimelineView(timeline: .constant(.list(list: list)),
|
TimelineView(timeline: .constant(.list(list: list)),
|
||||||
|
pinnedFilters: .constant([]),
|
||||||
selectedTagGroup: .constant(nil),
|
selectedTagGroup: .constant(nil),
|
||||||
scrollToTopSignal: .constant(0),
|
scrollToTopSignal: .constant(0),
|
||||||
canFilterTimeline: false)
|
canFilterTimeline: false)
|
||||||
|
@ -53,6 +55,7 @@ extension View {
|
||||||
AccountsListView(mode: .accountsList(accounts: accounts))
|
AccountsListView(mode: .accountsList(accounts: accounts))
|
||||||
case .trendingTimeline:
|
case .trendingTimeline:
|
||||||
TimelineView(timeline: .constant(.trending),
|
TimelineView(timeline: .constant(.trending),
|
||||||
|
pinnedFilters: .constant([]),
|
||||||
selectedTagGroup: .constant(nil),
|
selectedTagGroup: .constant(nil),
|
||||||
scrollToTopSignal: .constant(0),
|
scrollToTopSignal: .constant(0),
|
||||||
canFilterTimeline: false)
|
canFilterTimeline: false)
|
||||||
|
|
|
@ -29,6 +29,7 @@ struct TimelineTab: View {
|
||||||
@Query(sort: \TagGroup.creationDate, order: .reverse) var tagGroups: [TagGroup]
|
@Query(sort: \TagGroup.creationDate, order: .reverse) var tagGroups: [TagGroup]
|
||||||
|
|
||||||
@AppStorage("last_timeline_filter") var lastTimelineFilter: TimelineFilter = .home
|
@AppStorage("last_timeline_filter") var lastTimelineFilter: TimelineFilter = .home
|
||||||
|
@AppStorage("timeline_pinned_filters") private var pinnedFilters: [TimelineFilter] = []
|
||||||
|
|
||||||
private let canFilterTimeline: Bool
|
private let canFilterTimeline: Bool
|
||||||
|
|
||||||
|
@ -41,6 +42,7 @@ struct TimelineTab: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack(path: $routerPath.path) {
|
NavigationStack(path: $routerPath.path) {
|
||||||
TimelineView(timeline: $timeline,
|
TimelineView(timeline: $timeline,
|
||||||
|
pinnedFilters: $pinnedFilters,
|
||||||
selectedTagGroup: $selectedTagGroup,
|
selectedTagGroup: $selectedTagGroup,
|
||||||
scrollToTopSignal: $scrollToTopSignal,
|
scrollToTopSignal: $scrollToTopSignal,
|
||||||
canFilterTimeline: canFilterTimeline)
|
canFilterTimeline: canFilterTimeline)
|
||||||
|
@ -119,96 +121,13 @@ struct TimelineTab: View {
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var timelineFilterButton: some View {
|
private var timelineFilterButton: some View {
|
||||||
if timeline.supportNewestPagination {
|
latestOrResumeButtons
|
||||||
Button {
|
pinMenuButton
|
||||||
timeline = .latest
|
timelineFiltersButtons
|
||||||
} label: {
|
listsFiltersButons
|
||||||
Label(TimelineFilter.latest.localizedTitle(), systemImage: TimelineFilter.latest.iconName() ?? "")
|
tagsFiltersButtons
|
||||||
}
|
localTimelinesFiltersButtons
|
||||||
if timeline == .home {
|
tagGroupsFiltersButtons
|
||||||
Button {
|
|
||||||
timeline = .resume
|
|
||||||
} label: {
|
|
||||||
VStack {
|
|
||||||
Label(TimelineFilter.resume.localizedTitle(),
|
|
||||||
systemImage: TimelineFilter.resume.iconName() ?? "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Divider()
|
|
||||||
}
|
|
||||||
ForEach(TimelineFilter.availableTimeline(client: client), id: \.self) { timeline in
|
|
||||||
Button {
|
|
||||||
self.timeline = timeline
|
|
||||||
} label: {
|
|
||||||
Label(timeline.localizedTitle(), systemImage: timeline.iconName() ?? "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !currentAccount.lists.isEmpty {
|
|
||||||
Menu("timeline.filter.lists") {
|
|
||||||
ForEach(currentAccount.sortedLists) { list in
|
|
||||||
Button {
|
|
||||||
timeline = .list(list: list)
|
|
||||||
} label: {
|
|
||||||
Label(list.title, systemImage: "list.bullet")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
routerPath.presentedSheet = .listCreate
|
|
||||||
} label: {
|
|
||||||
Label("account.list.create", systemImage: "plus")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !currentAccount.tags.isEmpty {
|
|
||||||
Menu("timeline.filter.tags") {
|
|
||||||
ForEach(currentAccount.sortedTags) { tag in
|
|
||||||
Button {
|
|
||||||
timeline = .hashtag(tag: tag.name, accountId: nil)
|
|
||||||
} label: {
|
|
||||||
Label("#\(tag.name)", systemImage: "number")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Menu("timeline.filter.local") {
|
|
||||||
ForEach(localTimelines) { remoteLocal in
|
|
||||||
Button {
|
|
||||||
timeline = .remoteLocal(server: remoteLocal.instance, filter: .local)
|
|
||||||
} label: {
|
|
||||||
VStack {
|
|
||||||
Label(remoteLocal.instance, systemImage: "dot.radiowaves.right")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
routerPath.presentedSheet = .addRemoteLocalTimeline
|
|
||||||
} label: {
|
|
||||||
Label("timeline.filter.add-local", systemImage: "badge.plus.radiowaves.right")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Menu("timeline.filter.tag-groups") {
|
|
||||||
ForEach(tagGroups) { group in
|
|
||||||
Button {
|
|
||||||
selectedTagGroup = group
|
|
||||||
timeline = .tagGroup(title: group.title, tags: group.tags)
|
|
||||||
} label: {
|
|
||||||
VStack {
|
|
||||||
let icon = group.symbolName.isEmpty ? "number" : group.symbolName
|
|
||||||
Label(group.title, systemImage: icon)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
routerPath.presentedSheet = .addTagGroup
|
|
||||||
} label: {
|
|
||||||
Label("timeline.filter.add-tag-groups", systemImage: "plus")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var addAccountButton: some View {
|
private var addAccountButton: some View {
|
||||||
|
@ -263,6 +182,136 @@ struct TimelineTab: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var latestOrResumeButtons: some View {
|
||||||
|
if timeline.supportNewestPagination {
|
||||||
|
Button {
|
||||||
|
timeline = .latest
|
||||||
|
} label: {
|
||||||
|
Label(TimelineFilter.latest.localizedTitle(), systemImage: TimelineFilter.latest.iconName() ?? "")
|
||||||
|
}
|
||||||
|
if timeline == .home {
|
||||||
|
Button {
|
||||||
|
timeline = .resume
|
||||||
|
} label: {
|
||||||
|
VStack {
|
||||||
|
Label(TimelineFilter.resume.localizedTitle(),
|
||||||
|
systemImage: TimelineFilter.resume.iconName() ?? "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var pinMenuButton: some View {
|
||||||
|
let index = pinnedFilters.firstIndex(where: { $0.id == timeline.id})
|
||||||
|
Button {
|
||||||
|
withAnimation {
|
||||||
|
if let index {
|
||||||
|
pinnedFilters.remove(at: index)
|
||||||
|
} else {
|
||||||
|
pinnedFilters.append(timeline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
if index != nil {
|
||||||
|
Label("status.action.unpin", systemImage: "pin.slash")
|
||||||
|
} else {
|
||||||
|
Label("status.action.pin", systemImage: "pin")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var timelineFiltersButtons: some View {
|
||||||
|
ForEach(TimelineFilter.availableTimeline(client: client), id: \.self) { timeline in
|
||||||
|
Button {
|
||||||
|
self.timeline = timeline
|
||||||
|
} label: {
|
||||||
|
Label(timeline.localizedTitle(), systemImage: timeline.iconName() ?? "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var listsFiltersButons: some View {
|
||||||
|
if !currentAccount.lists.isEmpty {
|
||||||
|
Menu("timeline.filter.lists") {
|
||||||
|
ForEach(currentAccount.sortedLists) { list in
|
||||||
|
Button {
|
||||||
|
timeline = .list(list: list)
|
||||||
|
} label: {
|
||||||
|
Label(list.title, systemImage: "list.bullet")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
routerPath.presentedSheet = .listCreate
|
||||||
|
} label: {
|
||||||
|
Label("account.list.create", systemImage: "plus")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var tagsFiltersButtons: some View {
|
||||||
|
if !currentAccount.tags.isEmpty {
|
||||||
|
Menu("timeline.filter.tags") {
|
||||||
|
ForEach(currentAccount.sortedTags) { tag in
|
||||||
|
Button {
|
||||||
|
timeline = .hashtag(tag: tag.name, accountId: nil)
|
||||||
|
} label: {
|
||||||
|
Label("#\(tag.name)", systemImage: "number")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var localTimelinesFiltersButtons: some View {
|
||||||
|
Menu("timeline.filter.local") {
|
||||||
|
ForEach(localTimelines) { remoteLocal in
|
||||||
|
Button {
|
||||||
|
timeline = .remoteLocal(server: remoteLocal.instance, filter: .local)
|
||||||
|
} label: {
|
||||||
|
VStack {
|
||||||
|
Label(remoteLocal.instance, systemImage: "dot.radiowaves.right")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
routerPath.presentedSheet = .addRemoteLocalTimeline
|
||||||
|
} label: {
|
||||||
|
Label("timeline.filter.add-local", systemImage: "badge.plus.radiowaves.right")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var tagGroupsFiltersButtons: some View {
|
||||||
|
Menu("timeline.filter.tag-groups") {
|
||||||
|
ForEach(tagGroups) { group in
|
||||||
|
Button {
|
||||||
|
selectedTagGroup = group
|
||||||
|
timeline = .tagGroup(title: group.title, tags: group.tags)
|
||||||
|
} label: {
|
||||||
|
VStack {
|
||||||
|
let icon = group.symbolName.isEmpty ? "number" : group.symbolName
|
||||||
|
Label(group.title, systemImage: icon)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
routerPath.presentedSheet = .addTagGroup
|
||||||
|
} label: {
|
||||||
|
Label("timeline.filter.add-tag-groups", systemImage: "plus")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func resetTimelineFilter() {
|
private func resetTimelineFilter() {
|
||||||
if client.isAuth, canFilterTimeline {
|
if client.isAuth, canFilterTimeline {
|
||||||
|
|
|
@ -29,7 +29,8 @@ public enum RemoteTimelineFilter: String, CaseIterable, Hashable, Equatable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum TimelineFilter: Hashable, Equatable {
|
public enum TimelineFilter: Hashable, Equatable, Identifiable {
|
||||||
|
|
||||||
case home, local, federated, trending
|
case home, local, federated, trending
|
||||||
case hashtag(tag: String, accountId: String?)
|
case hashtag(tag: String, accountId: String?)
|
||||||
case tagGroup(title: String, tags: [String])
|
case tagGroup(title: String, tags: [String])
|
||||||
|
@ -38,6 +39,10 @@ public enum TimelineFilter: Hashable, Equatable {
|
||||||
case latest
|
case latest
|
||||||
case resume
|
case resume
|
||||||
|
|
||||||
|
public var id: String {
|
||||||
|
title
|
||||||
|
}
|
||||||
|
|
||||||
public func hash(into hasher: inout Hasher) {
|
public func hash(into hasher: inout Hasher) {
|
||||||
hasher.combine(title)
|
hasher.combine(title)
|
||||||
}
|
}
|
||||||
|
@ -204,7 +209,7 @@ extension TimelineFilter: Codable {
|
||||||
accountId: accountId
|
accountId: accountId
|
||||||
)
|
)
|
||||||
case .tagGroup:
|
case .tagGroup:
|
||||||
var nestedContainer = try container.nestedUnkeyedContainer(forKey: .hashtag)
|
var nestedContainer = try container.nestedUnkeyedContainer(forKey: .tagGroup)
|
||||||
let title = try nestedContainer.decode(String.self)
|
let title = try nestedContainer.decode(String.self)
|
||||||
let tags = try nestedContainer.decode([String].self)
|
let tags = try nestedContainer.decode([String].self)
|
||||||
self = .tagGroup(
|
self = .tagGroup(
|
||||||
|
@ -259,7 +264,7 @@ extension TimelineFilter: Codable {
|
||||||
case let .list(list):
|
case let .list(list):
|
||||||
try container.encode(list, forKey: .list)
|
try container.encode(list, forKey: .list)
|
||||||
case let .remoteLocal(server, filter):
|
case let .remoteLocal(server, filter):
|
||||||
var nestedContainer = container.nestedUnkeyedContainer(forKey: .hashtag)
|
var nestedContainer = container.nestedUnkeyedContainer(forKey: .remoteLocal)
|
||||||
try nestedContainer.encode(server)
|
try nestedContainer.encode(server)
|
||||||
try nestedContainer.encode(filter)
|
try nestedContainer.encode(filter)
|
||||||
case .latest:
|
case .latest:
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
import SwiftUI
|
||||||
|
import Env
|
||||||
|
import Models
|
||||||
|
import DesignSystem
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
struct TimelineQuickAccessPills: View {
|
||||||
|
@Environment(Theme.self) private var theme
|
||||||
|
@Environment(CurrentAccount.self) private var currentAccount
|
||||||
|
|
||||||
|
@Binding var pinnedFilters: [TimelineFilter]
|
||||||
|
@Binding var timeline: TimelineFilter
|
||||||
|
|
||||||
|
@State private var draggedFilter: TimelineFilter?
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ScrollView(.horizontal) {
|
||||||
|
HStack {
|
||||||
|
ForEach(pinnedFilters) { filter in
|
||||||
|
makePill(filter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.scrollClipDisabled()
|
||||||
|
.scrollIndicators(.never)
|
||||||
|
.listRowInsets(EdgeInsets(top: 8, leading: .layoutPadding, bottom: 8, trailing: .layoutPadding))
|
||||||
|
#if !os(visionOS)
|
||||||
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
#endif
|
||||||
|
.listRowSeparator(.hidden)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private func makePill(_ filter: TimelineFilter) -> some View {
|
||||||
|
if !isFilterSupport(filter) {
|
||||||
|
EmptyView()
|
||||||
|
} else if filter == timeline {
|
||||||
|
makeButton(filter)
|
||||||
|
.buttonStyle(.borderedProminent)
|
||||||
|
} else {
|
||||||
|
makeButton(filter)
|
||||||
|
.buttonStyle(.bordered)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func makeButton(_ filter: TimelineFilter) -> some View {
|
||||||
|
Button {
|
||||||
|
timeline = filter
|
||||||
|
} label: {
|
||||||
|
Label(filter.localizedTitle(), systemImage: filter.iconName() ?? "")
|
||||||
|
.font(.callout)
|
||||||
|
}
|
||||||
|
.transition(.push(from: .leading).combined(with: .opacity))
|
||||||
|
.onDrag {
|
||||||
|
draggedFilter = filter
|
||||||
|
return NSItemProvider()
|
||||||
|
}
|
||||||
|
.onDrop(of: [.text], delegate: PillDropDelegate(destinationItem: filter,
|
||||||
|
items: $pinnedFilters,
|
||||||
|
draggedItem: $draggedFilter))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func isFilterSupport(_ filter: TimelineFilter) -> Bool {
|
||||||
|
switch filter {
|
||||||
|
case .list(let list):
|
||||||
|
return currentAccount.lists.contains(where: { $0.id == list.id })
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PillDropDelegate: DropDelegate {
|
||||||
|
let destinationItem: TimelineFilter
|
||||||
|
@Binding var items: [TimelineFilter]
|
||||||
|
@Binding var draggedItem: TimelineFilter?
|
||||||
|
|
||||||
|
func dropUpdated(info: DropInfo) -> DropProposal? {
|
||||||
|
return DropProposal(operation: .move)
|
||||||
|
}
|
||||||
|
|
||||||
|
func performDrop(info: DropInfo) -> Bool {
|
||||||
|
draggedItem = nil
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func dropEntered(info: DropInfo) {
|
||||||
|
if let draggedItem {
|
||||||
|
let fromIndex = items.firstIndex(of: draggedItem)
|
||||||
|
if let fromIndex {
|
||||||
|
let toIndex = items.firstIndex(of: destinationItem)
|
||||||
|
if let toIndex, fromIndex != toIndex {
|
||||||
|
withAnimation {
|
||||||
|
self.items.move(fromOffsets: IndexSet(integer: fromIndex), toOffset: (toIndex > fromIndex ? (toIndex + 1) : toIndex))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ public struct TimelineView: View {
|
||||||
@State private var collectionView: UICollectionView?
|
@State private var collectionView: UICollectionView?
|
||||||
|
|
||||||
@Binding var timeline: TimelineFilter
|
@Binding var timeline: TimelineFilter
|
||||||
|
@Binding var pinnedFilters: [TimelineFilter]
|
||||||
@Binding var selectedTagGroup: TagGroup?
|
@Binding var selectedTagGroup: TagGroup?
|
||||||
@Binding var scrollToTopSignal: Int
|
@Binding var scrollToTopSignal: Int
|
||||||
|
|
||||||
|
@ -33,11 +34,13 @@ public struct TimelineView: View {
|
||||||
private let canFilterTimeline: Bool
|
private let canFilterTimeline: Bool
|
||||||
|
|
||||||
public init(timeline: Binding<TimelineFilter>,
|
public init(timeline: Binding<TimelineFilter>,
|
||||||
|
pinnedFilters: Binding<[TimelineFilter]>,
|
||||||
selectedTagGroup: Binding<TagGroup?>,
|
selectedTagGroup: Binding<TagGroup?>,
|
||||||
scrollToTopSignal: Binding<Int>,
|
scrollToTopSignal: Binding<Int>,
|
||||||
canFilterTimeline: Bool)
|
canFilterTimeline: Bool)
|
||||||
{
|
{
|
||||||
_timeline = timeline
|
_timeline = timeline
|
||||||
|
_pinnedFilters = pinnedFilters
|
||||||
_selectedTagGroup = selectedTagGroup
|
_selectedTagGroup = selectedTagGroup
|
||||||
_scrollToTopSignal = scrollToTopSignal
|
_scrollToTopSignal = scrollToTopSignal
|
||||||
self.canFilterTimeline = canFilterTimeline
|
self.canFilterTimeline = canFilterTimeline
|
||||||
|
@ -48,7 +51,7 @@ public struct TimelineView: View {
|
||||||
ZStack(alignment: .top) {
|
ZStack(alignment: .top) {
|
||||||
List {
|
List {
|
||||||
scrollToTopView
|
scrollToTopView
|
||||||
TimelineTagGroupheaderView(group: $selectedTagGroup, timeline: $viewModel.timeline)
|
TimelineTagGroupheaderView(group: $selectedTagGroup, timeline: $timeline)
|
||||||
TimelineTagHeaderView(tag: $viewModel.tag)
|
TimelineTagHeaderView(tag: $viewModel.tag)
|
||||||
switch viewModel.timeline {
|
switch viewModel.timeline {
|
||||||
case .remoteLocal:
|
case .remoteLocal:
|
||||||
|
@ -77,6 +80,14 @@ public struct TimelineView: View {
|
||||||
PendingStatusesObserverView(observer: viewModel.pendingStatusesObserver)
|
PendingStatusesObserverView(observer: viewModel.pendingStatusesObserver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.safeAreaInset(edge: .top) {
|
||||||
|
if canFilterTimeline, !pinnedFilters.isEmpty {
|
||||||
|
TimelineQuickAccessPills(pinnedFilters: $pinnedFilters, timeline: $timeline)
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
.padding(.horizontal, .layoutPadding)
|
||||||
|
.background(theme.primaryBackgroundColor.opacity(0.50).background(Material.ultraThin))
|
||||||
|
}
|
||||||
|
}
|
||||||
.onChange(of: viewModel.scrollToIndex) { _, newValue in
|
.onChange(of: viewModel.scrollToIndex) { _, newValue in
|
||||||
if let collectionView,
|
if let collectionView,
|
||||||
let newValue,
|
let newValue,
|
||||||
|
|
Loading…
Reference in New Issue