Files
damus/damus/Views/ProfileView.swift
Terry Yiu d01e7c0595 Fix localization bugs and export localizations
Changelog-Fixes: Fix some localization bugs
Closes: #320
2023-01-14 08:54:43 -08:00

443 lines
18 KiB
Swift
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// ProfileView.swift
// damus
//
// Created by William Casarin on 2022-04-23.
//
import SwiftUI
enum ProfileTab: Hashable {
case posts
case following
}
enum FollowState {
case follows
case following
case unfollowing
case unfollows
}
func follow_btn_txt(_ fs: FollowState) -> String {
switch fs {
case .follows:
return NSLocalizedString("Unfollow", comment: "Button to unfollow a user.")
case .following:
return NSLocalizedString("Following...", comment: "Label to indicate that the user is in the process of following another user.")
case .unfollowing:
return NSLocalizedString("Unfollowing...", comment: "Label to indicate that the user is in the process of unfollowing another user.")
case .unfollows:
return NSLocalizedString("Follow", comment: "Button to follow a user.")
}
}
func follow_btn_enabled_state(_ fs: FollowState) -> Bool {
switch fs {
case .follows:
return true
case .following:
return false
case .unfollowing:
return false
case .unfollows:
return true
}
}
struct ProfileNameView: View {
let pubkey: String
let profile: Profile?
let damus: DamusState
var body: some View {
Group {
if let real_name = profile?.display_name {
VStack(alignment: .leading) {
Text(real_name)
.font(.title3.weight(.bold))
ProfileName(pubkey: pubkey, profile: profile, prefix: "@", damus: damus, show_friend_confirmed: true)
.font(.callout)
.foregroundColor(.gray)
KeyView(pubkey: pubkey)
.pubkey_context_menu(bech32_pubkey: pubkey)
}
} else {
VStack(alignment: .leading) {
ProfileName(pubkey: pubkey, profile: profile, damus: damus, show_friend_confirmed: true)
.font(.title3.weight(.bold))
KeyView(pubkey: pubkey)
.pubkey_context_menu(bech32_pubkey: pubkey)
}
}
}
}
}
struct EditButton: View {
let damus_state: DamusState
@Environment(\.colorScheme) var colorScheme
var body: some View {
NavigationLink(destination: EditMetadataView(damus_state: damus_state)) {
Text("Edit", comment: "Button to edit user's profile.")
.frame(height: 30)
.padding(.horizontal,25)
.font(.caption.weight(.bold))
.foregroundColor(fillColor())
.cornerRadius(24)
.overlay {
RoundedRectangle(cornerRadius: 24)
.stroke(borderColor(), lineWidth: 1)
}
}
}
func fillColor() -> Color {
colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite")
}
func borderColor() -> Color {
colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite")
}
}
struct ProfileView: View {
let damus_state: DamusState
let zoom_size: CGFloat = 350
@State private var selected_tab: ProfileTab = .posts
@StateObject var profile: ProfileModel
@StateObject var followers: FollowersModel
@State private var showingEditProfile = false
@State var showing_select_wallet: Bool = false
@State var is_zoomed: Bool = false
@State var show_share_sheet: Bool = false
@StateObject var user_settings = UserSettingsStore()
@Environment(\.dismiss) var dismiss
@Environment(\.colorScheme) var colorScheme
// 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 {
colorScheme == .light ? Color("DamusLightGrey") : Color("DamusDarkGrey")
}
func imageBorderColor() -> Color {
colorScheme == .light ? Color("DamusWhite") : Color("DamusBlack")
}
func LNButton(lnurl: String, profile: Profile) -> some View {
Button(action: {
if user_settings.show_wallet_selector {
showing_select_wallet = true
} else {
open_with_wallet(wallet: user_settings.default_wallet.model, invoice: lnurl)
}
}) {
Image(systemName: "bolt.circle")
.symbolRenderingMode(.palette)
.foregroundStyle(colorScheme == .dark ? .white : .black, colorScheme == .dark ? .white : .black)
.font(.system(size: 32).weight(.thin))
.contextMenu {
Button {
UIPasteboard.general.string = profile.lnurl ?? ""
} label: {
Label(NSLocalizedString("Copy LNURL", comment: "Context menu option for copying a user's Lightning URL."), systemImage: "doc.on.doc")
}
}
}
.cornerRadius(24)
.sheet(isPresented: $showing_select_wallet, onDismiss: {showing_select_wallet = false}) {
SelectWalletView(showingSelectWallet: $showing_select_wallet, invoice: lnurl)
.environmentObject(user_settings)
}
}
static let markdown = Markdown()
var ShareButton: some View {
Button(action: {
show_share_sheet = true
}) {
Image(systemName: "square.and.arrow.up.circle.fill")
.symbolRenderingMode(.palette)
.font(.system(size: 32))
.padding()
.foregroundStyle(.white, .black, .black.opacity(0.8))
}
}
var DMButton: some View {
let dm_model = damus_state.dms.lookup_or_create(profile.pubkey)
let dmview = DMChatView(damus_state: damus_state, pubkey: profile.pubkey)
.environmentObject(dm_model)
return NavigationLink(destination: dmview) {
Image(systemName: "bubble.left.circle")
.symbolRenderingMode(.palette)
.font(.system(size: 32).weight(.thin))
.foregroundStyle(colorScheme == .dark ? .white : .black, colorScheme == .dark ? .white : .black)
}
}
var TopSection: some View {
ZStack(alignment: .top) {
GeometryReader { geo in
BannerImageView(pubkey: profile.pubkey, profiles: damus_state.profiles)
.aspectRatio(contentMode: .fill)
.frame(width: geo.size.width, height: BANNER_HEIGHT)
.clipped()
ShareButton
.offset(x: geo.size.width - 80.0, y: 50.0 )
}.frame(height: BANNER_HEIGHT)
VStack(alignment: .leading) {
let data = damus_state.profiles.lookup(id: profile.pubkey)
let pfp_size: CGFloat = 90.0
HStack(alignment: .center) {
ProfilePicView(pubkey: profile.pubkey, size: pfp_size, highlight: .custom(imageBorderColor(), 4.0), profiles: damus_state.profiles)
.onTapGesture {
is_zoomed.toggle()
}
.sheet(isPresented: $is_zoomed) {
ProfilePicView(pubkey: profile.pubkey, size: zoom_size, highlight: .none, profiles: damus_state.profiles)
}
.offset(y: -(pfp_size/2.0)) // Increase if set a frame
Spacer()
Group {
if let profile = data {
if let lnurl = profile.lnurl, lnurl != "" {
LNButton(lnurl: lnurl, profile: profile)
}
}
DMButton
if profile.pubkey != damus_state.pubkey {
FollowButtonView(
target: profile.get_follow_target(),
follow_state: damus_state.contacts.follow_state(profile.pubkey)
)
} else if damus_state.keypair.privkey != nil {
NavigationLink(destination: EditMetadataView(damus_state: damus_state)) {
EditButton(damus_state: damus_state)
}
}
}
.offset(y: -15.0) // Increase if set a frame
}
ProfileNameView(pubkey: profile.pubkey, profile: data, damus: damus_state)
//.padding(.bottom)
.padding(.top,-(pfp_size/2.0))
Text(ProfileView.markdown.process(data?.about ?? ""))
.font(.subheadline)
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 {
NavigationLink(destination: UserRelaysView(state: damus_state, pubkey: profile.pubkey, relays: Array(relays.keys).sorted())) {
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'.")
}
.buttonStyle(PlainButtonStyle())
}
}
}
.padding(.horizontal,18)
//.offset(y:120)
.padding(.top,150)
}
}
var FollowersCount: some View {
HStack {
if followers.count == nil {
Image(systemName: "square.and.arrow.down")
Text("Followers", comment: "Label describing followers of a user.")
.font(.subheadline)
.foregroundColor(.gray)
} else {
let followerCount = followers.count!
Text("\(Text("\(followerCount)", comment: "Number of people following a user.").font(.subheadline.weight(.medium))) \(Text(String(format: NSLocalizedString("followers_count", comment: "Part of a larger sentence to describe how many people are following a user."), followerCount)).font(.subheadline).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many people are following a user. In source English, the first variable is the number of followers, and the second variable is 'Follower' or 'Followers'.")
}
}
}
var body: some View {
VStack(alignment: .leading) {
ScrollView {
TopSection
Divider()
InnerTimelineView(events: $profile.events, damus: damus_state, show_friend_icon: false, filter: { _ in true })
}
.frame(maxHeight: .infinity, alignment: .topLeading)
}
.frame(maxWidth: .infinity, alignment: .topLeading)
.onReceive(handle_notify(.switched_timeline)) { _ in
dismiss()
}
.onAppear() {
profile.subscribe()
//followers.subscribe()
}
.onDisappear {
profile.unsubscribe()
followers.unsubscribe()
// our profilemodel needs a bit more help
}
.sheet(isPresented: $show_share_sheet) {
if let npub = bech32_pubkey(profile.pubkey) {
if let url = URL(string: "https://damus.io/" + npub) {
ShareSheet(activityItems: [url])
}
}
}
.ignoresSafeArea()
}
}
struct ProfileView_Previews: PreviewProvider {
static var previews: some View {
let ds = test_damus_state()
let followers = FollowersModel(damus_state: ds, target: ds.pubkey)
let profile_model = ProfileModel(pubkey: ds.pubkey, damus: ds)
ProfileView(damus_state: ds, profile: profile_model, followers: followers)
}
}
func test_damus_state() -> DamusState {
let pubkey = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"
let damus = DamusState(pool: RelayPool(), keypair: Keypair(pubkey: pubkey, privkey: "privkey"), likes: EventCounter(our_pubkey: pubkey), boosts: EventCounter(our_pubkey: pubkey), contacts: Contacts(our_pubkey: pubkey), tips: TipCounter(our_pubkey: pubkey), profiles: Profiles(), dms: DirectMessagesModel(), previews: PreviewCache())
let prof = Profile(name: "damus", display_name: "damus", about: "iOS app!", picture: "https://damus.io/img/logo.png", banner: "", website: "https://damus.io", lud06: nil, lud16: "jb55@sendsats.lol", nip05: "damus.io")
let tsprof = TimestampedProfile(profile: prof, timestamp: 0)
damus.profiles.add(id: pubkey, profile: tsprof)
return damus
}
struct KeyView: View {
let pubkey: String
@Environment(\.colorScheme) var colorScheme
@State private var isCopied = false
func fillColor() -> Color {
colorScheme == .light ? Color("DamusLightGrey") : Color("DamusDarkGrey")
}
func keyColor() -> Color {
colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite")
}
var body: some View {
let bech32 = bech32_pubkey(pubkey) ?? pubkey
HStack {
RoundedRectangle(cornerRadius: 24)
.frame(width: 275, height:22)
.foregroundColor(fillColor())
.overlay(
HStack {
Button {
UIPasteboard.general.string = bech32
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
isCopied = true
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
isCopied = false
}
} label: {
Label(NSLocalizedString("Public Key", comment: "Label indicating that the text is a user's public account key."), systemImage: "key.fill")
.font(.custom("key", size: 12.0))
.labelStyle(IconOnlyLabelStyle())
.foregroundStyle(hex_to_rgb(pubkey))
.symbolRenderingMode(.palette)
}
.padding(.leading,4)
Text(abbrev_pubkey(bech32, amount: 16))
.font(.footnote)
.foregroundColor(keyColor())
.offset(x:-3) // Not sure why this is needed.
}
)
if isCopied != true {
Button {
UIPasteboard.general.string = bech32
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
isCopied = true
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
isCopied = false
}
} label: {
Label {
Text("Public key", comment: "Label indicating that the text is a user's public account key.")
} icon: {
Image("ic-copy")
.contentShape(Rectangle())
.frame(width: 20, height: 20)
}
.labelStyle(IconOnlyLabelStyle())
.symbolRenderingMode(.hierarchical)
}
} else {
HStack {
Image("ic-tick")
.frame(width: 20, height: 20)
Text(NSLocalizedString("Copied", comment: "Label indicating that a user's key was copied."))
.font(.footnote)
.foregroundColor(Color("DamusGreen"))
}
}
}
}
}