Mercurial > public > simoleon
changeset 21:c3dda63f50ed v1.1
Added Core Data and UI changes
- Implement Watchlist
- Change conversion design
- Improve UX
author | Dennis Concepción Martín <dennisconcepcionmartin@gmail.com> |
---|---|
date | Mon, 19 Jul 2021 19:27:12 +0100 |
parents | f8aabe5b7251 |
children | 3596690dda73 |
files | Simoleon.xcodeproj/project.pbxproj Simoleon.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved Simoleon.xcodeproj/project.xcworkspace/xcuserdata/dennis.xcuserdatad/UserInterfaceState.xcuserstate Simoleon/ContentView.swift Simoleon/Conversion.swift Simoleon/Favourites.swift Simoleon/Helpers/ConditionalWrapper.swift Simoleon/Helpers/ConversionBox.swift Simoleon/Helpers/CurrencyButton.swift Simoleon/Helpers/CurrencyRow.swift Simoleon/Helpers/CurrencySelector.swift Simoleon/Helpers/FavouriteButton.swift Simoleon/Helpers/ResignKeyboard.swift Simoleon/Helpers/Sidebar.swift Simoleon/Models/Favourite+CoreDataClass.swift Simoleon/Models/Favourite+CoreDataProperties.swift Simoleon/Persistence.swift Simoleon/Simoleon.xcdatamodeld/Simoleon.xcdatamodel/contents |
diffstat | 18 files changed, 404 insertions(+), 170 deletions(-) [+] |
line wrap: on
line diff
--- a/Simoleon.xcodeproj/project.pbxproj Mon Jul 19 10:12:23 2021 +0100 +++ b/Simoleon.xcodeproj/project.pbxproj Mon Jul 19 19:27:12 2021 +0100 @@ -15,12 +15,16 @@ 95AEBCA326A0900E00613729 /* CurrencyQuoteModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95AEBCA226A0900E00613729 /* CurrencyQuoteModel.swift */; }; 95B54F4426A4842C001DC0D8 /* Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95B54F4326A4842C001DC0D8 /* Conversion.swift */; }; 95B54F4626A48852001DC0D8 /* CurrencySelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95B54F4526A48852001DC0D8 /* CurrencySelector.swift */; }; - 95B54F4826A4954B001DC0D8 /* CurrencyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95B54F4726A4954B001DC0D8 /* CurrencyButton.swift */; }; 95B54F4A26A4A450001DC0D8 /* ConversionBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95B54F4926A4A450001DC0D8 /* ConversionBox.swift */; }; - 95B54F4D26A4A64F001DC0D8 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 95B54F4C26A4A64F001DC0D8 /* Introspect */; }; 95B54F4F26A4AC52001DC0D8 /* ContentViewPad.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95B54F4E26A4AC52001DC0D8 /* ContentViewPad.swift */; }; 95B54F5126A4ACAC001DC0D8 /* Sidebar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95B54F5026A4ACAC001DC0D8 /* Sidebar.swift */; }; 95C02C8B269B61680061DD6D /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 95C02C8A269B61680061DD6D /* Alamofire */; }; + 95C5179126A5DC8E00BC2B24 /* ConditionalWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C5179026A5DC8E00BC2B24 /* ConditionalWrapper.swift */; }; + 95C5179926A5EC9F00BC2B24 /* FavouriteButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C5179826A5EC9F00BC2B24 /* FavouriteButton.swift */; }; + 95C5179C26A5EFBE00BC2B24 /* Favourite+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C5179A26A5EFBE00BC2B24 /* Favourite+CoreDataClass.swift */; }; + 95C5179D26A5EFBE00BC2B24 /* Favourite+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C5179B26A5EFBE00BC2B24 /* Favourite+CoreDataProperties.swift */; }; + 95C5179F26A5F34200BC2B24 /* Favourites.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C5179E26A5F34200BC2B24 /* Favourites.swift */; }; + 95C517A126A5F6C000BC2B24 /* ResignKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C517A026A5F6C000BC2B24 /* ResignKeyboard.swift */; }; 95C5B2282697752600941585 /* SimoleonApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C5B2272697752600941585 /* SimoleonApp.swift */; }; 95C5B22C2697752700941585 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 95C5B22B2697752700941585 /* Assets.xcassets */; }; 95C5B22F2697752700941585 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 95C5B22E2697752700941585 /* Preview Assets.xcassets */; }; @@ -59,10 +63,15 @@ 95AEBCA226A0900E00613729 /* CurrencyQuoteModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyQuoteModel.swift; sourceTree = "<group>"; }; 95B54F4326A4842C001DC0D8 /* Conversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Conversion.swift; sourceTree = "<group>"; }; 95B54F4526A48852001DC0D8 /* CurrencySelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencySelector.swift; sourceTree = "<group>"; }; - 95B54F4726A4954B001DC0D8 /* CurrencyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyButton.swift; sourceTree = "<group>"; }; 95B54F4926A4A450001DC0D8 /* ConversionBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversionBox.swift; sourceTree = "<group>"; }; 95B54F4E26A4AC52001DC0D8 /* ContentViewPad.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentViewPad.swift; sourceTree = "<group>"; }; 95B54F5026A4ACAC001DC0D8 /* Sidebar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sidebar.swift; sourceTree = "<group>"; }; + 95C5179026A5DC8E00BC2B24 /* ConditionalWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalWrapper.swift; sourceTree = "<group>"; }; + 95C5179826A5EC9F00BC2B24 /* FavouriteButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavouriteButton.swift; sourceTree = "<group>"; }; + 95C5179A26A5EFBE00BC2B24 /* Favourite+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Favourite+CoreDataClass.swift"; sourceTree = "<group>"; }; + 95C5179B26A5EFBE00BC2B24 /* Favourite+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Favourite+CoreDataProperties.swift"; sourceTree = "<group>"; }; + 95C5179E26A5F34200BC2B24 /* Favourites.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Favourites.swift; sourceTree = "<group>"; }; + 95C517A026A5F6C000BC2B24 /* ResignKeyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResignKeyboard.swift; sourceTree = "<group>"; }; 95C5B2242697752600941585 /* Simoleon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Simoleon.app; sourceTree = BUILT_PRODUCTS_DIR; }; 95C5B2272697752600941585 /* SimoleonApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimoleonApp.swift; sourceTree = "<group>"; }; 95C5B22B2697752700941585 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; @@ -88,7 +97,6 @@ buildActionMask = 2147483647; files = ( 95C02C8B269B61680061DD6D /* Alamofire in Frameworks */, - 95B54F4D26A4A64F001DC0D8 /* Introspect in Frameworks */, 95E7643A269E0037008E9F31 /* CloudKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -113,6 +121,8 @@ 95559331269B094A000FD726 /* Models */ = { isa = PBXGroup; children = ( + 95C5179A26A5EFBE00BC2B24 /* Favourite+CoreDataClass.swift */, + 95C5179B26A5EFBE00BC2B24 /* Favourite+CoreDataProperties.swift */, 95AEBC9A26A04A4200613729 /* CurrencyMetadataModel.swift */, 95AEBCA226A0900E00613729 /* CurrencyQuoteModel.swift */, ); @@ -165,6 +175,7 @@ 95AEBC9426A03ECB00613729 /* ContentView.swift */, 95B54F4E26A4AC52001DC0D8 /* ContentViewPad.swift */, 95B54F4326A4842C001DC0D8 /* Conversion.swift */, + 95C5179E26A5F34200BC2B24 /* Favourites.swift */, 95C5B22B2697752700941585 /* Assets.xcassets */, 95C5B2302697752700941585 /* Persistence.swift */, 95C5B2352697752700941585 /* Info.plist */, @@ -216,11 +227,13 @@ 95FE659A269AFB44008745DE /* Helpers */ = { isa = PBXGroup; children = ( - 95B54F4726A4954B001DC0D8 /* CurrencyButton.swift */, 95B54F4526A48852001DC0D8 /* CurrencySelector.swift */, 95AEBC9C26A04D4600613729 /* CurrencyRow.swift */, 95B54F4926A4A450001DC0D8 /* ConversionBox.swift */, 95B54F5026A4ACAC001DC0D8 /* Sidebar.swift */, + 95C5179026A5DC8E00BC2B24 /* ConditionalWrapper.swift */, + 95C5179826A5EC9F00BC2B24 /* FavouriteButton.swift */, + 95C517A026A5F6C000BC2B24 /* ResignKeyboard.swift */, ); path = Helpers; sourceTree = "<group>"; @@ -243,7 +256,6 @@ name = Simoleon; packageProductDependencies = ( 95C02C8A269B61680061DD6D /* Alamofire */, - 95B54F4C26A4A64F001DC0D8 /* Introspect */, ); productName = Simoleon; productReference = 95C5B2242697752600941585 /* Simoleon.app */; @@ -318,7 +330,6 @@ mainGroup = 95C5B21B2697752600941585; packageReferences = ( 95C02C89269B61680061DD6D /* XCRemoteSwiftPackageReference "Alamofire" */, - 95B54F4B26A4A64F001DC0D8 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */, ); productRefGroup = 95C5B2252697752600941585 /* Products */; projectDirPath = ""; @@ -365,18 +376,23 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 95C5179926A5EC9F00BC2B24 /* FavouriteButton.swift in Sources */, + 95C5179C26A5EFBE00BC2B24 /* Favourite+CoreDataClass.swift in Sources */, 95C5B2312697752700941585 /* Persistence.swift in Sources */, 95AEBC9526A03ECB00613729 /* ContentView.swift in Sources */, 95AEBC9B26A04A4200613729 /* CurrencyMetadataModel.swift in Sources */, 9555933A269B0AB8000FD726 /* ParseJson.swift in Sources */, + 95C5179D26A5EFBE00BC2B24 /* Favourite+CoreDataProperties.swift in Sources */, + 95C5179F26A5F34200BC2B24 /* Favourites.swift in Sources */, 95C5B2282697752600941585 /* SimoleonApp.swift in Sources */, 95B54F4A26A4A450001DC0D8 /* ConversionBox.swift in Sources */, + 95C517A126A5F6C000BC2B24 /* ResignKeyboard.swift in Sources */, 95AEBC9D26A04D4600613729 /* CurrencyRow.swift in Sources */, 95AEBCA326A0900E00613729 /* CurrencyQuoteModel.swift in Sources */, - 95B54F4826A4954B001DC0D8 /* CurrencyButton.swift in Sources */, 95B54F4F26A4AC52001DC0D8 /* ContentViewPad.swift in Sources */, 95B54F4426A4842C001DC0D8 /* Conversion.swift in Sources */, 95C5B2342697752700941585 /* Simoleon.xcdatamodeld in Sources */, + 95C5179126A5DC8E00BC2B24 /* ConditionalWrapper.swift in Sources */, 95B54F5126A4ACAC001DC0D8 /* Sidebar.swift in Sources */, 95B54F4626A48852001DC0D8 /* CurrencySelector.swift in Sources */, ); @@ -547,7 +563,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1; + MARKETING_VERSION = 1.2; PRODUCT_BUNDLE_IDENTIFIER = io.dennistech.Simoleon; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -572,7 +588,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1; + MARKETING_VERSION = 1.2; PRODUCT_BUNDLE_IDENTIFIER = io.dennistech.Simoleon; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -706,14 +722,6 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 95B54F4B26A4A64F001DC0D8 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/siteline/SwiftUI-Introspect.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 0.1.3; - }; - }; 95C02C89269B61680061DD6D /* XCRemoteSwiftPackageReference "Alamofire" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/Alamofire/Alamofire.git"; @@ -725,11 +733,6 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 95B54F4C26A4A64F001DC0D8 /* Introspect */ = { - isa = XCSwiftPackageProductDependency; - package = 95B54F4B26A4A64F001DC0D8 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; - productName = Introspect; - }; 95C02C8A269B61680061DD6D /* Alamofire */ = { isa = XCSwiftPackageProductDependency; package = 95C02C89269B61680061DD6D /* XCRemoteSwiftPackageReference "Alamofire" */;
--- a/Simoleon.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved Mon Jul 19 10:12:23 2021 +0100 +++ b/Simoleon.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved Mon Jul 19 19:27:12 2021 +0100 @@ -9,15 +9,6 @@ "revision": "f96b619bcb2383b43d898402283924b80e2c4bae", "version": "5.4.3" } - }, - { - "package": "Introspect", - "repositoryURL": "https://github.com/siteline/SwiftUI-Introspect.git", - "state": { - "branch": null, - "revision": "2e09be8af614401bc9f87d40093ec19ce56ccaf2", - "version": "0.1.3" - } } ] },
Binary file Simoleon.xcodeproj/project.xcworkspace/xcuserdata/dennis.xcuserdatad/UserInterfaceState.xcuserstate has changed
--- a/Simoleon/ContentView.swift Mon Jul 19 10:12:23 2021 +0100 +++ b/Simoleon/ContentView.swift Mon Jul 19 19:27:12 2021 +0100 @@ -8,11 +8,32 @@ import SwiftUI struct ContentView: View { + @State private var tab: Tab = .convert var body: some View { - NavigationView { + TabView(selection: $tab) { Conversion() + .tabItem { + Label("Convert", systemImage: "arrow.counterclockwise.circle") + } + .tag(Tab.convert) + + Favourites() + .tabItem { + Label("Favourites", systemImage: "star") + } + .tag(Tab.favourites) + + Text("Settings") + .tabItem { + Label("Settings", systemImage: "gear") + } + .tag(Tab.settings) } } + + private enum Tab { + case convert, favourites, settings + } } struct ContentView_Previews: PreviewProvider {
--- a/Simoleon/Conversion.swift Mon Jul 19 10:12:23 2021 +0100 +++ b/Simoleon/Conversion.swift Mon Jul 19 19:27:12 2021 +0100 @@ -9,14 +9,12 @@ import Alamofire struct Conversion: View { - @State private var mainCurrency = "USD" - @State private var secondaryCurrency = "GBP" + @State private var currencyPair = "USD/GBP" @State private var amountToConvert = "1000" @State private var price: Double = 1.00 @State private var showingConversion = false @State private var showingCurrencySelector = false - @State private var selectingMainCurrency = false - @State private var currencyPairNotFound = false + @State private var isEditing = false let currencyMetadata: [String: CurrencyMetadataModel] = parseJson("CurrencyMetadata.json") @@ -24,57 +22,66 @@ ScrollView(showsIndicators: false) { VStack(alignment: .leading) { HStack { - Button(action: { selectingMainCurrency = true; showingCurrencySelector = true }) { - CurrencyButton(currency: $mainCurrency) + Button(action: { showingCurrencySelector = true }) { + RoundedRectangle(cornerRadius: 25) + .foregroundColor(Color(.secondarySystemBackground)) + .frame(height: 75) + .overlay(CurrencyRow(currencyPair: currencyPair).padding(.horizontal)) } - Button(action: { selectingMainCurrency = false; showingCurrencySelector = true }) { - CurrencyButton(currency: $secondaryCurrency) - } + FavouriteButton(currencyPair: currencyPair) } ConversionBox( - mainCurrency: $mainCurrency, - secondaryCurrency: $secondaryCurrency, + currencyPair: $currencyPair, amountToConvert: $amountToConvert, price: $price, showingConversion: $showingConversion, showingCurrencySelector: $showingCurrencySelector, - currencyPairNotFound: $currencyPairNotFound + isEditing: $isEditing ) } .padding() - .onAppear { requestApi(mainCurrency, secondaryCurrency) } + .onAppear { request(currencyPair) } .onChange(of: showingCurrencySelector, perform: { showingCurrencySelector in if !showingCurrencySelector { - requestApi(mainCurrency, secondaryCurrency) + request(currencyPair) } }) .sheet(isPresented: $showingCurrencySelector) { - CurrencySelector( - mainCurrencySelected: $mainCurrency, - secondaryCurrencySelected: $secondaryCurrency, - showingCurrencySelector: $showingCurrencySelector, - selectingMainCurrency: $selectingMainCurrency - ) + CurrencySelector(currencyPair: $currencyPair, showingCurrencySelector: $showingCurrencySelector) } } - .navigationBarHidden(true) + .if(UIDevice.current.userInterfaceIdiom == .phone) { content in + NavigationView { + content + .navigationTitle("Conversion") + .toolbar { + ToolbarItem(placement: .cancellationAction) { + if isEditing { + Button("Cancel", action: { + UIApplication.shared.dismissKeyboard() + isEditing = false + }) + } + } + } + } + } } - private func requestApi(_ mainCurrency: String, _ secondaryCurrency: String) { - let url = "https://api.1forge.com/quotes?pairs=\(mainCurrency)/\(secondaryCurrency)&api_key=BFWeJQ3jJtqqpDv5ArNis59pAlFcQ4KF" + private func request(_ currencyPair: String) { + let url = "https://api.1forge.com/quotes?pairs=\(currencyPair)&api_key=BFWeJQ3jJtqqpDv5ArNis59pAlFcQ4KF" AF.request(url).responseDecodable(of: [CurrencyQuoteModel].self) { response in self.showingConversion = false - self.currencyPairNotFound = false if let currencyQuotes = response.value { if let price = currencyQuotes.first?.price { self.price = price self.showingConversion = true } else { - self.currencyPairNotFound = true +// Handle error } } else { // Handle error @@ -86,8 +93,6 @@ struct Conversion_Previews: PreviewProvider { static var previews: some View { - NavigationView { - Conversion() - } + Conversion() } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Simoleon/Favourites.swift Mon Jul 19 19:27:12 2021 +0100 @@ -0,0 +1,56 @@ +// +// Favourites.swift +// Simoleon +// +// Created by Dennis Concepci贸n Mart铆n on 19/07/2021. +// + +import SwiftUI + +struct Favourites: View { + @Environment(\.managedObjectContext) private var viewContext + @FetchRequest( + sortDescriptors: [NSSortDescriptor(keyPath: \Favourite.currencyPair, ascending: true)], + animation: .default) + private var favourite: FetchedResults<Favourite> + + var body: some View { + List { + ForEach(favourite) { favourite in + CurrencyRow(currencyPair: favourite.currencyPair) + } + .onDelete(perform: removeFromFavourites) + } + .if(UIDevice.current.userInterfaceIdiom == .phone) { content in + NavigationView { + content + .navigationTitle("Favourites") + .toolbar { + #if os(iOS) + EditButton() + #endif + } + } + } + } + + private func removeFromFavourites(offsets: IndexSet) { + withAnimation { + offsets.map { favourite[$0] }.forEach(viewContext.delete) + + do { + try viewContext.save() + } catch { + let nsError = error as NSError + fatalError("Unresolved error \(nsError), \(nsError.userInfo)") + } + } + } +} + +struct Favourites_Previews: PreviewProvider { + static var previews: some View { + Favourites() + .environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Simoleon/Helpers/ConditionalWrapper.swift Mon Jul 19 19:27:12 2021 +0100 @@ -0,0 +1,19 @@ +// +// ConditionalWrapper.swift +// Simoleon +// +// Created by Dennis Concepci贸n Mart铆n on 19/07/2021. +// + +import SwiftUI + +extension View { + @ViewBuilder + func `if`<Content: View>(_ conditional: Bool, content: (Self) -> Content) -> some View { + if conditional { + content(self) + } else { + self + } + } +}
--- a/Simoleon/Helpers/ConversionBox.swift Mon Jul 19 10:12:23 2021 +0100 +++ b/Simoleon/Helpers/ConversionBox.swift Mon Jul 19 19:27:12 2021 +0100 @@ -6,44 +6,47 @@ // import SwiftUI -import Introspect struct ConversionBox: View { - @Binding var mainCurrency: String - @Binding var secondaryCurrency: String + @Binding var currencyPair: String @Binding var amountToConvert: String @Binding var price: Double @Binding var showingConversion: Bool @Binding var showingCurrencySelector: Bool - @Binding var currencyPairNotFound: Bool - - @State private var showingCancelationButton = false + @Binding var isEditing: Bool let currencyMetadata: [String: CurrencyMetadataModel] = parseJson("CurrencyMetadata.json") var body: some View { VStack(alignment: .leading) { - Text("\(currencyMetadata[mainCurrency]!.name) (\(mainCurrency))") + let currencies = currencyPair.split(separator: "/") + Text("\(currencyMetadata[String(currencies[0])]!.name) (\(String(currencies[0])))") .font(.callout) .fontWeight(.semibold) .padding(.top, 40) ZStack(alignment: .trailing) { - TextField("Enter amount", text: $amountToConvert) - .keyboardType(.decimalPad) - .font(Font.title.weight(.semibold)) - .lineLimit(1) - .padding(.bottom, 10) - .introspectTextField { textField in - if !showingCurrencySelector { - textField.becomeFirstResponder() - } - } + TextField("Enter amount", text: $amountToConvert) { startedEditing in + if startedEditing { + withAnimation { + isEditing = true + } + } + } + onCommit: { + withAnimation { + isEditing = false + } + } + .keyboardType(.decimalPad) + .font(Font.title.weight(.semibold)) + .lineLimit(1) + .padding(.bottom, 10) } Divider() - Text("\(currencyMetadata[secondaryCurrency]!.name) (\(secondaryCurrency))") + Text("\(currencyMetadata[String(currencies[1])]!.name) (\(String(currencies[1])))") .font(.callout) .fontWeight(.semibold) .padding(.top, 10) @@ -54,13 +57,8 @@ .lineLimit(1) .padding(.top, 5) } else { - if currencyPairNotFound { - Text("The currency pair selected is not supported yet 馃槩") - .padding(.top, 5) - } else { - ProgressView() - .padding(.top, 5) - } + ProgressView() + .padding(.top, 5) } } } @@ -80,6 +78,6 @@ struct ConversionBox_Previews: PreviewProvider { static var previews: some View { - ConversionBox(mainCurrency: .constant("USD"), secondaryCurrency: .constant("GBP"), amountToConvert: .constant("1000"), price: .constant(1), showingConversion: .constant(true), showingCurrencySelector: .constant(false), currencyPairNotFound: .constant(false)) + ConversionBox(currencyPair: .constant("USD/GBP"), amountToConvert: .constant("1000"), price: .constant(1), showingConversion: .constant(false), showingCurrencySelector: .constant(false), isEditing: .constant(false)) } }
--- a/Simoleon/Helpers/CurrencyButton.swift Mon Jul 19 10:12:23 2021 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,39 +0,0 @@ -// -// CurrencyButton.swift -// Simoleon -// -// Created by Dennis Concepci贸n Mart铆n on 18/07/2021. -// - -import SwiftUI - -struct CurrencyButton: View { - @Binding var currency: String - let currencyMetadata: [String: CurrencyMetadataModel] = parseJson("CurrencyMetadata.json") - - var body: some View { - RoundedRectangle(cornerRadius: 25) - .foregroundColor(Color(.secondarySystemBackground)) - .frame(height: 75) - .overlay( - HStack { - Image(currencyMetadata[currency]!.flag) - .resizable() - .aspectRatio(contentMode: .fill) - .frame(width: 30, height: 30) - .clipShape(Circle()) - .overlay(Circle().stroke(Color(.systemGray), lineWidth: 1)) - - Text("\(currency)") - .fontWeight(.semibold) - .foregroundColor(Color("PlainButton")) - } - ) - } -} - -struct CurrencyButton_Previews: PreviewProvider { - static var previews: some View { - CurrencyButton(currency: .constant("USD")) - } -}
--- a/Simoleon/Helpers/CurrencyRow.swift Mon Jul 19 10:12:23 2021 +0100 +++ b/Simoleon/Helpers/CurrencyRow.swift Mon Jul 19 19:27:12 2021 +0100 @@ -8,37 +8,40 @@ import SwiftUI struct CurrencyRow: View { - var currency: String + var currencyPair: String let currencyMetadata: [String: CurrencyMetadataModel] = parseJson("CurrencyMetadata.json") var body: some View { HStack { - Image(currencyMetadata[currency]!.flag) + let currencies = currencyPair.split(separator: "/") + Image(currencyMetadata[String(currencies[0])]!.flag) .resizable() .aspectRatio(contentMode: .fill) .frame(width: 30, height: 30) .clipShape(Circle()) .overlay(Circle().stroke(Color(.systemGray), lineWidth: 1)) - VStack(alignment: .leading) { - Text("\(currency)") - .fontWeight(.semibold) - .foregroundColor(Color("PlainButton")) - - Text("\(currencyMetadata[currency]!.name)") - .font(.footnote) - .fontWeight(.semibold) - .foregroundColor(Color("PlainButton")) - .opacity(0.5) - .lineLimit(1) - } - .padding(.horizontal) + Image(currencyMetadata[String(currencies[1])]!.flag) + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: 30, height: 30) + .clipShape(Circle()) + .overlay(Circle().stroke(Color(.systemGray), lineWidth: 1)) + .offset(x: -20) + .padding(.trailing, -20) + + Text("From \(String(currencies[0])) to \(String(currencies[1]))") + .fontWeight(.semibold) + .foregroundColor(Color("PlainButton")) + .padding(.leading) + + Spacer() } } } struct CurrencyRow_Previews: PreviewProvider { static var previews: some View { - CurrencyRow(currency: "USD") + CurrencyRow(currencyPair: "USD/GBP") } }
--- a/Simoleon/Helpers/CurrencySelector.swift Mon Jul 19 10:12:23 2021 +0100 +++ b/Simoleon/Helpers/CurrencySelector.swift Mon Jul 19 19:27:12 2021 +0100 @@ -8,58 +8,78 @@ import SwiftUI struct CurrencySelector: View { - @Binding var mainCurrencySelected: String - @Binding var secondaryCurrencySelected: String + @Binding var currencyPair: String @Binding var showingCurrencySelector: Bool - @Binding var selectingMainCurrency: Bool + @State private var searchCurrency = "" + @State private var searching = false var body: some View { NavigationView { Form { - ForEach(generateCurrencyList(), id: \.self) { currency in - Button(action: { select(currency) }) { - CurrencyRow(currency: currency) + TextField("Search ...", text: $searchCurrency) { startedEditing in + if startedEditing { + withAnimation { + searching = true + } + } + } onCommit: { + withAnimation { + searching = false + } + } + + Section(header: Text("All currencies")) { + ForEach(currencyPairs(), id: \.self) { currencyPair in + Button(action: { + self.currencyPair = currencyPair + showingCurrencySelector = false + }) { + CurrencyRow(currencyPair: currencyPair) + } } } } + .gesture(DragGesture() + .onChanged({ _ in + UIApplication.shared.dismissKeyboard() + }) + ) .navigationTitle("Currencies") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .confirmationAction) { Button("OK", action: { showingCurrencySelector = false }) } + + ToolbarItem(placement: .cancellationAction) { + if searching { + Button("Cancel") { + searchCurrency = "" + withAnimation { + searching = false + UIApplication.shared.dismissKeyboard() + } + } + } + } } } } - private func generateCurrencyList() -> [String] { + private func currencyPairs() -> [String] { let currencyPairs: [String] = parseJson("CurrencyPairs.json") - var currencies: [String] = [] - - for currencyPair in currencyPairs { - let splittedCurrencies = currencyPair.split(separator: "/") - let mainCurrency = String(splittedCurrencies[0]) - if !currencies.contains(mainCurrency) { - currencies.append(mainCurrency) - } - } - return currencies - } - - private func select(_ currency: String) { - if selectingMainCurrency { - self.mainCurrencySelected = currency + if searchCurrency.isEmpty { + return currencyPairs } else { - self.secondaryCurrencySelected = currency + return currencyPairs.filter { $0.contains(searchCurrency.uppercased()) } } - - showingCurrencySelector = false } } + struct CurrencySelector_Previews: PreviewProvider { static var previews: some View { - CurrencySelector(mainCurrencySelected: .constant(""), secondaryCurrencySelected: .constant(""), showingCurrencySelector: .constant(false), selectingMainCurrency: .constant(true)) + CurrencySelector(currencyPair: .constant("USD/GBP"), showingCurrencySelector: .constant(false)) } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Simoleon/Helpers/FavouriteButton.swift Mon Jul 19 19:27:12 2021 +0100 @@ -0,0 +1,86 @@ +// +// FavouriteButton.swift +// Simoleon +// +// Created by Dennis Concepci贸n Mart铆n on 19/07/2021. +// + +import SwiftUI + +struct FavouriteButton: View { + var currencyPair: String + @Environment(\.managedObjectContext) private var viewContext + @FetchRequest(sortDescriptors: []) private var favourite: FetchedResults<Favourite> + + var body: some View { + Button(action: { + if isFavourite() { + removeFromFavourites() + } else { + addToFavourites() + } + }) { + RoundedRectangle(cornerRadius: 25) + .foregroundColor(Color(.secondarySystemBackground)) + .frame(width: 75, height: 75) + .overlay( + Image(systemName: generateStar()) + .font(.system(size: 28)) + .foregroundColor(Color(.systemYellow)) + + ) + } + } + + private func isFavourite() -> Bool { + let favouriteCurrencyPairs = favourite.map { $0.currencyPair } + + if favouriteCurrencyPairs.contains(currencyPair) { + return true + } else { + return false + } + } + + private func generateStar() -> String { + if isFavourite() { + return "star.fill" + } else { + return "star" + } + } + + private func removeFromFavourites() { + withAnimation { + let favouriteObject = favourite.first(where: { $0.currencyPair == currencyPair }) + viewContext.delete(favouriteObject ?? Favourite()) + + do { + try viewContext.save() + } catch { + let nsError = error as NSError + fatalError("Unresolved error \(nsError), \(nsError.userInfo)") + } + } + } + + private func addToFavourites() { + withAnimation { + let favourite = Favourite(context: viewContext) + favourite.currencyPair = currencyPair + + do { + try viewContext.save() + } catch { + let nsError = error as NSError + fatalError("Unresolved error \(nsError), \(nsError.userInfo)") + } + } + } +} + +struct FavouriteButton_Previews: PreviewProvider { + static var previews: some View { + FavouriteButton(currencyPair: "USD/GBP") + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Simoleon/Helpers/ResignKeyboard.swift Mon Jul 19 19:27:12 2021 +0100 @@ -0,0 +1,14 @@ +// +// ResignKeyboard.swift +// Simoleon +// +// Created by Dennis Concepci贸n Mart铆n on 19/07/2021. +// + +import SwiftUI + +extension UIApplication { + func dismissKeyboard() { + sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + } +}
--- a/Simoleon/Helpers/Sidebar.swift Mon Jul 19 10:12:23 2021 +0100 +++ b/Simoleon/Helpers/Sidebar.swift Mon Jul 19 19:27:12 2021 +0100 @@ -11,7 +11,15 @@ var body: some View { List { NavigationLink(destination: Conversion()) { - Label("Convert", systemImage: "glass") + Label("Convert", systemImage: "arrow.counterclockwise.circle") + } + + NavigationLink(destination: Text("Favourites")) { + Label("Favourites", systemImage: "star") + } + + NavigationLink(destination: Text("Settings")) { + Label("Settings", systemImage: "gear") } } .listStyle(SidebarListStyle())
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Simoleon/Models/Favourite+CoreDataClass.swift Mon Jul 19 19:27:12 2021 +0100 @@ -0,0 +1,15 @@ +// +// Favourite+CoreDataClass.swift +// Simoleon +// +// Created by Dennis Concepci贸n Mart铆n on 19/07/2021. +// +// + +import Foundation +import CoreData + +@objc(Favourite) +public class Favourite: NSManagedObject { + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Simoleon/Models/Favourite+CoreDataProperties.swift Mon Jul 19 19:27:12 2021 +0100 @@ -0,0 +1,25 @@ +// +// Favourite+CoreDataProperties.swift +// Simoleon +// +// Created by Dennis Concepci贸n Mart铆n on 19/07/2021. +// +// + +import Foundation +import CoreData + + +extension Favourite { + + @nonobjc public class func fetchRequest() -> NSFetchRequest<Favourite> { + return NSFetchRequest<Favourite>(entityName: "Favourite") + } + + @NSManaged public var currencyPair: String + +} + +extension Favourite : Identifiable { + +}
--- a/Simoleon/Persistence.swift Mon Jul 19 10:12:23 2021 +0100 +++ b/Simoleon/Persistence.swift Mon Jul 19 19:27:12 2021 +0100 @@ -17,6 +17,11 @@ let newItem = Item(context: viewContext) newItem.timestamp = Date() } + + for _ in 0..<10 { + let favourite = Favourite(context: viewContext) + favourite.currencyPair = "USD/GBP" + } do { try viewContext.save() } catch {
--- a/Simoleon/Simoleon.xcdatamodeld/Simoleon.xcdatamodel/contents Mon Jul 19 10:12:23 2021 +0100 +++ b/Simoleon/Simoleon.xcdatamodeld/Simoleon.xcdatamodel/contents Mon Jul 19 19:27:12 2021 +0100 @@ -1,9 +1,13 @@ <?xml version="1.0" encoding="UTF-8" standalone="yes"?> -<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1" systemVersion="11A491" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="true" userDefinedModelVersionIdentifier=""> +<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="18154" systemVersion="20F71" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="YES" userDefinedModelVersionIdentifier=""> + <entity name="Favourite" representedClassName="Favourite" syncable="YES"> + <attribute name="currencyPair" optional="YES" attributeType="String"/> + </entity> <entity name="Item" representedClassName="Item" syncable="YES" codeGenerationType="class"> <attribute name="timestamp" optional="YES" attributeType="Date" usesScalarValueType="NO"/> </entity> <elements> <element name="Item" positionX="-63" positionY="-18" width="128" height="44"/> + <element name="Favourite" positionX="-63" positionY="-9" width="128" height="44"/> </elements> </model> \ No newline at end of file