changeset 185:2fc95efcb1ee

connect backend
author Dennis Concepcion Martin <dennisconcepcionmartin@gmail.com>
date Wed, 22 Dec 2021 16:12:23 +0100
parents 7cb2b0b2b3f3
children 1ebd1c5dd302
files Simoleon.xcodeproj/project.pbxproj Simoleon/Assets.xcassets/AccentColor.colorset/Contents.json Simoleon/ConversionView.swift Simoleon/Functions/ConditionalWrapper.swift Simoleon/Functions/GetFlagName.swift Simoleon/Functions/HttpRequest.swift Simoleon/Functions/ReadJson.swift Simoleon/Helpers/ConditionalWrapper.swift Simoleon/Helpers/CurrencyBox.swift Simoleon/Helpers/CurrencyConversion.swift Simoleon/Helpers/CurrencyList.swift Simoleon/Helpers/CurrencyRow.swift Simoleon/Helpers/CurrencySelector.swift Simoleon/Helpers/CurrencySelectorLabel.swift Simoleon/Helpers/CurrencyTextfield.swift Simoleon/Helpers/FavoriteButton.swift Simoleon/Helpers/Flag.swift Simoleon/Helpers/ReadJson.swift Simoleon/Models/CurrencyConversionModel.swift SimoleonTests/SimoleonTests.swift
diffstat 20 files changed, 559 insertions(+), 151 deletions(-) [+]
line wrap: on
line diff
--- a/Simoleon.xcodeproj/project.pbxproj	Mon Dec 20 12:28:22 2021 +0100
+++ b/Simoleon.xcodeproj/project.pbxproj	Wed Dec 22 16:12:23 2021 +0100
@@ -16,16 +16,23 @@
 		9511E31B2760B8D7005EEE8D /* SimoleonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9511E31A2760B8D7005EEE8D /* SimoleonTests.swift */; };
 		9511E3252760B8D7005EEE8D /* SimoleonUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9511E3242760B8D7005EEE8D /* SimoleonUITests.swift */; };
 		9511E3272760B8D7005EEE8D /* SimoleonUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9511E3262760B8D7005EEE8D /* SimoleonUITestsLaunchTests.swift */; };
+		9518BB5F2771C4B1002C410E /* CurrencyTextfield.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9518BB5E2771C4B1002C410E /* CurrencyTextfield.swift */; };
+		9518BB612771CA1D002C410E /* CurrencyConversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9518BB602771CA1D002C410E /* CurrencyConversion.swift */; };
+		9518BB632771CE15002C410E /* CurrencyConversionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9518BB622771CE15002C410E /* CurrencyConversionModel.swift */; };
+		9518BB652771D12D002C410E /* FavoriteButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9518BB642771D12D002C410E /* FavoriteButton.swift */; };
+		9518BB672771D26F002C410E /* HttpRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9518BB662771D26F002C410E /* HttpRequest.swift */; };
 		95384AD7277077310045D838 /* CurrencyList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95384AD6277077310045D838 /* CurrencyList.swift */; };
+		95394FA32770B9D800E4F732 /* CurrencyRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95394FA22770B9D800E4F732 /* CurrencyRow.swift */; };
+		95394FA62770BA8800E4F732 /* GetFlagName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95394FA52770BA8800E4F732 /* GetFlagName.swift */; };
+		95394FA82770BC4100E4F732 /* Flag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95394FA72770BC4100E4F732 /* Flag.swift */; };
 		954573012760C2030084FFC7 /* ConversionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 954573002760C2030084FFC7 /* ConversionView.swift */; };
 		954573042760C2DF0084FFC7 /* ConditionalWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 954573032760C2DF0084FFC7 /* ConditionalWrapper.swift */; };
-		954573062760C4810084FFC7 /* CurrencySelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 954573052760C4810084FFC7 /* CurrencySelector.swift */; };
-		954573082760C4B00084FFC7 /* CurrencyBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 954573072760C4B00084FFC7 /* CurrencyBox.swift */; };
 		9545730B2760C5FC0084FFC7 /* SupportedCurrencies.json in Resources */ = {isa = PBXBuildFile; fileRef = 9545730A2760C5FC0084FFC7 /* SupportedCurrencies.json */; };
 		9545730D2760C77C0084FFC7 /* ReadJson.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9545730C2760C77C0084FFC7 /* ReadJson.swift */; };
 		954573102760C8980084FFC7 /* SupportedCurrencyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9545730F2760C8980084FFC7 /* SupportedCurrencyModel.swift */; };
 		954573122760C8D60084FFC7 /* SupportedPairModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 954573112760C8D60084FFC7 /* SupportedPairModel.swift */; };
 		954573162760CE3B0084FFC7 /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 954573152760CE3B0084FFC7 /* CloudKit.framework */; };
