diff --git a/EmpowerPlant.xcodeproj/project.pbxproj b/EmpowerPlant.xcodeproj/project.pbxproj index 22b5613..d526ae6 100644 --- a/EmpowerPlant.xcodeproj/project.pbxproj +++ b/EmpowerPlant.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 674899180B469D888700A555 /* Pods_EmpowerPlant.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 74014747DE857D26E9BD7B24 /* Pods_EmpowerPlant.framework */; }; + 843BD60F2AD08CE900B0098F /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843BD60E2AD08CE900B0098F /* Utils.swift */; }; 8B21663C29D3F8C80009C890 /* RandomErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B21663B29D3F8C80009C890 /* RandomErrors.swift */; }; 8BA3AB382A20212C00BE1EA8 /* CartViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BA3AB372A20212C00BE1EA8 /* CartViewControllerTests.swift */; }; D15EDF12282BF7FB00FC13D6 /* Product+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = D15EDF11282BF7FB00FC13D6 /* Product+CoreDataProperties.swift */; }; @@ -37,6 +38,7 @@ /* Begin PBXFileReference section */ 57BE79023D12DCDCB69BA73D /* Pods-EmpowerPlant.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-EmpowerPlant.release.xcconfig"; path = "Target Support Files/Pods-EmpowerPlant/Pods-EmpowerPlant.release.xcconfig"; sourceTree = ""; }; 74014747DE857D26E9BD7B24 /* Pods_EmpowerPlant.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_EmpowerPlant.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 843BD60E2AD08CE900B0098F /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; 846BEA1A2ABE46880032F77F /* upload-symbols.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "upload-symbols.sh"; sourceTree = ""; }; 8474F0482ACCE2D800F21E06 /* deploy_project.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = deploy_project.sh; sourceTree = ""; }; 8474F04D2ACE54F300F21E06 /* .github */ = {isa = PBXFileReference; lastKnownFileType = folder; path = .github; sourceTree = ""; }; @@ -130,6 +132,7 @@ D15EDF13282BF80400FC13D6 /* Product+CoreDataClass.swift */, D15EDF11282BF7FB00FC13D6 /* Product+CoreDataProperties.swift */, D17C73D127D83321006650AF /* ListAppViewController.swift */, + 843BD60E2AD08CE900B0098F /* Utils.swift */, D15FCDA727E00F0D00258BF3 /* Model.xcdatamodeld */, D17C73CB27D82EB8006650AF /* EmpowerPlantViewController.swift */, D19EBE6E2805ED52007022DC /* ShoppingCart.swift */, @@ -330,6 +333,7 @@ D17C73CF27D82ED1006650AF /* CartViewController.swift in Sources */, 8B21663C29D3F8C80009C890 /* RandomErrors.swift in Sources */, D19EBE6F2805ED52007022DC /* ShoppingCart.swift in Sources */, + 843BD60F2AD08CE900B0098F /* Utils.swift in Sources */, D17C73B327D8291D006650AF /* AppDelegate.swift in Sources */, D17C73B527D8291D006650AF /* SceneDelegate.swift in Sources */, D15EDF12282BF7FB00FC13D6 /* Product+CoreDataProperties.swift in Sources */, @@ -461,8 +465,8 @@ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; + OTHER_SWIFT_FLAGS = "-D DEBUG"; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; diff --git a/EmpowerPlant.xcodeproj/xcshareddata/xcschemes/EmpowerPlant.xcscheme b/EmpowerPlant.xcodeproj/xcshareddata/xcschemes/EmpowerPlant.xcscheme index e42ed4b..30a919c 100644 --- a/EmpowerPlant.xcodeproj/xcshareddata/xcschemes/EmpowerPlant.xcscheme +++ b/EmpowerPlant.xcodeproj/xcshareddata/xcschemes/EmpowerPlant.xcscheme @@ -67,6 +67,12 @@ ReferencedContainer = "container:EmpowerPlant.xcodeproj"> + + + + Bool { // Override point for customization after application launch. SentrySDK.start { options in - options.dsn = "https://c88045e430864a8e864af6233e7c18ea@o87286.ingest.sentry.io/6249899" -// options.debug = true // Enabled debug when first installing is always helpful + options.dsn = "https://c88045e430864a8e864af6233e7c18ea@o87286.ingest.sentry.io/6249899" + #if DEBUG + options.debug = true + #endif - // Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring. - // We recommend adjusting this value in production. - options.tracesSampleRate = 1.0 - options.profilesSampleRate = 1.0 - options.enableCoreDataTracing = true - options.enableFileIOTracing = true - options.attachScreenshot = true - options.attachViewHierarchy = true - options.enableTimeToFullDisplayTracing = true - options.enableUserInteractionTracing = false - } + // Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring. + // We recommend adjusting this value in production. + options.tracesSampleRate = 1.0 + options.profilesSampleRate = 1.0 + options.enableCoreDataTracing = true + options.enableFileIOTracing = true + options.attachScreenshot = true + options.attachViewHierarchy = true + options.enableTimeToFullDisplayTracing = true + options.enableAutoPerformanceTracing = true + options.enableUserInteractionTracing = false + } SentrySDK.configureScope{ scope in scope.setTag(value: ["corporate", "enterprise", "self-serve"].randomElement() ?? "unknown", key: "customer.type") scope.setTag(value: ProcessInfo.processInfo.environment["USER"] ?? "tda", key: "se") } + + if ProcessInfo.processInfo.arguments.contains("--wipe-db") { + wipeDB() + } + return true } @@ -87,10 +95,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { do { try context.save() } catch { - // Replace this implementation with code to handle the error appropriately. - // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. - let nserror = error as NSError - fatalError("Unresolved error \(nserror), \(nserror.userInfo)") + // TODO: error } } } diff --git a/EmpowerPlant/Base.lproj/Main.storyboard b/EmpowerPlant/Base.lproj/Main.storyboard index 998cc56..7683b65 100644 --- a/EmpowerPlant/Base.lproj/Main.storyboard +++ b/EmpowerPlant/Base.lproj/Main.storyboard @@ -17,15 +17,8 @@ - - - - - - - - + @@ -49,7 +42,7 @@ - + diff --git a/EmpowerPlant/CartViewController.swift b/EmpowerPlant/CartViewController.swift index 31cd381..2ed4594 100644 --- a/EmpowerPlant/CartViewController.swift +++ b/EmpowerPlant/CartViewController.swift @@ -23,6 +23,7 @@ class CartViewController: UIViewController, UITableViewDelegate, UITableViewData let tableView: UITableView = { let table = UITableView() table.register(UITableViewCell.self, forCellReuseIdentifier: "cell") + table.translatesAutoresizingMaskIntoConstraints = false return table }() @@ -44,13 +45,17 @@ class CartViewController: UIViewController, UITableViewDelegate, UITableViewData self.view.addSubview(tableView) tableView.delegate = self tableView.dataSource = self - - // Comment this out and to see the green background and no data in the rows - tableView.frame = view.bounds + + NSLayoutConstraint.activate([ + tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), + tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), + tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), + ]) configureNavigationItems() - // TODO make this 'total' appear in a UI element + // TODO: make this 'total' appear in a UI element print("CartViewController | TOTAL", ShoppingCart.instance.total) SentrySDK.reportFullyDisplayed() } @@ -77,7 +82,7 @@ class CartViewController: UIViewController, UITableViewDelegate, UITableViewData request.httpMethod = "POST" let json: [String: Any] = [ - "form": ["email":"will@chat.io"], // TODO email update + check if all tx's+errors have email + "form": ["email":"will@chat.io"], // TODO: email update + check if all tx's+errors have email "cart": [ "total": 100, "quantities": ["4": 3], @@ -128,10 +133,10 @@ class CartViewController: UIViewController, UITableViewDelegate, UITableViewData // total DONE // quantities DONE below - // items TODO + // TODO: items let json: [String: Any] = [ - "form": ["email":"will@chat.io"], // TODO email update + check if all tx's+errors have email + "form": ["email":"will@chat.io"], // TODO: email update + check if all tx's+errors have email "cart": [ "total": ShoppingCart.instance.total, "quantities": [ @@ -151,7 +156,7 @@ class CartViewController: UIViewController, UITableViewDelegate, UITableViewData } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - // TODO could compute the length based on length of quantities.botanaVoice, plantStroller, nodeVoices, etc. + // TODO: could compute the length based on length of quantities.botanaVoice, plantStroller, nodeVoices, etc. // or continue showing all products, even if quantity is 0. the screen looks more full this way return 4 // products.count } diff --git a/EmpowerPlant/EmpowerPlantViewController.swift b/EmpowerPlant/EmpowerPlantViewController.swift index 5bf92a6..a9c09bd 100644 --- a/EmpowerPlant/EmpowerPlantViewController.swift +++ b/EmpowerPlant/EmpowerPlantViewController.swift @@ -8,14 +8,15 @@ import UIKit import Sentry -class EmpowerPlantViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { - +class EmpowerPlantViewController: UIViewController { + // CoreData database let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext let tableView: UITableView = { - let table = UITableView() + let table = UITableView(frame: .zero, style: .plain) table.register(UITableViewCell.self, forCellReuseIdentifier: "cell") + table.translatesAutoresizingMaskIntoConstraints = false return table }() @@ -24,20 +25,23 @@ class EmpowerPlantViewController: UIViewController, UITableViewDelegate, UITable override func viewDidLoad() { super.viewDidLoad() - SentrySDK.reportFullyDisplayed() - title = "Empower Plant" - + title = "Empower Plants" + self.view.addSubview(tableView) tableView.delegate = self tableView.dataSource = self - - // Comment this out and to see the green background and no data in the rows - tableView.frame = view.bounds + NSLayoutConstraint.activate([ + tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), + tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), + tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), + ]) // Configures the nav bar buttons configureNavigationItems() - - /* TODO + + // ???: looks like this was already done? + /* TODO: implement: 1 get products from server (so we get http.client span) 2 check if any products in Core Data -> If Not -> insert the products from response into Core Data 3 get products from DB (so we get db.query span) and reload the table with this data @@ -47,6 +51,14 @@ class EmpowerPlantViewController: UIViewController, UITableViewDelegate, UITable getAllProductsFromDb() readCurrentDirectory() + NotificationCenter.default.addObserver(forName: modifiedDBNotificationName, object: nil, queue: nil) { _ in + self.getAllProductsFromDb() + } + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + SentrySDK.reportFullyDisplayed() } func readCurrentDirectory() { @@ -58,7 +70,7 @@ class EmpowerPlantViewController: UIViewController, UITableViewDelegate, UITable readDirectory(path: path) } } catch { - + // TODO: error } } @@ -77,28 +89,29 @@ class EmpowerPlantViewController: UIViewController, UITableViewDelegate, UITable } } } catch { + // TODO: error } } func fibonacciSeries(num: Int) -> Int{ - // The value of 0th and 1st number of the fibonacci series are 0 and 1 - var n1 = 0 - var n2 = 1 - - // To store the result - var nR = 0 - // Adding two previous numbers to find ith number of the series - for _ in 0.. 0 { + operations.append(saveOp) + OperationQueue.main.addOperations(operations, waitUntilFinished: false) } } } else { print("Invalid Response") + // TODO: error } } else if let error = error { print("HTTP Request Failed \(error)") + // TODO: error } } - + task.resume() } @@ -248,16 +336,19 @@ class EmpowerPlantViewController: UIViewController, UITableViewDelegate, UITable func goToListApp() { self.performSegue(withIdentifier: "goToListApp", sender: self) } - + @objc func refreshTable() { - // TODO why is this executing so many times on load? - // print("> refresh table") + // ???: why is this executing so many times on load? + // !!!: because it is called from createProduct, which is called for each item in the response from the network request to get products from server. In general, it's better to use UITableView.insertRow(...) instead of UITableView.reloadData() when simply adding things to the table. DispatchQueue.main.async { self.tableView.reloadData() } } - +} + +// MARK: UITableViewDataSource +extension EmpowerPlantViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return products.count } @@ -270,32 +361,17 @@ class EmpowerPlantViewController: UIViewController, UITableViewDelegate, UITable return cell } + func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return "\(products.count) items" + } +} + +// MARK: UITableViewDelegate +extension EmpowerPlantViewController: UITableViewDelegate { // Code that executes on Click'ing table row, adds the product item to shopping cart func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let product = products[indexPath.row] ShoppingCart.addProduct(product: product) } - - - // Don't deprecate this until major release of this demo - func updateProduct(product: Product, newTitle: String) { - product.title = newTitle - do { - try context.save() - } - catch { - - } - } - - /* - // MARK: - Navigation - - // In a storyboard-based application, you will often want to do a little preparation before navigation - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - // Get the new view controller using segue.destination. - // Pass the selected object to the new view controller. - } - */ } diff --git a/EmpowerPlant/ListAppViewController.swift b/EmpowerPlant/ListAppViewController.swift index 475c26e..fec1bea 100644 --- a/EmpowerPlant/ListAppViewController.swift +++ b/EmpowerPlant/ListAppViewController.swift @@ -85,6 +85,7 @@ class ListAppViewController: UIViewController { // It contains all data but mutations only influence the event being sent scope.setTag(value: "value", key: "myTag") } + // TODO: error } } diff --git a/EmpowerPlant/Product+CoreDataClass.swift b/EmpowerPlant/Product+CoreDataClass.swift index b3d886b..43d565d 100644 --- a/EmpowerPlant/Product+CoreDataClass.swift +++ b/EmpowerPlant/Product+CoreDataClass.swift @@ -17,7 +17,7 @@ public class Product: NSManagedObject { } -// TODO Deprecate this soon +// TODO: Deprecate this soon // This was all the boilerplate needed for mapping the HTTP Response directly into a Product CoreData Class, // in addition to changes needed in XCode settings for the Product Entity // enum CodingKeys: String, CodingKey { diff --git a/EmpowerPlant/Product+CoreDataProperties.swift b/EmpowerPlant/Product+CoreDataProperties.swift index feffe9a..214b73c 100644 --- a/EmpowerPlant/Product+CoreDataProperties.swift +++ b/EmpowerPlant/Product+CoreDataProperties.swift @@ -13,7 +13,9 @@ import CoreData extension Product { @nonobjc public class func fetchRequest() -> NSFetchRequest { - return NSFetchRequest(entityName: "Product") + let fr = NSFetchRequest(entityName: "Product") + fr.sortDescriptors = [.init(key: "title", ascending: true)] + return fr } // @NSManaged public var text: String? diff --git a/EmpowerPlant/Utils.swift b/EmpowerPlant/Utils.swift new file mode 100644 index 0000000..0c01399 --- /dev/null +++ b/EmpowerPlant/Utils.swift @@ -0,0 +1,24 @@ +// +// Utils.swift +// EmpowerPlant +// +// Created by Andrew McKnight on 10/6/23. +// + +import UIKit + +public let modifiedDBNotificationName = Notification.Name("io.sentry.empowerplants.newly-generated-db-items-available") + +public func wipeDB() { + guard let url = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.persistentStoreCoordinator.persistentStores.first?.url else { + // TODO: error + return + } + + do { + try FileManager.default.removeItem(at: url) + } catch { + // TODO: error + return + } +} diff --git a/Podfile b/Podfile index 1193b28..1fe3cf9 100644 --- a/Podfile +++ b/Podfile @@ -7,4 +7,5 @@ target 'EmpowerPlant' do # Pods for EmpowerPlant pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '8.10.0' + pod 'SwiftMessages' end diff --git a/upload-symbols.sh b/upload-symbols.sh index 1dc2e35..b540b8a 100755 --- a/upload-symbols.sh +++ b/upload-symbols.sh @@ -33,11 +33,7 @@ if which sentry-cli >/dev/null; then exit 1 fi - ERROR=$(sentry-cli upload-dif --force-foreground --include-sources --log-level debug -o $SENTRY_ORG -p $SENTRY_PROJECT --auth-token $SENTRY_AUTH_TOKEN "$DWARF_DSYM_FOLDER_PATH" ) - if [ ! $? -eq 0 ]; then - echo "warning: sentry-cli - $ERROR" - exit 1 - fi +# sentry-cli upload-dif --force-foreground --include-sources -o $SENTRY_ORG -p $SENTRY_PROJECT --auth-token $SENTRY_AUTH_TOKEN "$DWARF_DSYM_FOLDER_PATH" else echo "error: sentry-cli not installed, download from https://github.com/getsentry/sentry-cli/releases" exit 1