changeset 389:db8bc3ed526a

Implementing add to watchlist feature from SearchView
author Dennis Concepción Martín <66180929+denniscm190@users.noreply.github.com>
date Sun, 25 Apr 2021 16:42:26 +0200
parents 79c39987aaa4
children 6303385b3629
files LazyBear.xcodeproj/project.pbxproj LazyBear.xcodeproj/project.xcworkspace/xcuserdata/dennis.xcuserdatad/UserInterfaceState.xcuserstate LazyBear/Views/Global Helpers/Company list/ExtensiveList.swift LazyBear/Views/Global Helpers/Company list/Helpers/ToolbarMenu.swift LazyBear/Views/Global Helpers/ExtensiveList.swift LazyBear/Views/Profile/Helpers/RenameSheet.swift LazyBear/Views/Profile/ProfileView.swift LazyBear/Views/Search/CompanyList.swift LazyBear/Views/Search/Helpers/SearchedCompanyItem.swift LazyBear/Views/Search/SearchView.swift
diffstat 10 files changed, 274 insertions(+), 157 deletions(-) [+]
line wrap: on
line diff
--- a/LazyBear.xcodeproj/project.pbxproj	Sat Apr 24 17:44:02 2021 +0200
+++ b/LazyBear.xcodeproj/project.pbxproj	Sun Apr 25 16:42:26 2021 +0200
@@ -7,6 +7,7 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		950272CD2635AACD003E779D /* ToolbarMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950272CC2635AACD003E779D /* ToolbarMenu.swift */; };
 		950C36E3260FB6180081CF53 /* HapticsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950C36E2260FB6180081CF53 /* HapticsManager.swift */; };
 		950C57132629EF9100F234FE /* LazyBearTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950C57122629EF9100F234FE /* LazyBearTests.swift */; };
 		950C57232629EFC200F234FE /* LazyBearUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950C57222629EFC200F234FE /* LazyBearUITests.swift */; };
@@ -77,6 +78,7 @@
 /* End PBXContainerItemProxy section */
 
 /* Begin PBXFileReference section */
