Merge 'ux: Seamless Timeline'

ericholguin (1):
      ux: Seamless Timeline
This commit is contained in:
William Casarin
2024-10-08 09:55:14 +02:00
20 changed files with 280 additions and 62 deletions

View File

@@ -351,7 +351,6 @@
4CE879582996C45300F758CC /* ZapsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE879572996C45300F758CC /* ZapsView.swift */; };
4CE8795B2996C47A00F758CC /* ZapsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8795A2996C47A00F758CC /* ZapsModel.swift */; };
4CE9FBBA2A6B3C63007E485C /* nostrdb.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CE9FBB82A6B3B26007E485C /* nostrdb.c */; settings = {COMPILER_FLAGS = "-w"; }; };
4CED18FD2C84B28F006AF665 /* PostingTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C8711DD2C460C06007879C2 /* PostingTimelineView.swift */; };
4CEE2AED2805B22500AB5EEF /* NostrRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AEC2805B22500AB5EEF /* NostrRequest.swift */; };
4CEE2AF1280B216B00AB5EEF /* EventDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF0280B216B00AB5EEF /* EventDetailView.swift */; };
4CEE2AF3280B25C500AB5EEF /* ProfilePicView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF2280B25C500AB5EEF /* ProfilePicView.swift */; };
@@ -398,6 +397,9 @@
5C0567582C8FBC560073F23A /* NDBSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0567572C8FBC560073F23A /* NDBSearchView.swift */; };
5C0567592C8FBDE30073F23A /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2277EE92A089BD5006C3807 /* Router.swift */; };
5C05675A2C8FBDE70073F23A /* NDBSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0567572C8FBC560073F23A /* NDBSearchView.swift */; };
5C0567532C8B5F9C0073F23A /* PostingTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C8711DD2C460C06007879C2 /* PostingTimelineView.swift */; };
5C0567552C8B60C20073F23A /* OffsetExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0567542C8B60C20073F23A /* OffsetExtension.swift */; };
5C0567562C8B60E60073F23A /* OffsetExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0567542C8B60C20073F23A /* OffsetExtension.swift */; };
5C0707D12A1ECB38004E7B51 /* DamusLogoGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */; };
5C14C29B2BBBA29C00079FD2 /* RelaySoftwareDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29A2BBBA29C00079FD2 /* RelaySoftwareDetail.swift */; };
5C14C29D2BBBA40B00079FD2 /* RelayAdminDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29C2BBBA40B00079FD2 /* RelayAdminDetail.swift */; };
@@ -1837,6 +1839,7 @@
50C3E0892AA8E3F7006A4BC0 /* AVPlayer+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVPlayer+Additions.swift"; sourceTree = "<group>"; };
50DA11252A16A23F00236234 /* Launch.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Launch.storyboard; sourceTree = "<group>"; };
5C0567572C8FBC560073F23A /* NDBSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NDBSearchView.swift; sourceTree = "<group>"; };
5C0567542C8B60C20073F23A /* OffsetExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OffsetExtension.swift; sourceTree = "<group>"; };
5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusLogoGradient.swift; sourceTree = "<group>"; };
5C14C29A2BBBA29C00079FD2 /* RelaySoftwareDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaySoftwareDetail.swift; sourceTree = "<group>"; };
5C14C29C2BBBA40B00079FD2 /* RelayAdminDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayAdminDetail.swift; sourceTree = "<group>"; };
@@ -3267,6 +3270,7 @@
4C7D09752A0AF19E00943473 /* FillAndStroke.swift */,
D72E12772BEED22400F4F781 /* Array.swift */,
D78DB85A2C20FE4F00F0AB12 /* VectorMath.swift */,
5C0567542C8B60C20073F23A /* OffsetExtension.swift */,
);
path = Extensions;
sourceTree = "<group>";
@@ -3806,6 +3810,7 @@
50C3E08A2AA8E3F7006A4BC0 /* AVPlayer+Additions.swift in Sources */,
4C198DF229F88C6B004C165C /* BlurHashDecode.swift in Sources */,
F75BA12F29A18EF500E10810 /* BookmarksView.swift in Sources */,
5C0567552C8B60C20073F23A /* OffsetExtension.swift in Sources */,
4CB883B6297730E400DC99E7 /* LNUrls.swift in Sources */,
4C7FF7D52823313F009601DB /* Mentions.swift in Sources */,
4C32B94D2A9AD44700DC3548 /* Offset.swift in Sources */,
@@ -4262,7 +4267,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4CED18FD2C84B28F006AF665 /* PostingTimelineView.swift in Sources */,
D73E5E202C6A97F4007EB227 /* AttachedWalletNotify.swift in Sources */,
D73E5E212C6A97F4007EB227 /* DisplayTabBarNotify.swift in Sources */,
D73E5E222C6A97F4007EB227 /* BroadcastNotify.swift in Sources */,
@@ -4454,6 +4458,7 @@
D73E5EDD2C6A97F4007EB227 /* NotificationSettingsView.swift in Sources */,
D73E5EDE2C6A97F4007EB227 /* AppearanceSettingsView.swift in Sources */,
D73E5EDF2C6A97F4007EB227 /* KeySettingsView.swift in Sources */,
5C0567562C8B60E60073F23A /* OffsetExtension.swift in Sources */,
D73E5EE02C6A97F4007EB227 /* ZapSettingsView.swift in Sources */,
D73E5F792C6A9C4C007EB227 /* HomeModel.swift in Sources */,
D73E5EE12C6A97F4007EB227 /* TranslationSettingsView.swift in Sources */,
@@ -4527,6 +4532,7 @@
D73E5F242C6A97F4007EB227 /* HighlightLink.swift in Sources */,
D73E5F252C6A97F4007EB227 /* HighlightEventRef.swift in Sources */,
D73E5F262C6A97F4007EB227 /* HighlightDraftContentView.swift in Sources */,
5C0567532C8B5F9C0073F23A /* PostingTimelineView.swift in Sources */,
D73E5F272C6A97F4007EB227 /* TimeDot.swift in Sources */,
D73E5F282C6A97F4007EB227 /* EventTop.swift in Sources */,
D73E5F292C6A97F4007EB227 /* ReplyDescription.swift in Sources */,

