Merge tag 'v1.7-rc2'
v1.7 Madeira release RC255c26d22cbv1.7 (11)4c8134908cEnable IAP feature for release3ac7d75235Add UI error message when IAP succeeds but receipt verification failsb49a5f4d29Purple: Improve UX on Damus Purple renewals3569919eafAdd Damus Purple impending expiry notification support
This commit is contained in:
181
damus/Views/Notifications/DamusAppNotificationView.swift
Normal file
181
damus/Views/Notifications/DamusAppNotificationView.swift
Normal file
@@ -0,0 +1,181 @@
|
||||
//
|
||||
// DamusAppNotificationView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Daniel D’Aquino on 2024-02-23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
fileprivate let DEEP_WEBSITE_LINK = false
|
||||
|
||||
// TODO: Load products in a more dynamic way (if we move forward with checkout deep linking)
|
||||
fileprivate let PURPLE_ONE_MONTH = "purple_one_month"
|
||||
fileprivate let PURPLE_ONE_YEAR = "purple_one_year"
|
||||
|
||||
struct DamusAppNotificationView: View {
|
||||
let damus_state: DamusState
|
||||
let notification: DamusAppNotification
|
||||
var relative_date: String {
|
||||
let formatter = RelativeDateTimeFormatter()
|
||||
formatter.unitsStyle = .abbreviated
|
||||
if abs(notification.notification_timestamp.timeIntervalSinceNow) > 60 {
|
||||
return formatter.localizedString(for: notification.notification_timestamp, relativeTo: Date.now)
|
||||
}
|
||||
else {
|
||||
return NSLocalizedString("now", comment: "Relative time label that indicates a notification happened now")
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack(spacing: 15) {
|
||||
AppIcon()
|
||||
.frame(width: 50, height: 50)
|
||||
.clipShape(.rect(cornerSize: CGSize(width: 10.0, height: 10.0)))
|
||||
.shadow(radius: 5, y: 5)
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
HStack(alignment: .center, spacing: 3) {
|
||||
Text(NSLocalizedString("Damus", comment: "Name of the app for the title of an internal notification"))
|
||||
.font(.body.weight(.bold))
|
||||
Text("·")
|
||||
.foregroundStyle(.secondary)
|
||||
Text(relative_date)
|
||||
.font(.system(size: 16))
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
HStack(spacing: 3) {
|
||||
Image("check-circle.fill")
|
||||
.resizable()
|
||||
.frame(width: 15, height: 15)
|
||||
Text(NSLocalizedString("Internal app notification", comment: "Badge indicating that a notification is an official internal app notification"))
|
||||
.font(.caption2)
|
||||
.bold()
|
||||
}
|
||||
.foregroundColor(Color.white)
|
||||
.padding(.vertical, 3)
|
||||
.padding(.horizontal, 8)
|
||||
.background(PinkGradient)
|
||||
.cornerRadius(30.0)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.padding(.bottom, 2)
|
||||
switch notification.content {
|
||||
case .purple_impending_expiration(let days_remaining, _):
|
||||
PurpleExpiryNotificationView(damus_state: self.damus_state, days_remaining: days_remaining, expired: false)
|
||||
case .purple_expired(expiry_date: _):
|
||||
PurpleExpiryNotificationView(damus_state: self.damus_state, days_remaining: 0, expired: true)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.top, 5)
|
||||
.padding(.bottom, 15)
|
||||
|
||||
ThiccDivider()
|
||||
}
|
||||
}
|
||||
|
||||
struct PurpleExpiryNotificationView: View {
|
||||
let damus_state: DamusState
|
||||
let days_remaining: Int
|
||||
let expired: Bool
|
||||
|
||||
func try_to_open_verified_checkout(product_template_name: String) {
|
||||
Task {
|
||||
do {
|
||||
let url = try await damus_state.purple.generate_verified_ln_checkout_link(product_template_name: product_template_name)
|
||||
await self.open_url(url: url)
|
||||
}
|
||||
catch {
|
||||
await self.open_url(url: damus_state.purple.environment.purple_landing_page_url().appendingPathComponent("checkout"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func open_url(url: URL) {
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text(self.message())
|
||||
.multilineTextAlignment(.leading)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.font(eventviewsize_to_font(.normal, font_size: damus_state.settings.font_size))
|
||||
if DEEP_WEBSITE_LINK {
|
||||
// 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.
|
||||
HStack {
|
||||
Button(action: {
|
||||
self.try_to_open_verified_checkout(product_template_name: "purple_one_month")
|
||||
}, label: {
|
||||
Text("Renew (1 mo)", comment: "Button to take user to renew subscription for one month")
|
||||
})
|
||||
.buttonStyle(GradientButtonStyle())
|
||||
Button(action: {
|
||||
self.try_to_open_verified_checkout(product_template_name: "purple_one_year")
|
||||
}, label: {
|
||||
Text("Renew (1 yr)", comment: "Button to take user to renew subscription for one year")
|
||||
})
|
||||
.buttonStyle(GradientButtonStyle())
|
||||
}
|
||||
}
|
||||
else {
|
||||
NavigationLink(destination: DamusPurpleView(damus_state: damus_state), label: {
|
||||
HStack {
|
||||
Text("Manage subscription", comment: "Button to take user to manage Damus Purple subscription")
|
||||
.font(eventviewsize_to_font(.normal, font_size: damus_state.settings.font_size))
|
||||
Image("arrow-right")
|
||||
.font(eventviewsize_to_font(.normal, font_size: damus_state.settings.font_size))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func message() -> String {
|
||||
if expired == true {
|
||||
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.")
|
||||
}
|
||||
if days_remaining == 1 {
|
||||
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.")
|
||||
}
|
||||
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.")
|
||||
return String(format: message_format, String(days_remaining))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// `AppIcon` code from: https://stackoverflow.com/a/65153628 and licensed with CC BY-SA 4.0 with the following modifications:
|
||||
// - Made image resizable using `.resizable()`
|
||||
extension Bundle {
|
||||
var iconFileName: String? {
|
||||
guard let icons = infoDictionary?["CFBundleIcons"] as? [String: Any],
|
||||
let primaryIcon = icons["CFBundlePrimaryIcon"] as? [String: Any],
|
||||
let iconFiles = primaryIcon["CFBundleIconFiles"] as? [String],
|
||||
let iconFileName = iconFiles.last
|
||||
else { return nil }
|
||||
return iconFileName
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate struct AppIcon: View {
|
||||
var body: some View {
|
||||
Bundle.main.iconFileName
|
||||
.flatMap { UIImage(named: $0) }
|
||||
.map { Image(uiImage: $0).resizable() }
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
VStack {
|
||||
ThiccDivider()
|
||||
DamusAppNotificationView(damus_state: test_damus_state, notification: .init(content: .purple_impending_expiration(days_remaining: 3, expiry_date: 1709156602), timestamp: Date.now))
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
DamusAppNotificationView(damus_state: test_damus_state, notification: .init(content: .purple_expired(expiry_date: 1709156602), timestamp: Date.now))
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import SwiftUI
|
||||
enum ShowItem {
|
||||
case show(NostrEvent?)
|
||||
case dontshow(NostrEvent?)
|
||||
case show_damus_app_notification(DamusAppNotification)
|
||||
}
|
||||
|
||||
func notification_item_event(events: EventCache, notif: NotificationItem) -> ShowItem {
|
||||
@@ -24,6 +25,8 @@ func notification_item_event(events: EventCache, notif: NotificationItem) -> Sho
|
||||
return .dontshow(events.lookup(evid))
|
||||
case .profile_zap:
|
||||
return .show(nil)
|
||||
case .damus_app_notification(let app_notification):
|
||||
return .show_damus_app_notification(app_notification)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +66,8 @@ struct NotificationItemView: View {
|
||||
EventView(damus: state, event: ev, options: options)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
case .damus_app_notification(let notification):
|
||||
DamusAppNotificationView(damus_state: state, notification: notification)
|
||||
}
|
||||
|
||||
ThiccDivider()
|
||||
@@ -79,6 +84,8 @@ struct NotificationItemView: View {
|
||||
if let ev {
|
||||
Item(ev)
|
||||
}
|
||||
case .show_damus_app_notification(let notification):
|
||||
DamusAppNotificationView(damus_state: state, notification: notification)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ struct DamusPurpleNewUserOnboardingView: View {
|
||||
guard let account = try? await damus_state.purple.fetch_account(pubkey: damus_state.pubkey), account.active else {
|
||||
return
|
||||
}
|
||||
// Let's mark onboarding as "shown"
|
||||
damus_state.purple.onboarding_status.onboarding_was_shown = true
|
||||
// Let's notify other views across SwiftUI to update our user's Purple status.
|
||||
notify(.purple_account_update(account))
|
||||
}
|
||||
|
||||
@@ -144,6 +144,12 @@ struct DamusPurpleView: View, DamusPurpleStoreKitManagerDelegate {
|
||||
// If account is no longer active or was purchased via IAP, then show IAP purchase/manage options
|
||||
if let account_uuid {
|
||||
DamusPurpleView.IAPProductStateView(products: products, purchased: purchased, account_uuid: account_uuid, subscribe: subscribe)
|
||||
if let iap_error {
|
||||
Text(String(format: NSLocalizedString("There has been an unexpected error with the in-app purchase. Please try again later or contact support@damus.io. Error: %@", comment: "In-app purchase error message for the user"), iap_error))
|
||||
.foregroundStyle(.red)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
else {
|
||||
ProgressView()
|
||||
|
||||
Reference in New Issue
Block a user