Merge 'ux: Seamless Timeline'
ericholguin (1):
ux: Seamless Timeline
This commit is contained in:
@@ -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 */,
|
||||
|
||||
@@ -46,7 +46,6 @@ struct CustomPicker<SelectionValue: Hashable>: View {
|
||||
.accentColor(tag == selection ? textColor() : .gray)
|
||||
}
|
||||
}
|
||||
.background(Color(UIColor.systemBackground))
|
||||
}
|
||||
|
||||
func textColor() -> Color {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
InnerTimelineView(events: EventHolder(events: bookmarks, incoming: []), damus: state, filter: noneFilter)
|
||||
}
|
||||
.padding(.bottom, 10 + tabHeight + getSafeAreaBottom())
|
||||
}
|
||||
}
|
||||
.onReceive(handle_notify(.switched_timeline)) { _ in
|
||||
|
||||
@@ -135,6 +135,9 @@ struct ChatroomThreadView: View {
|
||||
}
|
||||
.padding(.top)
|
||||
EndBlock()
|
||||
|
||||
HStack {}
|
||||
.frame(height: tabHeight + getSafeAreaBottom())
|
||||
}
|
||||
.onReceive(handle_notify(.post), perform: { notify in
|
||||
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)
|
||||
.contextMenu {
|
||||
Button {
|
||||
|
||||
@@ -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)))))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user