DamusAppNotificationView.swift (8335B)
1 // 2 // DamusAppNotificationView.swift 3 // damus 4 // 5 // Created by Daniel D’Aquino on 2024-02-23. 6 // 7 8 import SwiftUI 9 10 fileprivate let DEEP_WEBSITE_LINK = false 11 12 // TODO: Load products in a more dynamic way (if we move forward with checkout deep linking) 13 fileprivate let PURPLE_ONE_MONTH = "purple_one_month" 14 fileprivate let PURPLE_ONE_YEAR = "purple_one_year" 15 16 struct DamusAppNotificationView: View { 17 let damus_state: DamusState 18 let notification: DamusAppNotification 19 var relative_date: String { 20 let formatter = RelativeDateTimeFormatter() 21 formatter.unitsStyle = .abbreviated 22 if abs(notification.notification_timestamp.timeIntervalSinceNow) > 60 { 23 return formatter.localizedString(for: notification.notification_timestamp, relativeTo: Date.now) 24 } 25 else { 26 return NSLocalizedString("now", comment: "Relative time label that indicates a notification happened now") 27 } 28 } 29 30 var body: some View { 31 VStack(spacing: 0) { 32 VStack(alignment: .leading, spacing: 8) { 33 HStack(spacing: 15) { 34 AppIcon() 35 .frame(width: 50, height: 50) 36 .clipShape(.rect(cornerSize: CGSize(width: 10.0, height: 10.0))) 37 .shadow(radius: 5, y: 5) 38 VStack(alignment: .leading, spacing: 5) { 39 HStack(alignment: .center, spacing: 3) { 40 Text("Damus", comment: "Name of the app for the title of an internal notification") 41 .font(.body.weight(.bold)) 42 Text(verbatim: "·") 43 .foregroundStyle(.secondary) 44 Text(relative_date) 45 .font(.system(size: 16)) 46 .foregroundColor(.gray) 47 } 48 HStack(spacing: 3) { 49 Image("check-circle.fill") 50 .resizable() 51 .frame(width: 15, height: 15) 52 Text("Internal app notification", comment: "Badge indicating that a notification is an official internal app notification") 53 .font(.caption2) 54 .bold() 55 } 56 .foregroundColor(Color.white) 57 .padding(.vertical, 3) 58 .padding(.horizontal, 8) 59 .background(PinkGradient) 60 .cornerRadius(30.0) 61 } 62 Spacer() 63 } 64 .padding(.bottom, 2) 65 switch notification.content { 66 case .purple_impending_expiration(let days_remaining, _): 67 PurpleExpiryNotificationView(damus_state: self.damus_state, days_remaining: days_remaining, expired: false) 68 case .purple_expired(expiry_date: _): 69 PurpleExpiryNotificationView(damus_state: self.damus_state, days_remaining: 0, expired: true) 70 } 71 } 72 .padding(.horizontal) 73 .padding(.top, 5) 74 .padding(.bottom, 15) 75 76 ThiccDivider() 77 } 78 } 79 80 struct PurpleExpiryNotificationView: View { 81 let damus_state: DamusState 82 let days_remaining: Int 83 let expired: Bool 84 85 func try_to_open_verified_checkout(product_template_name: String) { 86 Task { 87 do { 88 let url = try await damus_state.purple.generate_verified_ln_checkout_link(product_template_name: product_template_name) 89 await self.open_url(url: url) 90 } 91 catch { 92 await self.open_url(url: damus_state.purple.environment.purple_landing_page_url().appendingPathComponent("checkout")) 93 } 94 } 95 } 96 97 @MainActor 98 func open_url(url: URL) { 99 UIApplication.shared.open(url) 100 } 101 102 var body: some View { 103 VStack(alignment: .leading, spacing: 12) { 104 Text(self.message()) 105 .multilineTextAlignment(.leading) 106 .frame(maxWidth: .infinity, alignment: .leading) 107 .font(eventviewsize_to_font(.normal, font_size: damus_state.settings.font_size)) 108 if DEEP_WEBSITE_LINK { 109 // TODO: It might be better to fetch products from the server instead of hardcoding them here. As of writing this is disabled, so not a big concern. 110 HStack { 111 Button(action: { 112 self.try_to_open_verified_checkout(product_template_name: "purple_one_month") 113 }, label: { 114 Text("Renew (1 mo)", comment: "Button to take user to renew subscription for one month") 115 }) 116 .buttonStyle(GradientButtonStyle()) 117 Button(action: { 118 self.try_to_open_verified_checkout(product_template_name: "purple_one_year") 119 }, label: { 120 Text("Renew (1 yr)", comment: "Button to take user to renew subscription for one year") 121 }) 122 .buttonStyle(GradientButtonStyle()) 123 } 124 } 125 else { 126 NavigationLink(destination: DamusPurpleView(damus_state: damus_state), label: { 127 HStack { 128 Text("Manage subscription", comment: "Button to take user to manage Damus Purple subscription") 129 .font(eventviewsize_to_font(.normal, font_size: damus_state.settings.font_size)) 130 Image("arrow-right") 131 .font(eventviewsize_to_font(.normal, font_size: damus_state.settings.font_size)) 132 } 133 }) 134 } 135 } 136 } 137 138 func message() -> String { 139 if expired == true { 140 return NSLocalizedString("Your Purple subscription has expired. Renew?", comment: "A notification message explaining to the user that their Damus Purple Subscription has expired, prompting them to renew.") 141 } 142 if days_remaining == 1 { 143 return NSLocalizedString("Your Purple subscription expires in 1 day. Renew?", comment: "A notification message explaining to the user that their Damus Purple Subscription is expiring in one day, prompting them to renew.") 144 } 145 let message_format = NSLocalizedString("Your Purple subscription expires in %@ days. Renew?", comment: "A notification message explaining to the user that their Damus Purple Subscription is expiring soon, prompting them to renew.") 146 return String(format: message_format, String(days_remaining)) 147 } 148 } 149 } 150 151 // `AppIcon` code from: https://stackoverflow.com/a/65153628 and licensed with CC BY-SA 4.0 with the following modifications: 152 // - Made image resizable using `.resizable()` 153 extension Bundle { 154 var iconFileName: String? { 155 guard let icons = infoDictionary?["CFBundleIcons"] as? [String: Any], 156 let primaryIcon = icons["CFBundlePrimaryIcon"] as? [String: Any], 157 let iconFiles = primaryIcon["CFBundleIconFiles"] as? [String], 158 let iconFileName = iconFiles.last 159 else { return nil } 160 return iconFileName 161 } 162 } 163 164 fileprivate struct AppIcon: View { 165 var body: some View { 166 Bundle.main.iconFileName 167 .flatMap { UIImage(named: $0) } 168 .map { Image(uiImage: $0).resizable() } 169 } 170 } 171 172 #Preview { 173 VStack { 174 ThiccDivider() 175 DamusAppNotificationView(damus_state: test_damus_state, notification: .init(content: .purple_impending_expiration(days_remaining: 3, expiry_date: 1709156602), timestamp: Date.now)) 176 } 177 } 178 179 #Preview { 180 DamusAppNotificationView(damus_state: test_damus_state, notification: .init(content: .purple_expired(expiry_date: 1709156602), timestamp: Date.now)) 181 }