+		95CDF81E2770A082005ED28E /* CurrencySelectorLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95CDF81D2770A082005ED28E /* CurrencySelectorLabel.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -58,11 +65,17 @@
 		9511E3202760B8D7005EEE8D /* SimoleonUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SimoleonUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
 		9511E3242760B8D7005EEE8D /* SimoleonUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimoleonUITests.swift; sourceTree = "<group>"; };
 		9511E3262760B8D7005EEE8D /* SimoleonUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimoleonUITestsLaunchTests.swift; sourceTree = "<group>"; };
+		9518BB5E2771C4B1002C410E /* CurrencyTextfield.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyTextfield.swift; sourceTree = "<group>"; };
+		9518BB602771CA1D002C410E /* CurrencyConversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyConversion.swift; sourceTree = "<group>"; };
+		9518BB622771CE15002C410E /* CurrencyConversionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyConversionModel.swift; sourceTree = "<group>"; };
+		9518BB642771D12D002C410E /* FavoriteButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteButton.swift; sourceTree = "<group>"; };
+		9518BB662771D26F002C410E /* HttpRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HttpRequest.swift; sourceTree = "<group>"; };
 		95384AD6277077310045D838 /* CurrencyList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyList.swift; sourceTree = "<group>"; };
+		95394FA22770B9D800E4F732 /* CurrencyRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyRow.swift; sourceTree = "<group>"; };
+		95394FA52770BA8800E4F732 /* GetFlagName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetFlagName.swift; sourceTree = "<group>"; };
+		95394FA72770BC4100E4F732 /* Flag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Flag.swift; sourceTree = "<group>"; };
 		954573002760C2030084FFC7 /* ConversionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversionView.swift; sourceTree = "<group>"; };
 		954573032760C2DF0084FFC7 /* ConditionalWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalWrapper.swift; sourceTree = "<group>"; };
-		954573052760C4810084FFC7 /* CurrencySelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencySelector.swift; sourceTree = "<group>"; };
-		954573072760C4B00084FFC7 /* CurrencyBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyBox.swift; sourceTree = "<group>"; };
 		9545730A2760C5FC0084FFC7 /* SupportedCurrencies.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = SupportedCurrencies.json; sourceTree = "<group>"; };
 		9545730C2760C77C0084FFC7 /* ReadJson.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadJson.swift; sourceTree = "<group>"; };
 		9545730F2760C8980084FFC7 /* SupportedCurrencyModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportedCurrencyModel.swift; sourceTree = "<group>"; };
@@ -70,6 +83,7 @@
 		954573132760CE380084FFC7 /* Simoleon.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Simoleon.entitlements; sourceTree = "<group>"; };
 		954573152760CE3B0084FFC7 /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; };
 		954573172760CE490084FFC7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
+		95CDF81D2770A082005ED28E /* CurrencySelectorLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencySelectorLabel.swift; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -132,6 +146,7 @@
 				954573002760C2030084FFC7 /* ConversionView.swift */,
 				954573022760C2CE0084FFC7 /* Helpers */,
 				9545730E2760C8840084FFC7 /* Models */,
+				95394FA42770BA7200E4F732 /* Functions */,
 				954573092760C5DC0084FFC7 /* Resources */,
 				9511E30A2760B8D7005EEE8D /* Preview Content */,
 			);
@@ -163,14 +178,27 @@
 			path = SimoleonUITests;
 			sourceTree = "<group>";
 		};
