Created
April 13, 2024 19:41
-
-
Save Max094Reikeb/595b36ffaeb4ddab5274ad08a2c7bbe0 to your computer and use it in GitHub Desktop.
View to create/modify a subscription
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftData | |
import SwiftUI | |
struct SubscriptionView: View { | |
@Environment(\.dismiss) var dismiss | |
@Environment(\.colorScheme) var colorScheme | |
@Environment(\.modelContext) private var modelContext | |
@Query private var subscriptions: [Subscription] | |
@Query private var categories: [Category] | |
@AppStorage("preferedCurrencyCode") private var preferedCurrencyCode = Currency.preview.first!.code | |
@AppStorage("darkModeSetting") private var darkModeSetting: DarkModeSetting = .system | |
var subscription: Subscription? | |
private var notifications = Notifications() | |
@FocusState private var focusedNameKeyboard: Bool | |
@State private var lastEditedSubscriptionId: UUID? | |
@State var name = "" | |
@State var category: Category? = nil | |
@State var iconName = Icon.emojis.first! | |
@State var iconColor = Icon.IconColor.red | |
@State var referenceBillDate = Date() | |
@State var invoiceTime = InvoiceTime.preview.first! | |
@State var currency = Currency.preview.first! | |
@State var price = 9.99 | |
@State var notification = false | |
@State var showIconView = false | |
@State var showExtendedDate = false | |
@State var showCycleOption = false | |
@State var showPrice = false | |
let formatter: NumberFormatter = { | |
let formatter = NumberFormatter() | |
formatter.numberStyle = .decimal | |
return formatter | |
}() | |
init(subscription: Subscription? = nil) { | |
self.subscription = subscription | |
} | |
var body: some View { | |
NavigationView { | |
ZStack { | |
VStack { | |
Form { | |
Section { | |
HStack { | |
Spacer() | |
Button(action: { | |
self.focusedNameKeyboard = false | |
self.showIconView = true | |
}) { | |
ZStack { | |
RoundedRectangle(cornerRadius: 25.0) | |
.fill(Color(iconColor.color)) | |
.frame(width: 80, height: 80) | |
Text(iconName) | |
.foregroundStyle(Color.white) | |
.font(.system(size: 42)) | |
} | |
} | |
Spacer() | |
}.listRowBackground(Color.clear) | |
} | |
Section(footer: Text("Notifications are used to remind you when a subscription cycle is close to an end.", comment: "Footer explaining what are notifications for")) { | |
HStack { | |
Text("Name", comment: "The name of the subscription") | |
.lineLimit(1) | |
Spacer() | |
TextField("Name", text: $name) | |
.multilineTextAlignment(.trailing) | |
.focused($focusedNameKeyboard) | |
.submitLabel(.next) | |
.lineLimit(1) | |
} | |
Picker(String(localized: "Category", comment: "The category of the subscription"), selection: $category) { | |
Text("None", comment: "None category").tag(nil as Category?) | |
Divider() | |
ForEach(categories) { category in | |
Text(category.name).tag(category as Category?) | |
} | |
} | |
Toggle(String(localized: "Notifications", comment: "Notifications of the app"), isOn: $notification) | |
.toggleStyle(.switch) | |
.onChange(of: notification) { _, _ in | |
withAnimation { | |
self.focusedNameKeyboard = false | |
showExtendedDate = false | |
showCycleOption = false | |
showPrice = false | |
} | |
} | |
} | |
Section { | |
HStack { | |
Text("Reference bill", comment: "The referenced bill of the subscription") | |
Spacer() | |
DatePicker("Reference bill", selection: $referenceBillDate, displayedComponents: .date) | |
.labelsHidden() | |
.datePickerColor(darkModeSetting, colorScheme, showExtendedDate) | |
}.onTapGesture { | |
withAnimation { | |
showExtendedDate.toggle() | |
self.focusedNameKeyboard = false | |
showCycleOption = false | |
showPrice = false | |
} | |
} | |
if showExtendedDate { | |
DatePicker(selection: $referenceBillDate, displayedComponents: .date) { | |
Text("Reference bill", comment: "The referenced bill of the subscription") | |
} | |
.datePickerStyle(.graphical) | |
} | |
Button(action: { | |
withAnimation { | |
showCycleOption.toggle() | |
showExtendedDate = false | |
showPrice = false | |
self.focusedNameKeyboard = false | |
} | |
}) { | |
HStack { | |
Text("Billing cycle", comment: "The billing cycle of the subscription") | |
.foregroundStyle(Color.foreground) | |
Spacer() | |
Text(invoiceTime.translateLenght()) | |
} | |
} | |
if showCycleOption { | |
HStack { | |
InvoicePicker(timeBetweenInvoices: $invoiceTime) | |
} | |
} | |
Button(action: { | |
withAnimation { | |
showPrice.toggle() | |
showExtendedDate = false | |
showCycleOption = false | |
self.focusedNameKeyboard = false | |
} | |
}) { | |
HStack { | |
Text(subscription == nil ? String(localized: "Start price", comment: "The start price of the subscription") : String(localized: "Current price", comment: "The current price of the subscription")) | |
.foregroundStyle(Color.foreground) | |
Spacer() | |
Text(price, format: .currency(code: currency.code).precision(.fractionLength(2))) | |
} | |
} | |
if showPrice { | |
HStack { | |
CurrencyPicker(currency: $currency) | |
TextField("Current price", value: $price, formatter: formatter) | |
.textFieldStyle(.roundedBorder) | |
.padding() | |
.keyboardType(.decimalPad) | |
} | |
} | |
} | |
} | |
.padding(.top, -20) | |
.scrollDismissesKeyboard(.interactively) | |
} | |
.navigationTitle(subscription == nil ? String(localized: "Add Subscription", comment: "Title for the new subscription page") : String(localized: "Modify Subscription", comment: "Title for the modification page")) | |
.navigationBarTitleDisplayMode(.inline) | |
.toolbar { | |
ToolbarItem(placement: .cancellationAction) { | |
Button(String(localized: "Cancel", comment: "Button to cancel the addition/modification of a subscription"), role: .cancel) { | |
self.focusedNameKeyboard = false | |
dismiss() | |
} | |
} | |
ToolbarItem(placement: .confirmationAction) { | |
saveButton() | |
} | |
} | |
.sheet(isPresented: $showIconView) { | |
SubscriptionIconView(iconColor: $iconColor, iconEmoji: $iconName) | |
} | |
} | |
.navigationViewStyle(.stack) | |
} | |
.onAppear { | |
if let subscriptionToEdit = subscription { | |
if lastEditedSubscriptionId != subscriptionToEdit.id { | |
name = subscriptionToEdit.name | |
category = subscriptionToEdit.category | |
iconName = subscriptionToEdit.icon.emoji | |
iconColor = subscriptionToEdit.icon.color | |
referenceBillDate = subscriptionToEdit.referenceBillDate | |
invoiceTime = subscriptionToEdit.timeBetweenInvoices | |
currency = subscriptionToEdit.currency | |
price = subscriptionToEdit.getCurrentPrices().first!.price | |
notification = subscriptionToEdit.notifications | |
lastEditedSubscriptionId = subscriptionToEdit.id | |
} | |
} else { | |
name = "" | |
category = nil | |
iconName = Icon.emojis.first! | |
iconColor = Icon.IconColor.red | |
referenceBillDate = Date() | |
invoiceTime = InvoiceTime(number: 1, lenght: .month) | |
currency = Currency.preview.first(where: { $0.code == preferedCurrencyCode })! | |
price = 9.99 | |
notification = false | |
lastEditedSubscriptionId = nil | |
} | |
} | |
} | |
private func saveButton() -> some View { | |
return Button(subscription == nil ? String(localized: "Add", comment: "Button to add a subscription") : String(localized: "Edit", comment: "Button to modify a subscription")) { | |
if notification { | |
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in | |
if granted { print("Notification access!") } else { print("No access granted!") } | |
} | |
} | |
save() | |
self.focusedNameKeyboard = false | |
dismiss() | |
}.disabled(name.isEmpty) | |
} | |
private func save() { | |
if let subscription { | |
subscription.name = name | |
subscription.category = category | |
subscription.icon.emoji = iconName | |
subscription.icon.color = iconColor | |
subscription.referenceBillDate = referenceBillDate | |
subscription.timeBetweenInvoices = invoiceTime | |
subscription.currency = currency | |
if (subscription.getCurrentPrices().first!.price != price) { | |
subscription.prices.append(Prices(startDate: Date(), price: price)) | |
} | |
subscription.notifications = notification | |
if subscription.notificationID != "" { | |
notifications.removeScheduledNotification(notificationID: subscription.notificationID) | |
} | |
if notification { | |
subscription.notificationID = notifications.scheduleNotification(subscription: subscription) | |
} | |
do { | |
try modelContext.save() | |
} catch { | |
print("Error when ModelContext is saved: \(error.localizedDescription)") | |
} | |
} else { | |
let newSubscription = Subscription(icon: Icon(color: iconColor, emoji: iconName), name: name, category: category, referenceBillDate: referenceBillDate, timeBetweenInvoices: invoiceTime, currency: currency, notifications: notification, notificationID: "") | |
modelContext.insert(newSubscription) | |
newSubscription.prices.append(Prices(startDate: Date(), price: price)) | |
if notification { | |
newSubscription.notificationID = notifications.scheduleNotification(subscription: newSubscription) | |
} | |
do { | |
try modelContext.save() | |
} catch { | |
print("Error when ModelContext is saved: \(error.localizedDescription)") | |
} | |
} | |
dismiss() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment