changeset 407:c804ce7a1560

Implementing Insider networking
author Dennis Concepción Martín <66180929+denniscm190@users.noreply.github.com>
date Sun, 06 Jun 2021 13:11:41 +0200
parents 09d05e48462f
children f9611c94d636
files LazyBear.xcodeproj/project.pbxproj LazyBear.xcodeproj/project.xcworkspace/xcuserdata/dennis.xcuserdatad/UserInterfaceState.xcuserstate LazyBear/Global Models/InsiderRosterModel.swift LazyBear/Global Models/SummaryInsiderModel.swift LazyBear/Global functions/ConvertEpoch.swift LazyBear/Views/Company/Chart.swift LazyBear/Views/Company/CompanyView.swift LazyBear/Views/Company/Helpers/InsiderRow.swift LazyBear/Views/Company/Helpers/NewsRow.swift LazyBear/Views/Company/Insiders.swift LazyBear/Views/Company/Networking/Company.swift LazyBear/Views/Company/Networking/InsidersResponse.swift
diffstat 12 files changed, 137 insertions(+), 76 deletions(-) [+]
line wrap: on
line diff
--- a/LazyBear.xcodeproj/project.pbxproj	Sat Jun 05 19:05:13 2021 +0200
+++ b/LazyBear.xcodeproj/project.pbxproj	Sun Jun 06 13:11:41 2021 +0200
@@ -35,7 +35,7 @@
 		955E73392623568F005652FF /* Home.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955E73382623568F005652FF /* Home.swift */; };
 		955E733C262356F3005652FF /* HomeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955E733B262356F3005652FF /* HomeResponse.swift */; };
 		95602702265ABB440046F97E /* InsidersResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95602701265ABB440046F97E /* InsidersResponse.swift */; };
-		95602704265ABB990046F97E /* SummaryInsiderModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95602703265ABB990046F97E /* SummaryInsiderModel.swift */; };
+		95602704265ABB990046F97E /* InsiderRosterModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95602703265ABB990046F97E /* InsiderRosterModel.swift */; };
 		95602706265ABC660046F97E /* Insiders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95602705265ABC660046F97E /* Insiders.swift */; };
 		95606D082647005B0072C02C /* StockCharts in Frameworks */ = {isa = PBXBuildFile; productRef = 95606D072647005B0072C02C /* StockCharts */; };
 		95613AD9264FC5A900D4CE8F /* Company.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95613AD8264FC5A900D4CE8F /* Company.swift */; };
@@ -131,7 +131,7 @@
 		955E73382623568F005652FF /* Home.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Home.swift; sourceTree = "<group>"; };
 		955E733B262356F3005652FF /* HomeResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeResponse.swift; sourceTree = "<group>"; };
 		95602701265ABB440046F97E /* InsidersResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsidersResponse.swift; sourceTree = "<group>"; };
-		95602703265ABB990046F97E /* SummaryInsiderModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SummaryInsiderModel.swift; sourceTree = "<group>"; };
+		95602703265ABB990046F97E /* InsiderRosterModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsiderRosterModel.swift; sourceTree = "<group>"; };
 		95602705265ABC660046F97E /* Insiders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Insiders.swift; sourceTree = "<group>"; };
 		95613AD8264FC5A900D4CE8F /* Company.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Company.swift; sourceTree = "<group>"; };
 		95613ADC264FC6A200D4CE8F /* ChartResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartResponse.swift; sourceTree = "<group>"; };
@@ -299,7 +299,7 @@
 				95A07F7526305AE3009865AA /* TradingDatesModel.swift */,
 				95613ADE264FC6FD00D4CE8F /* LatestNewsModel.swift */,
 				9594F03F2651355B00CFA8D4 /* HistoricalPricesModel.swift */,
-				95602703265ABB990046F97E /* SummaryInsiderModel.swift */,
+				95602703265ABB990046F97E /* InsiderRosterModel.swift */,
 			);
 			path = "Global Models";
 			sourceTree = "<group>";
