changeset 335:2dad5828ccf6

HomeView implemented
author Dennis Concepción Martín <66180929+denniscm190@users.noreply.github.com>
date Wed, 31 Mar 2021 17:06:57 +0200
parents d7927e7eeff1
children 6f904b166564
files LazyBear.xcodeproj/project.pbxproj LazyBear.xcodeproj/project.xcworkspace/xcuserdata/dennis.xcuserdatad/UserInterfaceState.xcuserstate LazyBear.xcodeproj/xcshareddata/xcschemes/LazyBear.xcscheme LazyBear/Global Models/IntradayPricesModel.swift LazyBear/Preview Content/Home/Networking/HomeDataPreview.swift LazyBear/Views/Global Helpers/LineView.swift LazyBear/Views/Home/Helpers/TopStockItem.swift LazyBear/Views/Home/Helpers/TopStockRow.swift LazyBear/Views/Home/HomeView.swift LazyBear/Views/Networking/HomeData.swift
diffstat 10 files changed, 181 insertions(+), 53 deletions(-) [+]
line wrap: on
line diff
--- a/LazyBear.xcodeproj/project.pbxproj	Tue Mar 30 23:15:57 2021 +0200
+++ b/LazyBear.xcodeproj/project.pbxproj	Wed Mar 31 17:06:57 2021 +0200
@@ -33,6 +33,8 @@
 		95893DCE2613C46B003698C5 /* SectorPerformanceModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95893DCD2613C46B003698C5 /* SectorPerformanceModel.swift */; };
 		958A735225E0170900FD7ECA /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 958A735125E0170900FD7ECA /* CloudKit.framework */; };
 		95AD4A2D26078C1400498079 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95AD4A2C26078C1400498079 /* ContentView.swift */; };
+		95BC3B4F261476FB00FC3A12 /* IntradayPricesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BC3B4E261476FB00FC3A12 /* IntradayPricesModel.swift */; };
+		95E745DA2614624500744A1E /* HomeDataPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95E745D92614624500744A1E /* HomeDataPreview.swift */; };
 		95ECCA5D2612169200A67EFA /* LineShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95ECCA5C2612169200A67EFA /* LineShape.swift */; };
 		95ECCA60261216D500A67EFA /* LineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95ECCA5F261216D500A67EFA /* LineView.swift */; };
 /* End PBXBuildFile section */
@@ -68,6 +70,8 @@
 		958A734E25E016FD00FD7ECA /* LazyBear.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LazyBear.entitlements; sourceTree = "<group>"; };
 		958A735125E0170900FD7ECA /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; };
 		95AD4A2C26078C1400498079 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
+		95BC3B4E261476FB00FC3A12 /* IntradayPricesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntradayPricesModel.swift; sourceTree = "<group>"; };
+		95E745D92614624500744A1E /* HomeDataPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeDataPreview.swift; sourceTree = "<group>"; };
 		95ECCA5C2612169200A67EFA /* LineShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineShape.swift; sourceTree = "<group>"; };
 		95ECCA5F261216D500A67EFA /* LineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineView.swift; sourceTree = "<group>"; };
 /* End PBXFileReference section */
@@ -117,6 +121,7 @@
 				954D7EA5260BBA6600A13C50 /* WatchlistCompany+CoreDataClass.swift */,
 				954D7EA6260BBA6600A13C50 /* WatchlistCompany+CoreDataProperties.swift */,
 				952045232610FD7F00A76362 /* CompanyRowModel.swift */,
+				95BC3B4E261476FB00FC3A12 /* IntradayPricesModel.swift */,
 			);
 			path = "Global Models";
 			sourceTree = "<group>";
@@ -185,6 +190,7 @@
 		95672B9425DDA54700DCBE4A /* Preview Content */ = {
 			isa = PBXGroup;
 			children = (
+				95E745D72614622C00744A1E /* Home */,
 				95672B9525DDA54700DCBE4A /* Preview Assets.xcassets */,
 			);
 			path = "Preview Content";
@@ -227,6 +233,22 @@
 			path = Views;
 			sourceTree = "<group>";
 		};
