mirror of
git://jb55.com/damus
synced 2024-10-06 11:43:21 +00:00
Merge remote-tracking branch 'oleg/custom-profile-navbar'
Changelog-Added: Improved profile navbar
This commit is contained in:
commit
c100c6db47
@ -127,14 +127,6 @@ struct ImageView: View {
|
|||||||
@State private var selectedIndex = 0
|
@State private var selectedIndex = 0
|
||||||
@State var showMenu = true
|
@State var showMenu = true
|
||||||
|
|
||||||
var safeAreaInsets: UIEdgeInsets? {
|
|
||||||
return UIApplication
|
|
||||||
.shared
|
|
||||||
.connectedScenes
|
|
||||||
.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
|
|
||||||
.first { $0.isKeyWindow }?.safeAreaInsets
|
|
||||||
}
|
|
||||||
|
|
||||||
var navBarView: some View {
|
var navBarView: some View {
|
||||||
VStack {
|
VStack {
|
||||||
HStack {
|
HStack {
|
||||||
@ -180,8 +172,8 @@ struct ImageView: View {
|
|||||||
ZoomableScrollView {
|
ZoomableScrollView {
|
||||||
ImageContainerView(url: urls[index])
|
ImageContainerView(url: urls[index])
|
||||||
.aspectRatio(contentMode: .fit)
|
.aspectRatio(contentMode: .fit)
|
||||||
.padding(.top, safeAreaInsets?.top)
|
.padding(.top, Theme.safeAreaInsets?.top)
|
||||||
.padding(.bottom, safeAreaInsets?.bottom)
|
.padding(.bottom, Theme.safeAreaInsets?.bottom)
|
||||||
}
|
}
|
||||||
.modifier(SwipeToDismissModifier(minDistance: 50, onDismiss: {
|
.modifier(SwipeToDismissModifier(minDistance: 50, onDismiss: {
|
||||||
presentationMode.wrappedValue.dismiss()
|
presentationMode.wrappedValue.dismiss()
|
||||||
@ -210,7 +202,7 @@ struct ImageView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.animation(.easeInOut, value: showMenu)
|
.animation(.easeInOut, value: showMenu)
|
||||||
.padding(.bottom, safeAreaInsets?.bottom)
|
.padding(.bottom, Theme.safeAreaInsets?.bottom)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,4 +25,12 @@ class Theme {
|
|||||||
|
|
||||||
UINavigationBar.appearance().tintColor = tintColor ?? titleColor ?? .black
|
UINavigationBar.appearance().tintColor = tintColor ?? titleColor ?? .black
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static var safeAreaInsets: UIEdgeInsets? {
|
||||||
|
return UIApplication
|
||||||
|
.shared
|
||||||
|
.connectedScenes
|
||||||
|
.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
|
||||||
|
.first { $0.isKeyWindow }?.safeAreaInsets
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ struct ProfileNameView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
if let real_name = profile?.display_name {
|
if let real_name = profile?.display_name {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
Text(real_name)
|
Text(real_name)
|
||||||
.font(.title3.weight(.bold))
|
.font(.title3.weight(.bold))
|
||||||
HStack(alignment: .center, spacing: spacing) {
|
HStack(alignment: .center, spacing: spacing) {
|
||||||
@ -30,6 +30,7 @@ struct ProfileNameView: View {
|
|||||||
FollowsYou()
|
FollowsYou()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Spacer()
|
||||||
KeyView(pubkey: pubkey)
|
KeyView(pubkey: pubkey)
|
||||||
.pubkey_context_menu(bech32_pubkey: pubkey)
|
.pubkey_context_menu(bech32_pubkey: pubkey)
|
||||||
}
|
}
|
||||||
|
@ -80,9 +80,24 @@ struct EditButton: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct VisualEffectView: UIViewRepresentable {
|
||||||
|
var effect: UIVisualEffect?
|
||||||
|
|
||||||
|
func makeUIView(context: UIViewRepresentableContext<Self>) -> UIVisualEffectView {
|
||||||
|
UIVisualEffectView()
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIView(_ uiView: UIVisualEffectView, context: UIViewRepresentableContext<Self>) {
|
||||||
|
uiView.effect = effect
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct ProfileView: View {
|
struct ProfileView: View {
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
let zoom_size: CGFloat = 350
|
let pfp_size: CGFloat = 90.0
|
||||||
|
let bannerHeight: CGFloat = 150.0
|
||||||
|
|
||||||
|
static let markdown = Markdown()
|
||||||
|
|
||||||
@State private var selected_tab: ProfileTab = .posts
|
@State private var selected_tab: ProfileTab = .posts
|
||||||
@StateObject var profile: ProfileModel
|
@StateObject var profile: ProfileModel
|
||||||
@ -92,20 +107,12 @@ struct ProfileView: View {
|
|||||||
@State var is_zoomed: Bool = false
|
@State var is_zoomed: Bool = false
|
||||||
@State var show_share_sheet: Bool = false
|
@State var show_share_sheet: Bool = false
|
||||||
@State var action_sheet_presented: Bool = false
|
@State var action_sheet_presented: Bool = false
|
||||||
|
@State var yOffset: CGFloat = 0
|
||||||
|
|
||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) var dismiss
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
@Environment(\.openURL) var openURL
|
@Environment(\.openURL) var openURL
|
||||||
|
@Environment(\.presentationMode) var presentationMode
|
||||||
// We just want to have a white "< Home" text here, however,
|
|
||||||
// setting the initialiser is causing issues, and it's late.
|
|
||||||
// Ref: https://blog.techchee.com/navigation-bar-title-style-color-and-custom-back-button-in-swiftui/
|
|
||||||
/*
|
|
||||||
init(damus_state: DamusState, zoom_size: CGFloat = 350) {
|
|
||||||
self.damus_state = damus_state
|
|
||||||
self.zoom_size = zoom_size
|
|
||||||
Theme.navigationBarColors(background: nil, titleColor: .white, tintColor: nil)
|
|
||||||
}*/
|
|
||||||
|
|
||||||
func fillColor() -> Color {
|
func fillColor() -> Color {
|
||||||
colorScheme == .light ? Color("DamusLightGrey") : Color("DamusDarkGrey")
|
colorScheme == .light ? Color("DamusLightGrey") : Color("DamusDarkGrey")
|
||||||
@ -115,7 +122,98 @@ struct ProfileView: View {
|
|||||||
colorScheme == .light ? Color("DamusWhite") : Color("DamusBlack")
|
colorScheme == .light ? Color("DamusWhite") : Color("DamusBlack")
|
||||||
}
|
}
|
||||||
|
|
||||||
func LNButton(lnurl: String, profile: Profile) -> some View {
|
func bannerBlurViewOpacity() -> Double {
|
||||||
|
let progress = -(yOffset + navbarHeight) / 100
|
||||||
|
return Double(-yOffset > navbarHeight ? progress : 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
var bannerSection: some View {
|
||||||
|
GeometryReader { proxy -> AnyView in
|
||||||
|
|
||||||
|
let minY = proxy.frame(in: .global).minY
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.yOffset = minY
|
||||||
|
}
|
||||||
|
|
||||||
|
return AnyView(
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
ZStack {
|
||||||
|
BannerImageView(pubkey: profile.pubkey, profiles: damus_state.profiles)
|
||||||
|
.aspectRatio(contentMode: .fill)
|
||||||
|
.frame(width: proxy.size.width, height: minY > 0 ? bannerHeight + minY : bannerHeight)
|
||||||
|
.clipped()
|
||||||
|
|
||||||
|
VisualEffectView(effect: UIBlurEffect(style: .systemUltraThinMaterial)).opacity(bannerBlurViewOpacity())
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider().opacity(bannerBlurViewOpacity())
|
||||||
|
}
|
||||||
|
.frame(height: minY > 0 ? bannerHeight + minY : nil)
|
||||||
|
.offset(y: minY > 0 ? -minY : -minY < navbarHeight ? 0 : -minY - navbarHeight)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
.frame(height: bannerHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
var navbarHeight: CGFloat {
|
||||||
|
return 100.0 - (Theme.safeAreaInsets?.top ?? 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
func navImage(systemImage: String) -> some View {
|
||||||
|
Image(systemName: systemImage)
|
||||||
|
.frame(width: 33, height: 33)
|
||||||
|
.background(Color.black.opacity(0.6))
|
||||||
|
.clipShape(Circle())
|
||||||
|
}
|
||||||
|
|
||||||
|
var navBackButton: some View {
|
||||||
|
Button {
|
||||||
|
presentationMode.wrappedValue.dismiss()
|
||||||
|
} label: {
|
||||||
|
navImage(systemImage: "chevron.left")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var navActionSheetButton: some View {
|
||||||
|
Button(action: {
|
||||||
|
action_sheet_presented = true
|
||||||
|
}) {
|
||||||
|
navImage(systemImage: "ellipsis")
|
||||||
|
}
|
||||||
|
.confirmationDialog(NSLocalizedString("Actions", comment: "Title for confirmation dialog to either share, report, or block a profile."), isPresented: $action_sheet_presented) {
|
||||||
|
Button(NSLocalizedString("Share", comment: "Button to share the link to a profile.")) {
|
||||||
|
show_share_sheet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only allow reporting if logged in with private key and the currently viewed profile is not the logged in profile.
|
||||||
|
if profile.pubkey != damus_state.pubkey && damus_state.is_privkey_user {
|
||||||
|
Button(NSLocalizedString("Report", comment: "Button to report a profile."), role: .destructive) {
|
||||||
|
let target: ReportTarget = .user(profile.pubkey)
|
||||||
|
notify(.report, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(NSLocalizedString("Block", comment: "Button to block a profile."), role: .destructive) {
|
||||||
|
notify(.block, profile.pubkey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var customNavbar: some View {
|
||||||
|
HStack {
|
||||||
|
navBackButton
|
||||||
|
Spacer()
|
||||||
|
navActionSheetButton
|
||||||
|
}
|
||||||
|
.padding(.top, 5)
|
||||||
|
.padding(.horizontal)
|
||||||
|
.accentColor(Color("DamusWhite"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func lnButton(lnurl: String, profile: Profile) -> some View {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
if damus_state.settings.show_wallet_selector {
|
if damus_state.settings.show_wallet_selector {
|
||||||
showing_select_wallet = true
|
showing_select_wallet = true
|
||||||
@ -139,46 +237,8 @@ struct ProfileView: View {
|
|||||||
SelectWalletView(showingSelectWallet: $showing_select_wallet, our_pubkey: damus_state.pubkey, invoice: lnurl)
|
SelectWalletView(showingSelectWallet: $showing_select_wallet, our_pubkey: damus_state.pubkey, invoice: lnurl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static let markdown = Markdown()
|
|
||||||
|
|
||||||
var ActionSheetButton: some View {
|
var dmButton: some View {
|
||||||
Button(action: {
|
|
||||||
action_sheet_presented = true
|
|
||||||
}) {
|
|
||||||
Image(systemName: "ellipsis.circle")
|
|
||||||
.profile_button_style(scheme: colorScheme)
|
|
||||||
}
|
|
||||||
.confirmationDialog(NSLocalizedString("Actions", comment: "Title for confirmation dialog to either share, report, or block a profile."), isPresented: $action_sheet_presented) {
|
|
||||||
Button(NSLocalizedString("Share", comment: "Button to share the link to a profile.")) {
|
|
||||||
show_share_sheet = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only allow reporting if logged in with private key and the currently viewed profile is not the logged in profile.
|
|
||||||
if profile.pubkey != damus_state.pubkey && damus_state.is_privkey_user {
|
|
||||||
Button(NSLocalizedString("Report", comment: "Button to report a profile."), role: .destructive) {
|
|
||||||
let target: ReportTarget = .user(profile.pubkey)
|
|
||||||
notify(.report, target)
|
|
||||||
}
|
|
||||||
|
|
||||||
Button(NSLocalizedString("Block", comment: "Button to block a profile."), role: .destructive) {
|
|
||||||
notify(.block, profile.pubkey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var ShareButton: some View {
|
|
||||||
Button(action: {
|
|
||||||
show_share_sheet = true
|
|
||||||
}) {
|
|
||||||
Image(systemName: "square.and.arrow.up.circle")
|
|
||||||
.profile_button_style(scheme: colorScheme)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var DMButton: some View {
|
|
||||||
let dm_model = damus_state.dms.lookup_or_create(profile.pubkey)
|
let dm_model = damus_state.dms.lookup_or_create(profile.pubkey)
|
||||||
let dmview = DMChatView(damus_state: damus_state, pubkey: profile.pubkey)
|
let dmview = DMChatView(damus_state: damus_state, pubkey: profile.pubkey)
|
||||||
.environmentObject(dm_model)
|
.environmentObject(dm_model)
|
||||||
@ -187,44 +247,17 @@ struct ProfileView: View {
|
|||||||
.profile_button_style(scheme: colorScheme)
|
.profile_button_style(scheme: colorScheme)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getScrollOffset(_ geometry: GeometryProxy) -> CGFloat {
|
|
||||||
geometry.frame(in: .global).minY
|
|
||||||
}
|
|
||||||
|
|
||||||
private func getHeightForHeaderImage(_ geometry: GeometryProxy) -> CGFloat {
|
|
||||||
let offset = getScrollOffset(geometry)
|
|
||||||
let imageHeight = 150.0
|
|
||||||
|
|
||||||
if offset > 0 {
|
|
||||||
return imageHeight + offset
|
|
||||||
}
|
|
||||||
|
|
||||||
return imageHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
private func getOffsetForHeaderImage(_ geometry: GeometryProxy) -> CGFloat {
|
|
||||||
let offset = getScrollOffset(geometry)
|
|
||||||
|
|
||||||
// Image was pulled down
|
|
||||||
if offset > 0 {
|
|
||||||
return -offset
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func ActionSection(profile_data: Profile?) -> some View {
|
func actionSection(profile_data: Profile?) -> some View {
|
||||||
return Group {
|
return Group {
|
||||||
ActionSheetButton
|
|
||||||
|
|
||||||
if let profile = profile_data {
|
if let profile = profile_data {
|
||||||
if let lnurl = profile.lnurl, lnurl != "" {
|
if let lnurl = profile.lnurl, lnurl != "" {
|
||||||
LNButton(lnurl: lnurl, profile: profile)
|
lnButton(lnurl: lnurl, profile: profile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DMButton
|
dmButton
|
||||||
|
|
||||||
if profile.pubkey != damus_state.pubkey {
|
if profile.pubkey != damus_state.pubkey {
|
||||||
FollowButtonView(
|
FollowButtonView(
|
||||||
@ -241,110 +274,42 @@ struct ProfileView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NameSection(profile_data: Profile?) -> some View {
|
func pfpOffset() -> CGFloat {
|
||||||
|
let progress = -yOffset / navbarHeight
|
||||||
|
let offset = (pfp_size / 4.0) * (progress < 1.0 ? progress : 1)
|
||||||
|
return offset > 0 ? offset : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func pfpScale() -> CGFloat {
|
||||||
|
let progress = -yOffset / navbarHeight
|
||||||
|
let scale = 1.0 - (0.5 * (progress < 1.0 ? progress : 1))
|
||||||
|
return scale < 1 ? scale : 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func nameSection(profile_data: Profile?) -> some View {
|
||||||
return Group {
|
return Group {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
ProfilePicView(pubkey: profile.pubkey, size: pfp_size, highlight: .custom(imageBorderColor(), 4.0), profiles: damus_state.profiles)
|
ProfilePicView(pubkey: profile.pubkey, size: pfp_size, highlight: .custom(imageBorderColor(), 4.0), profiles: damus_state.profiles)
|
||||||
|
.padding(.top, -(pfp_size / 2.0))
|
||||||
|
.offset(y: pfpOffset())
|
||||||
|
.scaleEffect(pfpScale())
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
is_zoomed.toggle()
|
is_zoomed.toggle()
|
||||||
}
|
}
|
||||||
.fullScreenCover(isPresented: $is_zoomed) {
|
.fullScreenCover(isPresented: $is_zoomed) {
|
||||||
ProfileZoomView(pubkey: profile.pubkey, profiles: damus_state.profiles) }
|
ProfileZoomView(pubkey: profile.pubkey, profiles: damus_state.profiles) }
|
||||||
.offset(y: -(pfp_size/2.0)) // Increase if set a frame
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
ActionSection(profile_data: profile_data)
|
actionSection(profile_data: profile_data)
|
||||||
.offset(y: -15.0) // Increase if set a frame
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let follows_you = profile.follows(pubkey: damus_state.pubkey)
|
let follows_you = profile.follows(pubkey: damus_state.pubkey)
|
||||||
ProfileNameView(pubkey: profile.pubkey, profile: profile_data, follows_you: follows_you, damus: damus_state)
|
ProfileNameView(pubkey: profile.pubkey, profile: profile_data, follows_you: follows_you, damus: damus_state)
|
||||||
//.padding(.bottom)
|
|
||||||
.padding(.top,-(pfp_size/2.0))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var pfp_size: CGFloat {
|
var followersCount: some View {
|
||||||
return 90.0
|
|
||||||
}
|
|
||||||
|
|
||||||
var TopSection: some View {
|
|
||||||
ZStack(alignment: .top) {
|
|
||||||
GeometryReader { geometry in
|
|
||||||
BannerImageView(pubkey: profile.pubkey, profiles: damus_state.profiles)
|
|
||||||
.aspectRatio(contentMode: .fill)
|
|
||||||
.frame(width: geometry.size.width, height: self.getHeightForHeaderImage(geometry))
|
|
||||||
.clipped()
|
|
||||||
.offset(x: 0, y: self.getOffsetForHeaderImage(geometry))
|
|
||||||
|
|
||||||
}.frame(height: BANNER_HEIGHT)
|
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 8.0) {
|
|
||||||
let profile_data = damus_state.profiles.lookup(id: profile.pubkey)
|
|
||||||
|
|
||||||
NameSection(profile_data: profile_data)
|
|
||||||
|
|
||||||
Text(ProfileView.markdown.process(profile_data?.about ?? ""))
|
|
||||||
.font(.subheadline).textSelection(.enabled)
|
|
||||||
|
|
||||||
if let url = profile_data?.website_url {
|
|
||||||
WebsiteLink(url: url)
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
|
||||||
|
|
||||||
HStack {
|
|
||||||
if let contact = profile.contacts {
|
|
||||||
let contacts = contact.referenced_pubkeys.map { $0.ref_id }
|
|
||||||
let following_model = FollowingModel(damus_state: damus_state, contacts: contacts)
|
|
||||||
NavigationLink(destination: FollowingView(damus_state: damus_state, following: following_model, whos: profile.pubkey)) {
|
|
||||||
HStack {
|
|
||||||
Text("\(Text(String("\(profile.following)")).font(.subheadline.weight(.medium))) \(Text("Following", comment: "Part of a larger sentence to describe how many profiles a user is following.").font(.subheadline).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many profiles a user is following. In source English, the first variable is the number of profiles being followed, and the second variable is 'Following'.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.buttonStyle(PlainButtonStyle())
|
|
||||||
}
|
|
||||||
let fview = FollowersView(damus_state: damus_state, whos: profile.pubkey)
|
|
||||||
.environmentObject(followers)
|
|
||||||
if followers.contacts != nil {
|
|
||||||
NavigationLink(destination: fview) {
|
|
||||||
FollowersCount
|
|
||||||
}
|
|
||||||
.buttonStyle(PlainButtonStyle())
|
|
||||||
} else {
|
|
||||||
FollowersCount
|
|
||||||
.onTapGesture {
|
|
||||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
|
||||||
followers.contacts = []
|
|
||||||
followers.subscribe()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let relays = profile.relays {
|
|
||||||
// Only open relay config view if the user is logged in with private key and they are looking at their own profile.
|
|
||||||
let relay_text = Text("\(Text(String("\(relays.keys.count)")).font(.subheadline.weight(.medium))) \(Text(String(format: NSLocalizedString("relays_count", comment: "Part of a larger sentence to describe how many relay servers a user is connected."), relays.keys.count)).font(.subheadline).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many relay servers a user is connected. In source English, the first variable is the number of relay servers, and the second variable is 'Relay' or 'Relays'.")
|
|
||||||
if profile.pubkey == damus_state.pubkey && damus_state.is_privkey_user {
|
|
||||||
NavigationLink(destination: RelayConfigView(state: damus_state)) {
|
|
||||||
relay_text
|
|
||||||
}
|
|
||||||
.buttonStyle(PlainButtonStyle())
|
|
||||||
} else {
|
|
||||||
NavigationLink(destination: UserRelaysView(state: damus_state, pubkey: profile.pubkey, relays: Array(relays.keys).sorted())) {
|
|
||||||
relay_text
|
|
||||||
}
|
|
||||||
.buttonStyle(PlainButtonStyle())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.horizontal,18)
|
|
||||||
//.offset(y:120)
|
|
||||||
.padding(.top,150)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var FollowersCount: some View {
|
|
||||||
HStack {
|
HStack {
|
||||||
if followers.count == nil {
|
if followers.count == nil {
|
||||||
Image(systemName: "square.and.arrow.down")
|
Image(systemName: "square.and.arrow.down")
|
||||||
@ -357,20 +322,91 @@ struct ProfileView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var aboutSection: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 8.0) {
|
||||||
|
let profile_data = damus_state.profiles.lookup(id: profile.pubkey)
|
||||||
|
|
||||||
|
nameSection(profile_data: profile_data)
|
||||||
|
|
||||||
|
Text(ProfileView.markdown.process(profile_data?.about ?? ""))
|
||||||
|
.font(.subheadline).textSelection(.enabled)
|
||||||
|
|
||||||
|
if let url = profile_data?.website_url {
|
||||||
|
WebsiteLink(url: url)
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
if let contact = profile.contacts {
|
||||||
|
let contacts = contact.referenced_pubkeys.map { $0.ref_id }
|
||||||
|
let following_model = FollowingModel(damus_state: damus_state, contacts: contacts)
|
||||||
|
NavigationLink(destination: FollowingView(damus_state: damus_state, following: following_model, whos: profile.pubkey)) {
|
||||||
|
HStack {
|
||||||
|
Text("\(Text("\(profile.following)", comment: "Number of profiles a user is following.").font(.subheadline.weight(.medium))) \(Text("Following", comment: "Part of a larger sentence to describe how many profiles a user is following.").font(.subheadline).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many profiles a user is following. In source English, the first variable is the number of profiles being followed, and the second variable is 'Following'.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
}
|
||||||
|
let fview = FollowersView(damus_state: damus_state, whos: profile.pubkey)
|
||||||
|
.environmentObject(followers)
|
||||||
|
if followers.contacts != nil {
|
||||||
|
NavigationLink(destination: fview) {
|
||||||
|
followersCount
|
||||||
|
}
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
} else {
|
||||||
|
followersCount
|
||||||
|
.onTapGesture {
|
||||||
|
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||||
|
followers.contacts = []
|
||||||
|
followers.subscribe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let relays = profile.relays {
|
||||||
|
// Only open relay config view if the user is logged in with private key and they are looking at their own profile.
|
||||||
|
let relay_text = Text("\(Text("\(relays.keys.count)", comment: "Number of relay servers a user is connected.").font(.subheadline.weight(.medium))) \(Text(String(format: NSLocalizedString("relays_count", comment: "Part of a larger sentence to describe how many relay servers a user is connected."), relays.keys.count)).font(.subheadline).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many relay servers a user is connected. In source English, the first variable is the number of relay servers, and the second variable is 'Relay' or 'Relays'.")
|
||||||
|
if profile.pubkey == damus_state.pubkey && damus_state.is_privkey_user {
|
||||||
|
NavigationLink(destination: RelayConfigView(state: damus_state)) {
|
||||||
|
relay_text
|
||||||
|
}
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
} else {
|
||||||
|
NavigationLink(destination: UserRelaysView(state: damus_state, pubkey: profile.pubkey, relays: Array(relays.keys).sorted())) {
|
||||||
|
relay_text
|
||||||
|
}
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
VStack(alignment: .leading) {
|
ScrollView(.vertical) {
|
||||||
ScrollView {
|
VStack(spacing: 0) {
|
||||||
TopSection
|
bannerSection
|
||||||
|
.zIndex(1)
|
||||||
Divider()
|
|
||||||
|
|
||||||
InnerTimelineView(events: $profile.events, damus: damus_state, show_friend_icon: false, filter: { _ in true })
|
VStack() {
|
||||||
|
aboutSection
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
|
||||||
|
InnerTimelineView(events: $profile.events, damus: damus_state, show_friend_icon: false, filter: { _ in true })
|
||||||
|
}
|
||||||
|
.padding(.horizontal, Theme.safeAreaInsets?.left)
|
||||||
|
.zIndex(-yOffset > navbarHeight ? 0 : 1)
|
||||||
}
|
}
|
||||||
.frame(maxHeight: .infinity, alignment: .topLeading)
|
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .topLeading)
|
.ignoresSafeArea()
|
||||||
|
.navigationTitle("")
|
||||||
|
.navigationBarHidden(true)
|
||||||
|
.overlay(customNavbar, alignment: .top)
|
||||||
.onReceive(handle_notify(.switched_timeline)) { _ in
|
.onReceive(handle_notify(.switched_timeline)) { _ in
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
@ -390,7 +426,6 @@ struct ProfileView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.ignoresSafeArea()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -403,7 +438,6 @@ struct ProfileView_Previews: PreviewProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func test_damus_state() -> DamusState {
|
func test_damus_state() -> DamusState {
|
||||||
let pubkey = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"
|
let pubkey = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"
|
||||||
let damus = DamusState.empty
|
let damus = DamusState.empty
|
||||||
|
@ -5,84 +5,99 @@
|
|||||||
// Created by scoder1747 on 12/27/22.
|
// Created by scoder1747 on 12/27/22.
|
||||||
//
|
//
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
private struct ImageContainerView: View {
|
||||||
|
|
||||||
|
@ObservedObject var imageModel: KFImageModel
|
||||||
|
|
||||||
|
@State private var image: UIImage?
|
||||||
|
@State private var showShareSheet = false
|
||||||
|
|
||||||
|
init(url: URL?) {
|
||||||
|
self.imageModel = KFImageModel(
|
||||||
|
url: url,
|
||||||
|
fallbackUrl: nil,
|
||||||
|
maxByteSize: 2000000, // 2 MB
|
||||||
|
downsampleSize: CGSize(width: 400, height: 400)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct ImageHandler: ImageModifier {
|
||||||
|
@Binding var handler: UIImage?
|
||||||
|
|
||||||
|
func modify(_ image: UIImage) -> UIImage {
|
||||||
|
handler = image
|
||||||
|
return image
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
|
||||||
|
KFAnimatedImage(imageModel.url)
|
||||||
|
.callbackQueue(.dispatch(.global(qos: .background)))
|
||||||
|
.processingQueue(.dispatch(.global(qos: .background)))
|
||||||
|
.cacheOriginalImage()
|
||||||
|
.configure { view in
|
||||||
|
view.framePreloadCount = 1
|
||||||
|
}
|
||||||
|
.scaleFactor(UIScreen.main.scale)
|
||||||
|
.loadDiskFileSynchronously()
|
||||||
|
.fade(duration: 0.1)
|
||||||
|
.imageModifier(ImageHandler(handler: $image))
|
||||||
|
.onFailure { _ in
|
||||||
|
imageModel.downloadFailed()
|
||||||
|
}
|
||||||
|
.id(imageModel.refreshID)
|
||||||
|
.clipShape(Circle())
|
||||||
|
.modifier(ImageContextMenuModifier(url: imageModel.url, image: image, showShareSheet: $showShareSheet))
|
||||||
|
.sheet(isPresented: $showShareSheet) {
|
||||||
|
ShareSheet(activityItems: [imageModel.url])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct ProfileZoomView: View {
|
struct ProfileZoomView: View {
|
||||||
|
|
||||||
@Environment(\.presentationMode) var presentationMode
|
|
||||||
let pubkey: String
|
let pubkey: String
|
||||||
let profiles: Profiles
|
let profiles: Profiles
|
||||||
|
|
||||||
@GestureState private var scaleState: CGFloat = 1
|
@Environment(\.presentationMode) var presentationMode
|
||||||
@GestureState private var offsetState = CGSize.zero
|
|
||||||
|
var navBarView: some View {
|
||||||
@State private var offset = CGSize.zero
|
HStack {
|
||||||
@State private var scale: CGFloat = 1
|
Button(action: {
|
||||||
|
presentationMode.wrappedValue.dismiss()
|
||||||
func resetStatus(){
|
}, label: {
|
||||||
self.offset = CGSize.zero
|
Image(systemName: "xmark")
|
||||||
self.scale = 1
|
.frame(width: 33, height: 33)
|
||||||
}
|
.background(.regularMaterial)
|
||||||
|
.clipShape(Circle())
|
||||||
var zoomGesture: some Gesture {
|
})
|
||||||
MagnificationGesture()
|
|
||||||
.updating($scaleState) { currentState, gestureState, _ in
|
Spacer()
|
||||||
gestureState = currentState
|
|
||||||
}
|
|
||||||
.onEnded { value in
|
|
||||||
scale *= value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var dragGesture: some Gesture {
|
|
||||||
DragGesture()
|
|
||||||
.updating($offsetState) { currentState, gestureState, _ in
|
|
||||||
gestureState = currentState.translation
|
|
||||||
}.onEnded { value in
|
|
||||||
offset.height += value.translation.height
|
|
||||||
offset.width += value.translation.width
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var doubleTapGesture : some Gesture {
|
|
||||||
TapGesture(count: 2).onEnded { value in
|
|
||||||
resetStatus()
|
|
||||||
}
|
}
|
||||||
|
.padding()
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack(alignment: .topLeading) {
|
ZStack {
|
||||||
Color("DamusDarkGrey") // Or Color("DamusBlack")
|
Color(.systemBackground)
|
||||||
.edgesIgnoringSafeArea(.all)
|
.ignoresSafeArea()
|
||||||
|
|
||||||
Button {
|
ZoomableScrollView {
|
||||||
|
ImageContainerView(url: get_profile_url(picture: nil, pubkey: pubkey, profiles: profiles))
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.padding(.top, Theme.safeAreaInsets?.top)
|
||||||
|
.padding(.bottom, Theme.safeAreaInsets?.bottom)
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
.ignoresSafeArea()
|
||||||
|
.modifier(SwipeToDismissModifier(minDistance: 50, onDismiss: {
|
||||||
presentationMode.wrappedValue.dismiss()
|
presentationMode.wrappedValue.dismiss()
|
||||||
} label: {
|
}))
|
||||||
Image(systemName: "xmark")
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.font(.subheadline)
|
|
||||||
.padding(.leading, 20)
|
|
||||||
}
|
|
||||||
.zIndex(1)
|
|
||||||
|
|
||||||
VStack(alignment: .center) {
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
ProfilePicView(pubkey: pubkey, size: 200.0, highlight: .none, profiles: profiles)
|
|
||||||
.padding(100)
|
|
||||||
.scaledToFit()
|
|
||||||
.scaleEffect(self.scale * scaleState)
|
|
||||||
.offset(x: offset.width + offsetState.width, y: offset.height + offsetState.height)
|
|
||||||
.gesture(SimultaneousGesture(zoomGesture, dragGesture))
|
|
||||||
.gesture(doubleTapGesture)
|
|
||||||
.modifier(SwipeToDismissModifier(minDistance: nil, onDismiss: {
|
|
||||||
presentationMode.wrappedValue.dismiss()
|
|
||||||
}))
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.overlay(navBarView, alignment: .top)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user