+		950272CC2635AACD003E779D /* ToolbarMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarMenu.swift; sourceTree = "<group>"; };
 		950C36E2260FB6180081CF53 /* HapticsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticsManager.swift; sourceTree = "<group>"; };
 		950C57102629EF9100F234FE /* LazyBearTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LazyBearTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
 		950C57122629EF9100F234FE /* LazyBearTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyBearTests.swift; sourceTree = "<group>"; };
@@ -163,6 +165,23 @@
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
+		950272CA2635AA9F003E779D /* Company list */ = {
+			isa = PBXGroup;
+			children = (
+				95721DB3262787EF00EC527B /* ExtensiveList.swift */,
+				950272CB2635AABC003E779D /* Helpers */,
+			);
+			path = "Company list";
+			sourceTree = "<group>";
+		};
+		950272CB2635AABC003E779D /* Helpers */ = {
+			isa = PBXGroup;
+			children = (
+				950272CC2635AACD003E779D /* ToolbarMenu.swift */,
+			);
+			path = Helpers;
+			sourceTree = "<group>";
+		};
 		950C57112629EF9100F234FE /* LazyBearTests */ = {
 			isa = PBXGroup;
 			children = (
@@ -324,12 +343,12 @@
 		95893DD22613CAB5003698C5 /* Global Helpers */ = {
 			isa = PBXGroup;
 			children = (
+				950272CA2635AA9F003E779D /* Company list */,
 				95ECCA5F261216D500A67EFA /* LineView.swift */,
 				95ECCA5C2612169200A67EFA /* LineShape.swift */,
 				95A5188526186F590002D27C /* PriceView.swift */,
 				9550444826111FC9000E0BCB /* StockRow.swift */,
 				9550444B26111FED000E0BCB /* StockItem.swift */,
-				95721DB3262787EF00EC527B /* ExtensiveList.swift */,
 				95BD2FB226341D36008B6752 /* BlurBackground.swift */,
 			);
 			path = "Global Helpers";
@@ -554,6 +573,7 @@
 				9550444326111E7A000E0BCB /* SectorRow.swift in Sources */,
 				95721DB826278EC100EC527B /* CurrencyListItem.swift in Sources */,
 				95ECCA60261216D500A67EFA /* LineView.swift in Sources */,
+				950272CD2635AACD003E779D /* ToolbarMenu.swift in Sources */,
 				9550443A26111B2B000E0BCB /* HomeView.swift in Sources */,
 				95672B9825DDA54700DCBE4A /* Persistence.swift in Sources */,
 				95A7C0742616409D003E2EC1 /* ParseJSON.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/Views/Global Helpers/Company list/ExtensiveList.swift	Sun Apr 25 16:42:26 2021 +0200
@@ -0,0 +1,138 @@
+//
+//  ExtensiveList.swift
+//  LazyBear
+//
+//  Created by Dennis Concepción Martín on 14/4/21.
+//
+
+import SwiftUI
+
+struct ExtensiveList: View {
+    var listName: String
+    var list: [String: QuoteModel]?
+    var intradayPrices: [String: [IntradayPriceModel]]?
+    var latestCurrencies: [String: CurrencyModel]?
+    var addOnDelete: Bool
+    
+    @Environment(\.presentationMode) private var presentationMode
+    @Environment(\.managedObjectContext) private var moc
+    @FetchRequest(entity: WatchlistCompany.entity(), sortDescriptors: [])
+    var watchlistCompany: FetchedResults<WatchlistCompany>
+    
+    @State private var isEditMode: EditMode = .inactive
+    @State private var showRenameAction = false
+    @State private var showDeleteAlert = false
+    @State private var showSearchView = false
+    
+    var body: some View {
+        NavigationView {
+            ZStack {
+                VStack {
+                    if let list = list {
+                        List {
+                            ForEach(Array(list.keys.sorted()), id: \.self) { companySymbol in
+                                    StockItem(symbol: companySymbol,
+                                              company: list[companySymbol]!,
+                                              intradayPrices: intradayPrices?[companySymbol],
+                                              orientation: .horizontal,
+                                              hidePriceView: self.isEditMode == .active  // Hide on EditMode
+                                    )
+                                
+                            }
+                            .onDelete(perform: addOnDelete ? deleteCompany: nil)
+                        }
+                    }
+                    
+                    if let latestCurrencies = latestCurrencies {
+                        List(Array(latestCurrencies.keys.sorted()), id: \.self) { currencySymbol in
+                            CurrencyListItem(currencySymbol: currencySymbol, currency: latestCurrencies[currencySymbol]!)
+                            
+                        }
+                    }
+                }
+                
+                // Blur background
+                Color(.black)
+                    .edgesIgnoringSafeArea(.all)
+                    .opacity(showRenameAction ? 0.2: 0)
+                    .animation(.easeInOut)
+                    .onTapGesture { showRenameAction = false }
+                
+                // Show rename Action Sheet
+                RenameSheet(listName: listName, showRenameAction: $showRenameAction, presentationMode: presentationMode)
+                    .offset(y: showRenameAction ? 0: 700)
+                    .animation(.easeInOut)
+            }
+            // Show delete list alert
+            .alert(isPresented: $showDeleteAlert) {
+                Alert(
+                    title: Text("Are you sure you want to delete this list?"),
+                    message: Text("This action can't be undo"),
+                    primaryButton: .destructive(Text("Delete")) { deleteList() },
+                    secondaryButton: .cancel()
+                )
+            }
+            .sheet(isPresented: $showSearchView) {
+                SearchView()
+            }
+            .navigationTitle(listName)
+            .navigationBarTitleDisplayMode(.inline)
+            .toolbar {
+                ToolbarItem(placement: .cancellationAction) {
+                    if addOnDelete {
+                        EditButton()
+                    } else {
+                        Button(action: { presentationMode.wrappedValue.dismiss() }) {
+                            Image(systemName: "multiply")
+                                .imageScale(.large)
+                        }
+                    }
+                }
+                ToolbarItem(placement: .navigationBarTrailing) {
+                    if addOnDelete {
+                        ToolbarMenu(showRenameAction: $showRenameAction,
+                                    showSearchView: $showSearchView,
+                                    showDeleteAlert: $showDeleteAlert
+                        )
+                    }
+                }
+            }
+            .environment(\.editMode, self.$isEditMode)  // Always after Toolbar
+        }
+    }
+    
+    // Delete company from watchlist
+    private func deleteCompany(at offsets: IndexSet) {
+        for index in offsets {
+            let company = watchlistCompany[index]
+            moc.delete(company)
+        }
+        do {
+            try moc.save()
+            print("Company deleted")
+        } catch {
+            print(error.localizedDescription)
+        }
+    }
+    
+    // Remove entire watchlist
+    private func deleteList() {
+        let selectedWatchlist = watchlistCompany.filter({ $0.watchlist == listName })
+        for company in selectedWatchlist {
+            moc.delete(company)
+        }
+        do {
+            try moc.save()
+            print("List deleted")
+            presentationMode.wrappedValue.dismiss()  // Dismiss view
+        } catch {
+            print(error.localizedDescription)
+        }
+    }
+}
+
+struct ExtensiveList_Previews: PreviewProvider {
+    static var previews: some View {
+        ExtensiveList(listName: "List name", addOnDelete: false)
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LazyBear/Views/Global Helpers/Company list/Helpers/ToolbarMenu.swift	Sun Apr 25 16:42:26 2021 +0200
@@ -0,0 +1,44 @@
+//
+//  ToolbarMenu.swift
+//  LazyBear
+//
+//  Created by Dennis Concepción Martín on 25/4/21.
+//
+
+import SwiftUI
+
+struct ToolbarMenu: View {
+    @Binding var showRenameAction: Bool
+    @Binding var showSearchView: Bool
+    @Binding var showDeleteAlert: Bool
+    
+    var body: some View {
+        Menu {
+            Section {
+                Button(action: { showRenameAction = true }) {
+                    Label("Rename list", systemImage: "square.and.pencil")
+                }
+
+                Button(action: { showSearchView = true }) {
+                    Label("Add company", systemImage: "plus")
+                }
+            }
+
+            Section(header: Text("Secondary actions")) {
+                Button(action: { showDeleteAlert = true }) {
+                    Label("Delete list", systemImage: "trash")
+                }
+            }
+        }
+        label: {
+            Label("Options", systemImage: "ellipsis.circle")
+                .imageScale(.large)
+        }
+    }
+}
+
+struct ToolbarMenu_Previews: PreviewProvider {
+    static var previews: some View {
+        ToolbarMenu(showRenameAction: .constant(false), showSearchView: .constant(false), showDeleteAlert: .constant(false))
+    }
+}
--- a/LazyBear/Views/Global Helpers/ExtensiveList.swift	Sat Apr 24 17:44:02 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,151 +0,0 @@
-//
-//  ExtensiveList.swift
-//  LazyBear
-//
-//  Created by Dennis Concepción Martín on 14/4/21.
-//
-
-import SwiftUI
-
-struct ExtensiveList: View {
-    var listName: String
-    var list: [String: QuoteModel]?
-    var intradayPrices: [String: [IntradayPriceModel]]?
-    var latestCurrencies: [String: CurrencyModel]?
-    var addOnDelete: Bool
-    
-    @Environment(\.presentationMode) private var presentationMode
-    @Environment(\.managedObjectContext) private var moc
-    @FetchRequest(entity: WatchlistCompany.entity(), sortDescriptors: [])
-    var watchlistCompany: FetchedResults<WatchlistCompany>
-    
-    @State private var isEditMode: EditMode = .inactive
-    @State private var showRenameAction = false
-    @State private var showDeleteAlert = false
-    
-    var body: some View {
-        NavigationView {
-            ZStack {
-                VStack {
-                    if let list = list {
-                        List {
-                            ForEach(Array(list.keys.sorted()), id: \.self) { companySymbol in
-                                    StockItem(symbol: companySymbol,
-                                              company: list[companySymbol]!,
-                                              intradayPrices: intradayPrices?[companySymbol],
-                                              orientation: .horizontal,
-                                              hidePriceView: self.isEditMode == .active  // Hide on EditMode
-                                    )
-                                
-                            }
-                            .onDelete(perform: addOnDelete ? deleteCompany: nil)
-                        }
-                    }
-                    
-                    if let latestCurrencies = latestCurrencies {
-                        List(Array(latestCurrencies.keys.sorted()), id: \.self) { currencySymbol in
-                            CurrencyListItem(currencySymbol: currencySymbol, currency: latestCurrencies[currencySymbol]!)
-                            
-                        }
-                    }
-                }
-                
-                // Blur background
-                Color(.black)
-                    .edgesIgnoringSafeArea(.all)
-                    .opacity(showRenameAction ? 0.2: 0)
-                    .animation(.easeInOut)
-                    .onTapGesture { showRenameAction = false }
-                
-                // Show rename Action Sheet
-                RenameSheet(listName: listName, showRenameAction: $showRenameAction, presentationMode: presentationMode)
-                    .offset(y: showRenameAction ? 0: 700)
-                    .animation(.easeInOut)
-            }
-            // Show delete list alert
-            .alert(isPresented: $showDeleteAlert) {
-                Alert(
-                    title: Text("Are you sure you want to delete this list?"),
-                    message: Text("This action can't be undo"),
-                    primaryButton: .destructive(Text("Delete")) { deleteList() },
-                    secondaryButton: .cancel()
-                )
-            }
-            .navigationTitle(listName)
-            .navigationBarTitleDisplayMode(.inline)
-            .toolbar {
-                ToolbarItem(placement: .cancellationAction) {
-                    if addOnDelete {
-                        EditButton()
-                    } else {
-                        Button(action: { presentationMode.wrappedValue.dismiss() }) {
-                            Image(systemName: "multiply")
-                                .imageScale(.large)
-                        }
-                    }
-                }
-                ToolbarItem(placement: .navigationBarTrailing) {
-                    if addOnDelete {
-                        Menu {
-                            Section {
-                                Button(action: { self.showRenameAction = true }) {
-                                    Label("Rename list", systemImage: "square.and.pencil")
-                                }
-
-                                Button(action: { print("Add company") }) {
-                                    Label("Add company", systemImage: "plus")
-                                }
-                            }
-
-                            Section(header: Text("Secondary actions")) {
-                                Button(action: { self.showDeleteAlert = true }) {
-                                    Label("Delete list", systemImage: "trash")
-                                }
-                            }
-                        }
-                        label: {
-                            Label("Options", systemImage: "ellipsis.circle")
-                                .imageScale(.large)
-                        }
-                    }
-                }
-            }
-            .environment(\.editMode, self.$isEditMode)  // Always after Toolbar
-        }
-    }
-    
-    // Delete company from watchlist
-    private func deleteCompany(at offsets: IndexSet) {
-        for index in offsets {
-            let company = watchlistCompany[index]
-            moc.delete(company)
-        }
-        do {
-            try moc.save()
-            print("Company deleted")
-        } catch {
-            // Error
-        }
-    }
-    
-    // Remove entire watchlist
-    private func deleteList() {
-        let selectedWatchlist = watchlistCompany.filter({ $0.watchlist == listName })
-        for company in selectedWatchlist {
-            moc.delete(company)
-        }
-        do {
-            try moc.save()
-            print("List deleted")
-            presentationMode.wrappedValue.dismiss()  // Dismiss view
-        } catch {
-            print(error.localizedDescription)
-        }
-    }
-}
-
-struct ExtensiveList_Previews: PreviewProvider {
-    static var previews: some View {
-        ExtensiveList(listName: "List name", addOnDelete: false)
-    }
-}
--- a/LazyBear/Views/Profile/Helpers/RenameSheet.swift	Sat Apr 24 17:44:02 2021 +0200
+++ b/LazyBear/Views/Profile/Helpers/RenameSheet.swift	Sun Apr 25 16:42:26 2021 +0200
@@ -19,7 +19,7 @@
     var watchlistCompany: FetchedResults<WatchlistCompany>
     
     var body: some View {
-        RoundedRectangle(cornerRadius: 20)
+        RoundedRectangle(cornerRadius: 15)
             .frame(width: 280, height: 180)
             .foregroundColor(Color(.secondarySystemBackground))
             .overlay(
@@ -32,10 +32,10 @@
                     
                     Spacer()
                     TextField("Technologies, banks...", text: $newListName)
-                        .padding(8)
+                        .padding(7)
                         .background(
                             Color(.systemBackground)
-                                .cornerRadius(8)
+                                .cornerRadius(7)
                         )
                     
                     Divider()
@@ -67,7 +67,7 @@
             )
         .background(
             BlurBackground(style: .systemMaterial)
-               .clipShape(RoundedRectangle(cornerRadius: 20))
+               .clipShape(RoundedRectangle(cornerRadius: 15))
         )
     }
     
--- a/LazyBear/Views/Profile/ProfileView.swift	Sat Apr 24 17:44:02 2021 +0200
+++ b/LazyBear/Views/Profile/ProfileView.swift	Sun Apr 25 16:42:26 2021 +0200
@@ -12,6 +12,10 @@
     @ObservedObject var profile = Profile()
     @FetchRequest(entity: WatchlistCompany.entity(), sortDescriptors: [])
     var watchlistCompanies: FetchedResults<WatchlistCompany>
+    
+    // Refresh view when watchlistCompanies change
+    @State private var refreshing = false
+    private var didSave =  NotificationCenter.default.publisher(for: .NSManagedObjectContextDidSave)
 
     var body: some View {
         if profile.showView {
@@ -34,6 +38,10 @@
                             .listRowInsets(EdgeInsets())
                         }
                     }
+                    // The listener for refresh the view
+                    .onReceive(self.didSave) { _ in
+                        self.refreshing.toggle()
+                    }
                 }
                 .navigationTitle("My profile")
                 .navigationBarTitleDisplayMode(.inline)
--- a/LazyBear/Views/Search/CompanyList.swift	Sat Apr 24 17:44:02 2021 +0200
+++ b/LazyBear/Views/Search/CompanyList.swift	Sun Apr 25 16:42:26 2021 +0200
@@ -12,7 +12,9 @@
     
     var body: some View {
         List(searchResult, id: \.self) { company in
-            SearchedCompanyItem(company: company)
+            NavigationLink(destination: Text("Hello")) {
+                SearchedCompanyItem(company: company)
+            }
         }
         .listStyle(GroupedListStyle())
     }
--- a/LazyBear/Views/Search/Helpers/SearchedCompanyItem.swift	Sat Apr 24 17:44:02 2021 +0200
+++ b/LazyBear/Views/Search/Helpers/SearchedCompanyItem.swift	Sun Apr 25 16:42:26 2021 +0200
@@ -10,8 +10,28 @@
 struct SearchedCompanyItem: View {
     var company: SearchResponse
     
+    @Environment(\.managedObjectContext) private var moc
+    @FetchRequest(entity: WatchlistCompany.entity(), sortDescriptors: [])
+    var watchlistCompany: FetchedResults<WatchlistCompany>
+    
+    @State private var showingActionSheet = false
+    
     var body: some View {
+        let watchlistSymbols = watchlistCompany.map { $0.symbol }
         HStack {
+            Button(action: { self.showingActionSheet = true }) {
+                if watchlistSymbols.contains(company.symbol!) {
+                    Image(systemName: "star.fill")
+                        .foregroundColor(.yellow)
+                        .imageScale(.large)
+                } else {
+                    Image(systemName: "star")
+                        .foregroundColor(.yellow)
+                        .imageScale(.large)
+                }
+            }
+            .buttonStyle(PlainButtonStyle())
+            
             VStack(alignment: .leading) {
                 Text(company.symbol!.uppercased())
                     .fontWeight(.semibold)
@@ -29,6 +49,41 @@
                 Text(company.region!)
             }
         }
+        .actionSheet(isPresented: $showingActionSheet) {
+            ActionSheet(title: Text("Your watchlists"), message: Text("Select"), buttons: generateButtons())
+        }
+    }
+    
+    // Get watchlist names -> generate buttons
+    private func generateButtons() -> [ActionSheet.Button] {
+        var actionButtons = [ActionSheet.Button]()
+        let watchlists = Set(watchlistCompany.map { $0.watchlist })
+        
+        for watchlistName in watchlists {
+            actionButtons.append(
+                .default(Text(watchlistName)) {
+                    addCompany(company.symbol!, company.securityName!, watchlistName)
+                }
+            )
+        }
+        
+        actionButtons.append(.cancel())
+        
+        return actionButtons
+    }
+    
+    // Add to watchlist
+    private func addCompany(_ symbol: String, _ name: String, _ watchlist: String) {
+        let watchlistCompany = WatchlistCompany(context: moc)
+        watchlistCompany.symbol = symbol
+        watchlistCompany.name = name
+        watchlistCompany.watchlist = watchlist
+        do {
+            try moc.save()
+            print("Company saved")
+        } catch {
+            print(error.localizedDescription)
+        }
     }
 }
 
--- a/LazyBear/Views/Search/SearchView.swift	Sat Apr 24 17:44:02 2021 +0200
+++ b/LazyBear/Views/Search/SearchView.swift	Sun Apr 25 16:42:26 2021 +0200
@@ -10,6 +10,7 @@
 
 struct SearchView: View {
     @ObservedObject var search = Search()
+    @Environment(\.presentationMode) private var presentationMode
     @State private var searchedText = String()
     
     var body: some View {