mirror of
https://github.com/neon443/NearFuture.git
synced 2026-03-11 06:49:12 +00:00
Compare commits
31 Commits
a40d4f4300
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3501a9379 | ||
|
|
6f870ffa4f | ||
|
|
9841574d37 | ||
|
|
899304833c | ||
|
|
4629d4f75f | ||
|
|
be68c44ffe | ||
|
|
3fe9077e69 | ||
|
|
121dd79d54 | ||
|
|
01116c7fcb | ||
|
|
3690a9e4d2 | ||
|
|
c393404fec | ||
|
|
3ee22da036 | ||
|
|
2b25ddf9b3 | ||
|
|
01ff82181a | ||
|
|
b7ef7b4e19 | ||
|
|
7727f14ad4 | ||
|
|
e4107a2faa | ||
|
|
5ec16dd67a | ||
|
|
0cffe243eb | ||
|
|
6533fb85ed | ||
|
|
5c667679d5 | ||
|
|
5dd25f1ede | ||
|
|
d80011ea27 | ||
|
|
d5580e52f5 | ||
|
|
84a7091e05 | ||
|
|
44b40894e4 | ||
|
|
e4842bd29a | ||
|
|
b378a831be | ||
|
|
2ff96a7093 | ||
|
|
4f5e31a6f3 | ||
|
|
266b27d817 |
@@ -12,6 +12,6 @@ TEAM_ID = 8JGND254B7
|
|||||||
BUNDLE_ID = com.neon443.NearFuture
|
BUNDLE_ID = com.neon443.NearFuture
|
||||||
BUNDLE_ID_WIDGETS = com.neon443.NearFuture.widgets
|
BUNDLE_ID_WIDGETS = com.neon443.NearFuture.widgets
|
||||||
GROUP_ID = group.NearFuture
|
GROUP_ID = group.NearFuture
|
||||||
VERSION = 5
|
VERSION = 5.0.1
|
||||||
NAME = Near Future
|
NAME = Near Future
|
||||||
BUILD_NUMBER = 1
|
BUILD_NUMBER = 52
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ struct NearFutureApp: App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
WindowGroup("Edit Event", for: Event.ID.self) { $eventID in
|
WindowGroup("Edit Event", for: Event.ID.self) { $eventID in
|
||||||
|
if viewModel.events.first(where: {$0.id == eventID}) == nil {
|
||||||
|
AddEventView(
|
||||||
|
viewModel: viewModel
|
||||||
|
)
|
||||||
|
} else {
|
||||||
EditEventView(
|
EditEventView(
|
||||||
viewModel: viewModel,
|
viewModel: viewModel,
|
||||||
event: Binding(
|
event: Binding(
|
||||||
@@ -39,16 +44,14 @@ struct NearFutureApp: App {
|
|||||||
viewModel.events.first(where: {$0.id == eventID}) ?? viewModel.template
|
viewModel.events.first(where: {$0.id == eventID}) ?? viewModel.template
|
||||||
},
|
},
|
||||||
set: { newValue in
|
set: { newValue in
|
||||||
if let eventIndex = viewModel.events.firstIndex(where: {
|
viewModel.editEvent(newValue)
|
||||||
$0.id == eventID
|
|
||||||
}) {
|
|
||||||
viewModel.events[eventIndex] = newValue
|
|
||||||
}
|
|
||||||
viewModel.saveEvents()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.defaultSize(width: 480, height: 550)
|
||||||
|
.windowIdealSize(.fitToContent)
|
||||||
.restorationBehavior(.disabled)
|
.restorationBehavior(.disabled)
|
||||||
|
|
||||||
Window("About Near Future", id: "about") {
|
Window("About Near Future", id: "about") {
|
||||||
|
|||||||
@@ -19,6 +19,14 @@ struct ArchiveView: View {
|
|||||||
ScrollView {
|
ScrollView {
|
||||||
ForEach(filteredEvents) { event in
|
ForEach(filteredEvents) { event in
|
||||||
EventListView(viewModel: viewModel, event: event)
|
EventListView(viewModel: viewModel, event: event)
|
||||||
|
.contextMenu() {
|
||||||
|
Button(role: .destructive) {
|
||||||
|
viewModel.removeEvent(event)
|
||||||
|
} label: {
|
||||||
|
Label("Delete", systemImage: "trash")
|
||||||
|
.tint(.red )
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.scrollContentBackground(.hidden)
|
.scrollContentBackground(.hidden)
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
NavigationLink {
|
NavigationLink {
|
||||||
SymbolsPicker(
|
SymbolsPicker(
|
||||||
selection: $symbolSearchInput
|
selection: .constant(""),
|
||||||
|
browsing: true
|
||||||
)
|
)
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "star.circle")
|
Image(systemName: "star.circle")
|
||||||
@@ -65,9 +66,7 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
.sheet(isPresented: $showAddEventView) {
|
.sheet(isPresented: $showAddEventView) {
|
||||||
AddEventView(
|
AddEventView(
|
||||||
viewModel: viewModel,
|
viewModel: viewModel
|
||||||
event: $viewModel.editableTemplate,
|
|
||||||
adding: true
|
|
||||||
)
|
)
|
||||||
.presentationSizing(.page)
|
.presentationSizing(.page)
|
||||||
}
|
}
|
||||||
@@ -75,8 +74,7 @@ struct ContentView: View {
|
|||||||
Button() {
|
Button() {
|
||||||
showAddEventView.toggle()
|
showAddEventView.toggle()
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "plus")
|
Label("New", systemImage: "plus")
|
||||||
Text("New")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -34,6 +34,14 @@ struct HomeView: View {
|
|||||||
if filteredEvents.contains(event) {
|
if filteredEvents.contains(event) {
|
||||||
EventListView(viewModel: viewModel, event: event)
|
EventListView(viewModel: viewModel, event: event)
|
||||||
.id(event)
|
.id(event)
|
||||||
|
.contextMenu() {
|
||||||
|
Button(role: .destructive) {
|
||||||
|
viewModel.removeEvent(event)
|
||||||
|
} label: {
|
||||||
|
Label("Delete", systemImage: "trash")
|
||||||
|
.tint(.red)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,15 +70,12 @@
|
|||||||
A979F6102D270AF90094C0B3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A979F60F2D270AF80094C0B3 /* Assets.xcassets */; };
|
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, ); }; };
|
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 */; };
|
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 */; };
|
A98C20CE2DE7308E0008D61C /* ArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20CD2DE7308E0008D61C /* ArchiveView.swift */; };
|
||||||
A98C20D02DE731BD0008D61C /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20CF2DE731BD0008D61C /* HomeView.swift */; };
|
A98C20D02DE731BD0008D61C /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20CF2DE731BD0008D61C /* HomeView.swift */; };
|
||||||
A98C20D42DE7339E0008D61C /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20D32DE7339E0008D61C /* AboutView.swift */; };
|
A98C20D42DE7339E0008D61C /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20D32DE7339E0008D61C /* AboutView.swift */; };
|
||||||
A9C769A22DFDD1FC00082FFF /* SymbolsPicker.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A9C769A02DFDD1FC00082FFF /* SymbolsPicker.storyboard */; };
|
A9BAC6882DFF242300EC8E44 /* CompleteEventButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BAC6872DFF238100EC8E44 /* CompleteEventButton.swift */; };
|
||||||
A9C769A32DFDD1FC00082FFF /* SymbolsPicker.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A9C769A02DFDD1FC00082FFF /* SymbolsPicker.storyboard */; };
|
A9BAC6892DFF242300EC8E44 /* CompleteEventButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BAC6872DFF238100EC8E44 /* CompleteEventButton.swift */; };
|
||||||
A9C769A52DFDD27500082FFF /* SymbolsPickerStoryboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C769A42DFDD27500082FFF /* SymbolsPickerStoryboard.swift */; };
|
A9D1C34D2DFE10FA00703C2D /* EventListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8402DCAABE00064DCA0 /* EventListView.swift */; };
|
||||||
A9C769A62DFDD27500082FFF /* SymbolsPickerStoryboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C769A42DFDD27500082FFF /* SymbolsPickerStoryboard.swift */; };
|
|
||||||
A9C769A72DFDD27500082FFF /* SymbolsPickerStoryboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C769A42DFDD27500082FFF /* SymbolsPickerStoryboard.swift */; };
|
|
||||||
A9FC7EEA2D2823920020D75B /* NearFutureWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FC7EE92D28238A0020D75B /* NearFutureWidgets.swift */; };
|
A9FC7EEA2D2823920020D75B /* NearFutureWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FC7EE92D28238A0020D75B /* NearFutureWidgets.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
@@ -159,12 +156,10 @@
|
|||||||
A979F6092D270AF00094C0B3 /* NearFutureWidgetsBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgetsBundle.swift; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
A9BAC6872DFF238100EC8E44 /* CompleteEventButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompleteEventButton.swift; sourceTree = "<group>"; };
|
||||||
A9C769A42DFDD27500082FFF /* SymbolsPickerStoryboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymbolsPickerStoryboard.swift; sourceTree = "<group>"; };
|
|
||||||
A9FC7EE92D28238A0020D75B /* NearFutureWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgets.swift; sourceTree = "<group>"; };
|
A9FC7EE92D28238A0020D75B /* NearFutureWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgets.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
@@ -222,7 +217,6 @@
|
|||||||
children = (
|
children = (
|
||||||
A90D49332DDE0FAF00781124 /* ContentViewMac.swift */,
|
A90D49332DDE0FAF00781124 /* ContentViewMac.swift */,
|
||||||
A98C20CF2DE731BD0008D61C /* HomeView.swift */,
|
A98C20CF2DE731BD0008D61C /* HomeView.swift */,
|
||||||
A98C20CA2DE730740008D61C /* EventListViewMac.swift */,
|
|
||||||
A98C20CD2DE7308E0008D61C /* ArchiveView.swift */,
|
A98C20CD2DE7308E0008D61C /* ArchiveView.swift */,
|
||||||
A91EF80F2DFCB66C00B8463D /* SettingsView.swift */,
|
A91EF80F2DFCB66C00B8463D /* SettingsView.swift */,
|
||||||
);
|
);
|
||||||
@@ -236,6 +230,7 @@
|
|||||||
A90D49512DDE2D0000781124 /* Extensions.swift */,
|
A90D49512DDE2D0000781124 /* Extensions.swift */,
|
||||||
A90D49202DDE0A3B00781124 /* Model */,
|
A90D49202DDE0A3B00781124 /* Model */,
|
||||||
A91EF80A2DFC910000B8463D /* ViewModifiers.swift */,
|
A91EF80A2DFC910000B8463D /* ViewModifiers.swift */,
|
||||||
|
A9BAC6872DFF238100EC8E44 /* CompleteEventButton.swift */,
|
||||||
);
|
);
|
||||||
path = Shared;
|
path = Shared;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -252,8 +247,6 @@
|
|||||||
children = (
|
children = (
|
||||||
A91EF8172DFD77BF00B8463D /* SymbolsLoader.swift */,
|
A91EF8172DFD77BF00B8463D /* SymbolsLoader.swift */,
|
||||||
A91EF81B2DFD796600B8463D /* SymbolsPicker.swift */,
|
A91EF81B2DFD796600B8463D /* SymbolsPicker.swift */,
|
||||||
A9C769A02DFDD1FC00082FFF /* SymbolsPicker.storyboard */,
|
|
||||||
A9C769A42DFDD27500082FFF /* SymbolsPickerStoryboard.swift */,
|
|
||||||
);
|
);
|
||||||
path = SymbolsPicker;
|
path = SymbolsPicker;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -516,7 +509,6 @@
|
|||||||
files = (
|
files = (
|
||||||
A90D49442DDE1C7600781124 /* Tints.xcassets in Resources */,
|
A90D49442DDE1C7600781124 /* Tints.xcassets in Resources */,
|
||||||
A920C2922D24011A00E4F9B1 /* Preview Assets.xcassets in Resources */,
|
A920C2922D24011A00E4F9B1 /* Preview Assets.xcassets in Resources */,
|
||||||
A9C769A22DFDD1FC00082FFF /* SymbolsPicker.storyboard in Resources */,
|
|
||||||
A949F8322DCAAA8A0064DCA0 /* NearFutureIcon.png in Resources */,
|
A949F8322DCAAA8A0064DCA0 /* NearFutureIcon.png in Resources */,
|
||||||
A920C28E2D24011A00E4F9B1 /* Assets.xcassets in Resources */,
|
A920C28E2D24011A00E4F9B1 /* Assets.xcassets in Resources */,
|
||||||
);
|
);
|
||||||
@@ -527,7 +519,6 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
A90D49462DDE1C7A00781124 /* Tints.xcassets in Resources */,
|
A90D49462DDE1C7A00781124 /* Tints.xcassets in Resources */,
|
||||||
A9C769A32DFDD1FC00082FFF /* SymbolsPicker.storyboard in Resources */,
|
|
||||||
A979F6102D270AF90094C0B3 /* Assets.xcassets in Resources */,
|
A979F6102D270AF90094C0B3 /* Assets.xcassets in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -539,11 +530,10 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
A9C769A52DFDD27500082FFF /* SymbolsPickerStoryboard.swift in Sources */,
|
|
||||||
A91EF80E2DFC9A0C00B8463D /* WhatsNewView.swift in Sources */,
|
A91EF80E2DFC9A0C00B8463D /* WhatsNewView.swift in Sources */,
|
||||||
A91EF8192DFD77BF00B8463D /* SymbolsLoader.swift in Sources */,
|
A91EF8192DFD77BF00B8463D /* SymbolsLoader.swift in Sources */,
|
||||||
A95E9EE42DFC77D400ED655F /* ImportView.swift in Sources */,
|
A95E9EE42DFC77D400ED655F /* ImportView.swift in Sources */,
|
||||||
A98C20CB2DE730740008D61C /* EventListViewMac.swift in Sources */,
|
A9BAC6892DFF242300EC8E44 /* CompleteEventButton.swift in Sources */,
|
||||||
A91EF80C2DFC910000B8463D /* ViewModifiers.swift in Sources */,
|
A91EF80C2DFC910000B8463D /* ViewModifiers.swift in Sources */,
|
||||||
A95E9ED92DFC742B00ED655F /* AccentIcon.swift in Sources */,
|
A95E9ED92DFC742B00ED655F /* AccentIcon.swift in Sources */,
|
||||||
A91EF8102DFCB66C00B8463D /* SettingsView.swift in Sources */,
|
A91EF8102DFCB66C00B8463D /* SettingsView.swift in Sources */,
|
||||||
@@ -551,6 +541,7 @@
|
|||||||
A95E9EE52DFC77E200ED655F /* ExportView.swift in Sources */,
|
A95E9EE52DFC77E200ED655F /* ExportView.swift in Sources */,
|
||||||
A91EF8132DFCC87D00B8463D /* EditEventView.swift in Sources */,
|
A91EF8132DFCC87D00B8463D /* EditEventView.swift in Sources */,
|
||||||
A91EF8142DFCC87D00B8463D /* AddEventView.swift in Sources */,
|
A91EF8142DFCC87D00B8463D /* AddEventView.swift in Sources */,
|
||||||
|
A9D1C34D2DFE10FA00703C2D /* EventListView.swift in Sources */,
|
||||||
A98C20D42DE7339E0008D61C /* AboutView.swift in Sources */,
|
A98C20D42DE7339E0008D61C /* AboutView.swift in Sources */,
|
||||||
A98C20CE2DE7308E0008D61C /* ArchiveView.swift in Sources */,
|
A98C20CE2DE7308E0008D61C /* ArchiveView.swift in Sources */,
|
||||||
A98C20D02DE731BD0008D61C /* HomeView.swift in Sources */,
|
A98C20D02DE731BD0008D61C /* HomeView.swift in Sources */,
|
||||||
@@ -588,8 +579,8 @@
|
|||||||
A949F8512DCAABE00064DCA0 /* ExportView.swift in Sources */,
|
A949F8512DCAABE00064DCA0 /* ExportView.swift in Sources */,
|
||||||
A95E9ED82DFC742B00ED655F /* AccentIcon.swift in Sources */,
|
A95E9ED82DFC742B00ED655F /* AccentIcon.swift in Sources */,
|
||||||
A90D49532DDE2D0000781124 /* Extensions.swift in Sources */,
|
A90D49532DDE2D0000781124 /* Extensions.swift in Sources */,
|
||||||
A9C769A62DFDD27500082FFF /* SymbolsPickerStoryboard.swift in Sources */,
|
|
||||||
A949F8522DCAABE00064DCA0 /* iCloudSettingsView.swift in Sources */,
|
A949F8522DCAABE00064DCA0 /* iCloudSettingsView.swift in Sources */,
|
||||||
|
A9BAC6882DFF242300EC8E44 /* CompleteEventButton.swift in Sources */,
|
||||||
A949F8532DCAABE00064DCA0 /* ImportView.swift in Sources */,
|
A949F8532DCAABE00064DCA0 /* ImportView.swift in Sources */,
|
||||||
A949F8542DCAABE00064DCA0 /* SettingsView.swift in Sources */,
|
A949F8542DCAABE00064DCA0 /* SettingsView.swift in Sources */,
|
||||||
A91EF81E2DFD796600B8463D /* SymbolsPicker.swift in Sources */,
|
A91EF81E2DFD796600B8463D /* SymbolsPicker.swift in Sources */,
|
||||||
@@ -609,7 +600,6 @@
|
|||||||
A95E9EDA2DFC742B00ED655F /* AccentIcon.swift in Sources */,
|
A95E9EDA2DFC742B00ED655F /* AccentIcon.swift in Sources */,
|
||||||
A91EF80D2DFC910000B8463D /* ViewModifiers.swift in Sources */,
|
A91EF80D2DFC910000B8463D /* ViewModifiers.swift in Sources */,
|
||||||
A91EF8182DFD77BF00B8463D /* SymbolsLoader.swift in Sources */,
|
A91EF8182DFD77BF00B8463D /* SymbolsLoader.swift in Sources */,
|
||||||
A9C769A72DFDD27500082FFF /* SymbolsPickerStoryboard.swift in Sources */,
|
|
||||||
A91EF8072DFC8B8B00B8463D /* ColorCodable.swift in Sources */,
|
A91EF8072DFC8B8B00B8463D /* ColorCodable.swift in Sources */,
|
||||||
A9FC7EEA2D2823920020D75B /* NearFutureWidgets.swift in Sources */,
|
A9FC7EEA2D2823920020D75B /* NearFutureWidgets.swift in Sources */,
|
||||||
A979F60C2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift in Sources */,
|
A979F60C2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift in Sources */,
|
||||||
@@ -649,6 +639,7 @@
|
|||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
@@ -661,7 +652,7 @@
|
|||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 6.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
@@ -680,6 +671,7 @@
|
|||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
@@ -692,7 +684,7 @@
|
|||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 6.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
@@ -845,6 +837,7 @@
|
|||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "Near Future";
|
INFOPLIST_KEY_CFBundleDisplayName = "Near Future";
|
||||||
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
|
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
||||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
|
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
|
||||||
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
|
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
|
||||||
@@ -873,7 +866,8 @@
|
|||||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES;
|
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_STRICT_CONCURRENCY = complete;
|
||||||
|
SWIFT_VERSION = 6.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
@@ -897,6 +891,7 @@
|
|||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "Near Future";
|
INFOPLIST_KEY_CFBundleDisplayName = "Near Future";
|
||||||
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
|
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
||||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
|
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
|
||||||
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
|
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
|
||||||
@@ -922,7 +917,8 @@
|
|||||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES;
|
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_STRICT_CONCURRENCY = complete;
|
||||||
|
SWIFT_VERSION = 6.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
|
|||||||
@@ -23,18 +23,39 @@ struct ArchiveView: View {
|
|||||||
} else {
|
} else {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
ForEach(filteredEvents) { event in
|
ForEach(filteredEvents) { event in
|
||||||
|
NavigationLink() {
|
||||||
|
EditEventView(
|
||||||
|
viewModel: viewModel,
|
||||||
|
event: Binding(
|
||||||
|
get: { event },
|
||||||
|
set: { newValue in
|
||||||
|
viewModel.editEvent(newValue)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} label: {
|
||||||
EventListView(viewModel: viewModel, event: event)
|
EventListView(viewModel: viewModel, event: event)
|
||||||
|
.id(event)
|
||||||
|
}
|
||||||
.transition(.moveAndFadeReversed)
|
.transition(.moveAndFadeReversed)
|
||||||
.id(event.complete)
|
.contextMenu() {
|
||||||
|
Button(role: .destructive) {
|
||||||
|
viewModel.removeEvent(event)
|
||||||
|
} label: {
|
||||||
|
Label("Delete", systemImage: "trash")
|
||||||
|
.tint(.red)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
}
|
}
|
||||||
.animation(.default, value: filteredEvents)
|
.animation(.default, value: filteredEvents)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.transition(.opacity)
|
||||||
.scrollContentBackground(.hidden)
|
.scrollContentBackground(.hidden)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .topBarTrailing) {
|
ToolbarItem(placement: .primaryAction) {
|
||||||
AddEventButton(showingAddEventView: $showAddEvent)
|
AddEventButton(showingAddEventView: $showAddEvent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,9 +64,7 @@ struct ArchiveView: View {
|
|||||||
}
|
}
|
||||||
.sheet(isPresented: $showAddEvent) {
|
.sheet(isPresented: $showAddEvent) {
|
||||||
AddEventView(
|
AddEventView(
|
||||||
viewModel: viewModel,
|
viewModel: viewModel
|
||||||
event: $viewModel.editableTemplate,
|
|
||||||
adding: true
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import SwiftData
|
|||||||
enum Tab {
|
enum Tab {
|
||||||
case home
|
case home
|
||||||
case archive
|
case archive
|
||||||
|
case symbols
|
||||||
case stats
|
case stats
|
||||||
case settings
|
case settings
|
||||||
}
|
}
|
||||||
@@ -33,6 +34,14 @@ struct ContentView: View {
|
|||||||
Label("Archive", systemImage: "tray.full")
|
Label("Archive", systemImage: "tray.full")
|
||||||
}
|
}
|
||||||
.tag(Tab.archive)
|
.tag(Tab.archive)
|
||||||
|
SymbolsPicker(
|
||||||
|
selection: .constant(""),
|
||||||
|
browsing: true
|
||||||
|
)
|
||||||
|
.tabItem {
|
||||||
|
Label("Symbols", systemImage: "star.circle")
|
||||||
|
}
|
||||||
|
.tag(Tab.symbols)
|
||||||
StatsView(viewModel: viewModel)
|
StatsView(viewModel: viewModel)
|
||||||
// SymbolsPickerStoryboardUIViewRepresentable()
|
// SymbolsPickerStoryboardUIViewRepresentable()
|
||||||
.tabItem {
|
.tabItem {
|
||||||
|
|||||||
@@ -10,14 +10,12 @@ import SwiftUI
|
|||||||
struct AddEventView: View {
|
struct AddEventView: View {
|
||||||
@ObservedObject var viewModel: EventViewModel
|
@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 showNeedsNameAlert: Bool = false
|
||||||
@State var isSymbolPickerPresented: Bool = false
|
@State var isSymbolPickerPresented: Bool = false
|
||||||
|
|
||||||
@State private var bye: Bool = false
|
|
||||||
|
|
||||||
@FocusState private var focusedField: Field?
|
@FocusState private var focusedField: Field?
|
||||||
private enum Field {
|
private enum Field {
|
||||||
case Name, Notes
|
case Name, Notes
|
||||||
@@ -25,6 +23,14 @@ struct AddEventView: View {
|
|||||||
|
|
||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) var dismiss
|
||||||
|
|
||||||
|
var isMac: Bool {
|
||||||
|
if #available(iOS 1, *) {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
if !adding {
|
if !adding {
|
||||||
@@ -64,17 +70,18 @@ struct AddEventView: View {
|
|||||||
|
|
||||||
// dscription
|
// dscription
|
||||||
ZStack {
|
ZStack {
|
||||||
TextField("Event Notes", text: $event.notes)
|
if event.notes.isEmpty {
|
||||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
HStack {
|
||||||
.padding(.trailing, event.notes.isEmpty ? 0 : 30)
|
Text("Event Notes")
|
||||||
.animation(.spring, value: event.notes)
|
.opacity(0.5)
|
||||||
.focused($focusedField, equals: Field.Notes)
|
Spacer()
|
||||||
.submitLabel(.done)
|
|
||||||
.onSubmit {
|
|
||||||
focusedField = nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
TextEditor(text: $event.notes)
|
||||||
|
.lineLimit(10)
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorPicker("Event Color", selection: $event.color.colorBind)
|
||||||
|
|
||||||
// date picker
|
// date picker
|
||||||
HStack {
|
HStack {
|
||||||
@@ -82,6 +89,8 @@ struct AddEventView: View {
|
|||||||
DatePicker("", selection: $event.date, displayedComponents: .date)
|
DatePicker("", selection: $event.date, displayedComponents: .date)
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.datePickerStyle(.wheel)
|
.datePickerStyle(.wheel)
|
||||||
|
#else
|
||||||
|
.datePickerStyle(.graphical)
|
||||||
#endif
|
#endif
|
||||||
Spacer()
|
Spacer()
|
||||||
Button() {
|
Button() {
|
||||||
@@ -100,6 +109,9 @@ struct AddEventView: View {
|
|||||||
selection: $event.date,
|
selection: $event.date,
|
||||||
displayedComponents: .hourAndMinute
|
displayedComponents: .hourAndMinute
|
||||||
)
|
)
|
||||||
|
#if os(macOS)
|
||||||
|
.datePickerStyle(.stepperField)
|
||||||
|
#endif
|
||||||
|
|
||||||
// re-ocurrence Picker
|
// re-ocurrence Picker
|
||||||
Picker("Recurrence", selection: $event.recurrence) {
|
Picker("Recurrence", selection: $event.recurrence) {
|
||||||
@@ -138,16 +150,14 @@ struct AddEventView: View {
|
|||||||
viewModel.addEvent(
|
viewModel.addEvent(
|
||||||
newEvent: event
|
newEvent: event
|
||||||
)
|
)
|
||||||
bye.toggle()
|
|
||||||
resetAddEventView()
|
resetAddEventView()
|
||||||
|
#if canImport(UIKit)
|
||||||
|
UINotificationFeedbackGenerator().notificationOccurred(.success)
|
||||||
|
#endif
|
||||||
} label: {
|
} label: {
|
||||||
Text("Save")
|
Label("Save", systemImage: "checkmark")
|
||||||
.font(.headline)
|
|
||||||
.cornerRadius(10)
|
|
||||||
.buttonStyle(BorderedProminentButtonStyle())
|
|
||||||
}
|
}
|
||||||
.tint(.accent)
|
.tint(.accent)
|
||||||
.modifier(hapticSuccess(trigger: bye))
|
|
||||||
.disabled(event.name.isEmpty)
|
.disabled(event.name.isEmpty)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
if event.name.isEmpty {
|
if event.name.isEmpty {
|
||||||
@@ -164,9 +174,24 @@ struct AddEventView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ToolbarItem(placement: .confirmationAction) {
|
||||||
|
if !adding {
|
||||||
|
Button() {
|
||||||
|
viewModel.editEvent(event)
|
||||||
|
dismiss()
|
||||||
|
#if canImport(UIKit)
|
||||||
|
UINotificationFeedbackGenerator().notificationOccurred(.success)
|
||||||
|
#endif
|
||||||
|
} label: {
|
||||||
|
Label("Done", systemImage: "checkmark")
|
||||||
|
}
|
||||||
|
.disabled(event.name == "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.scrollContentBackground(.hidden)
|
}
|
||||||
|
.navigationTitle("Editing \(event.name) - Ne")
|
||||||
|
}
|
||||||
|
.scrollContentBackground(isMac ? .automatic : .hidden)
|
||||||
.presentationDragIndicator(.visible)
|
.presentationDragIndicator(.visible)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,7 +210,6 @@ struct AddEventView: View {
|
|||||||
.sheet(isPresented: .constant(true)) {
|
.sheet(isPresented: .constant(true)) {
|
||||||
AddEventView(
|
AddEventView(
|
||||||
viewModel: vm,
|
viewModel: vm,
|
||||||
event: .constant(vm.template),
|
|
||||||
adding: true
|
adding: true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,37 +12,13 @@ struct EditEventView: View {
|
|||||||
@ObservedObject var viewModel: EventViewModel
|
@ObservedObject var viewModel: EventViewModel
|
||||||
@Binding var event: Event
|
@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 {
|
var body: some View {
|
||||||
AddEventView(
|
AddEventView(
|
||||||
viewModel: viewModel,
|
viewModel: viewModel,
|
||||||
event: $event,
|
event: event,
|
||||||
adding: false //bc we editing existing event
|
adding: false //bc we editing existing event
|
||||||
)
|
)
|
||||||
.navigationTitle("Edit Event")
|
.navigationTitle("Edit Event")
|
||||||
.toolbar {
|
|
||||||
ToolbarItem(/*placement: .topBarTrailing*/) {
|
|
||||||
Button() {
|
|
||||||
saveEdits()
|
|
||||||
} label: {
|
|
||||||
Text("Done")
|
|
||||||
.bold()
|
|
||||||
}
|
|
||||||
.disabled(event.name == "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,14 +12,87 @@ struct EventListView: View {
|
|||||||
@ObservedObject var viewModel: EventViewModel
|
@ObservedObject var viewModel: EventViewModel
|
||||||
@State var event: Event
|
@State var event: Event
|
||||||
|
|
||||||
|
@Environment(\.openWindow) var openWindow
|
||||||
|
|
||||||
|
@State var hovering: Bool = false
|
||||||
|
|
||||||
|
#if canImport(AppKit)
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationLink() {
|
ZStack {
|
||||||
EditEventView(
|
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,
|
viewModel: viewModel,
|
||||||
event: $event
|
event: $event
|
||||||
)
|
)
|
||||||
} label: {
|
}
|
||||||
ZStack {
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
}
|
||||||
|
.onHover { isHovering in
|
||||||
|
withAnimation {
|
||||||
|
hovering.toggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onTapGesture {
|
||||||
|
openWindow(value: event.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
RoundedRectangle(cornerRadius: 5)
|
RoundedRectangle(cornerRadius: 5)
|
||||||
.frame(width: 7)
|
.frame(width: 7)
|
||||||
@@ -78,86 +151,32 @@ struct EventListView: View {
|
|||||||
.foregroundStyle(event.date.timeIntervalSinceNow < 0 ? .red : .one)
|
.foregroundStyle(event.date.timeIntervalSinceNow < 0 ? .red : .one)
|
||||||
.multilineTextAlignment(.trailing)
|
.multilineTextAlignment(.trailing)
|
||||||
}
|
}
|
||||||
Button() {
|
CompleteEventButton(
|
||||||
withAnimation {
|
viewModel: viewModel,
|
||||||
event.complete.toggle()
|
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)
|
.padding(.vertical, 5)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: 15)
|
||||||
.stroke(
|
.stroke(.one.opacity(0.5), lineWidth: 1)
|
||||||
.one.opacity(0.5),
|
|
||||||
lineWidth: 1
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.clipShape(
|
|
||||||
RoundedRectangle(cornerRadius: 10)
|
|
||||||
)
|
)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 15))
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
}
|
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||||
.contextMenu() {
|
|
||||||
Button(role: .destructive) {
|
Button(role: .destructive) {
|
||||||
let eventToModify = viewModel.events.firstIndex() { currEvent in
|
viewModel.removeEvent(event)
|
||||||
currEvent.id == event.id
|
|
||||||
}
|
|
||||||
if let eventToModify = eventToModify {
|
|
||||||
viewModel.events.remove(at: eventToModify)
|
|
||||||
viewModel.saveEvents()
|
|
||||||
}
|
|
||||||
} label: {
|
} label: {
|
||||||
Label("Delete", systemImage: "trash")
|
Label("Delete", systemImage: "trash")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview("EventListView") {
|
#Preview("EventListView") {
|
||||||
let vm = dummyEventViewModel()
|
let vm = dummyEventViewModel()
|
||||||
ZStack {
|
ZStack {
|
||||||
Color.black
|
|
||||||
VStack {
|
|
||||||
ForEach(0..<50) { _ in
|
|
||||||
Rectangle()
|
|
||||||
.foregroundStyle(randomColor().opacity(0.5))
|
|
||||||
.padding(-10)
|
|
||||||
}
|
|
||||||
.ignoresSafeArea(.all)
|
|
||||||
.blur(radius: 5)
|
|
||||||
}
|
|
||||||
VStack {
|
VStack {
|
||||||
ForEach(vm.events) { event in
|
ForEach(vm.events) { event in
|
||||||
EventListView(
|
EventListView(
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ struct HomeView: View {
|
|||||||
@ObservedObject var viewModel: EventViewModel
|
@ObservedObject var viewModel: EventViewModel
|
||||||
@ObservedObject var settingsModel: SettingsViewModel
|
@ObservedObject var settingsModel: SettingsViewModel
|
||||||
|
|
||||||
@State private var event: Event = dummyEventViewModel().template
|
|
||||||
@State private var showingAddEventView: Bool = false
|
@State private var showingAddEventView: Bool = false
|
||||||
@State private var searchInput: String = ""
|
@State private var searchInput: String = ""
|
||||||
@Environment(\.colorScheme) var appearance
|
@Environment(\.colorScheme) var appearance
|
||||||
@@ -48,9 +47,29 @@ struct HomeView: View {
|
|||||||
ScrollView {
|
ScrollView {
|
||||||
// LazyVStack {
|
// LazyVStack {
|
||||||
ForEach(filteredEvents) { event in
|
ForEach(filteredEvents) { event in
|
||||||
|
NavigationLink() {
|
||||||
|
EditEventView(
|
||||||
|
viewModel: viewModel,
|
||||||
|
event: Binding(
|
||||||
|
get: { event },
|
||||||
|
set: { newValue in
|
||||||
|
viewModel.editEvent(newValue)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} label: {
|
||||||
EventListView(viewModel: viewModel, event: event)
|
EventListView(viewModel: viewModel, event: event)
|
||||||
|
.id(event)
|
||||||
|
}
|
||||||
.transition(.moveAndFade)
|
.transition(.moveAndFade)
|
||||||
.id(event.complete)
|
.contextMenu() {
|
||||||
|
Button(role: .destructive) {
|
||||||
|
viewModel.removeEvent(event)
|
||||||
|
} label: {
|
||||||
|
Label("Delete", systemImage: "trash")
|
||||||
|
.tint(.red)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
// }
|
// }
|
||||||
@@ -70,13 +89,11 @@ struct HomeView: View {
|
|||||||
.modifier(navigationInlineLarge())
|
.modifier(navigationInlineLarge())
|
||||||
.sheet(isPresented: $showingAddEventView) {
|
.sheet(isPresented: $showingAddEventView) {
|
||||||
AddEventView(
|
AddEventView(
|
||||||
viewModel: viewModel,
|
viewModel: viewModel
|
||||||
event: $event,
|
|
||||||
adding: true //adding event
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .topBarTrailing) {
|
ToolbarItem(placement: .primaryAction) {
|
||||||
AddEventButton(showingAddEventView: $showingAddEventView)
|
AddEventButton(showingAddEventView: $showingAddEventView)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,65 +66,19 @@ struct ImportView: View {
|
|||||||
fgColor = .yellow
|
fgColor = .yellow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.blur(radius: showAlert ? 2 : 0)
|
.alert("Are you sure?", isPresented: $showAlert) {
|
||||||
Group {
|
Button(role: .destructive) {
|
||||||
Rectangle()
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
||||||
.foregroundStyle(replaceCurrentEvents ? .red.opacity(0.25) : .black.opacity(0.2))
|
|
||||||
.animation(.default, value: replaceCurrentEvents)
|
|
||||||
.ignoresSafeArea()
|
|
||||||
ZStack {
|
|
||||||
Rectangle()
|
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 25))
|
|
||||||
VStack(alignment: .center) {
|
|
||||||
Text("Are you sure?")
|
|
||||||
.font(.largeTitle)
|
|
||||||
.bold()
|
|
||||||
.foregroundStyle(replaceCurrentEvents ? .red : .two)
|
|
||||||
.animation(.default, value: replaceCurrentEvents)
|
|
||||||
Text("This will replace your current events!")
|
|
||||||
.lineLimit(nil)
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
.opacity(replaceCurrentEvents ? 1 : 0)
|
|
||||||
.animation(.default, value: replaceCurrentEvents)
|
|
||||||
.foregroundStyle(.two)
|
|
||||||
Toggle("Replace Events", isOn: $replaceCurrentEvents)
|
|
||||||
.foregroundStyle(.two)
|
|
||||||
Spacer()
|
|
||||||
HStack {
|
|
||||||
Button() {
|
|
||||||
withAnimation {
|
|
||||||
showAlert.toggle()
|
|
||||||
}
|
|
||||||
importEvents()
|
importEvents()
|
||||||
} label: {
|
} label: {
|
||||||
Text("cancel")
|
Text("Replace Events")
|
||||||
.font(.title2)
|
|
||||||
.bold()
|
|
||||||
}
|
}
|
||||||
.buttonStyle(BorderedProminentButtonStyle())
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
Button() {
|
Button() {
|
||||||
withAnimation {
|
|
||||||
showAlert.toggle()
|
|
||||||
}
|
|
||||||
importEvents()
|
importEvents()
|
||||||
} label: {
|
} label: {
|
||||||
Text("yes")
|
Text("Add to Events")
|
||||||
.font(.title2)
|
.foregroundStyle(.one)
|
||||||
.bold()
|
|
||||||
}
|
}
|
||||||
.buttonStyle(BorderedProminentButtonStyle())
|
|
||||||
}
|
}
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
.frame(maxWidth: 250, maxHeight: 250)
|
|
||||||
}
|
|
||||||
.opacity(showAlert ? 1 : 0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,25 +154,25 @@ struct EventWidgetView: View {
|
|||||||
.foregroundColor(event.date < Date() ? .red : .primary)
|
.foregroundColor(event.date < Date() ? .red : .primary)
|
||||||
.padding(.trailing, -12)
|
.padding(.trailing, -12)
|
||||||
} else {
|
} else {
|
||||||
Button(
|
// Button(
|
||||||
intent: CompleteEvent(
|
// intent: CompleteEvent(
|
||||||
eventID: IntentParameter(
|
// eventID: IntentParameter(
|
||||||
title: LocalizedStringResource(
|
// title: LocalizedStringResource(
|
||||||
stringLiteral: event.id.uuidString
|
// stringLiteral: event.id.uuidString
|
||||||
)
|
// )
|
||||||
)
|
// )
|
||||||
)
|
// )
|
||||||
) {
|
// ) {
|
||||||
if event.complete {
|
// if event.complete {
|
||||||
Circle()
|
// Circle()
|
||||||
.frame(width: 10)
|
// .frame(width: 10)
|
||||||
.foregroundStyle(.green)
|
// .foregroundStyle(.green)
|
||||||
} else {
|
// } else {
|
||||||
Circle()
|
// Circle()
|
||||||
.frame(width: 10)
|
// .frame(width: 10)
|
||||||
.foregroundStyle(.gray)
|
// .foregroundStyle(.gray)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
Text(daysUntilEvent(event.date).long)
|
Text(daysUntilEvent(event.date).long)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.multilineTextAlignment(.trailing)
|
.multilineTextAlignment(.trailing)
|
||||||
|
|||||||
30
README.md
30
README.md
@@ -1,6 +1,32 @@
|
|||||||
# NearFuture
|
<div align="center">
|
||||||
|
<br/>
|
||||||
|
<p>
|
||||||
|
<img src="https://github.com/neon443/NearFuture/blob/main/Resources/Assets.xcassets/AppIcon.appiconset/NearFutureIcon.png?raw=true" title="dockphobia" alt="dockphobia icon" width="100" />
|
||||||
|
</p>
|
||||||
|
<h3>Near Future</h3>
|
||||||
|
<p>
|
||||||
|
<a href="https://apps.apple.com/us/app/near-future-event-tracker/id6744963429">
|
||||||
|
download
|
||||||
|
<img alt="GitHub Release" src="https://img.shields.io/itunes/v/6744963429">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
make your Dock scared of the mouse
|
||||||
|
<br/>
|
||||||
|
<a href="https://neon443.github.io">
|
||||||
|
made by neon443
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<br/>
|
||||||
|
</div>
|
||||||
|
|
||||||
[App Store](https://apps.apple.com/us/app/near-future-event-tracker/id6744963429)
|
<div align="center">
|
||||||
|
<a href="https://shipwrecked.hackclub.com/?t=ghrm" target="_blank">
|
||||||
|
<img src="https://hc-cdn.hel1.your-objectstorage.com/s/v3/739361f1d440b17fc9e2f74e49fc185d86cbec14_badge.png"
|
||||||
|
alt="This project is part of Shipwrecked, the world's first hackathon on an island!"
|
||||||
|
style="width: 25%;">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
**Near Future** is a SwiftUI App to help people track upcoming events - Holidays, Trips, Birthdays, Weddings, Anniversaries.
|
**Near Future** is a SwiftUI App to help people track upcoming events - Holidays, Trips, Birthdays, Weddings, Anniversaries.
|
||||||
|
|
||||||
|
|||||||
137
Shared/CompleteEventButton.swift
Normal file
137
Shared/CompleteEventButton.swift
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
//
|
||||||
|
// CompleteEventButton.swift
|
||||||
|
// NearFuture
|
||||||
|
//
|
||||||
|
// Created by neon443 on 15/06/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct CompleteEventButton: View {
|
||||||
|
@ObservedObject var viewModel: EventViewModel
|
||||||
|
@Binding var event: Event
|
||||||
|
|
||||||
|
@MainActor @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.02, repeats: true) { timer in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
guard completeInProgress else { return }
|
||||||
|
guard let timer = self.timer else { return }
|
||||||
|
guard timer.isValid else { return }
|
||||||
|
let elapsed = Date().timeIntervalSince(completeStartTime)
|
||||||
|
progress = min(1, elapsed)
|
||||||
|
#if canImport(UIKit)
|
||||||
|
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if progress >= 1 {
|
||||||
|
withAnimation { completeInProgress = false }
|
||||||
|
viewModel.completeEvent(&event)
|
||||||
|
#if canImport(UIKit)
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now()+0.02) {
|
||||||
|
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))
|
||||||
|
}
|
||||||
@@ -22,14 +22,14 @@ extension View {
|
|||||||
extension AnyTransition {
|
extension AnyTransition {
|
||||||
static var moveAndFade: AnyTransition {
|
static var moveAndFade: AnyTransition {
|
||||||
.asymmetric(
|
.asymmetric(
|
||||||
insertion: .move(edge: .leading),
|
insertion: .opacity,
|
||||||
removal: .move(edge: .trailing)
|
removal: .move(edge: .trailing)
|
||||||
)
|
)
|
||||||
.combined(with: .opacity)
|
.combined(with: .opacity)
|
||||||
}
|
}
|
||||||
static var moveAndFadeReversed: AnyTransition {
|
static var moveAndFadeReversed: AnyTransition {
|
||||||
.asymmetric(
|
.asymmetric(
|
||||||
insertion: .move(edge: .trailing),
|
insertion: .opacity,
|
||||||
removal: .move(edge: .leading)
|
removal: .move(edge: .leading)
|
||||||
)
|
)
|
||||||
.combined(with: .opacity)
|
.combined(with: .opacity)
|
||||||
|
|||||||
@@ -160,20 +160,24 @@ class EventViewModel: ObservableObject, @unchecked Sendable {
|
|||||||
eventUUIDs.remove(at: remove)
|
eventUUIDs.remove(at: remove)
|
||||||
}
|
}
|
||||||
let components = getDateComponents(events[index].date)
|
let components = getDateComponents(events[index].date)
|
||||||
|
|
||||||
//check the notif matches event details
|
//check the notif matches event details
|
||||||
if req.content.title == events[index].name,
|
if req.content.title == events[index].name,
|
||||||
req.content.subtitle == events[index].notes,
|
req.content.subtitle == events[index].notes,
|
||||||
req.trigger == UNCalendarNotificationTrigger(dateMatching: components, repeats: false) {
|
req.trigger == UNCalendarNotificationTrigger(dateMatching: components, repeats: false) {
|
||||||
//if it does, make sure the notif delets if u complete the veent
|
//if it does, make sure the notif delets if u complete the veent or in the past
|
||||||
if events[index].complete {
|
if events[index].complete || events[index].date > .now {
|
||||||
cancelNotif(req.identifier)
|
cancelNotif(req.identifier)
|
||||||
|
} else {
|
||||||
|
//dont cancel the notif
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
//reschedult it because the event details have changed
|
||||||
cancelNotif(req.identifier)
|
cancelNotif(req.identifier)
|
||||||
scheduleEventNotif(events[index])
|
scheduleEventNotif(events[index])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//cancel if the event is deleted
|
//cancel notif if the event is deleted (doesnt exist/cannot be matched)
|
||||||
cancelNotif(req.identifier)
|
cancelNotif(req.identifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,6 +186,14 @@ class EventViewModel: ObservableObject, @unchecked Sendable {
|
|||||||
scheduleEventNotif(event)
|
scheduleEventNotif(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Task {
|
||||||
|
try? await UNUserNotificationCenter.current().setBadgeCount(await getNotifs().count)
|
||||||
|
}
|
||||||
|
print(eventUUIDs.count)
|
||||||
|
print(events.count(where: {!$0.complete && $0.date < .now}))
|
||||||
|
print(events.count(where: {!$0.complete && $0.date > .now}))
|
||||||
|
print(events.count(where: {!$0.complete}))
|
||||||
|
print(events.count(where: {$0.complete}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// save to local and icloud
|
// save to local and icloud
|
||||||
@@ -222,8 +234,31 @@ class EventViewModel: ObservableObject, @unchecked Sendable {
|
|||||||
saveEvents() //sync with icloud
|
saveEvents() //sync with icloud
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeEvent(at index: IndexSet) {
|
func editEvent(_ editedEvent: Event) {
|
||||||
events.remove(atOffsets: index)
|
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(_ eventToRemove: Event) {
|
||||||
|
let eventToModify = self.events.firstIndex() { currEvent in
|
||||||
|
currEvent.id == eventToRemove.id
|
||||||
|
}
|
||||||
|
if let eventToModify = eventToModify {
|
||||||
|
self.events.remove(at: eventToModify)
|
||||||
|
}
|
||||||
saveEvents() //sync local and icl
|
saveEvents() //sync local and icl
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -472,6 +507,7 @@ func getBuildID() -> String {
|
|||||||
return "\(build)"
|
return "\(build)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
func getDevice() -> (sf: String, label: String) {
|
func getDevice() -> (sf: String, label: String) {
|
||||||
#if canImport(UIKit)
|
#if canImport(UIKit)
|
||||||
let asi = ProcessInfo().isiOSAppOnMac
|
let asi = ProcessInfo().isiOSAppOnMac
|
||||||
@@ -489,57 +525,3 @@ func getDevice() -> (sf: String, label: String) {
|
|||||||
return (sf: "desktopcomputer", label: "Mac")
|
return (sf: "desktopcomputer", label: "Mac")
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Event: AppEntity {
|
|
||||||
static let defaultQuery = EventQuery()
|
|
||||||
|
|
||||||
static var typeDisplayRepresentation: TypeDisplayRepresentation {
|
|
||||||
TypeDisplayRepresentation("skdfj")
|
|
||||||
}
|
|
||||||
|
|
||||||
var displayRepresentation: DisplayRepresentation {
|
|
||||||
DisplayRepresentation("eventsss")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct EventQuery: EntityQuery, DynamicOptionsProvider {
|
|
||||||
typealias Entity = Event
|
|
||||||
@Dependency var vm: EventViewModel
|
|
||||||
func results() async throws -> some ResultsCollection {
|
|
||||||
return vm.events
|
|
||||||
}
|
|
||||||
// func defaultResult() async -> DefaultValue? {
|
|
||||||
// return vm.events[0]
|
|
||||||
// }
|
|
||||||
func entities(for identifiers: [Entity.ID]) async throws -> [Entity] {
|
|
||||||
return vm.events
|
|
||||||
}
|
|
||||||
func suggestedEntities() async throws -> some ResultsCollection {
|
|
||||||
return vm.events //lol cba
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CompleteEvent: AppIntent {
|
|
||||||
static var title: LocalizedStringResource = "Complete An Event"
|
|
||||||
static var description = IntentDescription("Mark an Event as complete.")
|
|
||||||
|
|
||||||
@Parameter(title: "Event ID")
|
|
||||||
var eventID: String
|
|
||||||
|
|
||||||
func perform() async throws -> some IntentResult {
|
|
||||||
print("s")
|
|
||||||
let viewModel = EventViewModel()
|
|
||||||
print("hip")
|
|
||||||
guard let eventUUID = UUID(uuidString: eventID) else {
|
|
||||||
print(":sdklfajk")
|
|
||||||
return .result()
|
|
||||||
}
|
|
||||||
print("hii")
|
|
||||||
if let eventToModify = viewModel.events.firstIndex(where: { $0.id == eventUUID }) {
|
|
||||||
print("hiii")
|
|
||||||
viewModel.events[eventToModify].complete = true
|
|
||||||
viewModel.saveEvents()
|
|
||||||
}
|
|
||||||
return .result()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ struct NFSettings: Codable, Equatable {
|
|||||||
var prevAppVersion: String = getVersion()+getBuildID()
|
var prevAppVersion: String = getVersion()+getBuildID()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
class SettingsViewModel: ObservableObject {
|
class SettingsViewModel: ObservableObject {
|
||||||
@Published var settings: NFSettings = NFSettings()
|
@Published var settings: NFSettings = NFSettings()
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ class SettingsViewModel: ObservableObject {
|
|||||||
"pink"
|
"pink"
|
||||||
]
|
]
|
||||||
|
|
||||||
@Published var device: (sf: String, label: String)
|
@Published var device: (sf: String, label: String) = ("", "")
|
||||||
|
|
||||||
init(load: Bool = true) {
|
init(load: Bool = true) {
|
||||||
self.device = getDevice()
|
self.device = getDevice()
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class SymbolsLoader: ObservableObject {
|
class SymbolsLoader: ObservableObject {
|
||||||
@Published var allSymbols: [String] = []
|
private var allSymbols: [String] = []
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.allSymbols = getAllSymbols()
|
self.allSymbols = getAllSymbols()
|
||||||
@@ -16,7 +16,7 @@ class SymbolsLoader: ObservableObject {
|
|||||||
|
|
||||||
func getSymbols(_ searched: String) -> [String] {
|
func getSymbols(_ searched: String) -> [String] {
|
||||||
if searched.isEmpty {
|
if searched.isEmpty {
|
||||||
return allSymbols
|
return []
|
||||||
} else {
|
} else {
|
||||||
return allSymbols.filter() { $0.localizedCaseInsensitiveContains(searched) }
|
return allSymbols.filter() { $0.localizedCaseInsensitiveContains(searched) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
@FocusState var searchfocuesd: Bool
|
||||||
|
|
||||||
@State var searchInput: String = ""
|
@State var searchInput: String = ""
|
||||||
|
@State var browsing: Bool = false
|
||||||
|
@Environment(\.dismiss) var dismiss
|
||||||
|
|
||||||
var symbols: [String] {
|
var symbols: [String] {
|
||||||
return symbolsLoader.getSymbols(searchInput)
|
return symbolsLoader.getSymbols(searchInput)
|
||||||
@@ -29,7 +31,17 @@ struct SymbolsPicker: View {
|
|||||||
NavigationStack {
|
NavigationStack {
|
||||||
GeometryReader { geo in
|
GeometryReader { geo in
|
||||||
ScrollView {
|
ScrollView {
|
||||||
if symbols.isEmpty {
|
if searchInput.isEmpty {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "magnifyingglass")
|
||||||
|
.resizable().scaledToFit()
|
||||||
|
.frame(width: 30)
|
||||||
|
Text("Start a Search")
|
||||||
|
.font(.title)
|
||||||
|
.bold()
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
} else if symbols.isEmpty {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "magnifyingglass")
|
Image(systemName: "magnifyingglass")
|
||||||
.resizable().scaledToFit()
|
.resizable().scaledToFit()
|
||||||
@@ -45,6 +57,8 @@ struct SymbolsPicker: View {
|
|||||||
ForEach(symbols, id: \.self) { symbol in
|
ForEach(symbols, id: \.self) { symbol in
|
||||||
Button() {
|
Button() {
|
||||||
selection = symbol
|
selection = symbol
|
||||||
|
searchInput = ""
|
||||||
|
dismiss()
|
||||||
} label: {
|
} label: {
|
||||||
VStack {
|
VStack {
|
||||||
Image(systemName: symbol)
|
Image(systemName: symbol)
|
||||||
@@ -62,7 +76,19 @@ struct SymbolsPicker: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.searchable(text: $searchInput)
|
.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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -8,48 +8,49 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct hapticHeavy: ViewModifier {
|
struct hapticHeavy<T: Equatable>: ViewModifier {
|
||||||
var trigger: any Equatable
|
var trigger: T
|
||||||
|
|
||||||
init(trigger: any Equatable) {
|
init(trigger: T) {
|
||||||
self.trigger = trigger
|
self.trigger = trigger
|
||||||
}
|
}
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
func body(content: Content) -> some View {
|
||||||
if #available(iOS 17, *) {
|
|
||||||
content
|
content
|
||||||
.sensoryFeedback(.impact(weight: .heavy, intensity: 1), trigger: trigger)
|
.onChange(of: trigger) { _ in
|
||||||
} else {
|
#if canImport(UIKit)
|
||||||
|
UIImpactFeedbackGenerator(style: .rigid).impactOccurred()
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct hapticSuccess<T: Equatable>: ViewModifier {
|
||||||
|
var trigger: T
|
||||||
|
|
||||||
|
init(trigger: T) {
|
||||||
|
self.trigger = trigger
|
||||||
|
}
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
content
|
content
|
||||||
|
.onChange(of: trigger) { _ in
|
||||||
|
#if canImport(UIKit)
|
||||||
|
UINotificationFeedbackGenerator().notificationOccurred(.success)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct glassButton: ViewModifier {
|
struct glassButton: ViewModifier {
|
||||||
func body(content: Content) -> some View {
|
func body(content: Content) -> some View {
|
||||||
if #available(iOS 19, macOS 16, *) {
|
#if swift(>=6.2)
|
||||||
content.buttonStyle(.glass)
|
content.buttonStyle(.glass)
|
||||||
} else {
|
#else
|
||||||
content.buttonStyle(.borderedProminent)
|
content.buttonStyle(.borderedProminent)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 15))
|
.clipShape(RoundedRectangle(cornerRadius: 15))
|
||||||
.tint(.two)
|
.tint(.two)
|
||||||
}
|
#endif
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct hapticSuccess: ViewModifier {
|
|
||||||
var trigger: any Equatable
|
|
||||||
|
|
||||||
init(trigger: any Equatable) {
|
|
||||||
self.trigger = trigger
|
|
||||||
}
|
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
|
||||||
if #available(iOS 17, *) {
|
|
||||||
content.sensoryFeedback(.success, trigger: trigger)
|
|
||||||
} else {
|
|
||||||
content
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user