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 {