mirror of
https://github.com/neon443/NearFuture.git
synced 2026-03-11 06:49:12 +00:00
Compare commits
7 Commits
a40d4f4300
...
feat-mac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4107a2faa | ||
|
|
5ec16dd67a | ||
|
|
0cffe243eb | ||
|
|
6533fb85ed | ||
|
|
5c667679d5 | ||
|
|
5dd25f1ede | ||
|
|
d80011ea27 |
@@ -32,6 +32,11 @@ struct NearFutureApp: App {
|
||||
}
|
||||
|
||||
WindowGroup("Edit Event", for: Event.ID.self) { $eventID in
|
||||
if viewModel.events.first(where: {$0.id == eventID}) == nil {
|
||||
AddEventView(
|
||||
viewModel: viewModel
|
||||
)
|
||||
} else {
|
||||
EditEventView(
|
||||
viewModel: viewModel,
|
||||
event: Binding(
|
||||
@@ -39,16 +44,14 @@ struct NearFutureApp: App {
|
||||
viewModel.events.first(where: {$0.id == eventID}) ?? viewModel.template
|
||||
},
|
||||
set: { newValue in
|
||||
if let eventIndex = viewModel.events.firstIndex(where: {
|
||||
$0.id == eventID
|
||||
}) {
|
||||
viewModel.events[eventIndex] = newValue
|
||||
}
|
||||
viewModel.saveEvents()
|
||||
viewModel.editEvent(newValue)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
.defaultSize(width: 480, height: 550)
|
||||
.windowIdealSize(.fitToContent)
|
||||
.restorationBehavior(.disabled)
|
||||
|
||||
Window("About Near Future", id: "about") {
|
||||
|
||||
@@ -37,7 +37,8 @@ struct ContentView: View {
|
||||
}
|
||||
NavigationLink {
|
||||
SymbolsPicker(
|
||||
selection: $symbolSearchInput
|
||||
selection: .constant(""),
|
||||
browsing: true
|
||||
)
|
||||
} label: {
|
||||
Image(systemName: "star.circle")
|
||||
@@ -65,9 +66,7 @@ struct ContentView: View {
|
||||
}
|
||||
.sheet(isPresented: $showAddEventView) {
|
||||
AddEventView(
|
||||
viewModel: viewModel,
|
||||
event: $viewModel.editableTemplate,
|
||||
adding: true
|
||||
viewModel: viewModel
|
||||
)
|
||||
.presentationSizing(.page)
|
||||
}
|
||||
@@ -75,8 +74,7 @@ struct ContentView: View {
|
||||
Button() {
|
||||
showAddEventView.toggle()
|
||||
} label: {
|
||||
Image(systemName: "plus")
|
||||
Text("New")
|
||||
Label("New", systemImage: "plus")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
//
|
||||
// EventListView.swift
|
||||
// MacNearFuture
|
||||
//
|
||||
// Created by neon443 on 21/05/2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct EventListView: View {
|
||||
@ObservedObject var viewModel: EventViewModel
|
||||
@State var event: Event
|
||||
|
||||
@State var largeTick: Bool = false
|
||||
@State var hovering: Bool = false
|
||||
|
||||
@Environment(\.openWindow) var openWindow
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color.black.opacity(hovering ? 0.5 : 0.0)
|
||||
HStack {
|
||||
RoundedRectangle(cornerRadius: 5)
|
||||
.frame(width: 7)
|
||||
.foregroundStyle(
|
||||
event.color.color.opacity(
|
||||
event.complete ? 0.5 : 1
|
||||
)
|
||||
)
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Image(systemName: event.symbol)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 20, height: 20)
|
||||
.shadow(radius: 5)
|
||||
.foregroundStyle(
|
||||
.one.opacity(
|
||||
event.complete ? 0.5 : 1
|
||||
)
|
||||
)
|
||||
Text("\(event.name)")
|
||||
.bold()
|
||||
.foregroundStyle(.one)
|
||||
.strikethrough(event.complete)
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
if !event.notes.isEmpty {
|
||||
Text(event.notes)
|
||||
.foregroundStyle(.one.opacity(0.8))
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
Text(
|
||||
event.date.formatted(
|
||||
date: .long,
|
||||
time: .shortened
|
||||
)
|
||||
)
|
||||
.foregroundStyle(
|
||||
.one.opacity(
|
||||
event.complete ? 0.5 : 1
|
||||
)
|
||||
)
|
||||
if event.recurrence != .none {
|
||||
Text("Occurs \(event.recurrence.rawValue)")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(
|
||||
.one.opacity(event.complete ? 0.5 : 1))
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
VStack {
|
||||
Text("\(daysUntilEvent(event.date).long)")
|
||||
.multilineTextAlignment(.trailing)
|
||||
.foregroundStyle(event.date.timeIntervalSinceNow < 0 ? .red : .one)
|
||||
}
|
||||
Button() {
|
||||
withAnimation {
|
||||
event.complete.toggle()
|
||||
}
|
||||
let eventToModify = viewModel.events.firstIndex() { currEvent in
|
||||
currEvent.id == event.id
|
||||
}
|
||||
if let eventToModify = eventToModify {
|
||||
viewModel.events[eventToModify] = event
|
||||
viewModel.saveEvents()
|
||||
}
|
||||
} label: {
|
||||
if event.complete {
|
||||
ZStack {
|
||||
Circle()
|
||||
.foregroundStyle(.green)
|
||||
Image(systemName: "checkmark")
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.scaledToFit()
|
||||
.bold()
|
||||
.frame(width: 15)
|
||||
}
|
||||
} else {
|
||||
Image(systemName: "circle")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.foregroundStyle(event.color.color)
|
||||
}
|
||||
}
|
||||
.onHover() { hovering in
|
||||
withAnimation {
|
||||
largeTick.toggle()
|
||||
}
|
||||
}
|
||||
.buttonStyle(.borderless)
|
||||
.scaleEffect(largeTick ? 1.5 : 1)
|
||||
.frame(maxWidth: 20)
|
||||
.shadow(radius: 5)
|
||||
.padding(.trailing, 15)
|
||||
.animation(
|
||||
.spring(response: 0.2, dampingFraction: 0.75, blendDuration: 2),
|
||||
value: largeTick
|
||||
)
|
||||
}
|
||||
.transition(.opacity)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
.onHover { isHovering in
|
||||
withAnimation {
|
||||
hovering.toggle()
|
||||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
openWindow(value: event.id)
|
||||
}
|
||||
.contextMenu() {
|
||||
Button(role: .destructive) {
|
||||
let eventToModify = viewModel.events.firstIndex() { currEvent in
|
||||
currEvent.id == event.id
|
||||
}
|
||||
if let eventToModify = eventToModify {
|
||||
viewModel.events.remove(at: eventToModify)
|
||||
viewModel.saveEvents()
|
||||
}
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview("EventListView") {
|
||||
let vm = dummyEventViewModel()
|
||||
ZStack {
|
||||
Color.black
|
||||
VStack {
|
||||
ForEach(0..<50) { _ in
|
||||
Rectangle()
|
||||
.foregroundStyle(randomColor().opacity(0.5))
|
||||
.padding(-10)
|
||||
}
|
||||
.ignoresSafeArea(.all)
|
||||
.blur(radius: 5)
|
||||
}
|
||||
VStack {
|
||||
ForEach(vm.events) { event in
|
||||
EventListView(
|
||||
viewModel: vm,
|
||||
event: event
|
||||
)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#Preview {
|
||||
EventListView(
|
||||
viewModel: dummyEventViewModel(),
|
||||
event: dummyEventViewModel().template
|
||||
)
|
||||
}
|
||||
@@ -70,15 +70,12 @@
|
||||
A979F6102D270AF90094C0B3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A979F60F2D270AF80094C0B3 /* Assets.xcassets */; };
|
||||
A979F6142D270AF90094C0B3 /* NearFutureWidgetsExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = A979F6022D270AF00094C0B3 /* NearFutureWidgetsExtension.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
A979F6182D2714310094C0B3 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C28B2D24011400E4F9B1 /* Events.swift */; };
|
||||
A98C20CB2DE730740008D61C /* EventListViewMac.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20CA2DE730740008D61C /* EventListViewMac.swift */; };
|
||||
A98C20CE2DE7308E0008D61C /* ArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20CD2DE7308E0008D61C /* ArchiveView.swift */; };
|
||||
A98C20D02DE731BD0008D61C /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20CF2DE731BD0008D61C /* HomeView.swift */; };
|
||||
A98C20D42DE7339E0008D61C /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20D32DE7339E0008D61C /* AboutView.swift */; };
|
||||
A9C769A22DFDD1FC00082FFF /* SymbolsPicker.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A9C769A02DFDD1FC00082FFF /* SymbolsPicker.storyboard */; };
|
||||
A9C769A32DFDD1FC00082FFF /* SymbolsPicker.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A9C769A02DFDD1FC00082FFF /* SymbolsPicker.storyboard */; };
|
||||
A9C769A52DFDD27500082FFF /* SymbolsPickerStoryboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C769A42DFDD27500082FFF /* SymbolsPickerStoryboard.swift */; };
|
||||
A9C769A62DFDD27500082FFF /* SymbolsPickerStoryboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C769A42DFDD27500082FFF /* SymbolsPickerStoryboard.swift */; };
|
||||
A9C769A72DFDD27500082FFF /* SymbolsPickerStoryboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C769A42DFDD27500082FFF /* SymbolsPickerStoryboard.swift */; };
|
||||
A9BAC6882DFF242300EC8E44 /* CompleteEventButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BAC6872DFF238100EC8E44 /* CompleteEventButton.swift */; };
|
||||
A9BAC6892DFF242300EC8E44 /* CompleteEventButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BAC6872DFF238100EC8E44 /* CompleteEventButton.swift */; };
|
||||
A9D1C34D2DFE10FA00703C2D /* EventListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8402DCAABE00064DCA0 /* EventListView.swift */; };
|
||||
A9FC7EEA2D2823920020D75B /* NearFutureWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FC7EE92D28238A0020D75B /* NearFutureWidgets.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@@ -159,12 +156,10 @@
|
||||
A979F6092D270AF00094C0B3 /* NearFutureWidgetsBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgetsBundle.swift; sourceTree = "<group>"; };
|
||||
A979F60B2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgetsLiveActivity.swift; sourceTree = "<group>"; };
|
||||
A979F60F2D270AF80094C0B3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
A98C20CA2DE730740008D61C /* EventListViewMac.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventListViewMac.swift; sourceTree = "<group>"; };
|
||||
A98C20CD2DE7308E0008D61C /* ArchiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveView.swift; sourceTree = "<group>"; };
|
||||
A98C20CF2DE731BD0008D61C /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
|
||||
A98C20D32DE7339E0008D61C /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
|
||||
A9C769A02DFDD1FC00082FFF /* SymbolsPicker.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SymbolsPicker.storyboard; sourceTree = "<group>"; };
|
||||
A9C769A42DFDD27500082FFF /* SymbolsPickerStoryboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymbolsPickerStoryboard.swift; sourceTree = "<group>"; };
|
||||
A9BAC6872DFF238100EC8E44 /* CompleteEventButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompleteEventButton.swift; sourceTree = "<group>"; };
|
||||
A9FC7EE92D28238A0020D75B /* NearFutureWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgets.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@@ -222,7 +217,6 @@
|
||||
children = (
|
||||
A90D49332DDE0FAF00781124 /* ContentViewMac.swift */,
|
||||
A98C20CF2DE731BD0008D61C /* HomeView.swift */,
|
||||
A98C20CA2DE730740008D61C /* EventListViewMac.swift */,
|
||||
A98C20CD2DE7308E0008D61C /* ArchiveView.swift */,
|
||||
A91EF80F2DFCB66C00B8463D /* SettingsView.swift */,
|
||||
);
|
||||
@@ -236,6 +230,7 @@
|
||||
A90D49512DDE2D0000781124 /* Extensions.swift */,
|
||||
A90D49202DDE0A3B00781124 /* Model */,
|
||||
A91EF80A2DFC910000B8463D /* ViewModifiers.swift */,
|
||||
A9BAC6872DFF238100EC8E44 /* CompleteEventButton.swift */,
|
||||
);
|
||||
path = Shared;
|
||||
sourceTree = "<group>";
|
||||
@@ -252,8 +247,6 @@
|
||||
children = (
|
||||
A91EF8172DFD77BF00B8463D /* SymbolsLoader.swift */,
|
||||
A91EF81B2DFD796600B8463D /* SymbolsPicker.swift */,
|
||||
A9C769A02DFDD1FC00082FFF /* SymbolsPicker.storyboard */,
|
||||
A9C769A42DFDD27500082FFF /* SymbolsPickerStoryboard.swift */,
|
||||
);
|
||||
path = SymbolsPicker;
|
||||
sourceTree = "<group>";
|
||||
@@ -516,7 +509,6 @@
|
||||
files = (
|
||||
A90D49442DDE1C7600781124 /* Tints.xcassets in Resources */,
|
||||
A920C2922D24011A00E4F9B1 /* Preview Assets.xcassets in Resources */,
|
||||
A9C769A22DFDD1FC00082FFF /* SymbolsPicker.storyboard in Resources */,
|
||||
A949F8322DCAAA8A0064DCA0 /* NearFutureIcon.png in Resources */,
|
||||
A920C28E2D24011A00E4F9B1 /* Assets.xcassets in Resources */,
|
||||
);
|
||||
@@ -527,7 +519,6 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
A90D49462DDE1C7A00781124 /* Tints.xcassets in Resources */,
|
||||
A9C769A32DFDD1FC00082FFF /* SymbolsPicker.storyboard in Resources */,
|
||||
A979F6102D270AF90094C0B3 /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -539,11 +530,10 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
A9C769A52DFDD27500082FFF /* SymbolsPickerStoryboard.swift in Sources */,
|
||||
A91EF80E2DFC9A0C00B8463D /* WhatsNewView.swift in Sources */,
|
||||
A91EF8192DFD77BF00B8463D /* SymbolsLoader.swift in Sources */,
|
||||
A95E9EE42DFC77D400ED655F /* ImportView.swift in Sources */,
|
||||
A98C20CB2DE730740008D61C /* EventListViewMac.swift in Sources */,
|
||||
A9BAC6892DFF242300EC8E44 /* CompleteEventButton.swift in Sources */,
|
||||
A91EF80C2DFC910000B8463D /* ViewModifiers.swift in Sources */,
|
||||
A95E9ED92DFC742B00ED655F /* AccentIcon.swift in Sources */,
|
||||
A91EF8102DFCB66C00B8463D /* SettingsView.swift in Sources */,
|
||||
@@ -551,6 +541,7 @@
|
||||
A95E9EE52DFC77E200ED655F /* ExportView.swift in Sources */,
|
||||
A91EF8132DFCC87D00B8463D /* EditEventView.swift in Sources */,
|
||||
A91EF8142DFCC87D00B8463D /* AddEventView.swift in Sources */,
|
||||
A9D1C34D2DFE10FA00703C2D /* EventListView.swift in Sources */,
|
||||
A98C20D42DE7339E0008D61C /* AboutView.swift in Sources */,
|
||||
A98C20CE2DE7308E0008D61C /* ArchiveView.swift in Sources */,
|
||||
A98C20D02DE731BD0008D61C /* HomeView.swift in Sources */,
|
||||
@@ -588,8 +579,8 @@
|
||||
A949F8512DCAABE00064DCA0 /* ExportView.swift in Sources */,
|
||||
A95E9ED82DFC742B00ED655F /* AccentIcon.swift in Sources */,
|
||||
A90D49532DDE2D0000781124 /* Extensions.swift in Sources */,
|
||||
A9C769A62DFDD27500082FFF /* SymbolsPickerStoryboard.swift in Sources */,
|
||||
A949F8522DCAABE00064DCA0 /* iCloudSettingsView.swift in Sources */,
|
||||
A9BAC6882DFF242300EC8E44 /* CompleteEventButton.swift in Sources */,
|
||||
A949F8532DCAABE00064DCA0 /* ImportView.swift in Sources */,
|
||||
A949F8542DCAABE00064DCA0 /* SettingsView.swift in Sources */,
|
||||
A91EF81E2DFD796600B8463D /* SymbolsPicker.swift in Sources */,
|
||||
@@ -609,7 +600,6 @@
|
||||
A95E9EDA2DFC742B00ED655F /* AccentIcon.swift in Sources */,
|
||||
A91EF80D2DFC910000B8463D /* ViewModifiers.swift in Sources */,
|
||||
A91EF8182DFD77BF00B8463D /* SymbolsLoader.swift in Sources */,
|
||||
A9C769A72DFDD27500082FFF /* SymbolsPickerStoryboard.swift in Sources */,
|
||||
A91EF8072DFC8B8B00B8463D /* ColorCodable.swift in Sources */,
|
||||
A9FC7EEA2D2823920020D75B /* NearFutureWidgets.swift in Sources */,
|
||||
A979F60C2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift in Sources */,
|
||||
|
||||
@@ -23,18 +23,31 @@ struct ArchiveView: View {
|
||||
} else {
|
||||
ScrollView {
|
||||
ForEach(filteredEvents) { event in
|
||||
NavigationLink() {
|
||||
EditEventView(
|
||||
viewModel: viewModel,
|
||||
event: Binding(
|
||||
get: { event },
|
||||
set: { newValue in
|
||||
viewModel.editEvent(newValue)
|
||||
}
|
||||
)
|
||||
)
|
||||
} label: {
|
||||
EventListView(viewModel: viewModel, event: event)
|
||||
.transition(.moveAndFadeReversed)
|
||||
.id(event.complete)
|
||||
}
|
||||
.transition(.moveAndFadeReversed)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.animation(.default, value: filteredEvents)
|
||||
}
|
||||
}
|
||||
.transition(.opacity)
|
||||
.scrollContentBackground(.hidden)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
AddEventButton(showingAddEventView: $showAddEvent)
|
||||
}
|
||||
}
|
||||
@@ -43,9 +56,7 @@ struct ArchiveView: View {
|
||||
}
|
||||
.sheet(isPresented: $showAddEvent) {
|
||||
AddEventView(
|
||||
viewModel: viewModel,
|
||||
event: $viewModel.editableTemplate,
|
||||
adding: true
|
||||
viewModel: viewModel
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import SwiftData
|
||||
enum Tab {
|
||||
case home
|
||||
case archive
|
||||
case symbols
|
||||
case stats
|
||||
case settings
|
||||
}
|
||||
@@ -33,6 +34,14 @@ struct ContentView: View {
|
||||
Label("Archive", systemImage: "tray.full")
|
||||
}
|
||||
.tag(Tab.archive)
|
||||
SymbolsPicker(
|
||||
selection: .constant(""),
|
||||
browsing: true
|
||||
)
|
||||
.tabItem {
|
||||
Label("Symbols", systemImage: "star.circle")
|
||||
}
|
||||
.tag(Tab.symbols)
|
||||
StatsView(viewModel: viewModel)
|
||||
// SymbolsPickerStoryboardUIViewRepresentable()
|
||||
.tabItem {
|
||||
|
||||
@@ -10,9 +10,9 @@ import SwiftUI
|
||||
struct AddEventView: View {
|
||||
@ObservedObject var viewModel: EventViewModel
|
||||
|
||||
@Binding var event: Event
|
||||
@State var event: Event = dummyEventViewModel().template
|
||||
|
||||
@State var adding: Bool
|
||||
@State var adding: Bool = true
|
||||
@State var showNeedsNameAlert: Bool = false
|
||||
@State var isSymbolPickerPresented: Bool = false
|
||||
|
||||
@@ -25,6 +25,14 @@ struct AddEventView: View {
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
var isMac: Bool {
|
||||
if #available(iOS 1, *) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
if !adding {
|
||||
@@ -75,6 +83,7 @@ struct AddEventView: View {
|
||||
}
|
||||
}
|
||||
|
||||
ColorPicker("Event Color", selection: $event.color.colorBind)
|
||||
|
||||
// date picker
|
||||
HStack {
|
||||
@@ -82,6 +91,8 @@ struct AddEventView: View {
|
||||
DatePicker("", selection: $event.date, displayedComponents: .date)
|
||||
#if os(iOS)
|
||||
.datePickerStyle(.wheel)
|
||||
#else
|
||||
.datePickerStyle(.graphical)
|
||||
#endif
|
||||
Spacer()
|
||||
Button() {
|
||||
@@ -100,6 +111,9 @@ struct AddEventView: View {
|
||||
selection: $event.date,
|
||||
displayedComponents: .hourAndMinute
|
||||
)
|
||||
#if os(macOS)
|
||||
.datePickerStyle(.stepperField)
|
||||
#endif
|
||||
|
||||
// re-ocurrence Picker
|
||||
Picker("Recurrence", selection: $event.recurrence) {
|
||||
@@ -141,10 +155,7 @@ struct AddEventView: View {
|
||||
bye.toggle()
|
||||
resetAddEventView()
|
||||
} label: {
|
||||
Text("Save")
|
||||
.font(.headline)
|
||||
.cornerRadius(10)
|
||||
.buttonStyle(BorderedProminentButtonStyle())
|
||||
Label("Save", systemImage: "checkmark")
|
||||
}
|
||||
.tint(.accent)
|
||||
.modifier(hapticSuccess(trigger: bye))
|
||||
@@ -164,9 +175,21 @@ struct AddEventView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
if !adding {
|
||||
Button() {
|
||||
viewModel.editEvent(event)
|
||||
dismiss()
|
||||
} label: {
|
||||
Label("Done", systemImage: "checkmark")
|
||||
}
|
||||
.disabled(event.name == "")
|
||||
}
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
}
|
||||
.navigationTitle("Editing \(event.name) - Ne")
|
||||
}
|
||||
.scrollContentBackground(isMac ? .automatic : .hidden)
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
}
|
||||
@@ -185,7 +208,6 @@ struct AddEventView: View {
|
||||
.sheet(isPresented: .constant(true)) {
|
||||
AddEventView(
|
||||
viewModel: vm,
|
||||
event: .constant(vm.template),
|
||||
adding: true
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,37 +12,13 @@ struct EditEventView: View {
|
||||
@ObservedObject var viewModel: EventViewModel
|
||||
@Binding var event: Event
|
||||
|
||||
fileprivate func saveEdits() {
|
||||
//if there is an event in vM.events with the id of the event we r editing,
|
||||
//firstindex - loops through the arr and finds first element where that events id matches editing event's id
|
||||
if let index = viewModel.events.firstIndex(where: { xEvent in
|
||||
xEvent.id == event.id
|
||||
}) {
|
||||
viewModel.events[index] = event
|
||||
}
|
||||
viewModel.saveEvents()
|
||||
|
||||
dismiss()
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
AddEventView(
|
||||
viewModel: viewModel,
|
||||
event: $event,
|
||||
event: event,
|
||||
adding: false //bc we editing existing event
|
||||
)
|
||||
.navigationTitle("Edit Event")
|
||||
.toolbar {
|
||||
ToolbarItem(/*placement: .topBarTrailing*/) {
|
||||
Button() {
|
||||
saveEdits()
|
||||
} label: {
|
||||
Text("Done")
|
||||
.bold()
|
||||
}
|
||||
.disabled(event.name == "")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,14 +12,100 @@ struct EventListView: View {
|
||||
@ObservedObject var viewModel: EventViewModel
|
||||
@State var event: Event
|
||||
|
||||
@Environment(\.openWindow) var openWindow
|
||||
|
||||
@State var hovering: Bool = false
|
||||
|
||||
#if canImport(AppKit)
|
||||
var body: some View {
|
||||
NavigationLink() {
|
||||
EditEventView(
|
||||
ZStack {
|
||||
Color.black.opacity(hovering ? 0.5 : 0.0)
|
||||
HStack {
|
||||
RoundedRectangle(cornerRadius: 5)
|
||||
.frame(width: 7)
|
||||
.foregroundStyle(
|
||||
event.color.color.opacity(
|
||||
event.complete ? 0.5 : 1
|
||||
)
|
||||
)
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Image(systemName: event.symbol)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 20, height: 20)
|
||||
.shadow(radius: 5)
|
||||
.foregroundStyle(
|
||||
.one.opacity(
|
||||
event.complete ? 0.5 : 1
|
||||
)
|
||||
)
|
||||
Text("\(event.name)")
|
||||
.bold()
|
||||
.foregroundStyle(.one)
|
||||
.strikethrough(event.complete)
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
if !event.notes.isEmpty {
|
||||
Text(event.notes)
|
||||
.foregroundStyle(.one.opacity(0.8))
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
Text(
|
||||
event.date.formatted(
|
||||
date: .long,
|
||||
time: .shortened
|
||||
)
|
||||
)
|
||||
.foregroundStyle(
|
||||
.one.opacity(
|
||||
event.complete ? 0.5 : 1
|
||||
)
|
||||
)
|
||||
if event.recurrence != .none {
|
||||
Text("Occurs \(event.recurrence.rawValue)")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(
|
||||
.one.opacity(event.complete ? 0.5 : 1))
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
VStack {
|
||||
Text("\(daysUntilEvent(event.date).long)")
|
||||
.multilineTextAlignment(.trailing)
|
||||
.foregroundStyle(event.date.timeIntervalSinceNow < 0 ? .red : .one)
|
||||
}
|
||||
CompleteEventButton(
|
||||
viewModel: viewModel,
|
||||
event: $event
|
||||
)
|
||||
}
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
.onHover { isHovering in
|
||||
withAnimation {
|
||||
hovering.toggle()
|
||||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
openWindow(value: event.id)
|
||||
}
|
||||
.contextMenu() {
|
||||
Button(role: .destructive) {
|
||||
let eventToModify = viewModel.events.firstIndex() { currEvent in
|
||||
currEvent.id == event.id
|
||||
}
|
||||
if let eventToModify = eventToModify {
|
||||
viewModel.events.remove(at: eventToModify)
|
||||
viewModel.saveEvents()
|
||||
}
|
||||
} label: {
|
||||
ZStack {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
var body: some View {
|
||||
HStack {
|
||||
RoundedRectangle(cornerRadius: 5)
|
||||
.frame(width: 7)
|
||||
@@ -78,56 +164,18 @@ struct EventListView: View {
|
||||
.foregroundStyle(event.date.timeIntervalSinceNow < 0 ? .red : .one)
|
||||
.multilineTextAlignment(.trailing)
|
||||
}
|
||||
Button() {
|
||||
withAnimation {
|
||||
event.complete.toggle()
|
||||
CompleteEventButton(
|
||||
viewModel: viewModel,
|
||||
event: $event
|
||||
)
|
||||
}
|
||||
let eventToModify = viewModel.events.firstIndex() { currEvent in
|
||||
currEvent.id == event.id
|
||||
}
|
||||
if let eventToModify = eventToModify {
|
||||
viewModel.events[eventToModify] = event
|
||||
viewModel.saveEvents()
|
||||
}
|
||||
} label: {
|
||||
if event.complete {
|
||||
ZStack {
|
||||
Circle()
|
||||
.foregroundStyle(.green)
|
||||
Image(systemName: "checkmark")
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.scaledToFit()
|
||||
.bold()
|
||||
.frame(width: 15)
|
||||
}
|
||||
} else {
|
||||
Image(systemName: "circle")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.foregroundStyle(event.color.color)
|
||||
}
|
||||
}
|
||||
.buttonStyle(.borderless)
|
||||
.frame(maxWidth: 25, maxHeight: 25)
|
||||
.shadow(radius: 5)
|
||||
.padding(.trailing, 5)
|
||||
.modifier(hapticSuccess(trigger: event.complete))
|
||||
}
|
||||
.transition(.opacity)
|
||||
.padding(.vertical, 5)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(
|
||||
.one.opacity(0.5),
|
||||
lineWidth: 1
|
||||
)
|
||||
)
|
||||
.clipShape(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
RoundedRectangle(cornerRadius: 15)
|
||||
.stroke(.one.opacity(0.5), lineWidth: 1)
|
||||
)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 15))
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
.contextMenu() {
|
||||
Button(role: .destructive) {
|
||||
let eventToModify = viewModel.events.firstIndex() { currEvent in
|
||||
@@ -142,7 +190,7 @@ struct EventListView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#Preview("EventListView") {
|
||||
|
||||
@@ -12,7 +12,6 @@ struct HomeView: View {
|
||||
@ObservedObject var viewModel: EventViewModel
|
||||
@ObservedObject var settingsModel: SettingsViewModel
|
||||
|
||||
@State private var event: Event = dummyEventViewModel().template
|
||||
@State private var showingAddEventView: Bool = false
|
||||
@State private var searchInput: String = ""
|
||||
@Environment(\.colorScheme) var appearance
|
||||
@@ -48,10 +47,22 @@ struct HomeView: View {
|
||||
ScrollView {
|
||||
// LazyVStack {
|
||||
ForEach(filteredEvents) { event in
|
||||
NavigationLink() {
|
||||
EditEventView(
|
||||
viewModel: viewModel,
|
||||
event: Binding(
|
||||
get: { event },
|
||||
set: { newValue in
|
||||
viewModel.editEvent(newValue)
|
||||
}
|
||||
)
|
||||
)
|
||||
} label: {
|
||||
EventListView(viewModel: viewModel, event: event)
|
||||
.transition(.moveAndFade)
|
||||
.id(event.complete)
|
||||
}
|
||||
.transition(.moveAndFade)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
// }
|
||||
if filteredEvents.isEmpty {
|
||||
@@ -70,13 +81,11 @@ struct HomeView: View {
|
||||
.modifier(navigationInlineLarge())
|
||||
.sheet(isPresented: $showingAddEventView) {
|
||||
AddEventView(
|
||||
viewModel: viewModel,
|
||||
event: $event,
|
||||
adding: true //adding event
|
||||
viewModel: viewModel
|
||||
)
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
AddEventButton(showingAddEventView: $showingAddEventView)
|
||||
}
|
||||
}
|
||||
|
||||
129
Shared/CompleteEventButton.swift
Normal file
129
Shared/CompleteEventButton.swift
Normal file
@@ -0,0 +1,129 @@
|
||||
//
|
||||
// CompleteEventButton.swift
|
||||
// NearFuture
|
||||
//
|
||||
// Created by neon443 on 15/06/2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct CompleteEventButton: View {
|
||||
@ObservedObject var viewModel: EventViewModel
|
||||
@Binding var event: Event
|
||||
|
||||
@State var timer: Timer?
|
||||
@State var largeTick: Bool = false
|
||||
@State var completeInProgress: Bool = false
|
||||
@State var completeStartTime: Date = .now
|
||||
@State var progress: Double = 0
|
||||
private let completeDuration: TimeInterval = 3.0
|
||||
|
||||
var isMac: Bool {
|
||||
#if canImport(AppKit)
|
||||
return true
|
||||
#else
|
||||
return false
|
||||
#endif
|
||||
}
|
||||
|
||||
func startCompleting() {
|
||||
#if canImport(UIKit)
|
||||
UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
|
||||
#endif
|
||||
withAnimation { completeInProgress = true }
|
||||
completeStartTime = .now
|
||||
progress = 0
|
||||
|
||||
timer = Timer(timeInterval: 0.01, repeats: true) { timer in
|
||||
guard completeInProgress else { return }
|
||||
guard timer.isValid else { return }
|
||||
let elapsed = Date().timeIntervalSince(completeStartTime)
|
||||
progress = min(1, elapsed)
|
||||
|
||||
if progress >= 1 {
|
||||
withAnimation { completeInProgress = false }
|
||||
viewModel.completeEvent(&event)
|
||||
#if canImport(UIKit)
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.success)
|
||||
#endif
|
||||
timer.invalidate()
|
||||
progress = 0
|
||||
}
|
||||
}
|
||||
RunLoop.main.add(timer!, forMode: .common)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if completeInProgress {
|
||||
ZStack {
|
||||
CircularProgressView(progress: $progress)
|
||||
Image(systemName: "xmark")
|
||||
.bold()
|
||||
}
|
||||
.onTapGesture {
|
||||
withAnimation { completeInProgress = false }
|
||||
}
|
||||
} else {
|
||||
Image(systemName: event.complete ? "checkmark.circle.fill" : "circle")
|
||||
.resizable().scaledToFit()
|
||||
.foregroundStyle(event.complete ? .green : event.color.color)
|
||||
.bold()
|
||||
.onTapGesture {
|
||||
startCompleting()
|
||||
}
|
||||
.onHover() { hovering in
|
||||
withAnimation {
|
||||
largeTick.toggle()
|
||||
}
|
||||
}
|
||||
.scaleEffect(largeTick ? 1.5 : 1)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: isMac ? 20 : 30)
|
||||
.shadow(color: .one.opacity(0.2), radius: 2.5)
|
||||
.padding(.trailing, isMac ? 15 : 5)
|
||||
.transition(.scale)
|
||||
.animation(.spring, value: completeInProgress)
|
||||
.animation(
|
||||
.spring(response: 0.2, dampingFraction: 0.75, blendDuration: 2),
|
||||
value: largeTick
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct CircularProgressView: View {
|
||||
@Binding var progress: Double
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Circle()
|
||||
.stroke(
|
||||
.two,
|
||||
lineWidth: 5
|
||||
)
|
||||
Circle()
|
||||
.trim(from: 0, to: progress)
|
||||
.stroke(
|
||||
.one,
|
||||
lineWidth: 5
|
||||
// style: StrokeStyle(
|
||||
// lineWidth: 5,
|
||||
// lineCap: .round
|
||||
// )
|
||||
)
|
||||
.rotationEffect(.degrees(-90))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
CompleteEventButton(
|
||||
viewModel: dummyEventViewModel(),
|
||||
event: .constant(dummyEventViewModel().example)
|
||||
)
|
||||
.scaleEffect(5)
|
||||
}
|
||||
|
||||
#Preview {
|
||||
CircularProgressView(progress: .constant(0.5))
|
||||
}
|
||||
@@ -222,6 +222,24 @@ class EventViewModel: ObservableObject, @unchecked Sendable {
|
||||
saveEvents() //sync with icloud
|
||||
}
|
||||
|
||||
func editEvent(_ editedEvent: Event) {
|
||||
if let index = events.firstIndex(where: { editedEvent.id == $0.id }) {
|
||||
self.events[index] = editedEvent
|
||||
saveEvents()
|
||||
}
|
||||
}
|
||||
|
||||
func completeEvent(_ event: inout Event) {
|
||||
withAnimation { event.complete.toggle() }
|
||||
let eventToModify = self.events.firstIndex() { currEvent in
|
||||
currEvent.id == event.id
|
||||
}
|
||||
if let eventToModify = eventToModify {
|
||||
self.events[eventToModify] = event
|
||||
self.saveEvents()
|
||||
}
|
||||
}
|
||||
|
||||
func removeEvent(at index: IndexSet) {
|
||||
events.remove(atOffsets: index)
|
||||
saveEvents() //sync local and icl
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="24093.7" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24053.1"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="collection view cell content view" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="s0d-6b-0kx">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="ViewController" id="Y6W-OH-hqX" customClass="ViewController" customModule="NearFuture" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="5EZ-qb-Rvc">
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" fixedFrame="YES" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="xBf-3w-4Ao">
|
||||
<rect key="frame" x="0.0" y="63" width="393" height="666"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<collectionViewFlowLayout key="collectionViewLayout" automaticEstimatedItemSize="YES" minimumLineSpacing="10" minimumInteritemSpacing="10" id="d2N-B5-D5N">
|
||||
<size key="itemSize" width="100" height="100"/>
|
||||
<size key="headerReferenceSize" width="0.0" height="0.0"/>
|
||||
<size key="footerReferenceSize" width="0.0" height="0.0"/>
|
||||
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||
</collectionViewFlowLayout>
|
||||
<cells>
|
||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="symbolCell" id="nKx-2O-Nq5" customClass="SymbolCell" customModule="NearFuture" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="100" height="100"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||
<collectionViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="3DG-aV-ZH4">
|
||||
<rect key="frame" x="0.0" y="0.0" width="100" height="100"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="AaY-Po-OoS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="100" height="100"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gGM-kQ-cA7">
|
||||
<rect key="frame" x="29" y="79" width="42" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</collectionViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="imageView" destination="AaY-Po-OoS" id="Swa-vO-Mtz"/>
|
||||
<outlet property="textLabel" destination="gGM-kQ-cA7" id="Qei-Qd-rn5"/>
|
||||
</connections>
|
||||
</collectionViewCell>
|
||||
</cells>
|
||||
</collectionView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="vDu-zF-Fre"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="collectionView" destination="xBf-3w-4Ao" id="c9h-Ew-aOX"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Ief-a0-LHa" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="139.69465648854961" y="65.492957746478879"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -14,6 +14,8 @@ struct SymbolsPicker: View {
|
||||
@FocusState var searchfocuesd: Bool
|
||||
|
||||
@State var searchInput: String = ""
|
||||
@State var browsing: Bool = false
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
var symbols: [String] {
|
||||
return symbolsLoader.getSymbols(searchInput)
|
||||
@@ -45,6 +47,8 @@ struct SymbolsPicker: View {
|
||||
ForEach(symbols, id: \.self) { symbol in
|
||||
Button() {
|
||||
selection = symbol
|
||||
searchInput = ""
|
||||
dismiss()
|
||||
} label: {
|
||||
VStack {
|
||||
Image(systemName: symbol)
|
||||
@@ -62,7 +66,19 @@ struct SymbolsPicker: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.searchable(text: $searchInput)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
if !browsing {
|
||||
Button() {
|
||||
searchInput = ""
|
||||
dismiss()
|
||||
} label: {
|
||||
Label("Cancel", systemImage: "xmark")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
//
|
||||
// SymbolsPickerStoryboard.swift
|
||||
// NearFuture
|
||||
//
|
||||
// Created by neon443 on 14/06/2025.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
#if canImport(UIKit)
|
||||
import UIKit
|
||||
#else
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
class ViewController: UIViewController {
|
||||
@IBOutlet weak var collectionView: UICollectionView!
|
||||
var symbolLoader: SymbolsLoader = SymbolsLoader()
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
|
||||
if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
|
||||
flowLayout.itemSize = CGSize(
|
||||
width: 100,
|
||||
height: 100
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
collectionView.delegate = self
|
||||
collectionView.dataSource = self
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewController: UICollectionViewDataSource {
|
||||
func numberOfSections(in collectionView: UICollectionView) -> Int {
|
||||
symbolLoader.allSymbols.count
|
||||
}
|
||||
|
||||
func collectionView(
|
||||
_ collectionView: UICollectionView,
|
||||
numberOfItemsInSection section: Int
|
||||
) -> Int {
|
||||
section
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "symbolCell", for: indexPath) as! SymbolCell
|
||||
|
||||
let imageView = cell.imageView
|
||||
imageView?.image = UIImage(systemName: symbolLoader.allSymbols[indexPath.item])!
|
||||
cell.textLabel?.text = "hi\(indexPath.row)"
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewController: UICollectionViewDelegate {
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
print(indexPath.item)
|
||||
}
|
||||
}
|
||||
|
||||
class SymbolCell: UICollectionViewCell {
|
||||
@IBOutlet weak var imageView: UIImageView!
|
||||
@IBOutlet weak var textLabel: UILabel!
|
||||
|
||||
}
|
||||
|
||||
struct SymbolsPickerStoryboardUIViewRepresentable: UIViewRepresentable {
|
||||
class Coordinator {
|
||||
var viewController: ViewController?
|
||||
}
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator()
|
||||
}
|
||||
|
||||
func makeUIView(context: Context) -> some UIView {
|
||||
let storyboard = UIStoryboard(name: "SymbolsPicker", bundle: nil)
|
||||
let viewController = storyboard.instantiateViewController(withIdentifier: "ViewController") as! ViewController
|
||||
context.coordinator.viewController = viewController
|
||||
return viewController.view
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: UIViewType, context: Context) {
|
||||
print()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user