damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

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 }