-		954573022760C2CE0084FFC7 /* Helpers */ = {
+		95394FA42770BA7200E4F732 /* Functions */ = {
 			isa = PBXGroup;
 			children = (
 				954573032760C2DF0084FFC7 /* ConditionalWrapper.swift */,
-				954573052760C4810084FFC7 /* CurrencySelector.swift */,
-				954573072760C4B00084FFC7 /* CurrencyBox.swift */,
 				9545730C2760C77C0084FFC7 /* ReadJson.swift */,
+				95394FA52770BA8800E4F732 /* GetFlagName.swift */,
+				9518BB662771D26F002C410E /* HttpRequest.swift */,
+			);
+			path = Functions;
+			sourceTree = "<group>";
+		};
+		954573022760C2CE0084FFC7 /* Helpers */ = {
+			isa = PBXGroup;
+			children = (
+				95394FA72770BC4100E4F732 /* Flag.swift */,
+				95CDF81D2770A082005ED28E /* CurrencySelectorLabel.swift */,
 				95384AD6277077310045D838 /* CurrencyList.swift */,
+				95394FA22770B9D800E4F732 /* CurrencyRow.swift */,
+				9518BB5E2771C4B1002C410E /* CurrencyTextfield.swift */,
+				9518BB602771CA1D002C410E /* CurrencyConversion.swift */,
+				9518BB642771D12D002C410E /* FavoriteButton.swift */,
 			);
 			path = Helpers;
 			sourceTree = "<group>";
@@ -188,6 +216,7 @@
 			children = (
 				9545730F2760C8980084FFC7 /* SupportedCurrencyModel.swift */,
 				954573112760C8D60084FFC7 /* SupportedPairModel.swift */,
+				9518BB622771CE15002C410E /* CurrencyConversionModel.swift */,
 			);
 			path = Models;
 			sourceTree = "<group>";
@@ -333,16 +362,23 @@
 			files = (
 				95384AD7277077310045D838 /* CurrencyList.swift in Sources */,
 				9545730D2760C77C0084FFC7 /* ReadJson.swift in Sources */,
+				9518BB672771D26F002C410E /* HttpRequest.swift in Sources */,
 				954573102760C8980084FFC7 /* SupportedCurrencyModel.swift in Sources */,
-				954573082760C4B00084FFC7 /* CurrencyBox.swift in Sources */,
+				9518BB652771D12D002C410E /* FavoriteButton.swift in Sources */,
 				954573042760C2DF0084FFC7 /* ConditionalWrapper.swift in Sources */,
+				9518BB612771CA1D002C410E /* CurrencyConversion.swift in Sources */,
+				9518BB5F2771C4B1002C410E /* CurrencyTextfield.swift in Sources */,
 				9511E30E2760B8D7005EEE8D /* Persistence.swift in Sources */,
+				95CDF81E2770A082005ED28E /* CurrencySelectorLabel.swift in Sources */,
+				95394FA32770B9D800E4F732 /* CurrencyRow.swift in Sources */,
+				9518BB632771CE15002C410E /* CurrencyConversionModel.swift in Sources */,
 				954573122760C8D60084FFC7 /* SupportedPairModel.swift in Sources */,
 				954573012760C2030084FFC7 /* ConversionView.swift in Sources */,
-				954573062760C4810084FFC7 /* CurrencySelector.swift in Sources */,
 				9511E3072760B8D6005EEE8D /* ContentView.swift in Sources */,
 				9511E3052760B8D6005EEE8D /* SimoleonApp.swift in Sources */,
 				9511E3112760B8D7005EEE8D /* Simoleon.xcdatamodeld in Sources */,
+				95394FA62770BA8800E4F732 /* GetFlagName.swift in Sources */,
+				95394FA82770BC4100E4F732 /* Flag.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
--- a/Simoleon/Assets.xcassets/AccentColor.colorset/Contents.json	Mon Dec 20 12:28:22 2021 +0100
+++ b/Simoleon/Assets.xcassets/AccentColor.colorset/Contents.json	Wed Dec 22 16:12:23 2021 +0100
@@ -1,6 +1,15 @@
 {
   "colors" : [
     {
+      "color" : {
+        "color-space" : "srgb",
+        "components" : {
+          "alpha" : "1.000",
+          "blue" : "0.420",
+          "green" : "0.835",
+          "red" : "0.388"
+        }
+      },
       "idiom" : "universal"
     }
   ],
--- a/Simoleon/ConversionView.swift	Mon Dec 20 12:28:22 2021 +0100
+++ b/Simoleon/ConversionView.swift	Wed Dec 22 16:12:23 2021 +0100
@@ -10,23 +10,101 @@
 struct ConversionView: View {
     var showNavigationView: Bool?
     
+    // CurrencySelector variables
+    @State private var baseCurrency = SupportedCurrencyResult(code: "EUR", name: "Euro", isCrypto: 0)
+    @State private var quoteCurrency = SupportedCurrencyResult(code: "CHF", name: "Swiss Franc", isCrypto: 0)
+    @State private var showingCurrencyList = false
+    @State private var selecting: Selection = .baseCurrency
+    
+    // CurrencyTextfield variables
+    @State private var amount = "1"
+    
+    // CurrencyConversion variables
+    @State private var showConversion = false
+    @State private var conversion = CurrencyConversionResponse(message: [CurrencyConversionResult]())
+    
     var body: some View {
         ScrollView(showsIndicators: false) {
             VStack(alignment: .leading, spacing: 20) {
+                // MARK: - Currency selector
                 HStack {
-                    CurrencySelector()
-                    // FavoriteButton
+                    Button(action: { showCurrencyList(selecting: .baseCurrency) }) {
+                       CurrencySelectorLabel(currency: baseCurrency)
+                    }
+                    
+                    Button(action: { showCurrencyList(selecting: .quoteCurrency)}) {
+                        CurrencySelectorLabel(currency: quoteCurrency)
+                    }
+                    
+                    // MARK: - Favorite button
+                    FavoriteButton()
+                    
                 }
+                .padding(.bottom)
                 
-                // ConversionBox
+                // MARK: - Conversion box
+                Text("\(baseCurrency.code) - \(baseCurrency.name)")
+                    .font(.callout)
+                    .fontWeight(.semibold)
+                
+                CurrencyTextfield(currencyCode: baseCurrency.code, amount: $amount)
+                    .onChange(of: amount) { _ in
+                        showConversion = false
+                        getConversion()
+                    }
+                
+                Divider()
+                Text("\(quoteCurrency.code) - \(quoteCurrency.name)")
+                    .font(.callout)
+                    .fontWeight(.semibold)
+                
+                CurrencyConversion(
+                    conversion: conversion,
+                    currencyCode: quoteCurrency.code,
+                    showConversion: $showConversion
+                )
             }
             .padding()
+            .sheet(isPresented: $showingCurrencyList) {
+                CurrencyList(baseCurrency: $baseCurrency, quoteCurrency: $quoteCurrency, selecting: selecting)
+            }
         }
+        .onAppear(perform: getConversion)
         .navigationTitle("Convert")
         .if(UIDevice.current.userInterfaceIdiom == .phone && showNavigationView ?? true) { content in
             NavigationView { content }
         }
     }
+    
+    // Change selection and show CurrencyList()
+    private func showCurrencyList(selecting: Selection) {
+        self.selecting = selecting
+        showingCurrencyList.toggle()
+    }
+    
+    // Request conversion
+    private func getConversion() {
+        guard let amount = Float(amount) else {
+            amount = ""
+            showConversion = true
+            return
+        }
+        
+        let currencyPair = "\(baseCurrency.code)\(quoteCurrency.code)"
+        let url = "https://api.simoleon.app/fx/convert?symbols=\(currencyPair)&amount=\(amount)"
+        httpRequest(url: url, model: CurrencyConversionResponse.self) { response in
+            conversion = response
+            if conversion.message.isEmpty {
+                // Handle exception
+            } else {
+                showConversion = true
+            }
+        }
+    }
+}
+
+enum Selection {
+    case baseCurrency, quoteCurrency
 }
 
 struct ConversionView_Previews: PreviewProvider {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Simoleon/Functions/ConditionalWrapper.swift	Wed Dec 22 16:12:23 2021 +0100
@@ -0,0 +1,20 @@
+//
+//  ConditionalWrapper.swift
+//  Simoleon
+//
+//  Created by Dennis Concepción Martín on 8/12/21.
+//
+
+import SwiftUI
+
+// MARK: Wrap view with some content given a condition
+extension View {
+    @ViewBuilder
+    func `if`<Content: View>(_ conditional: Bool, content: (Self) -> Content) -> some View {
+        if conditional {
+            content(self)
+        } else {
+            self
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Simoleon/Functions/GetFlagName.swift	Wed Dec 22 16:12:23 2021 +0100
@@ -0,0 +1,19 @@
+//
+//  GetFlagName.swift
+//  Simoleon
+//
+//  Created by Dennis Concepción Martín on 20/12/21.
+//
+
+import Foundation
+
+// Given currency code get flag name
+func getFlagName(currency: SupportedCurrencyResult) -> String {
+    guard currency.isCrypto == 0  else {
+        return ""
+    }
+    
+    let flagName = currency.code.dropLast()
+    
+    return String(flagName)
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Simoleon/Functions/HttpRequest.swift	Wed Dec 22 16:12:23 2021 +0100
@@ -0,0 +1,35 @@
+//
+//  HttpRequest.swift
+//  Simoleon
+//
+//  Created by Dennis Concepción Martín on 21/12/21.
+//
+
+import Foundation
+
+// Network request
+func httpRequest<T: Decodable>(url: String, model: T.Type, completion: @escaping (_ result: T) -> Void) {
+    guard let url = URL(string: url) else {
+        print("Invalid URL")
+        return
+    }
+    let request = URLRequest(url: url)
+    URLSession.shared.dataTask(with: request) { data, response, error in
+        if let data = data {
+            do {
+                // Decode response with the model passed
+                let decodedResponse = try JSONDecoder().decode(model, from: data)
+                DispatchQueue.main.async {
+                    completion(decodedResponse)
+                }
+                return
+            } catch {
+                // Return error regarding the escaping code
+                print(error)
+            }
+        }
+        // Error with the request
+        print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
+    }
+    .resume()
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Simoleon/Functions/ReadJson.swift	Wed Dec 22 16:12:23 2021 +0100
@@ -0,0 +1,31 @@
+//
+//  ReadJson.swift
+//  Simoleon
+//
+//  Created by Dennis Concepción Martín on 8/12/21.
+//
+
+import Foundation
+
+// Read JSON file
+func readJson<T: Decodable>(from filename: String) -> T {
+    let data: Data
+    
+    guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
+    else {
+        fatalError("Failed to locate \(filename)")
+    }
+    
+    do {
+        data = try Data(contentsOf: file)
+    } catch {
+        fatalError("Failed to load \(filename)")
+    }
+    
+    do {
+        let decoder = JSONDecoder()
+        return try decoder.decode(T.self, from: data)
+    } catch {
+        fatalError("Failed to decode \(filename)")
+    }
+}
--- a/Simoleon/Helpers/ConditionalWrapper.swift	Mon Dec 20 12:28:22 2021 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,19 +0,0 @@
-//
-//  ConditionalWrapper.swift
-//  Simoleon
-//
-//  Created by Dennis Concepción Martín on 8/12/21.
-//
-
-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/CurrencyBox.swift	Mon Dec 20 12:28:22 2021 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,31 +0,0 @@
-//
-//  CurrencyBox.swift
-//  Simoleon
-//
-//  Created by Dennis Concepción Martín on 8/12/21.
-//
-
-import SwiftUI
-
-struct CurrencyBox: View {
-    var body: some View {
-        RoundedRectangle(cornerRadius: 15)
-            .foregroundColor(Color(.secondarySystemBackground))
-            .frame(height: 60)
-            .overlay(
-                HStack {
-                    Image("FLAG_NAME")
-                        .resizable()
-                        .aspectRatio(contentMode: .fill)
-                        .frame(width: 35, height: 35)
-                        .clipShape(Circle())
-                }
-            )
-    }
-}
-
-struct CurrencyBox_Previews: PreviewProvider {
-    static var previews: some View {
-        CurrencyBox()
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Simoleon/Helpers/CurrencyConversion.swift	Wed Dec 22 16:12:23 2021 +0100
@@ -0,0 +1,82 @@
+//
+//  CurrencyConversion.swift
+//  Simoleon
+//
+//  Created by Dennis Concepción Martín on 21/12/21.
+//
+
+import SwiftUI
+
+struct CurrencyConversion: View {
+    var conversion: CurrencyConversionResponse
+    var currencyCode: String
+    @Binding var showConversion: Bool
+    
+    var body: some View {
+        VStack {
+            RoundedRectangle(cornerRadius: 15)
+                .frame(height: 60)
+                .foregroundColor(Color(.secondarySystemBackground))
+                .overlay(
+                    VStack {
+                        if showConversion {
+                            let amount = conversion.message.first!.amount
+                            let formattedAmount = format(currency: amount)
+                            Text(formattedAmount)
+                                .font(.title2)
+                        } else {
+                            ProgressView()
+                        }
+                    }
+                    .padding(.leading, 15)
+                    
+                    , alignment: .leading
+                )
+            
+            if showConversion {
+                let timestamp = conversion.message.first!.timestamp
+                Text("Last updated: \(converToDate(epoch: timestamp))")
+                    .font(.caption)
+                    .opacity(0.6)
+            }
+        }
+    }
+    
+    // Format conversion to specific currency format
+    private func format(currency: Double) -> String {
+        let formatter = NumberFormatter()
+        formatter.currencyCode = currencyCode
+        formatter.numberStyle = .currency
+        
+        return formatter.string(from: NSNumber(value: currency))!
+    }
+    
+    // COnvert epoch to date
+    private func converToDate(epoch: Int) -> String {
+        let dateFormatter = DateFormatter()
+        dateFormatter.timeStyle = DateFormatter.Style.medium
+        dateFormatter.dateStyle = DateFormatter.Style.medium
+        let date = Date(timeIntervalSince1970: TimeInterval(epoch/1000))
+        
+        return dateFormatter.string(from: date)
+    }
+}
+
+struct CurrencyConversion_Previews: PreviewProvider {
+    static var previews: some View {
+        CurrencyConversion(
+            conversion:
+                CurrencyConversionResponse(
+                    message: [
+                        CurrencyConversionResult(
+                            rate: 1.31,
+                            timestamp: 1288282222000,
+                            amount: 95.63
+                        )
+                    ]
+                ),
+            currencyCode: "CHF",
+            showConversion: .constant(true)
+        )
+    }
+}
--- a/Simoleon/Helpers/CurrencyList.swift	Mon Dec 20 12:28:22 2021 +0100
+++ b/Simoleon/Helpers/CurrencyList.swift	Wed Dec 22 16:12:23 2021 +0100
@@ -12,20 +12,28 @@
     @Binding var baseCurrency: SupportedCurrencyResult
     @Binding var quoteCurrency: SupportedCurrencyResult
     var selecting: Selection
+    @Environment(\.dismiss) var dismiss
     
     var body: some View {
         NavigationView {
             List {
                 let currencies = getCurrencies()
                 ForEach(currencies, id: \.self) { currency in
-                    Text(currency.code)
+                    CurrencyRow(currency: currency)
                 }
             }
             .navigationTitle("Currencies")
+            .toolbar {
+                ToolbarItem(placement: .destructiveAction) {
+                    Button(action: { dismiss() }) {
+                        Image(systemName: "multiply.circle.fill")
+                    }
+                }
+            }
         }
     }
     
-    // MARK: - Get compatible currencies given currency code
+    // Get compatible currencies given currency code
     func getCurrencies() -> [SupportedCurrencyResult] {
         let pairs: SupportedPairResponse = readJson(from: "SupportedCurrencies.json")
         let currencies: SupportedCurrencyResponse = readJson(from: "SupportedCurrencies.json")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Simoleon/Helpers/CurrencyRow.swift	Wed Dec 22 16:12:23 2021 +0100
@@ -0,0 +1,38 @@
+//
+//  CurrencyRow.swift
+//  Simoleon
+//
+//  Created by Dennis Concepción Martín on 20/12/21.
+//
+
+import SwiftUI
+
+struct CurrencyRow: View {
+    var currency: SupportedCurrencyResult
+    
+    var body: some View {
+        HStack {
+            let flagName = getFlagName(currency: currency)
+            Flag(flagName: flagName)
+            
+            VStack(alignment: .leading) {
+                Text(currency.code)
+                    .font(.headline)
+                
+                Text(currency.name)
+                    .font(.callout)
+                    .opacity(0.6)
+            }
+            .padding(.leading)
+        }
+    }
+}
+
+struct CurrencyRow_Previews: PreviewProvider {
+    static var previews: some View {
+        CurrencyRow(
+            currency:
+                SupportedCurrencyResult(code: "EUR", name: "Euro", isCrypto: 0)
+        )
+    }
+}
--- a/Simoleon/Helpers/CurrencySelector.swift	Mon Dec 20 12:28:22 2021 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-//
-//  CurrencySelector.swift
-//  Simoleon
-//
-//  Created by Dennis Concepción Martín on 8/12/21.
-//
-
-import SwiftUI
-
-enum Selection {
-    case baseCurrency, quoteCurrency
-}
-
-struct CurrencySelector: View {
-    @State private var baseCurrency = SupportedCurrencyResult(code: "EUR", name: "Euro", isCrypto: 0)
-    @State private var quoteCurrency = SupportedCurrencyResult(code: "CHF", name: "Swiss Franc", isCrypto: 0)
-    @State private var showingCurrencyList = false
-    @State private var selecting: Selection = .baseCurrency
-    
-    var body: some View {
-        HStack {
-            Button(action: {
-                selecting = .baseCurrency
-                showingCurrencyList.toggle()
-                
-            }) {
-                RoundedRectangle(cornerRadius: 15)
-                    .foregroundColor(Color(.secondarySystemBackground))
-                    .frame(height: 60)
-                    .overlay(
-                        Text(baseCurrency.code)
-                    )
-            }
-            
-            Button(action: {
-                selecting = .quoteCurrency
-                showingCurrencyList.toggle()
-            }) {
-                RoundedRectangle(cornerRadius: 15)
-                    .foregroundColor(Color(.secondarySystemBackground))
-                    .frame(height: 60)
-                    .overlay(
-                        Text(quoteCurrency.code)
-                    )
-            }
-        }
-        .sheet(isPresented: $showingCurrencyList) {
-            CurrencyList(baseCurrency: $baseCurrency, quoteCurrency: $quoteCurrency, selecting: selecting)
-        }
-    }
-}
-
-struct CurrencySelector_Previews: PreviewProvider {
-    static var previews: some View {
-        CurrencySelector()
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Simoleon/Helpers/CurrencySelectorLabel.swift	Wed Dec 22 16:12:23 2021 +0100
@@ -0,0 +1,36 @@
+//
+//  CurrencySelectorLabel.swift
+//  Simoleon
+//
+//  Created by Dennis Concepción Martín on 20/12/21.
+//
+
+import SwiftUI
+
+struct CurrencySelectorLabel: View {
+    var currency: SupportedCurrencyResult
+    
+    var body: some View {
+        RoundedRectangle(cornerRadius: 15)
+            .foregroundColor(Color(.secondarySystemBackground))
+            .frame(height: 60)
+            .overlay(
+                HStack {
+                    let flagName = getFlagName(currency: currency)
+                    Flag(flagName: flagName)
+                    Text(currency.code)
+                        .font(.headline)
+                        .foregroundColor(.primary)
+                        .padding(.leading, 5)
+                }
+            )
+    }
+}
+
+struct CurrencySelectorLabel_Previews: PreviewProvider {
+    static var previews: some View {
+        CurrencySelectorLabel(
+            currency: SupportedCurrencyResult(code: "EUR", name: "Euro", isCrypto: 0)
+        )
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Simoleon/Helpers/CurrencyTextfield.swift	Wed Dec 22 16:12:23 2021 +0100
@@ -0,0 +1,33 @@
+//
+//  CurrencyTextfield.swift
+//  Simoleon
+//
+//  Created by Dennis Concepción Martín on 21/12/21.
+//
+
+import SwiftUI
+
+struct CurrencyTextfield: View {
+    var currencyCode: String
+    @Binding var amount: String
+    
+    var body: some View {
+        VStack {
+        TextField("Enter the amount", text: $amount)
+            .keyboardType(.decimalPad)
+            .font(.title2)
+            .padding(15)
+            .background(
+                Color(.secondarySystemBackground)
+                    .cornerRadius(15)
+                    .frame(height: 55)
+            )
+        }
+    }
+}
+
+struct CurrencyTextfield_Previews: PreviewProvider {
+    static var previews: some View {
+        CurrencyTextfield(currencyCode: "USD", amount: .constant("1"))
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Simoleon/Helpers/FavoriteButton.swift	Wed Dec 22 16:12:23 2021 +0100
@@ -0,0 +1,62 @@
+//
+//  FavoriteButton.swift
+//  Simoleon
+//
+//  Created by Dennis Concepción Martín on 21/12/21.
+//
+
+import SwiftUI
+
+struct FavoriteButton: View {
+    @State private var scale: CGFloat = 1
+    
+    var body: some View {
+        Button(action: {}) {
+            RoundedRectangle(cornerRadius: 15)
+                .foregroundColor(Color(.secondarySystemBackground))
+                .frame(width: 60, height: 60)
+                .overlay(
+                    VStack {
+                        if isFavorite() {
+                            Image(systemName: "star.fill")
+                        } else {
+                            Image(systemName: "star")
+                        }
+                    }
+                        .font(.system(size: 28))
+                        .foregroundColor(Color(.systemYellow))
+                )
+        }
+        .scaleEffect(scale)
+        .animation(.linear(duration: 0.2), value: scale)
+    }
+    
+    // Add currency conversion to favorites
+    private func add() {
+        
+    }
+    
+    // Remove currency conversion from favorites
+    private func remove() {
+        
+    }
+    
+    // Check if currency conversion is in favorites
+    private func isFavorite() -> Bool {
+        return false
+    }
+    
+    // Animate favorite button 
+    private func animate() {
+        scale += 0.2
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
+            scale -= 0.2
+        }
+    }
+}
+
+struct FavoriteButton_Previews: PreviewProvider {
+    static var previews: some View {
+        FavoriteButton()
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Simoleon/Helpers/Flag.swift	Wed Dec 22 16:12:23 2021 +0100
@@ -0,0 +1,26 @@
+//
+//  Flag.swift
+//  Simoleon
+//
+//  Created by Dennis Concepción Martín on 20/12/21.
+//
+
+import SwiftUI
+
+struct Flag: View {
+    var flagName: String
+    
+    var body: some View {
+        Image(flagName)
+            .resizable()
+            .aspectRatio(contentMode: .fill)
+            .frame(width: 35, height: 35)
+            .clipShape(Circle())
+    }
+}
+
+struct Flag_Previews: PreviewProvider {
+    static var previews: some View {
+        Flag(flagName: "EU")
+    }
+}
--- a/Simoleon/Helpers/ReadJson.swift	Mon Dec 20 12:28:22 2021 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +0,0 @@
-//
-//  ReadJson.swift
-//  Simoleon
-//
-//  Created by Dennis Concepción Martín on 8/12/21.
-//
-
-import Foundation
-
-func readJson<T: Decodable>(from filename: String) -> T {
-    let data: Data
-    
-    guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
-    else {
-        fatalError("Failed to locate \(filename)")
-    }
-    
-    do {
-        data = try Data(contentsOf: file)
-    } catch {
-        fatalError("Failed to load \(filename)")
-    }
-    
-    do {
-        let decoder = JSONDecoder()
-        return try decoder.decode(T.self, from: data)
-    } catch {
-        fatalError("Failed to decode \(filename)")
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Simoleon/Models/CurrencyConversionModel.swift	Wed Dec 22 16:12:23 2021 +0100
@@ -0,0 +1,18 @@
+//
+//  CurrencyConversionModel.swift
+//  Simoleon
+//
+//  Created by Dennis Concepción Martín on 21/12/21.
+//
+
+import Foundation
+
+struct CurrencyConversionResponse: Codable {
+    var message: [CurrencyConversionResult]
+}
+
+struct CurrencyConversionResult: Codable {
+    var rate: Double
+    var timestamp: Int
+    var amount: Double
+}
--- a/SimoleonTests/SimoleonTests.swift	Mon Dec 20 12:28:22 2021 +0100
+++ b/SimoleonTests/SimoleonTests.swift	Wed Dec 22 16:12:23 2021 +0100
@@ -43,6 +43,20 @@
         let quoteCurrencies = currencyList.getCurrencies()
         XCTAssertEqual(quoteCurrencies.count, 18, "Quote currencies does not match")
     }
+    
+    func testGetCurrencyFlagName() throws {
+        let currency = SupportedCurrencyResult(code: "EUR", name: "Euro", isCrypto: 0)
+        let flagName = getFlagName(currency: currency)
+        
+        XCTAssertEqual(flagName, "EU", "Flag name does not match")
+    }
+    
+    func testGetCryptoFlagName() throws {
+        let currency = SupportedCurrencyResult(code: "BTC", name: "Bitcoin", isCrypto: 1)
+        let flagName = getFlagName(currency: currency)
+        
+        XCTAssertEqual(flagName, "BTC", "Flag name does not match")
+    }
 
     func testPerformanceExample() throws {
         // This is an example of a performance test case.