@@ -669,7 +669,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				95602704265ABB990046F97E /* SummaryInsiderModel.swift in Sources */,
+				95602704265ABB990046F97E /* InsiderRosterModel.swift in Sources */,
 				950C36E3260FB6180081CF53 /* HapticsManager.swift in Sources */,
 				95FBE0DC2619CA7200440386 /* ProfileView.swift in Sources */,
 				95A5188626186F590002D27C /* PriceView.swift in Sources */,
Binary file LazyBear.xcodeproj/project.xcworkspace/xcuserdata/dennis.xcuserdatad/UserInterfaceState.xcuserstate has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LazyBear/Global Models/InsiderRosterModel.swift	Sun Jun 06 13:11:41 2021 +0200
@@ -0,0 +1,14 @@
+//
+//  InsiderRosterModel.swift
+//  LazyBear
+//
+//  Created by Dennis Concepción Martín on 23/5/21.
+//
+
+import SwiftUI
+
+struct InsiderRosterModel: Codable, Hashable {
+    var entityName: String
+    var position: Int
+    var reportDate: Int
+}
--- a/LazyBear/Global Models/SummaryInsiderModel.swift	Sat Jun 05 19:05:13 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-//
-//  SummaryInsiderModel.swift
-//  LazyBear
-//
-//  Created by Dennis Concepción Martín on 23/5/21.
-//
-
-import SwiftUI
-
-struct SummaryInsiderModel: Codable {
-    var date: Int
-    var fullName: String
-    var netTransacted: Int
-    var reportedTitle: String
-    var totalBought: Int
-    var totalSold: Int
-    var updated: Int
-}
--- a/LazyBear/Global functions/ConvertEpoch.swift	Sat Jun 05 19:05:13 2021 +0200
+++ b/LazyBear/Global functions/ConvertEpoch.swift	Sun Jun 06 13:11:41 2021 +0200
@@ -7,14 +7,27 @@
 
 import SwiftUI
 
-// Convert Epoch time to human date
-func convertEpoch(_ miliseconds: Int) -> String {
+/*
+ Convert Epoch time (in miliseconds) to human readable date
+ */
+func convertEpoch(_ miliseconds: Int, _ interval: Bool) -> String {
     let now = Date() // Current date
+    
     // TimeInterval() function must be in seconds, not in miliseconds
-    let articlePublished = Date(timeIntervalSince1970: TimeInterval(miliseconds/1000))
+    let convertedDate = Date(timeIntervalSince1970: TimeInterval(miliseconds/1000))
+    
     let formatter = DateComponentsFormatter()
     formatter.unitsStyle = .abbreviated
-    let humanDate = formatter.string(from: articlePublished, to: now)!
+    
+    let dateFormatter = DateFormatter()
+    dateFormatter.dateStyle = .medium
+    
+    var humanDate = String()
+    if interval {
+        humanDate = formatter.string(from: convertedDate, to: now)!
+    } else {
+        humanDate = dateFormatter.string(from: convertedDate)
+    }
     
     return humanDate
 }
--- a/LazyBear/Views/Company/Chart.swift	Sat Jun 05 19:05:13 2021 +0200
+++ b/LazyBear/Views/Company/Chart.swift	Sun Jun 06 13:11:41 2021 +0200
@@ -60,9 +60,12 @@
                     )
                 
                 if let latestNews = company.chartData.latestNews {
-                    ForEach(latestNews, id: \.self) { new in
-                        NewsRow(new: new)
+                    VStack(spacing: 20) {
+                        ForEach(latestNews, id: \.self) { new in
+                            NewsRow(new: new)
+                        }
                     }
+                    .padding(.top)
                 }
             }
             .onAppear { self.timer = Timer.publish(every: 10, on: .main, in: .common).autoconnect() }  // Start timer
--- a/LazyBear/Views/Company/CompanyView.swift	Sat Jun 05 19:05:13 2021 +0200
+++ b/LazyBear/Views/Company/CompanyView.swift	Sun Jun 06 13:11:41 2021 +0200
@@ -16,26 +16,39 @@
     
     // Views
     @State private var showChartView = true