+		95E745D72614622C00744A1E /* Home */ = {
+			isa = PBXGroup;
+			children = (
+				95E745D82614623700744A1E /* Networking */,
+			);
+			path = Home;
+			sourceTree = "<group>";
+		};
+		95E745D82614623700744A1E /* Networking */ = {
+			isa = PBXGroup;
+			children = (
+				95E745D92614624500744A1E /* HomeDataPreview.swift */,
+			);
+			path = Networking;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 
 /* Begin PBXNativeTarget section */
@@ -303,6 +325,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				950C36E3260FB6180081CF53 /* HapticsManager.swift in Sources */,
+				95E745DA2614624500744A1E /* HomeDataPreview.swift in Sources */,
 				9550444926111FC9000E0BCB /* TopStockRow.swift in Sources */,
 				952045242610FD7F00A76362 /* CompanyRowModel.swift in Sources */,
 				9550444326111E7A000E0BCB /* SectorRow.swift in Sources */,
@@ -314,6 +337,7 @@
 				950C36E9260FBB550081CF53 /* UserSettings+CoreDataProperties.swift in Sources */,
 				954D7EA8260BBA6600A13C50 /* WatchlistCompany+CoreDataProperties.swift in Sources */,
 				951566E72613A2B6007C0F36 /* TradingDates.swift in Sources */,
+				95BC3B4F261476FB00FC3A12 /* IntradayPricesModel.swift in Sources */,
 				95893DC92613C421003698C5 /* HomeData.swift in Sources */,
 				95893DCE2613C46B003698C5 /* SectorPerformanceModel.swift in Sources */,
 				95672B8F25DDA54700DCBE4A /* LazyBearApp.swift in Sources */,
Binary file LazyBear.xcodeproj/project.xcworkspace/xcuserdata/dennis.xcuserdatad/UserInterfaceState.xcuserstate has changed
--- a/LazyBear.xcodeproj/xcshareddata/xcschemes/LazyBear.xcscheme	Tue Mar 30 23:15:57 2021 +0200
+++ b/LazyBear.xcodeproj/xcshareddata/xcschemes/LazyBear.xcscheme	Wed Mar 31 17:06:57 2021 +0200
@@ -31,7 +31,7 @@
       </Testables>
    </TestAction>
    <LaunchAction
-      buildConfiguration = "Debug"
+      buildConfiguration = "Release"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
       launchStyle = "0"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LazyBear/Global Models/IntradayPricesModel.swift	Wed Mar 31 17:06:57 2021 +0200
@@ -0,0 +1,23 @@
+//
+//  IntradayPricesModel.swift
+//  LazyBear
+//
+//  Created by Dennis Concepción Martín on 31/3/21.
+//
+
+import SwiftUI
+
+// Model prepared with batch
+
+struct IntradayPricesArray: Codable {
+    var intradayPrices: [IntradayPricesModel]
+
+    // Change key name
+    private enum CodingKeys : String, CodingKey {
+           case intradayPrices = "intraday-prices"
+       }
+}
+
+struct IntradayPricesModel: Codable {
+    var open: Double?
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LazyBear/Preview Content/Home/Networking/HomeDataPreview.swift	Wed Mar 31 17:06:57 2021 +0200
@@ -0,0 +1,27 @@
+//
+//  HomeDataPreview.swift
+//  LazyBear
+//
+//  Created by Dennis Concepción Martín on 31/3/21.
+//
+
+import SwiftUI
+
+class HomeDataPreview: ObservableObject {
+    @Published var sectorPerformance = [
+        SectorPerformanceModel(name: "Communication Services", performance: 0.043, lastUpdated: 1617177387000),
+        SectorPerformanceModel(name: "Consumer Discretionary", performance: 0.043, lastUpdated: 1617177387000),
+        SectorPerformanceModel(name: "Consumer Staples", performance: 0.043, lastUpdated: 1617177387000),
+        SectorPerformanceModel(name: "Energy", performance: 0.043, lastUpdated: 1617177387000),
+        SectorPerformanceModel(name: "Financials", performance: 0.043, lastUpdated: 1617177387000),
+        SectorPerformanceModel(name: "Health Care", performance: 0.043, lastUpdated: 1617177387000),
+        SectorPerformanceModel(name: "Industrials", performance: 0.043, lastUpdated: 1617177387000),
+        SectorPerformanceModel(name: "Materials", performance: 0.043, lastUpdated: 1617177387000),
+        SectorPerformanceModel(name: "Real Estate", performance: 0.043, lastUpdated: 1617177387000),
+        SectorPerformanceModel(name: "Technology", performance: 0.043, lastUpdated: 1617177387000),
+        SectorPerformanceModel(name: "Utilities", performance: 0.043, lastUpdated: 1617177387000),
+    ]
+    
+    
+    
+}
--- a/LazyBear/Views/Global Helpers/LineView.swift	Tue Mar 30 23:15:57 2021 +0200
+++ b/LazyBear/Views/Global Helpers/LineView.swift	Wed Mar 31 17:06:57 2021 +0200
@@ -8,11 +8,12 @@
 import SwiftUI
 
 struct LineView: View {
+    var data: [Double]
+    
     var body: some View {
         GeometryReader { proxy in
             VStack {
-                let sampleData = generateRandomSample()
-                LineShape(width: proxy.size.width, height: proxy.size.height, normalizedData: normalize(sampleData))
+                LineShape(width: proxy.size.width, height: proxy.size.height, normalizedData: normalize(data))
                     .stroke(lineWidth: 2)
                     .rotationEffect(.degrees(180), anchor: .center)
                     .rotation3DEffect(.degrees(180), axis: (x: 0.0, y: 1.0, z: 0.0))
@@ -33,9 +34,14 @@
 
         return normalData
     }
+}
+
+struct LineView_Previews: PreviewProvider {
+    static var previews: some View {
+        LineView(data: generateRandomSample())
+    }
     
-    // DELETE THIS FUNCTION ON PRODUCTION
-    private func generateRandomSample() -> [Double] {
+    static private func generateRandomSample() -> [Double] {
         var randomSample = [Double]()
         for _ in 1..<10 {
             randomSample.append(Double.random(in: 1...100))
@@ -44,9 +50,3 @@
         return randomSample
     }
 }
-
-struct LineView_Previews: PreviewProvider {
-    static var previews: some View {
-        LineView()
-    }
-}
--- a/LazyBear/Views/Home/Helpers/TopStockItem.swift	Tue Mar 30 23:15:57 2021 +0200
+++ b/LazyBear/Views/Home/Helpers/TopStockItem.swift	Wed Mar 31 17:06:57 2021 +0200
@@ -8,6 +8,8 @@
 import SwiftUI
 
 struct TopStockItem: View {
+    var company: CompanyRowModel
+    var intradayPricesArray: IntradayPricesArray
     
     var body: some View {
             RoundedRectangle(cornerRadius: 20)
@@ -17,21 +19,23 @@
                 .overlay(
                     VStack(alignment: .leading) {
                         Group {
-                            Text("Symbol".uppercased())
+                            Text(company.symbol.uppercased())
                                 .fontWeight(.semibold)
                                 .padding(.top)
                             
-                            Text("Company name".capitalized)
+                            Text(company.companyName.capitalized)
+                                .font(.callout)
                                 .fontWeight(.semibold)
                                 .opacity(0.6)
+                                .lineLimit(1)
                             
-                            Text("$120.20")
-                                .foregroundColor(.green)
+                            Text("$\(company.latestPrice, specifier: "%.2f")")
+                                .foregroundColor(company.changePercent < 0 ? .red: .green)
                                 .fontWeight(.semibold)
                                 .padding(.top)
                             
-                            Text("+\(1.22, specifier: "%.2f")%")
-                                .foregroundColor(.green)
+                            Text("\(company.changePercent*100, specifier: "%.2f")%")
+                                .foregroundColor(company.changePercent < 0 ? .red: .green)
                                 .fontWeight(.semibold)
                                 
                         }
@@ -39,21 +43,21 @@
                         
                         Spacer()
                          
-                        
-                        LineView()
-                            .foregroundColor(.green)
+                        let prices = intradayPricesArray.intradayPrices.compactMap { $0.open }
+                        // Compact Map will return an array without the nil values
+                        LineView(data: prices)
+                            .foregroundColor(company.changePercent < 0 ? .red: .green)
                             .padding(.vertical)
                             .clipped()
                             
-                            
                     }
                 )
     }
 }
 
-struct TopStockItem_Previews: PreviewProvider {
-    static var previews: some View {
-        TopStockItem()
-
-    }
-}
+//struct TopStockItem_Previews: PreviewProvider {
+//    static var previews: some View {
+//        TopStockItem(company: CompanyRowModel(symbol: "aapl", companyName: "apple inc", latestPrice: 120.30, changePercent: 0.03), intradayPrices: <#IntradayPricesArray#>)
+//
+//    }
+//}
--- a/LazyBear/Views/Home/Helpers/TopStockRow.swift	Tue Mar 30 23:15:57 2021 +0200
+++ b/LazyBear/Views/Home/Helpers/TopStockRow.swift	Wed Mar 31 17:06:57 2021 +0200
@@ -8,19 +8,25 @@
 import SwiftUI
 
 struct TopStockRow: View {
-    var keyTitle: String
+    var key: String
+    var list: [CompanyRowModel]
+    var intradayPricesDict: [String: IntradayPricesArray]
     
     var body: some View {
         VStack(alignment: .leading) {
-            Text(keyTitle)
+            Text(adaptTitle())
                 .font(.title3)
                 .fontWeight(.semibold)
                 .padding([.top, .horizontal])
             
             ScrollView(.horizontal, showsIndicators: false) {
                 HStack(spacing: 20) {
-                    ForEach((1..<10)) { _ in
-                        TopStockItem()
+                    ForEach(list, id: \.self) { company in
+                        let symbol = company.symbol.uppercased()
+                        if let prices = intradayPricesDict[symbol] {
+                            TopStockItem(company: company, intradayPricesArray: prices)
+                        }
+                        
                     }
                 }
                 .padding()
@@ -29,10 +35,20 @@
         }
         .padding(.bottom)
     }
+    
+    private func adaptTitle() -> String {
+        if key == "mostactive" {
+            
+            return "Most active"
+        } else {
+            return key.capitalized
+        }
+    }
 }
 
-struct TopStockRow_Previews: PreviewProvider {
-    static var previews: some View {
-        TopStockRow(keyTitle: "Sample title")
-    }
-}
+//
+//struct TopStockRow_Previews: PreviewProvider {
+//    static var previews: some View {
+//        TopStockRow(key: "Gainers", list:[CompanyRowModel](), intradayPrices: <#[String : IntradayPricesArray]#>)
+//    }
+//}
--- a/LazyBear/Views/Home/HomeView.swift	Tue Mar 30 23:15:57 2021 +0200
+++ b/LazyBear/Views/Home/HomeView.swift	Wed Mar 31 17:06:57 2021 +0200
@@ -10,6 +10,7 @@
 struct HomeView: View {
     @ObservedObject var homeData = HomeData()
     @State private var showTradingDates = false
+    @State private var timer = Timer.publish(every: 10, on: .main, in: .common).autoconnect()  // Set recurrent price request
     
     static let taskDateFormat: DateFormatter = {
         let formatter = DateFormatter()
@@ -25,10 +26,9 @@
                 SectorRow(sectorPerformance: homeData.sectorPerformance)
                     .listRowInsets(EdgeInsets())
                 
-                let keyTitles = ["Top gainers", "Top losers", "Most active"]
-                ForEach(keyTitles, id: \.self) { keyTitle in
-                    TopStockRow(keyTitle: keyTitle)
-                    
+                // Get keys of the dictionary list
+                ForEach(homeData.list.keys.sorted(), id: \.self) { key in
+                    TopStockRow(key: key, list: homeData.list[key] ?? [CompanyRowModel](), intradayPricesDict: homeData.intradayPrices)
                 }
                 .listRowInsets(EdgeInsets())
             }
@@ -41,15 +41,10 @@
                         Image(systemName: "calendar.badge.clock")
                     }
                 }
-                
-                ToolbarItem(placement: .navigationBarLeading) {
-                    Button(action: { homeData.getSectorPerformance() }) {
-                        Text("Test recall")
-                    }
-                }
             }
         }
-        .onAppear { homeData.getSectorPerformance() }
+        .onAppear { homeData.get() }
+        .onReceive(timer) {_ in homeData.get() }
         .sheet(isPresented: $showTradingDates) {
             TradingDates()
         }
--- a/LazyBear/Views/Networking/HomeData.swift	Tue Mar 30 23:15:57 2021 +0200
+++ b/LazyBear/Views/Networking/HomeData.swift	Wed Mar 31 17:06:57 2021 +0200
@@ -8,14 +8,53 @@
 import SwiftUI
 
 class HomeData: ObservableObject {
-    @Published var sectorPerformance = [SectorPerformanceModel]() { didSet { print(sectorPerformance) }}
+    @Published var sectorPerformance = [SectorPerformanceModel]()
+    @Published var list = ["mostactive": [CompanyRowModel](), "gainers": [CompanyRowModel](), "losers": [CompanyRowModel]()]
+    @Published var intradayPrices = [String(): IntradayPricesArray(intradayPrices: [IntradayPricesModel(open: Double())])]
     
+    private let baseUrl = Bundle.main.infoDictionary?["IEX_URL"] as? String ?? "Empty url"
+    private let apiKey = Bundle.main.infoDictionary?["IEX_API"] as? String ?? "Empty key"
     
-    let baseUrl = Bundle.main.infoDictionary?["IEX_URL"] as? String ?? "Empty url"
-    let apiKey = Bundle.main.infoDictionary?["IEX_API"] as? String ?? "Empty key"
-    
-    func getSectorPerformance() {
-        let url = "\(baseUrl)/stock/market/sector-performance?token=\(apiKey)"
+    // PRINCIPAL FUNCTION TO CALL
+    func get() {
+        // 1. Request sector performance
+        var url = "\(baseUrl)/stock/market/sector-performance?token=\(apiKey)"
         request(url: url, model: [SectorPerformanceModel].self) { self.sectorPerformance = $0 }
+        
+        // 2. Request lists
+        var semaphore = 0
+        for key in self.list.keys {
+            url = "\(self.baseUrl)/stock/market/collection/list?collectionName=\(key)&token=\(self.apiKey)"
+            request(url: url, model: [CompanyRowModel].self) { self.list[key] = $0; semaphore += 1  // Finish modifying dictionary
+                
+                if semaphore == 3 {  // When dictionary is modified
+                    
+                    // 3. Request intraday prices
+                    var symbols = [String]()
+                    for key in self.list.keys {  // Iterate throught the list
+                        if let companies = self.list[key] {  // Unwrap value
+                            for company in companies {  // Iterate inside the list through the companies
+                                symbols.append(company.symbol)  // Append symbol
+                            }
+                        }
+                    }
+                    
+                    // Now that I have all the symbols I can request the intraday prices and save it to the @Published var
+                    // First I have to concatenate the string to make the batch request
+                    url = "\(self.baseUrl)/stock/market/batch?symbols="
+                    for symbol in symbols {  // Concatenate symbol to the first part of the url
+                        if symbols.firstIndex(of: symbol) == 0 {
+                            url += symbol
+                        } else {
+                            url += ",\(symbol)"
+                        }
+                    }
+
+                    // Once it's made, I can append the final part of the url and make the request
+                    url = "\(url)&types=intraday-prices&token=\(self.apiKey)"
+                    request(url: url, model: [String: IntradayPricesArray].self) { self.intradayPrices = $0 }
+                }
+            }
+        }
     }
 }