Skip to content

Instantly share code, notes, and snippets.

@Max094Reikeb
Created April 13, 2024 19:41
Show Gist options
  • Save Max094Reikeb/595b36ffaeb4ddab5274ad08a2c7bbe0 to your computer and use it in GitHub Desktop.
Save Max094Reikeb/595b36ffaeb4ddab5274ad08a2c7bbe0 to your computer and use it in GitHub Desktop.
View to create/modify a subscription
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