From 7a2c31b194e55eb3aaef2bac00c9990642e5ab45 Mon Sep 17 00:00:00 2001 From: Marcin Czachurski Date: Tue, 18 Apr 2023 20:34:59 +0200 Subject: [PATCH] #48 Add more icons to navigation bar --- ...plicationSettings+CoreDataProperties.swift | 4 + CoreData/ApplicationSettingsHandler.swift | 18 +++ .../Vernissage.xcdatamodeld/.xccurrentversion | 2 +- .../Vernissage-011.xcdatamodel/contents | 100 ++++++++++++ Vernissage.xcodeproj/project.pbxproj | 8 +- .../Models/NavigationMenuItemDetails.swift | 22 +++ Vernissage/ViewModifiers/NavigationMenu.swift | 142 ++++++++++++++++-- Vernissage/Views/MainView.swift | 8 +- 8 files changed, 283 insertions(+), 21 deletions(-) create mode 100644 CoreData/Vernissage.xcdatamodeld/Vernissage-011.xcdatamodel/contents create mode 100644 Vernissage/Models/NavigationMenuItemDetails.swift diff --git a/CoreData/ApplicationSettings+CoreDataProperties.swift b/CoreData/ApplicationSettings+CoreDataProperties.swift index 2f5e82b..f34cf0e 100644 --- a/CoreData/ApplicationSettings+CoreDataProperties.swift +++ b/CoreData/ApplicationSettings+CoreDataProperties.swift @@ -32,6 +32,10 @@ extension ApplicationSettings { @NSManaged public var showAvatarsOnTimeline: Bool @NSManaged public var showFavouritesOnTimeline: Bool @NSManaged public var showAltIconOnTimeline: Bool + + @NSManaged public var customNavigationMenuItem1: Int32 + @NSManaged public var customNavigationMenuItem2: Int32 + @NSManaged public var customNavigationMenuItem3: Int32 } extension ApplicationSettings: Identifiable { diff --git a/CoreData/ApplicationSettingsHandler.swift b/CoreData/ApplicationSettingsHandler.swift index cf6208e..bb0542b 100644 --- a/CoreData/ApplicationSettingsHandler.swift +++ b/CoreData/ApplicationSettingsHandler.swift @@ -165,6 +165,24 @@ class ApplicationSettingsHandler { CoreDataHandler.shared.save() } + func set(customNavigationMenuItem1: Int32) { + let defaultSettings = self.get() + defaultSettings.customNavigationMenuItem1 = customNavigationMenuItem1 + CoreDataHandler.shared.save() + } + + func set(customNavigationMenuItem2: Int32) { + let defaultSettings = self.get() + defaultSettings.customNavigationMenuItem2 = customNavigationMenuItem2 + CoreDataHandler.shared.save() + } + + func set(customNavigationMenuItem3: Int32) { + let defaultSettings = self.get() + defaultSettings.customNavigationMenuItem3 = customNavigationMenuItem3 + CoreDataHandler.shared.save() + } + private func createApplicationSettingsEntity(viewContext: NSManagedObjectContext? = nil) -> ApplicationSettings { let context = viewContext ?? CoreDataHandler.shared.container.viewContext return ApplicationSettings(context: context) diff --git a/CoreData/Vernissage.xcdatamodeld/.xccurrentversion b/CoreData/Vernissage.xcdatamodeld/.xccurrentversion index 2f67f59..1407832 100644 --- a/CoreData/Vernissage.xcdatamodeld/.xccurrentversion +++ b/CoreData/Vernissage.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - Vernissage-010.xcdatamodel + Vernissage-011.xcdatamodel diff --git a/CoreData/Vernissage.xcdatamodeld/Vernissage-011.xcdatamodel/contents b/CoreData/Vernissage.xcdatamodeld/Vernissage-011.xcdatamodel/contents new file mode 100644 index 0000000..e5fa531 --- /dev/null +++ b/CoreData/Vernissage.xcdatamodeld/Vernissage-011.xcdatamodel/contents @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Vernissage.xcodeproj/project.pbxproj b/Vernissage.xcodeproj/project.pbxproj index a4d8e00..43795e5 100644 --- a/Vernissage.xcodeproj/project.pbxproj +++ b/Vernissage.xcodeproj/project.pbxproj @@ -84,6 +84,7 @@ F86B7221296C49A300EE59EC /* EmptyButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86B7220296C49A300EE59EC /* EmptyButtonStyle.swift */; }; F86BC9E929EBBB67009415EC /* ImageSaver.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86BC9E829EBBB66009415EC /* ImageSaver.swift */; }; F86BC9EB29EBDA2E009415EC /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86BC9EA29EBDA2E009415EC /* ActivityView.swift */; }; + F871F21D29EF0D7000A351EF /* NavigationMenuItemDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = F871F21C29EF0D7000A351EF /* NavigationMenuItemDetails.swift */; }; F8742FC429990AFB00E9642B /* ClientError.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8742FC329990AFB00E9642B /* ClientError.swift */; }; F8764187298ABB520057D362 /* ViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8764186298ABB520057D362 /* ViewState.swift */; }; F876418D298AE5020057D362 /* PaginableStatusesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F876418C298AE5020057D362 /* PaginableStatusesView.swift */; }; @@ -277,6 +278,8 @@ F86B7220296C49A300EE59EC /* EmptyButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyButtonStyle.swift; sourceTree = ""; }; F86BC9E829EBBB66009415EC /* ImageSaver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageSaver.swift; sourceTree = ""; }; F86BC9EA29EBDA2E009415EC /* ActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityView.swift; sourceTree = ""; }; + F871F21C29EF0D7000A351EF /* NavigationMenuItemDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationMenuItemDetails.swift; sourceTree = ""; }; + F871F21F29EF0FEC00A351EF /* Vernissage-011.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-011.xcdatamodel"; sourceTree = ""; }; F8742FC329990AFB00E9642B /* ClientError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientError.swift; sourceTree = ""; }; F8764186298ABB520057D362 /* ViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewState.swift; sourceTree = ""; }; F876418C298AE5020057D362 /* PaginableStatusesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginableStatusesView.swift; sourceTree = ""; }; @@ -483,6 +486,7 @@ F85D4DFD29B78C8400345267 /* HashtagModel.swift */, F89F57AF29D1C11200001EE3 /* RelationshipModel.swift */, F8DF38E329DD68820047F1AA /* ViewOffsetKey.swift */, + F871F21C29EF0D7000A351EF /* NavigationMenuItemDetails.swift */, ); path = Models; sourceTree = ""; @@ -1065,6 +1069,7 @@ F8742FC429990AFB00E9642B /* ClientError.swift in Sources */, F883402029B62AE900C3E096 /* SearchView.swift in Sources */, F88FAD2A295F43B8009B20C9 /* AccountData+CoreDataClass.swift in Sources */, + F871F21D29EF0D7000A351EF /* NavigationMenuItemDetails.swift in Sources */, F85DBF8F296732E20069BF89 /* AccountsView.swift in Sources */, F805DCF129DBEF83006A1FD9 /* ReportView.swift in Sources */, F8B0886029943498002AB40A /* OtherSectionView.swift in Sources */, @@ -1624,6 +1629,7 @@ F88C2476295C37BB0006098B /* Vernissage.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + F871F21F29EF0FEC00A351EF /* Vernissage-011.xcdatamodel */, F85B586C29ED169B00A16D12 /* Vernissage-010.xcdatamodel */, F8FFBD4929E99BEE0047EE80 /* Vernissage-009.xcdatamodel */, F8A4A88429E4099900267E36 /* Vernissage-008.xcdatamodel */, @@ -1636,7 +1642,7 @@ F8C937A929882CA90004D782 /* Vernissage-001.xcdatamodel */, F88C2477295C37BB0006098B /* Vernissage.xcdatamodel */, ); - currentVersion = F85B586C29ED169B00A16D12 /* Vernissage-010.xcdatamodel */; + currentVersion = F871F21F29EF0FEC00A351EF /* Vernissage-011.xcdatamodel */; path = Vernissage.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; diff --git a/Vernissage/Models/NavigationMenuItemDetails.swift b/Vernissage/Models/NavigationMenuItemDetails.swift new file mode 100644 index 0000000..8e2f0c3 --- /dev/null +++ b/Vernissage/Models/NavigationMenuItemDetails.swift @@ -0,0 +1,22 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the Apache License 2.0. +// + +import Foundation + +class NavigationMenuItemDetails: ObservableObject, Identifiable { + let id: Int32 + + @Published var viewMode: MainView.ViewMode + @Published var title: String + @Published var image: String + + init(id: Int32, viewMode: MainView.ViewMode, title: String, image: String) { + self.id = id + self.viewMode = viewMode + self.title = title + self.image = image + } +} diff --git a/Vernissage/ViewModifiers/NavigationMenu.swift b/Vernissage/ViewModifiers/NavigationMenu.swift index 256674f..ccb6025 100644 --- a/Vernissage/ViewModifiers/NavigationMenu.swift +++ b/Vernissage/ViewModifiers/NavigationMenu.swift @@ -9,10 +9,11 @@ import SwiftUI import EnvironmentKit import ServicesKit -public extension View { +extension View { func navigationMenu(menuPosition: Binding, + onViewModeIconTap: @escaping (MainView.ViewMode) -> Void, @ViewBuilder menuItems: @escaping () -> MenuItems) -> some View where MenuItems: View { - modifier(NavigationMenu(menuPosition: menuPosition, menuItems: menuItems)) + modifier(NavigationMenu(menuPosition: menuPosition, onViewModeIconTap: onViewModeIconTap, menuItems: menuItems)) } } @@ -20,10 +21,29 @@ private struct NavigationMenu: ViewModifier where MenuItems: View { @EnvironmentObject var routerPath: RouterPath private let menuItems: () -> MenuItems + private let onViewModeIconTap: (MainView.ViewMode) -> Void + private let imageFontSize = 20.0 + + private let customMenuItems = [ + NavigationMenuItemDetails(id: 1, viewMode: .home, title: "mainview.tab.homeTimeline", image: "house"), + NavigationMenuItemDetails(id: 2, viewMode: .local, title: "mainview.tab.localTimeline", image: "building"), + NavigationMenuItemDetails(id: 3, viewMode: .federated, title: "mainview.tab.federatedTimeline", image: "globe.europe.africa"), + NavigationMenuItemDetails(id: 4, viewMode: .search, title: "mainview.tab.search", image: "magnifyingglass"), + NavigationMenuItemDetails(id: 5, viewMode: .profile, title: "mainview.tab.userProfile", image: "person.crop.circle"), + NavigationMenuItemDetails(id: 6, viewMode: .notifications, title: "mainview.tab.notifications", image: "bell.badge") + ] + + @State private var selectedCustomMenuItems = [ + NavigationMenuItemDetails(id: 1, viewMode: .home, title: "mainview.tab.homeTimeline", image: "house"), + NavigationMenuItemDetails(id: 2, viewMode: .local, title: "mainview.tab.localTimeline", image: "building"), + NavigationMenuItemDetails(id: 3, viewMode: .profile, title: "mainview.tab.userProfile", image: "person.crop.circle") + ] + @Binding var menuPosition: MenuPosition - init(menuPosition: Binding, @ViewBuilder menuItems: @escaping () -> MenuItems) { + init(menuPosition: Binding, onViewModeIconTap: @escaping (MainView.ViewMode) -> Void, @ViewBuilder menuItems: @escaping () -> MenuItems) { self.menuItems = menuItems + self.onViewModeIconTap = onViewModeIconTap self._menuPosition = menuPosition } @@ -54,27 +74,50 @@ private struct NavigationMenu: ViewModifier where MenuItems: View { } } } + .onAppear { + self.loadCustomMenuItems() + } } } } @ViewBuilder private func menuContainerView() -> some View { - HStack(alignment: .center) { - if self.menuPosition == .bottomRight { - self.contextMenuView() - self.composeImageView() - } + if self.menuPosition == .bottomRight { + HStack(alignment: .center) { + HStack { + self.contextMenuView() + self.customMenuItemsView() + } + .frame(height: 50) + .padding(.horizontal, 8) + .background(.ultraThinMaterial) + .clipShape(Capsule()) - if self.menuPosition == .bottomLeft { self.composeImageView() - self.contextMenuView() + .frame(height: 50) + .padding(.horizontal, 8) + .background(.ultraThinMaterial) + .clipShape(Circle()) + } + } else { + HStack(alignment: .center) { + self.composeImageView() + .frame(height: 50) + .padding(.horizontal, 8) + .background(.ultraThinMaterial) + .clipShape(Circle()) + + HStack { + self.customMenuItemsView() + self.contextMenuView() + } + .frame(height: 50) + .padding(.horizontal, 8) + .background(.ultraThinMaterial) + .clipShape(Capsule()) } } - .frame(height: 44) - .padding(.horizontal, 8) - .background(.ultraThinMaterial) - .clipShape(Capsule()) } @ViewBuilder @@ -83,23 +126,90 @@ private struct NavigationMenu: ViewModifier where MenuItems: View { self.menuItems() } label: { Image(systemName: "ellipsis") - .font(.system(size: 26)) + .font(.system(size: self.imageFontSize)) .foregroundColor(.mainTextColor.opacity(0.75)) .padding(.vertical, 10) .padding(.horizontal, 8) } } + @ViewBuilder + private func customMenuItemsView() -> some View { + ForEach(self.selectedCustomMenuItems) { item in + self.customMenuItemView(customMenuItem: item) + } + } + + @ViewBuilder private func composeImageView() -> some View { Button { HapticService.shared.fireHaptic(of: .buttonPress) self.routerPath.presentedSheet = .newStatusEditor } label: { Image(systemName: "plus") - .font(.system(size: 26)) + .font(.system(size: self.imageFontSize)) .foregroundColor(.mainTextColor.opacity(0.75)) .padding(.vertical, 10) .padding(.horizontal, 8) } } + + @ViewBuilder + private func customMenuItemView(customMenuItem: NavigationMenuItemDetails) -> some View { + Button { + self.onViewModeIconTap(customMenuItem.viewMode) + } label: { + Image(systemName: customMenuItem.image) + .font(.system(size: self.imageFontSize)) + .foregroundColor(.mainTextColor.opacity(0.75)) + .padding(.vertical, 10) + .padding(.horizontal, 8) + }.contextMenu { + self.listOfIconsView(customMenuItem: customMenuItem) + } + } + + @ViewBuilder + private func listOfIconsView(customMenuItem: NavigationMenuItemDetails) -> some View { + ForEach(self.customMenuItems) { item in + Button { + withAnimation { + customMenuItem.title = item.title + customMenuItem.viewMode = item.viewMode + customMenuItem.image = item.image + } + + // Saving in core data. + switch customMenuItem.id { + case 1: + ApplicationSettingsHandler.shared.set(customNavigationMenuItem1: item.id) + case 2: + ApplicationSettingsHandler.shared.set(customNavigationMenuItem2: item.id) + case 3: + ApplicationSettingsHandler.shared.set(customNavigationMenuItem3: item.id) + default: + break + } + } label: { + Label(NSLocalizedString(item.title, comment: "Custom menu item"), systemImage: item.image) + } + } + } + + private func loadCustomMenuItems() { + let applicationSettings = ApplicationSettingsHandler.shared.get() + + self.setCustomMenuItem(menuId: 1, savedId: Int(applicationSettings.customNavigationMenuItem1)) + self.setCustomMenuItem(menuId: 2, savedId: Int(applicationSettings.customNavigationMenuItem2)) + self.setCustomMenuItem(menuId: 3, savedId: Int(applicationSettings.customNavigationMenuItem3)) + } + + private func setCustomMenuItem(menuId: Int, savedId: Int) { + if let selectedCustomMenuItem = self.selectedCustomMenuItems.first(where: { $0.id == menuId }), + let customMenuItem = self.customMenuItems.first(where: { $0.id == savedId }) { + selectedCustomMenuItem.title = customMenuItem.title + selectedCustomMenuItem.viewMode = customMenuItem.viewMode + selectedCustomMenuItem.image = customMenuItem.image + } + } } diff --git a/Vernissage/Views/MainView.swift b/Vernissage/Views/MainView.swift index cc6ad60..6fc7eb7 100644 --- a/Vernissage/Views/MainView.swift +++ b/Vernissage/Views/MainView.swift @@ -29,15 +29,17 @@ struct MainView: View { @FetchRequest(sortDescriptors: [SortDescriptor(\.acct, order: .forward)]) var dbAccounts: FetchedResults - private enum ViewMode { + public enum ViewMode { case home, local, federated, profile, notifications, trendingPhotos, trendingTags, trendingAccounts, search } var body: some View { self.getMainView() - .navigationMenu(menuPosition: $applicationState.menuPosition) { + .navigationMenu(menuPosition: $applicationState.menuPosition, onViewModeIconTap: { viewMode in + self.switchView(to: viewMode) + }, menuItems: { self.navigationMenuContent() - } + }) .navigationTitle(navBarTitle) .navigationBarTitleDisplayMode(.inline) .toolbar {