View File

@@ -46,7 +46,6 @@ struct CustomPicker<SelectionValue: Hashable>: View {
.accentColor(tag == selection ? textColor() : .gray)
}
}
.background(Color(UIColor.systemBackground))
}
func textColor() -> Color {

View File

@@ -61,6 +61,8 @@ func present_sheet(_ sheet: Sheets) {
notify(.present_sheet(sheet))
}
var tabHeight: CGFloat = 0.0
struct ContentView: View {
let keypair: Keypair
let appDelegate: AppDelegate?
@@ -89,6 +91,7 @@ struct ContentView: View {
@State var user_muted_confirm: Bool = false
@State var confirm_overwrite_mutelist: Bool = false
@State private var isSideBarOpened = false
@State var headerOffset: CGFloat = 0.0
var home: HomeModel = HomeModel()
@StateObject var navigationCoordinator: NavigationCoordinator = NavigationCoordinator()
@AppStorage("has_seen_suggested_users") private var hasSeenOnboardingSuggestions = false
@@ -131,7 +134,7 @@ struct ContentView: View {
}
case .home:
PostingTimelineView(damus_state: damus_state!, home: home, active_sheet: $active_sheet)
PostingTimelineView(damus_state: damus_state!, home: home, isSideBarOpened: $isSideBarOpened, active_sheet: $active_sheet, headerOffset: $headerOffset)
case .notifications:
NotificationsView(state: damus, notifications: home.notifications, subtitle: $menu_subtitle)
@@ -140,25 +143,16 @@ struct ContentView: View {
DirectMessagesView(damus_state: damus_state!, model: damus_state!.dms, settings: damus_state!.settings)
}
}
.background(DamusColors.adaptableWhite)
.edgesIgnoringSafeArea(selected_timeline != .home ? [] : [.top, .bottom])
.navigationBarTitle(timeline_name(selected_timeline), displayMode: .inline)
.toolbar(selected_timeline != .home ? .visible : .hidden)
.toolbar {
ToolbarItem(placement: .principal) {
VStack {
if selected_timeline == .home {
Image("damus-home")
.resizable()
.frame(width:30,height:30)
.shadow(color: DamusColors.purple, radius: 2)
.opacity(isSideBarOpened ? 0 : 1)
.animation(isSideBarOpened ? .none : .default, value: isSideBarOpened)
.onTapGesture {
isSideBarOpened.toggle()
}
} else {
timelineNavItem
.opacity(isSideBarOpened ? 0 : 1)
.animation(isSideBarOpened ? .none : .default, value: isSideBarOpened)
}
timelineNavItem
.opacity(isSideBarOpened ? 0 : 1)
.animation(isSideBarOpened ? .none : .default, value: isSideBarOpened)
}
}
}
@@ -237,9 +231,11 @@ struct ContentView: View {
}
}
}
.background(DamusColors.adaptableWhite)
.edgesIgnoringSafeArea(selected_timeline != .home ? [] : [.top, .bottom])
.tabViewStyle(.page(indexDisplayMode: .never))
.overlay(
SideMenuView(damus_state: damus_state!, isSidebarVisible: $isSideBarOpened.animation())
SideMenuView(damus_state: damus_state!, isSidebarVisible: $isSideBarOpened.animation(), selected: $selected_timeline)
)
.navigationDestination(for: Route.self) { route in
route.view(navigationCoordinator: navigationCoordinator, damusState: damus_state!)
@@ -249,13 +245,25 @@ struct ContentView: View {
}
}
.navigationViewStyle(.stack)
if !hide_bar {
TabBar(nstatus: home.notification_status, selected: $selected_timeline, settings: damus.settings, action: switch_timeline)
.padding([.bottom], 8)
.background(Color(uiColor: .systemBackground).ignoresSafeArea())
} else {
Text("")
.overlay(alignment: .bottom) {
if !hide_bar {
if !isSideBarOpened {
TabBar(nstatus: home.notification_status, navIsAtRoot: navIsAtRoot(), selected: $selected_timeline, headerOffset: $headerOffset, settings: damus.settings, action: switch_timeline)
.padding([.bottom], 8)
.background(selected_timeline != .home || (selected_timeline == .home && !self.navIsAtRoot()) ? DamusColors.adaptableWhite : DamusColors.adaptableWhite.opacity(abs(1.25 - (abs(headerOffset/100.0)))))
.anchorPreference(key: HeaderBoundsKey.self, value: .bounds){$0}
.overlayPreferenceValue(HeaderBoundsKey.self) { value in
GeometryReader{ proxy in
if let anchor = value{
Color.clear
.onAppear {
tabHeight = proxy[anchor].height
}
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,78 @@
//
// OffsetExtension.swift
// damus
//
// Created by eric on 9/6/24.
//
import SwiftUI
enum SwipeDirection {
case up
case down
case none
}
extension View {
@ViewBuilder
func offsetY(completion: @escaping (CGFloat, CGFloat)->())->some View {
self
.modifier(OffsetHelper(onChange: completion))
}
func safeArea() -> UIEdgeInsets {
guard let scene = this_app.connectedScenes.first as? UIWindowScene else{return .zero}
guard let safeArea = scene.windows.first?.safeAreaInsets else{return .zero}
return safeArea
}
}
struct OffsetHelper: ViewModifier{
var onChange: (CGFloat,CGFloat)->()
@State var currentOffset: CGFloat = 0
@State var previousOffset: CGFloat = 0
func body(content: Content) -> some View {
content
.overlay {
GeometryReader{proxy in
let minY = proxy.frame(in: .named("scroll")).minY
Color.clear
.preference(key: OffsetKey.self, value: minY)
.onPreferenceChange(OffsetKey.self) { value in
previousOffset = currentOffset
currentOffset = value
onChange(previousOffset,currentOffset)
}
}
}
}
}
struct OffsetKey: PreferenceKey {
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
}
}
struct HeaderBoundsKey: PreferenceKey{
static var defaultValue: Anchor<CGRect>?
static func reduce(value: inout Anchor<CGRect>?, nextValue: () -> Anchor<CGRect>?) {
value = nextValue()
}
}
func getSafeAreaTop()->CGFloat{
guard let scene = this_app.connectedScenes.first as? UIWindowScene else{return .zero}
guard let topSafeArea = scene.windows.first?.safeAreaInsets.top else{return .zero}
return topSafeArea
}
func getSafeAreaBottom()->CGFloat{
guard let scene = this_app.connectedScenes.first as? UIWindowScene else{return .zero}
guard let bottomSafeArea = scene.windows.first?.safeAreaInsets.bottom else{return .zero}
return bottomSafeArea
}

View File

@@ -39,6 +39,7 @@ struct BookmarksView: View {
ScrollView {
InnerTimelineView(events: EventHolder(events: bookmarks, incoming: []), damus: state, filter: noneFilter)
}
.padding(.bottom, 10 + tabHeight + getSafeAreaBottom())
}
}
.onReceive(handle_notify(.switched_timeline)) { _ in

View File

@@ -135,6 +135,9 @@ struct ChatroomThreadView: View {
}
.padding(.top)
EndBlock()
HStack {}
.frame(height: tabHeight + getSafeAreaBottom())
}
.onReceive(handle_notify(.post), perform: { notify in
switch notify {

View File

@@ -99,7 +99,10 @@ struct ConfigView: View {
}
}
Section(NSLocalizedString("Version", comment: "Section title for displaying the version number of the Damus app.")) {
Section(
header: Text(NSLocalizedString("Version", comment: "Section title for displaying the version number of the Damus app.")),
footer: Text("").padding(.bottom, tabHeight + getSafeAreaBottom())
) {
Text(verbatim: VersionInfo.version)
.contextMenu {
Button {

View File

@@ -66,7 +66,9 @@ struct TabButton: View {
struct TabBar: View {
var nstatus: NotificationStatusModel
var navIsAtRoot: Bool
@Binding var selected: Timeline
@Binding var headerOffset: CGFloat
let settings: UserSettingsStore
let action: (Timeline) -> ()
@@ -81,5 +83,6 @@ struct TabBar: View {
TabButton(timeline: .notifications, img: "notification-bell", selected: $selected, nstatus: nstatus, settings: settings, action: action).keyboardShortcut("4")
}
}
.opacity(selected != .home || (selected == .home && !navIsAtRoot) ? 1.0 : (abs(1.25 - (abs(headerOffset/100.0)))))
}
}

View File

@@ -86,7 +86,10 @@ struct MutelistView: View {
}
}
}
Section(NSLocalizedString("Threads", comment: "Section header title for a list of threads that are muted.")) {
Section(
header: Text(NSLocalizedString("Threads", comment: "Section header title for a list of threads that are muted.")),
footer: Text("").padding(.bottom, 10 + tabHeight + getSafeAreaBottom())
) {
ForEach(threads, id: \.self) { item in
if case let MuteItem.thread(note_id, _) = item {
if let event = damus_state.events.lookup(note_id) {

View File

@@ -203,7 +203,7 @@ struct EditMetadataView: View {
})
.buttonStyle(GradientButtonStyle(padding: 15))
.padding(.horizontal, 10)
.padding(.bottom, 10)
.padding(.bottom, 10 + tabHeight + getSafeAreaBottom())
.disabled(!didChange())
.opacity(!didChange() ? 0.5 : 1)
.disabled(profileUploadObserver.isLoading || bannerUploadObserver.isLoading)

View File

@@ -444,6 +444,7 @@ struct ProfileView: View {
.zIndex(-yOffset > navbarHeight ? 0 : 1)
}
}
.padding(.bottom, tabHeight + getSafeAreaBottom())
.ignoresSafeArea()
.navigationTitle("")
.navigationBarBackButtonHidden()
@@ -485,6 +486,7 @@ struct ProfileView: View {
PostButtonContainer(is_left_handed: damus_state.settings.left_handed) {
notify(.compose(.posting(.user(profile.pubkey))))
}
.padding(.bottom, tabHeight)
}
}
}

View File

@@ -22,6 +22,7 @@ struct ReactionsView: View {
}
.padding()
}
.padding(.bottom, tabHeight + getSafeAreaBottom())
.navigationBarTitle(NSLocalizedString("Reactions", comment: "Navigation bar title for Reactions view."))
.onAppear {
model.subscribe()

View File

@@ -13,15 +13,14 @@ struct SignalView: View {
var body: some View {
Group {
if signal.signal != signal.max_signal {
NavigationLink(value: Route.RelayConfig) {
Text("\(signal.signal)/\(signal.max_signal)", comment: "Fraction of how many of the user's relay servers that are operational.")
.font(.callout)
.foregroundColor(.gray)
}
} else {
Text("")
NavigationLink(value: Route.RelayConfig) {
Text("\(signal.signal)/\(signal.max_signal)", comment: "Fraction of how many of the user's relay servers that are operational.")
.font(.callout)
.foregroundColor(.gray)
}
.frame(width:50,height:30)
.opacity(signal.signal != signal.max_signal ? 1 : 0)
.disabled(signal.signal == signal.max_signal)
}
}

View File

@@ -20,6 +20,7 @@ struct RepostsView: View {
}
.padding()
}
.padding(.bottom, tabHeight + getSafeAreaBottom())
.navigationBarTitle(NSLocalizedString("Reposts", comment: "Navigation bar title for Reposts view."))
.onAppear {
model.subscribe()

View File

@@ -108,6 +108,7 @@ struct AppearanceSettingsView: View {
Section(
header: Text("Profiles", comment: "Section title for profile view configuration."),
footer: Text("Profile action sheets allow you to follow, zap, or DM profiles more quickly without having to view their full profile", comment: "Section footer clarifying what the profile action sheet feature does")
.padding(.bottom, tabHeight + getSafeAreaBottom())
) {
Toggle(NSLocalizedString("Show profile action sheets", comment: "Setting to show profile action sheets when clicking on a user's profile picture"), isOn: $settings.show_profile_action_sheet_on_pfp_click)
.toggleStyle(.switch)

View File

@@ -177,7 +177,10 @@ struct NotificationSettingsView: View {
.toggleStyle(.switch)
}
Section(header: Text("Notification Dots", comment: "Section header for notification indicator dot settings")) {
Section(
header: Text("Notification Dots", comment: "Section header for notification indicator dot settings"),
footer: Text("").padding(.bottom, tabHeight + getSafeAreaBottom())
) {
Toggle(NSLocalizedString("Zaps", comment: "Setting to enable Zap Local Notification"), isOn: indicator_binding(.zaps))
.toggleStyle(.switch)
Toggle(NSLocalizedString("Mentions", comment: "Setting to enable Mention Local Notification"), isOn: indicator_binding(.mentions))

View File

@@ -11,6 +11,7 @@ import SwiftUI
struct SideMenuView: View {
let damus_state: DamusState
@Binding var isSidebarVisible: Bool
@Binding var selected: Timeline
@State var confirm_logout: Bool = false
@State private var showQRCode = false
@@ -200,7 +201,7 @@ struct SideMenuView: View {
}
.padding(.top, verticalSpacing)
}
.padding(.top, -(padding / 2.0))
.padding(.top, selected != .home ? -(padding / 2.0) : 30)
.padding([.leading, .trailing, .bottom], padding)
}
.frame(width: sideBarWidth)
@@ -249,6 +250,6 @@ struct SideMenuView: View {
struct Previews_SideMenuView_Previews: PreviewProvider {
static var previews: some View {
let ds = test_damus_state
SideMenuView(damus_state: ds, isSidebarVisible: .constant(true))
SideMenuView(damus_state: ds, isSidebarVisible: .constant(true), selected: .constant(.home))
}
}

View File

@@ -16,11 +16,14 @@ struct PostingTimelineView: View {
@State var initialOffset: CGFloat?
@State var offset: CGFloat?
@State var showSearch: Bool = true
@Binding var isSideBarOpened: Bool
@Binding var active_sheet: Sheets?
@FocusState private var isSearchFocused: Bool
@State private var contentOffset: CGFloat = 0
@State private var indicatorWidth: CGFloat = 0
@State private var indicatorPosition: CGFloat = 0
@State var headerHeight: CGFloat = 0
@Binding var headerOffset: CGFloat
@SceneStorage("PostingTimelineView.filter_state") var filter_state : FilterState = .posts_and_replies
var mystery: some View {
@@ -35,8 +38,63 @@ struct PostingTimelineView: View {
}
func contentTimelineView(filter: (@escaping (NostrEvent) -> Bool)) -> some View {
TimelineView(events: home.events, loading: .constant(false), damus: damus_state, show_friend_icon: false, filter: filter) {
PullDownSearchView(state: damus_state, on_cancel: {})
TimelineView<AnyView>(events: home.events, loading: .constant(false), headerHeight: $headerHeight, headerOffset: $headerOffset, damus: damus_state, show_friend_icon: false, filter: filter)
}
func HeaderView()->some View {
VStack {
VStack(spacing: 0) {
// This is needed for the Dynamic Island
HStack {}
.frame(height: getSafeAreaTop())
HStack(alignment: .top) {
Button {
isSideBarOpened.toggle()
} label: {
ProfilePicView(pubkey: damus_state.pubkey, size: 32, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
.opacity(isSideBarOpened ? 0 : 1)
.animation(isSideBarOpened ? .none : .default, value: isSideBarOpened)
}
.disabled(isSideBarOpened)
Spacer()
Image("damus-home")
.resizable()
.frame(width:30,height:30)
.shadow(color: DamusColors.purple, radius: 2)
.opacity(isSideBarOpened ? 0 : 1)
.animation(isSideBarOpened ? .none : .default, value: isSideBarOpened)
.onTapGesture {
isSideBarOpened.toggle()
}
.padding(.leading)
Spacer()
HStack(alignment: .center) {
SignalView(state: damus_state, signal: home.signal)
}
}
.frame(maxWidth: .infinity, alignment: .trailing)
}
.padding(.horizontal, 20)
VStack(spacing: 0) {
CustomPicker(tabs: [
(NSLocalizedString("Notes", comment: "Label for filter for seeing only notes (instead of notes and replies)."), FilterState.posts),
(NSLocalizedString("Notes & Replies", comment: "Label for filter for seeing notes and replies (instead of only notes)."), FilterState.posts_and_replies)
],
selection: $filter_state)
Divider()
.frame(height: 1)
}
}
.background {
DamusColors.adaptableWhite
.ignoresSafeArea()
}
}
@@ -60,21 +118,26 @@ struct PostingTimelineView: View {
PostButtonContainer(is_left_handed: damus_state.settings.left_handed) {
self.active_sheet = .post(.posting(.none))
}
.padding(.bottom, tabHeight + getSafeAreaBottom())
.opacity((abs(1.25 - (abs(headerOffset/100.0)))))
}
}
}
.safeAreaInset(edge: .top, spacing: 0) {
VStack(spacing: 0) {
CustomPicker(tabs: [
(NSLocalizedString("Notes", comment: "Label for filter for seeing only notes (instead of notes and replies)."), FilterState.posts),
(NSLocalizedString("Notes & Replies", comment: "Label for filter for seeing notes and replies (instead of only notes)."), FilterState.posts_and_replies)
],
selection: $filter_state)
Divider()
.frame(height: 1)
}
.background(DamusColors.adaptableWhite)
.overlay(alignment: .top) {
HeaderView()
.anchorPreference(key: HeaderBoundsKey.self, value: .bounds){$0}
.overlayPreferenceValue(HeaderBoundsKey.self) { value in
GeometryReader{ proxy in
if let anchor = value{
Color.clear
.onAppear {
headerHeight = proxy[anchor].height
}
}
}
}
.offset(y: -headerOffset < headerHeight ? headerOffset : (headerOffset < 0 ? headerOffset : 0))
.opacity(1.0 - (abs(headerOffset/100.0)))
}
}
}

View File

@@ -10,6 +10,11 @@ import SwiftUI
struct TimelineView<Content: View>: View {
@ObservedObject var events: EventHolder
@Binding var loading: Bool
@Binding var headerHeight: CGFloat
@Binding var headerOffset: CGFloat
@State var shiftOffset: CGFloat = 0
@State var lastHeaderOffset: CGFloat = 0
@State var direction: SwipeDirection = .none
let damus: DamusState
let show_friend_icon: Bool
@@ -17,9 +22,23 @@ struct TimelineView<Content: View>: View {
let content: Content?
let apply_mute_rules: Bool
init(events: EventHolder, loading: Binding<Bool>, headerHeight: Binding<CGFloat>, headerOffset: Binding<CGFloat>, damus: DamusState, show_friend_icon: Bool, filter: @escaping (NostrEvent) -> Bool, apply_mute_rules: Bool = true, content: (() -> Content)? = nil) {
self.events = events
self._loading = loading
self._headerHeight = headerHeight
self._headerOffset = headerOffset
self.damus = damus
self.show_friend_icon = show_friend_icon
self.filter = filter
self.apply_mute_rules = apply_mute_rules
self.content = content?()
}
init(events: EventHolder, loading: Binding<Bool>, damus: DamusState, show_friend_icon: Bool, filter: @escaping (NostrEvent) -> Bool, apply_mute_rules: Bool = true, content: (() -> Content)? = nil) {
self.events = events
self._loading = loading
self._headerHeight = .constant(0.0)
self._headerOffset = .constant(0.0)
self.damus = damus
self.show_friend_icon = show_friend_icon
self.filter = filter
@@ -38,20 +57,43 @@ struct TimelineView<Content: View>: View {
content
}
Color.white.opacity(0)
Color.clear
.id("startblock")
.frame(height: 1)
.frame(height: 0)
InnerTimelineView(events: events, damus: damus, filter: loading ? { _ in true } : filter, apply_mute_rules: self.apply_mute_rules)
.redacted(reason: loading ? .placeholder : [])
.shimmer(loading)
.disabled(loading)
.background(GeometryReader { proxy -> Color in
handle_scroll_queue(proxy, queue: self.events)
return Color.clear
})
.padding(.top, headerHeight - getSafeAreaTop())
.offsetY { previous, current in
if previous > current{
if direction != .up && current < 0 {
shiftOffset = current - headerOffset
direction = .up
lastHeaderOffset = headerOffset
}
let offset = current < 0 ? (current - shiftOffset) : 0
headerOffset = (-offset < headerHeight ? (offset < 0 ? offset : 0) : -headerHeight)
}else {
if direction != .down {
shiftOffset = current
direction = .down
lastHeaderOffset = headerOffset
}
let offset = lastHeaderOffset + (current - shiftOffset)
headerOffset = (offset > 0 ? 0 : offset)
}
}
.background {
GeometryReader { proxy -> Color in
handle_scroll_queue(proxy, queue: self.events)
return Color.clear
}
}
}
//.buttonStyle(BorderlessButtonStyle())
.coordinateSpace(name: "scroll")
.onReceive(handle_notify(.scroll_to_top)) { () in
events.flush()

View File

@@ -28,6 +28,7 @@ struct ZapsView: View {
}
}
}
.padding(.bottom, tabHeight + getSafeAreaBottom())
.navigationBarTitle(NSLocalizedString("Zaps", comment: "Navigation bar title for the Zaps view."))
.onAppear {
model.subscribe()