// // PinnedTimelinesView.swift // Tusker // // Created by Shadowfacts on 12/20/22. // Copyright © 2022 Shadowfacts. All rights reserved. // import SwiftUI import Pachyderm struct PinnedTimelinesView: View { @EnvironmentObject private var mastodonController: MastodonController @ObservedObject private var accountPreferences: AccountPreferences @State private var isShowingAddHashtagSheet = false @State private var isShowingAddInstanceSheet = false // store this separately from AccountPreferences in the view, b/c the @LazilyDecoding wrapper breaks animations @State private var pinnedTimelines: [PinnedTimeline] init(accountPreferences: AccountPreferences) { self.accountPreferences = accountPreferences self.pinnedTimelines = accountPreferences.pinnedTimelines } var body: some View { Section { ForEach(pinnedTimelines, id: \.id) { timeline in HStack { Label { if case .list(id: let id) = timeline, let list = mastodonController.lists.first(where: { $0.id == id }) { Text(list.title) } else if case .tag(hashtag: let tag) = timeline { Text(tag) } else { Text(timeline.title) } } icon: { Image(uiImage: timeline.image.withRenderingMode(.alwaysTemplate)) } Spacer() Image(systemName: "line.3.horizontal") .foregroundColor(Color(.lightGray)) .accessibilityHidden(true) } .contextMenu { Button(role: .destructive) { pinnedTimelines.removeAll(where: { $0.id == timeline.id }) } label: { Label("Remove Pinned Timeline", systemImage: "trash") } } } .onMove { indices, newOffset in pinnedTimelines.move(fromOffsets: indices, toOffset: newOffset) } .onDelete(perform: pinnedTimelines.count == 1 ? nil : { indices in pinnedTimelines.remove(atOffsets: indices) }) Menu { ForEach([PinnedTimeline.home, .public(local: true), .public(local: false)], id: \.id) { timeline in Button { withAnimation { pinnedTimelines.append(timeline) } } label: { Label { Text(timeline.title) } icon: { Image(uiImage: timeline.image) } } .disabled(pinnedTimelines.contains(timeline)) } Menu("List…") { ForEach(mastodonController.lists, id: \.id) { list in Button { withAnimation { pinnedTimelines.append(.list(id: list.id)) } } label: { Text(list.title) } .disabled(pinnedTimelines.contains(.list(id: list.id))) } } Button { isShowingAddHashtagSheet = true } label: { Label("Hashtag…", systemImage: "number") } Button { isShowingAddInstanceSheet = true } label: { Label("Instance…", systemImage: "globe") } } label: { Label("Add…", systemImage: "plus") .padding(.horizontal, 20) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) } .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) } header: { Text("Pinned Timelines") } .sheet(isPresented: $isShowingAddHashtagSheet, content: { AddHashtagPinnedTimelineView(pinnedTimelines: $pinnedTimelines) }) .sheet(isPresented: $isShowingAddInstanceSheet, content: { AddInstancePinnedTimelineView(pinnedTimelines: $pinnedTimelines) .edgesIgnoringSafeArea(.bottom) }) .onReceive(accountPreferences.publisher(for: \.pinnedTimelinesData)) { _ in if pinnedTimelines != accountPreferences.pinnedTimelines { pinnedTimelines = accountPreferences.pinnedTimelines } } .onChange(of: pinnedTimelines) { newValue in if accountPreferences.pinnedTimelines != newValue { accountPreferences.pinnedTimelines = newValue } } } } fileprivate extension PinnedTimeline { var id: String { switch self { case .home: return "home" case .public(local: let local): return "public:\(local)" case .list(id: let id): return "list:\(id)" case .tag(hashtag: let tag): return "tag:\(tag)" case .instance(let url): return "instance:\(url.host!)" } } }