+    @State private var showInsiderView = false
     
     var body: some View {
         ScrollView {
             VStack {
                 CompanyHeader(symbol: symbol, showViewSelector: $showViewSelector)
+                    .padding(.bottom)
                 
                 // Chart View
                 if showChartView {
                     Chart(company: company, symbol: symbol)
+                } else if showInsiderView {
+                    Insiders(company: company, symbol: symbol)
                 }
             }
             .padding()
         }
         .actionSheet(isPresented: $showViewSelector) {
             ActionSheet(title: Text("Select an option"), buttons: [
-                .default(Text("Chart & News")) { showChartView = true },
+                .default(Text("Chart & News")) { resetViews(); showChartView = true },
+                .default(Text("Insiders")) { resetViews(); showInsiderView = true },
                 .cancel()
             ])
         }
     }
+    
+    /*
+     Hide all views to show later the one tapped
+     */
+    private func resetViews() {
+        showChartView = false
+        showInsiderView = false
+    }
 }
 
 struct CompanyView_Previews: PreviewProvider {
--- a/LazyBear/Views/Company/Helpers/InsiderRow.swift	Sat Jun 05 19:05:13 2021 +0200
+++ b/LazyBear/Views/Company/Helpers/InsiderRow.swift	Sun Jun 06 13:11:41 2021 +0200
@@ -10,19 +10,29 @@
 
 struct InsiderRow: View {
     var percentageOfWidth: CGFloat
+    var insiderRoster: InsiderRosterModel
     
     var body: some View {
         RowShape()
-            .frame(height: 120)
+            .frame(height: 105)
             .overlay(
                 VStack(alignment: .leading) {
-                    Text("Dennis Concepcion")
-                        .font(.title3)
-                        .fontWeight(.semibold)
+                    Text(insiderRoster.entityName.capitalized)
+                        .lineLimit(1)
+                        .font(.headline)
+                    
+                    Text("Last updated: \(convertEpoch(insiderRoster.reportDate, false))")
+                        .opacity(0.5)
+                        .font(.subheadline)
+                    
+                    HStack {
+                        Spacer()
+                        Text("\(insiderRoster.position) shares owned")
+                            .font(.caption)
+                            .opacity(0.5)
+                    }
 
-                    Text("Random guy")
                     CapsuleChartView(percentageOfWidth: percentageOfWidth)
-                        .padding(.top)
                 }
                 .padding()
                 ,alignment: .leading
@@ -32,6 +42,14 @@
 
 struct InsiderRow_Previews: PreviewProvider {
     static var previews: some View {
-        InsiderRow(percentageOfWidth: 0.6)
+        InsiderRow(
+            percentageOfWidth: 0.6,
+            insiderRoster:
+                InsiderRosterModel(
+                    entityName: "Dennis Concepcion",
+                    position: 1230,
+                    reportDate: 1234567
+                )
+        )
     }
 }
--- a/LazyBear/Views/Company/Helpers/NewsRow.swift	Sat Jun 05 19:05:13 2021 +0200
+++ b/LazyBear/Views/Company/Helpers/NewsRow.swift	Sun Jun 06 13:11:41 2021 +0200
@@ -13,48 +13,38 @@
     
     var body: some View {
         Button(action: { showingSafariView = true }) {
-            VStack(alignment: .leading) {
-                Text(new.source.uppercased())
-                    .font(.caption)
-                    .opacity(0.5)
-                
-                Text(new.headline)
-                    .font(.headline)
-                
-                Text(new.summary)
-                    .opacity(0.5)
-                    .font(.subheadline)
-                    .lineLimit(1)
-                    .padding(.bottom, 5)
-                
-                let humanDate = convertDate()
-                Text("\(humanDate) ago")
-                    .font(.caption2)
-                    .opacity(0.5)
-                
-                Divider()
-            }
+            RowShape()
+                .frame(height: 140)
+                .overlay(
+                    VStack(alignment: .leading) {
+                        Text(new.source.uppercased())
+                            .font(.caption)
+                            .opacity(0.5)
+                        
+                        Text(new.headline)
+                            .font(.headline)
+                        
+                        Text(new.summary)
+                            .opacity(0.5)
+                            .font(.subheadline)
+                            .lineLimit(1)
+                            .padding(.bottom, 5)
+                        
+                        let humanDate = convertEpoch(new.datetime, true)
+                        Text("\(humanDate) ago")
+                            .font(.caption2)
+                            .opacity(0.5)
+                        
+                    }
+                    .padding()
+                    ,alignment: .leading
+                )
         }
         .buttonStyle(PlainButtonStyle())
         .sheet(isPresented: $showingSafariView) {
             SFSafariViewWrapper(url: URL(string: new.url)!)
         }
     }
-    
-    /*
-     Convert Epoch time to human readable
-     */
-    private func convertDate() -> String {
-        let now = Date() // Current date
-        // Time when the article was published. Divide new.datetime by 1,000 because
-        // TimeInterval() function must be in seconds, not in miliseconds
-        let articlePublished = Date(timeIntervalSince1970: TimeInterval(new.datetime)/1000)
-        let formatter = DateComponentsFormatter()
-        formatter.unitsStyle = .full
-        let humanDate = formatter.string(from: articlePublished, to: now)!
-        
-        return humanDate
-    }
 }
 
 struct NewsRow_Previews: PreviewProvider {
--- a/LazyBear/Views/Company/Insiders.swift	Sat Jun 05 19:05:13 2021 +0200
+++ b/LazyBear/Views/Company/Insiders.swift	Sun Jun 06 13:11:41 2021 +0200
@@ -13,11 +13,37 @@
     
     var body: some View {
         if company.showInsidersView {
-            
+            VStack(alignment: .leading) {
+                HStack {
+                    Text("Top net buyers")
+                        .font(.title3)
+                        .fontWeight(.semibold)
+                    
+                    Spacer()
+                    Button("See all", action: {  })
+                }
+                
+                if let insiderSummer = company.insidersData.insiderRoster {
+                    
+                    // Get total shares owned by the top 10 insiders
+                    let totalPositions =  insiderSummer.map { $0.position }.reduce(0, +)
+                    VStack(alignment: .leading, spacing: 20) {
+                        ForEach(insiderSummer.prefix(10), id: \.self) { insider in
+                            
+                            // Compute percentage of ownership for each insider
+                            let percentage = Double(insider.position) / Double(totalPositions)
+                            
+                            InsiderRow(percentageOfWidth: CGFloat(percentage), insiderRoster: insider)
+                                .onAppear { print(percentage) }
+                        }
+                    }
+                }
+            }
         } else {
             ProgressView()
                 .onAppear {
-                    // request API
+                   let url = "https://api.lazybear.app/company/insiders/symbol=\(symbol)"
+                    company.request(url, .initial, "insider")
                 }
         }
     }
--- a/LazyBear/Views/Company/Networking/Company.swift	Sat Jun 05 19:05:13 2021 +0200
+++ b/LazyBear/Views/Company/Networking/Company.swift	Sun Jun 06 13:11:41 2021 +0200
@@ -33,6 +33,8 @@
         } else if view == "insider" {
             bazooka.request(url: url, model: InsidersResponse.self) { response in
                 self.insidersData = response
+                
+                self.showInsidersView = true
             }
         }
     }
--- a/LazyBear/Views/Company/Networking/InsidersResponse.swift	Sat Jun 05 19:05:13 2021 +0200
+++ b/LazyBear/Views/Company/Networking/InsidersResponse.swift	Sun Jun 06 13:11:41 2021 +0200
@@ -8,9 +8,9 @@
 import SwiftUI
 
 struct InsidersResponse: Codable {
-    var insiderSummary: [SummaryInsiderModel]?
+    var insiderRoster: [InsiderRosterModel]?
     
     private enum CodingKeys: String, CodingKey {
-        case insiderSummary = "insider_summary"
+        case insiderRoster = "insider_roster"
     }
 }