ux: Seamless Timeline
This PR is a ux change to make the header, tabbar, and post button disappear when the user scrolls. The main tabbar is now an overlay which means it will display over views, this was needed in order to get the timeline to extend behind it. However, this mean we must add bottom padding to any view where the main tabbar is present to account for the overlap. Changelog-Added: Disappearing header, tabbar, and post button on scroll Signed-off-by: ericholguin <ericholguin@apache.org>
This commit is contained in:
@@ -351,7 +351,6 @@
|
|||||||
4CE879582996C45300F758CC /* ZapsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE879572996C45300F758CC /* ZapsView.swift */; };
|
4CE879582996C45300F758CC /* ZapsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE879572996C45300F758CC /* ZapsView.swift */; };
|
||||||
4CE8795B2996C47A00F758CC /* ZapsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8795A2996C47A00F758CC /* ZapsModel.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"; }; };
|
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 */; };
|
4CEE2AED2805B22500AB5EEF /* NostrRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AEC2805B22500AB5EEF /* NostrRequest.swift */; };
|
||||||
4CEE2AF1280B216B00AB5EEF /* EventDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF0280B216B00AB5EEF /* EventDetailView.swift */; };
|
4CEE2AF1280B216B00AB5EEF /* EventDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF0280B216B00AB5EEF /* EventDetailView.swift */; };
|
||||||
4CEE2AF3280B25C500AB5EEF /* ProfilePicView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF2280B25C500AB5EEF /* ProfilePicView.swift */; };
|
4CEE2AF3280B25C500AB5EEF /* ProfilePicView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF2280B25C500AB5EEF /* ProfilePicView.swift */; };
|
||||||
@@ -395,6 +394,9 @@
|
|||||||
50B5685329F97CB400A23243 /* CredentialHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B5685229F97CB400A23243 /* CredentialHandler.swift */; };
|
50B5685329F97CB400A23243 /* CredentialHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B5685229F97CB400A23243 /* CredentialHandler.swift */; };
|
||||||
50C3E08A2AA8E3F7006A4BC0 /* AVPlayer+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C3E0892AA8E3F7006A4BC0 /* AVPlayer+Additions.swift */; };
|
50C3E08A2AA8E3F7006A4BC0 /* AVPlayer+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C3E0892AA8E3F7006A4BC0 /* AVPlayer+Additions.swift */; };
|
||||||
50DA11262A16A23F00236234 /* Launch.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 50DA11252A16A23F00236234 /* Launch.storyboard */; };
|
50DA11262A16A23F00236234 /* Launch.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 50DA11252A16A23F00236234 /* Launch.storyboard */; };
|
||||||
|
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 */; };
|
5C0707D12A1ECB38004E7B51 /* DamusLogoGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */; };
|
||||||
5C14C29B2BBBA29C00079FD2 /* RelaySoftwareDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29A2BBBA29C00079FD2 /* RelaySoftwareDetail.swift */; };
|
5C14C29B2BBBA29C00079FD2 /* RelaySoftwareDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29A2BBBA29C00079FD2 /* RelaySoftwareDetail.swift */; };
|
||||||
5C14C29D2BBBA40B00079FD2 /* RelayAdminDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29C2BBBA40B00079FD2 /* RelayAdminDetail.swift */; };
|
5C14C29D2BBBA40B00079FD2 /* RelayAdminDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29C2BBBA40B00079FD2 /* RelayAdminDetail.swift */; };
|
||||||
@@ -1834,6 +1836,7 @@
|
|||||||
50B5685229F97CB400A23243 /* CredentialHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialHandler.swift; sourceTree = "<group>"; };
|
50B5685229F97CB400A23243 /* CredentialHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialHandler.swift; sourceTree = "<group>"; };
|
||||||
50C3E0892AA8E3F7006A4BC0 /* AVPlayer+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVPlayer+Additions.swift"; sourceTree = "<group>"; };
|
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>"; };
|
50DA11252A16A23F00236234 /* Launch.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Launch.storyboard; 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>"; };
|
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>"; };
|
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>"; };
|
5C14C29C2BBBA40B00079FD2 /* RelayAdminDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayAdminDetail.swift; sourceTree = "<group>"; };
|
||||||
@@ -3263,6 +3266,7 @@
|
|||||||
4C7D09752A0AF19E00943473 /* FillAndStroke.swift */,
|
4C7D09752A0AF19E00943473 /* FillAndStroke.swift */,
|
||||||
D72E12772BEED22400F4F781 /* Array.swift */,
|
D72E12772BEED22400F4F781 /* Array.swift */,
|
||||||
D78DB85A2C20FE4F00F0AB12 /* VectorMath.swift */,
|
D78DB85A2C20FE4F00F0AB12 /* VectorMath.swift */,
|
||||||
|
5C0567542C8B60C20073F23A /* OffsetExtension.swift */,
|
||||||
);
|
);
|
||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -3802,6 +3806,7 @@
|
|||||||
50C3E08A2AA8E3F7006A4BC0 /* AVPlayer+Additions.swift in Sources */,
|
50C3E08A2AA8E3F7006A4BC0 /* AVPlayer+Additions.swift in Sources */,
|
||||||
4C198DF229F88C6B004C165C /* BlurHashDecode.swift in Sources */,
|
4C198DF229F88C6B004C165C /* BlurHashDecode.swift in Sources */,
|
||||||
F75BA12F29A18EF500E10810 /* BookmarksView.swift in Sources */,
|
F75BA12F29A18EF500E10810 /* BookmarksView.swift in Sources */,
|
||||||
|
5C0567552C8B60C20073F23A /* OffsetExtension.swift in Sources */,
|
||||||
4CB883B6297730E400DC99E7 /* LNUrls.swift in Sources */,
|
4CB883B6297730E400DC99E7 /* LNUrls.swift in Sources */,
|
||||||
4C7FF7D52823313F009601DB /* Mentions.swift in Sources */,
|
4C7FF7D52823313F009601DB /* Mentions.swift in Sources */,
|
||||||
4C32B94D2A9AD44700DC3548 /* Offset.swift in Sources */,
|
4C32B94D2A9AD44700DC3548 /* Offset.swift in Sources */,
|
||||||
@@ -4257,7 +4262,6 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
4CED18FD2C84B28F006AF665 /* PostingTimelineView.swift in Sources */,
|
|
||||||
D73E5E202C6A97F4007EB227 /* AttachedWalletNotify.swift in Sources */,
|
D73E5E202C6A97F4007EB227 /* AttachedWalletNotify.swift in Sources */,
|
||||||
D73E5E212C6A97F4007EB227 /* DisplayTabBarNotify.swift in Sources */,
|
D73E5E212C6A97F4007EB227 /* DisplayTabBarNotify.swift in Sources */,
|
||||||
D73E5E222C6A97F4007EB227 /* BroadcastNotify.swift in Sources */,
|
D73E5E222C6A97F4007EB227 /* BroadcastNotify.swift in Sources */,
|
||||||
@@ -4448,6 +4452,7 @@
|
|||||||
D73E5EDD2C6A97F4007EB227 /* NotificationSettingsView.swift in Sources */,
|
D73E5EDD2C6A97F4007EB227 /* NotificationSettingsView.swift in Sources */,
|
||||||
D73E5EDE2C6A97F4007EB227 /* AppearanceSettingsView.swift in Sources */,
|
D73E5EDE2C6A97F4007EB227 /* AppearanceSettingsView.swift in Sources */,
|
||||||
D73E5EDF2C6A97F4007EB227 /* KeySettingsView.swift in Sources */,
|
D73E5EDF2C6A97F4007EB227 /* KeySettingsView.swift in Sources */,
|
||||||
|
5C0567562C8B60E60073F23A /* OffsetExtension.swift in Sources */,
|
||||||
D73E5EE02C6A97F4007EB227 /* ZapSettingsView.swift in Sources */,
|
D73E5EE02C6A97F4007EB227 /* ZapSettingsView.swift in Sources */,
|
||||||
D73E5F792C6A9C4C007EB227 /* HomeModel.swift in Sources */,
|
D73E5F792C6A9C4C007EB227 /* HomeModel.swift in Sources */,
|
||||||
D73E5EE12C6A97F4007EB227 /* TranslationSettingsView.swift in Sources */,
|
D73E5EE12C6A97F4007EB227 /* TranslationSettingsView.swift in Sources */,
|
||||||
@@ -4521,6 +4526,7 @@
|
|||||||
D73E5F242C6A97F4007EB227 /* HighlightLink.swift in Sources */,
|
D73E5F242C6A97F4007EB227 /* HighlightLink.swift in Sources */,
|
||||||
D73E5F252C6A97F4007EB227 /* HighlightEventRef.swift in Sources */,
|
D73E5F252C6A97F4007EB227 /* HighlightEventRef.swift in Sources */,
|
||||||
D73E5F262C6A97F4007EB227 /* HighlightDraftContentView.swift in Sources */,
|
D73E5F262C6A97F4007EB227 /* HighlightDraftContentView.swift in Sources */,
|
||||||
|
5C0567532C8B5F9C0073F23A /* PostingTimelineView.swift in Sources */,
|
||||||
D73E5F272C6A97F4007EB227 /* TimeDot.swift in Sources */,
|
D73E5F272C6A97F4007EB227 /* TimeDot.swift in Sources */,
|
||||||
D73E5F282C6A97F4007EB227 /* EventTop.swift in Sources */,
|
D73E5F282C6A97F4007EB227 /* EventTop.swift in Sources */,
|
||||||
D73E5F292C6A97F4007EB227 /* ReplyDescription.swift in Sources */,
|
D73E5F292C6A97F4007EB227 /* ReplyDescription.swift in Sources */,
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ struct CustomPicker<SelectionValue: Hashable>: View {
|
|||||||
.accentColor(tag == selection ? textColor() : .gray)
|
.accentColor(tag == selection ? textColor() : .gray)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.background(Color(UIColor.systemBackground))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func textColor() -> Color {
|
func textColor() -> Color {
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ func present_sheet(_ sheet: Sheets) {
|
|||||||
notify(.present_sheet(sheet))
|
notify(.present_sheet(sheet))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tabHeight: CGFloat = 0.0
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
let keypair: Keypair
|
let keypair: Keypair
|
||||||
let appDelegate: AppDelegate?
|
let appDelegate: AppDelegate?
|
||||||
@@ -89,6 +91,7 @@ struct ContentView: View {
|
|||||||
@State var user_muted_confirm: Bool = false
|
@State var user_muted_confirm: Bool = false
|
||||||
@State var confirm_overwrite_mutelist: Bool = false
|
@State var confirm_overwrite_mutelist: Bool = false
|
||||||
@State private var isSideBarOpened = false
|
@State private var isSideBarOpened = false
|
||||||
|
@State var headerOffset: CGFloat = 0.0
|
||||||
var home: HomeModel = HomeModel()
|
var home: HomeModel = HomeModel()
|
||||||
@StateObject var navigationCoordinator: NavigationCoordinator = NavigationCoordinator()
|
@StateObject var navigationCoordinator: NavigationCoordinator = NavigationCoordinator()
|
||||||
@AppStorage("has_seen_suggested_users") private var hasSeenOnboardingSuggestions = false
|
@AppStorage("has_seen_suggested_users") private var hasSeenOnboardingSuggestions = false
|
||||||
@@ -131,7 +134,7 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case .home:
|
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:
|
case .notifications:
|
||||||
NotificationsView(state: damus, notifications: home.notifications, subtitle: $menu_subtitle)
|
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)
|
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)
|
.navigationBarTitle(timeline_name(selected_timeline), displayMode: .inline)
|
||||||
|
.toolbar(selected_timeline != .home ? .visible : .hidden)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .principal) {
|
ToolbarItem(placement: .principal) {
|
||||||
VStack {
|
VStack {
|
||||||
if selected_timeline == .home {
|
timelineNavItem
|
||||||
Image("damus-home")
|
.opacity(isSideBarOpened ? 0 : 1)
|
||||||
.resizable()
|
.animation(isSideBarOpened ? .none : .default, value: isSideBarOpened)
|
||||||
.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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -237,9 +231,11 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.background(DamusColors.adaptableWhite)
|
||||||
|
.edgesIgnoringSafeArea(selected_timeline != .home ? [] : [.top, .bottom])
|
||||||
.tabViewStyle(.page(indexDisplayMode: .never))
|
.tabViewStyle(.page(indexDisplayMode: .never))
|
||||||
.overlay(
|
.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
|
.navigationDestination(for: Route.self) { route in
|
||||||
route.view(navigationCoordinator: navigationCoordinator, damusState: damus_state!)
|
route.view(navigationCoordinator: navigationCoordinator, damusState: damus_state!)
|
||||||
@@ -249,13 +245,25 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationViewStyle(.stack)
|
.navigationViewStyle(.stack)
|
||||||
|
.overlay(alignment: .bottom) {
|
||||||
if !hide_bar {
|
if !hide_bar {
|
||||||
TabBar(nstatus: home.notification_status, selected: $selected_timeline, settings: damus.settings, action: switch_timeline)
|
if !isSideBarOpened {
|
||||||
.padding([.bottom], 8)
|
TabBar(nstatus: home.notification_status, navIsAtRoot: navIsAtRoot(), selected: $selected_timeline, headerOffset: $headerOffset, settings: damus.settings, action: switch_timeline)
|
||||||
.background(Color(uiColor: .systemBackground).ignoresSafeArea())
|
.padding([.bottom], 8)
|
||||||
} else {
|
.background(selected_timeline != .home || (selected_timeline == .home && !self.navIsAtRoot()) ? DamusColors.adaptableWhite : DamusColors.adaptableWhite.opacity(abs(1.25 - (abs(headerOffset/100.0)))))
|
||||||
Text("")
|
.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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
78
damus/Util/Extensions/OffsetExtension.swift
Normal file
78
damus/Util/Extensions/OffsetExtension.swift
Normal 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
|
||||||
|
}
|
||||||
@@ -39,6 +39,7 @@ struct BookmarksView: View {
|
|||||||
ScrollView {
|
ScrollView {
|
||||||
InnerTimelineView(events: EventHolder(events: bookmarks, incoming: []), damus: state, filter: noneFilter)
|
InnerTimelineView(events: EventHolder(events: bookmarks, incoming: []), damus: state, filter: noneFilter)
|
||||||
}
|
}
|
||||||
|
.padding(.bottom, 10 + tabHeight + getSafeAreaBottom())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.switched_timeline)) { _ in
|
.onReceive(handle_notify(.switched_timeline)) { _ in
|
||||||
|
|||||||
@@ -135,6 +135,9 @@ struct ChatroomThreadView: View {
|
|||||||
}
|
}
|
||||||
.padding(.top)
|
.padding(.top)
|
||||||
EndBlock()
|
EndBlock()
|
||||||
|
|
||||||
|
HStack {}
|
||||||
|
.frame(height: tabHeight + getSafeAreaBottom())
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.post), perform: { notify in
|
.onReceive(handle_notify(.post), perform: { notify in
|
||||||
switch notify {
|
switch notify {
|
||||||
|
|||||||
@@ -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)
|
Text(verbatim: VersionInfo.version)
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
Button {
|
Button {
|
||||||
|
|||||||
@@ -66,7 +66,9 @@ struct TabButton: View {
|
|||||||
|
|
||||||
struct TabBar: View {
|
struct TabBar: View {
|
||||||
var nstatus: NotificationStatusModel
|
var nstatus: NotificationStatusModel
|
||||||
|
var navIsAtRoot: Bool
|
||||||
@Binding var selected: Timeline
|
@Binding var selected: Timeline
|
||||||
|
@Binding var headerOffset: CGFloat
|
||||||
|
|
||||||
let settings: UserSettingsStore
|
let settings: UserSettingsStore
|
||||||
let action: (Timeline) -> ()
|
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")
|
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)))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
ForEach(threads, id: \.self) { item in
|
||||||
if case let MuteItem.thread(note_id, _) = item {
|
if case let MuteItem.thread(note_id, _) = item {
|
||||||
if let event = damus_state.events.lookup(note_id) {
|
if let event = damus_state.events.lookup(note_id) {
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ struct EditMetadataView: View {
|
|||||||
})
|
})
|
||||||
.buttonStyle(GradientButtonStyle(padding: 15))
|
.buttonStyle(GradientButtonStyle(padding: 15))
|
||||||
.padding(.horizontal, 10)
|
.padding(.horizontal, 10)
|
||||||
.padding(.bottom, 10)
|
.padding(.bottom, 10 + tabHeight + getSafeAreaBottom())
|
||||||
.disabled(!didChange())
|
.disabled(!didChange())
|
||||||
.opacity(!didChange() ? 0.5 : 1)
|
.opacity(!didChange() ? 0.5 : 1)
|
||||||
.disabled(profileUploadObserver.isLoading || bannerUploadObserver.isLoading)
|
.disabled(profileUploadObserver.isLoading || bannerUploadObserver.isLoading)
|
||||||
|
|||||||
@@ -444,6 +444,7 @@ struct ProfileView: View {
|
|||||||
.zIndex(-yOffset > navbarHeight ? 0 : 1)
|
.zIndex(-yOffset > navbarHeight ? 0 : 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.padding(.bottom, tabHeight + getSafeAreaBottom())
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
.navigationTitle("")
|
.navigationTitle("")
|
||||||
.navigationBarBackButtonHidden()
|
.navigationBarBackButtonHidden()
|
||||||
@@ -485,6 +486,7 @@ struct ProfileView: View {
|
|||||||
PostButtonContainer(is_left_handed: damus_state.settings.left_handed) {
|
PostButtonContainer(is_left_handed: damus_state.settings.left_handed) {
|
||||||
notify(.compose(.posting(.user(profile.pubkey))))
|
notify(.compose(.posting(.user(profile.pubkey))))
|
||||||
}
|
}
|
||||||
|
.padding(.bottom, tabHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ struct ReactionsView: View {
|
|||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
|
.padding(.bottom, tabHeight + getSafeAreaBottom())
|
||||||
.navigationBarTitle(NSLocalizedString("Reactions", comment: "Navigation bar title for Reactions view."))
|
.navigationBarTitle(NSLocalizedString("Reactions", comment: "Navigation bar title for Reactions view."))
|
||||||
.onAppear {
|
.onAppear {
|
||||||
model.subscribe()
|
model.subscribe()
|
||||||
|
|||||||
@@ -13,15 +13,14 @@ struct SignalView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
if signal.signal != signal.max_signal {
|
NavigationLink(value: Route.RelayConfig) {
|
||||||
NavigationLink(value: Route.RelayConfig) {
|
Text("\(signal.signal)/\(signal.max_signal)", comment: "Fraction of how many of the user's relay servers that are operational.")
|
||||||
Text("\(signal.signal)/\(signal.max_signal)", comment: "Fraction of how many of the user's relay servers that are operational.")
|
.font(.callout)
|
||||||
.font(.callout)
|
.foregroundColor(.gray)
|
||||||
.foregroundColor(.gray)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Text("")
|
|
||||||
}
|
}
|
||||||
|
.frame(width:50,height:30)
|
||||||
|
.opacity(signal.signal != signal.max_signal ? 1 : 0)
|
||||||
|
.disabled(signal.signal == signal.max_signal)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ struct RepostsView: View {
|
|||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
|
.padding(.bottom, tabHeight + getSafeAreaBottom())
|
||||||
.navigationBarTitle(NSLocalizedString("Reposts", comment: "Navigation bar title for Reposts view."))
|
.navigationBarTitle(NSLocalizedString("Reposts", comment: "Navigation bar title for Reposts view."))
|
||||||
.onAppear {
|
.onAppear {
|
||||||
model.subscribe()
|
model.subscribe()
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ struct AppearanceSettingsView: View {
|
|||||||
Section(
|
Section(
|
||||||
header: Text("Profiles", comment: "Section title for profile view configuration."),
|
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")
|
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)
|
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)
|
.toggleStyle(.switch)
|
||||||
|
|||||||
@@ -177,7 +177,10 @@ struct NotificationSettingsView: View {
|
|||||||
.toggleStyle(.switch)
|
.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))
|
Toggle(NSLocalizedString("Zaps", comment: "Setting to enable Zap Local Notification"), isOn: indicator_binding(.zaps))
|
||||||
.toggleStyle(.switch)
|
.toggleStyle(.switch)
|
||||||
Toggle(NSLocalizedString("Mentions", comment: "Setting to enable Mention Local Notification"), isOn: indicator_binding(.mentions))
|
Toggle(NSLocalizedString("Mentions", comment: "Setting to enable Mention Local Notification"), isOn: indicator_binding(.mentions))
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import SwiftUI
|
|||||||
struct SideMenuView: View {
|
struct SideMenuView: View {
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
@Binding var isSidebarVisible: Bool
|
@Binding var isSidebarVisible: Bool
|
||||||
|
@Binding var selected: Timeline
|
||||||
@State var confirm_logout: Bool = false
|
@State var confirm_logout: Bool = false
|
||||||
@State private var showQRCode = false
|
@State private var showQRCode = false
|
||||||
|
|
||||||
@@ -200,7 +201,7 @@ struct SideMenuView: View {
|
|||||||
}
|
}
|
||||||
.padding(.top, verticalSpacing)
|
.padding(.top, verticalSpacing)
|
||||||
}
|
}
|
||||||
.padding(.top, -(padding / 2.0))
|
.padding(.top, selected != .home ? -(padding / 2.0) : 30)
|
||||||
.padding([.leading, .trailing, .bottom], padding)
|
.padding([.leading, .trailing, .bottom], padding)
|
||||||
}
|
}
|
||||||
.frame(width: sideBarWidth)
|
.frame(width: sideBarWidth)
|
||||||
@@ -249,6 +250,6 @@ struct SideMenuView: View {
|
|||||||
struct Previews_SideMenuView_Previews: PreviewProvider {
|
struct Previews_SideMenuView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let ds = test_damus_state
|
let ds = test_damus_state
|
||||||
SideMenuView(damus_state: ds, isSidebarVisible: .constant(true))
|
SideMenuView(damus_state: ds, isSidebarVisible: .constant(true), selected: .constant(.home))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,11 +16,14 @@ struct PostingTimelineView: View {
|
|||||||
@State var initialOffset: CGFloat?
|
@State var initialOffset: CGFloat?
|
||||||
@State var offset: CGFloat?
|
@State var offset: CGFloat?
|
||||||
@State var showSearch: Bool = true
|
@State var showSearch: Bool = true
|
||||||
|
@Binding var isSideBarOpened: Bool
|
||||||
@Binding var active_sheet: Sheets?
|
@Binding var active_sheet: Sheets?
|
||||||
@FocusState private var isSearchFocused: Bool
|
@FocusState private var isSearchFocused: Bool
|
||||||
@State private var contentOffset: CGFloat = 0
|
@State private var contentOffset: CGFloat = 0
|
||||||
@State private var indicatorWidth: CGFloat = 0
|
@State private var indicatorWidth: CGFloat = 0
|
||||||
@State private var indicatorPosition: 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
|
@SceneStorage("PostingTimelineView.filter_state") var filter_state : FilterState = .posts_and_replies
|
||||||
|
|
||||||
var mystery: some View {
|
var mystery: some View {
|
||||||
@@ -35,8 +38,63 @@ struct PostingTimelineView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func contentTimelineView(filter: (@escaping (NostrEvent) -> Bool)) -> some 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) {
|
TimelineView<AnyView>(events: home.events, loading: .constant(false), headerHeight: $headerHeight, headerOffset: $headerOffset, damus: damus_state, show_friend_icon: false, filter: filter)
|
||||||
PullDownSearchView(state: damus_state, on_cancel: {})
|
}
|
||||||
|
|
||||||
|
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) {
|
PostButtonContainer(is_left_handed: damus_state.settings.left_handed) {
|
||||||
self.active_sheet = .post(.posting(.none))
|
self.active_sheet = .post(.posting(.none))
|
||||||
}
|
}
|
||||||
|
.padding(.bottom, tabHeight + getSafeAreaBottom())
|
||||||
|
.opacity((abs(1.25 - (abs(headerOffset/100.0)))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.safeAreaInset(edge: .top, spacing: 0) {
|
.overlay(alignment: .top) {
|
||||||
VStack(spacing: 0) {
|
HeaderView()
|
||||||
CustomPicker(tabs: [
|
.anchorPreference(key: HeaderBoundsKey.self, value: .bounds){$0}
|
||||||
(NSLocalizedString("Notes", comment: "Label for filter for seeing only notes (instead of notes and replies)."), FilterState.posts),
|
.overlayPreferenceValue(HeaderBoundsKey.self) { value in
|
||||||
(NSLocalizedString("Notes & Replies", comment: "Label for filter for seeing notes and replies (instead of only notes)."), FilterState.posts_and_replies)
|
GeometryReader{ proxy in
|
||||||
],
|
if let anchor = value{
|
||||||
selection: $filter_state)
|
Color.clear
|
||||||
|
.onAppear {
|
||||||
Divider()
|
headerHeight = proxy[anchor].height
|
||||||
.frame(height: 1)
|
}
|
||||||
}
|
}
|
||||||
.background(DamusColors.adaptableWhite)
|
}
|
||||||
|
}
|
||||||
|
.offset(y: -headerOffset < headerHeight ? headerOffset : (headerOffset < 0 ? headerOffset : 0))
|
||||||
|
.opacity(1.0 - (abs(headerOffset/100.0)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ import SwiftUI
|
|||||||
struct TimelineView<Content: View>: View {
|
struct TimelineView<Content: View>: View {
|
||||||
@ObservedObject var events: EventHolder
|
@ObservedObject var events: EventHolder
|
||||||
@Binding var loading: Bool
|
@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 damus: DamusState
|
||||||
let show_friend_icon: Bool
|
let show_friend_icon: Bool
|
||||||
@@ -17,9 +22,23 @@ struct TimelineView<Content: View>: View {
|
|||||||
let content: Content?
|
let content: Content?
|
||||||
let apply_mute_rules: Bool
|
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) {
|
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.events = events
|
||||||
self._loading = loading
|
self._loading = loading
|
||||||
|
self._headerHeight = .constant(0.0)
|
||||||
|
self._headerOffset = .constant(0.0)
|
||||||
self.damus = damus
|
self.damus = damus
|
||||||
self.show_friend_icon = show_friend_icon
|
self.show_friend_icon = show_friend_icon
|
||||||
self.filter = filter
|
self.filter = filter
|
||||||
@@ -38,20 +57,43 @@ struct TimelineView<Content: View>: View {
|
|||||||
content
|
content
|
||||||
}
|
}
|
||||||
|
|
||||||
Color.white.opacity(0)
|
Color.clear
|
||||||
.id("startblock")
|
.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)
|
InnerTimelineView(events: events, damus: damus, filter: loading ? { _ in true } : filter, apply_mute_rules: self.apply_mute_rules)
|
||||||
.redacted(reason: loading ? .placeholder : [])
|
.redacted(reason: loading ? .placeholder : [])
|
||||||
.shimmer(loading)
|
.shimmer(loading)
|
||||||
.disabled(loading)
|
.disabled(loading)
|
||||||
.background(GeometryReader { proxy -> Color in
|
.padding(.top, headerHeight - getSafeAreaTop())
|
||||||
handle_scroll_queue(proxy, queue: self.events)
|
.offsetY { previous, current in
|
||||||
return Color.clear
|
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")
|
.coordinateSpace(name: "scroll")
|
||||||
.onReceive(handle_notify(.scroll_to_top)) { () in
|
.onReceive(handle_notify(.scroll_to_top)) { () in
|
||||||
events.flush()
|
events.flush()
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ struct ZapsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.padding(.bottom, tabHeight + getSafeAreaBottom())
|
||||||
.navigationBarTitle(NSLocalizedString("Zaps", comment: "Navigation bar title for the Zaps view."))
|
.navigationBarTitle(NSLocalizedString("Zaps", comment: "Navigation bar title for the Zaps view."))
|
||||||
.onAppear {
|
.onAppear {
|
||||||
model.subscribe()
|
model.subscribe()
|
||||||
|
|||||||
Reference in New Issue
Block a user