23 Commits

Author SHA1 Message Date
neon443
e4107a2faa add success haptic and indentation 2025-06-15 21:24:15 +01:00
neon443
5ec16dd67a fix editing events on mac and ios
toolbar label()s instead of buton("label")s
fix event add/delete/tick animation
fix event complete progress on ios and mac and that it just spazzed out
browsing true for symbols browsing (not picking)
2025-06-15 21:18:58 +01:00
neon443
0cffe243eb completeevent button has a custo progress bar to work on ios aswell
extracted the com[lete button
2025-06-15 18:55:38 +01:00
neon443
6533fb85ed fix editing events 2025-06-15 15:41:37 +01:00
neon443
5c667679d5 fix "new edit event window" on mac not working
- it would save events with the same id and modify the previous one if u opened another one
add symbols to ios
date picker looks better mac
2025-06-15 15:34:08 +01:00
neon443
5dd25f1ede locked the freak in for this one -
can cancel events being completed!!!!!
added a cancel to symbolpicker
and made it dip after choosing
extracted completevent into viewmodel
eventlistview is pretty full rn -- need to cleana
added color picker ot addeventview wtf why wasnt it there
2025-06-14 21:32:11 +01:00
neon443
d80011ea27 remove my shitty uikit 2025-06-14 18:55:09 +01:00
neon443
a40d4f4300 ui cleanup 2025-06-14 18:53:34 +01:00
neon443
22af9fc060 hell nah fuck this 2025-06-14 18:27:13 +01:00
neon443
2dd2c51059 uikit shenanigans: collectionview for the symbol picker?? 2025-06-14 17:45:38 +01:00
neon443
4c9e72fad2 viewmodifier for backwards compat 2025-06-14 11:20:29 +01:00
neon443
cb26c69492 added a little help fow when nothing matches search 2025-06-14 11:17:41 +01:00
neon443
13ef94ea3e custom symbols picker 2025-06-14 10:59:48 +01:00
neon443
6e5b7adbc4 fix editing events on mac - they now reload on he fly
add search on mac
fix symbolspicker on mac
2025-06-14 10:24:03 +01:00
neon443
659f14d5a3 add events on mac 2025-06-13 22:00:28 +01:00
neon443
0663ba9e59 add settings on mac
add whats new on mac
clean up settings on ios
clean up whats new + liquid glass
2025-06-13 21:28:37 +01:00
neon443
6e7bc6c2d1 add viewmodifiers.siwft to conditionally do viewmodifiers
replaced all the .apply s everywhere
colorcodable into its own file
add icon changing for mac
2025-06-13 18:30:07 +01:00
neon443
3f21074091 moved icloud ui functions into viewModel
- idk why it was in the view
added settings for mac
fix addeventview
export view pasteboard for mac
move accent icon to new file
Archive view reversed - new to old
2025-06-13 17:15:23 +01:00
neon443
ee2e05c523 fix the cooked addeventview 2025-06-11 18:49:09 +01:00
neon443
e9e19e2659 Large rework:
Liquid Glass:
- remove ultrathinmaterial from all sheets
- button color change

remove the eventName eventDate etcs and replaced with an event var

Add addeventview for mac, cleanup #if s

clean up homeview
add .searchable
addeventview is horizontal i have no idea why
2025-06-11 18:34:17 +01:00
neon443
5ce48a4bc2 add new buttons 2025-06-10 15:09:10 +01:00
neon443
a7e09692f6 liquid glass update 1 2025-06-10 13:14:19 +01:00
neon443
559f891719 new liquid glass icon 2025-06-10 12:28:03 +01:00
37 changed files with 1237 additions and 918 deletions

View File

@@ -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 = 4.4.0 VERSION = 5
NAME = Near Future NAME = Near Future
BUILD_NUMBER = 1 BUILD_NUMBER = 1

View File

@@ -24,31 +24,35 @@ struct NearFutureApp: App {
.defaultSize(width: 550, height: 650) .defaultSize(width: 550, height: 650)
.commands { .commands {
CommandGroup(replacing: CommandGroupPlacement.appInfo) { CommandGroup(replacing: CommandGroupPlacement.appInfo) {
Button("about nf") { Button("About Near Future") {
openWindow(id: "about") openWindow(id: "about")
} }
} }
NearFutureCommands() NearFutureCommands()
} }
WindowGroup("edit Event", for: Event.ID.self) { $eventID in WindowGroup("Edit Event", for: Event.ID.self) { $eventID in
EditEventView( if viewModel.events.first(where: {$0.id == eventID}) == nil {
viewModel: viewModel, AddEventView(
event: Binding( viewModel: viewModel
get: {
viewModel.events.first(where: {$0.id == eventID})!
},
set: { newValue in
if let eventIndex = viewModel.events.firstIndex(where: {
$0.id == eventID
}) {
viewModel.events[eventIndex] = newValue
}
viewModel.saveEvents()
}
) )
) } else {
EditEventView(
viewModel: viewModel,
event: Binding(
get: {
viewModel.events.first(where: {$0.id == eventID}) ?? viewModel.template
},
set: { newValue in
viewModel.editEvent(newValue)
}
)
)
}
} }
.defaultSize(width: 480, height: 550)
.windowIdealSize(.fitToContent)
.restorationBehavior(.disabled)
Window("About Near Future", id: "about") { Window("About Near Future", id: "about") {
AboutView() AboutView()
@@ -59,7 +63,10 @@ struct NearFutureApp: App {
.defaultPosition(UnitPoint.center) .defaultPosition(UnitPoint.center)
Settings { Settings {
Text("wip") SettingsView(
viewModel: viewModel,
settingsModel: settingsModel
)
} }
} }
} }

View File

@@ -11,7 +11,7 @@ import SwiftUI
struct NearFutureCommands: Commands { struct NearFutureCommands: Commands {
var body: some Commands { var body: some Commands {
CommandGroup(after: CommandGroupPlacement.appInfo) { CommandGroup(after: CommandGroupPlacement.appInfo) {
Text("hi") // Text("hi")
} }
} }
} }

View File

@@ -11,8 +11,11 @@ struct ContentView: View {
@StateObject var viewModel: EventViewModel @StateObject var viewModel: EventViewModel
@StateObject var settingsModel: SettingsViewModel @StateObject var settingsModel: SettingsViewModel
var body: some View { @State private var showAddEventView: Bool = false
NavigationSplitView(preferredCompactColumn: .constant(.sidebar)) { @State private var symbolSearchInput: String = ""
var body: some View {
NavigationSplitView {
List { List {
NavigationLink { NavigationLink {
HomeView( HomeView(
@@ -32,14 +35,49 @@ struct ContentView: View {
Image(systemName: "tray.full") Image(systemName: "tray.full")
Text("Archive") Text("Archive")
} }
NavigationLink {
SymbolsPicker(
selection: .constant(""),
browsing: true
)
} label: {
Image(systemName: "star.circle")
Text("Symbols")
}
NavigationLink {
SettingsView(
viewModel: viewModel,
settingsModel: settingsModel
)
} label: {
Image(systemName: "gear")
Text("Settings")
}
} }
} detail: { } detail: {
Text("Welcome to Near Future")
} }
.tint(settingsModel.settings.tint.color) .tint(settingsModel.settings.tint.color)
.frame(minWidth: 450, minHeight: 550) .frame(minWidth: 450, minHeight: 550)
.containerBackground(.ultraThinMaterial, for: .window) .containerBackground(.regularMaterial, for: .window)
} .sheet(isPresented: $settingsModel.settings.showWhatsNew) {
WhatsNewView(settingsModel: settingsModel)
.presentationSizing(.form)
}
.sheet(isPresented: $showAddEventView) {
AddEventView(
viewModel: viewModel
)
.presentationSizing(.page)
}
.toolbar {
Button() {
showAddEventView.toggle()
} label: {
Label("New", systemImage: "plus")
}
}
}
} }
#Preview { #Preview {

View File

@@ -1,185 +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
)
.apply {
if #available(iOS 17, *) {
$0.sensoryFeedback(.success, trigger: event.complete)
}
}
}
.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
)
}

View File

@@ -11,20 +11,33 @@ struct HomeView: View {
@StateObject var viewModel: EventViewModel @StateObject var viewModel: EventViewModel
@StateObject var settingsModel: SettingsViewModel @StateObject var settingsModel: SettingsViewModel
@State private var searchInput: String = ""
var filteredEvents: [Event] { var filteredEvents: [Event] {
switch settingsModel.settings.showCompletedInHome { if searchInput.isEmpty {
case true: if settingsModel.settings.showCompletedInHome {
return viewModel.events return viewModel.events
case false: } else {
return viewModel.events.filter { !$0.complete } return viewModel.events.filter() { !$0.complete }
} }
} } else {
var body: some View { return viewModel.events.filter {
ScrollView { $0.name.localizedCaseInsensitiveContains(searchInput) ||
ForEach(filteredEvents) { event in $0.notes.localizedCaseInsensitiveContains(searchInput)
EventListView(viewModel: viewModel, event: event)
} }
} }
}
var body: some View {
ScrollView {
ForEach(viewModel.events) { event in
if filteredEvents.contains(event) {
EventListView(viewModel: viewModel, event: event)
.id(event)
}
}
}
.searchable(text: $searchInput)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
} }
} }

View File

@@ -1,48 +0,0 @@
//
// EditEventView.swift
// NearFuture
//
// Created by neon443 on 21/05/2025.
//
import SwiftUI
struct EditEventView: View {
@Environment(\.dismiss) var dismiss
@ObservedObject var viewModel: EventViewModel
@Binding var event: Event
var body: some View {
AddEventView(
viewModel: viewModel,
eventName: $event.name,
eventComplete: $event.complete,
eventCompleteDesc: $event.completeDesc,
eventSymbol: $event.symbol,
eventColor: $event.color.colorBind,
eventNotes: $event.notes,
eventDate: $event.date,
eventRecurrence: $event.recurrence,
adding: false //bc we editing existing event
)
.navigationTitle("Edit Event")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button() {
dismiss()
} label: {
Text("Done")
.bold()
}
.disabled(event.name == "")
}
}
}
}
#Preview {
EditEventView(
viewModel: dummyEventViewModel(),
event: .constant(dummyEventViewModel().template)
)
}

View File

@@ -0,0 +1,155 @@
//
// SettingsView.swift
// NearFuture
//
// Created by neon443 on 13/06/2025.
//
import SwiftUI
struct SettingsView: View {
@ObservedObject var viewModel: EventViewModel
@ObservedObject var settingsModel: SettingsViewModel
@State private var importStr: String = ""
func changeIcon(to toIcon: String) {
if let nsimage = NSImage(named: toIcon) {
let nsImageView = NSImageView(image: nsimage)
nsImageView.frame = NSRect(x: 0, y: 0, width: 128, height: 128)
NSApplication.shared.dockTile.contentView = nsImageView
NSApplication.shared.dockTile.display()
}
}
var body: some View {
NavigationStack {
List {
ScrollView(.horizontal) {
HStack {
ForEach(settingsModel.accentChoices, id: \.self) { choice in
let color = Color(nsColor: NSColor(named: "uiColors/\(choice)")!)
ZStack {
Button() {
settingsModel.changeTint(to: choice)
changeIcon(to: choice)
} label: {
Circle()
.foregroundStyle(color)
.frame(width: 30)
}
.buttonStyle(.plain)
if ColorCodable(color) == settingsModel.settings.tint {
let needContrast: Bool = ColorCodable(color) == settingsModel.settings.tint
Circle()
.foregroundStyle(needContrast ? .two : .one)
.frame(width: 10)
}
}
}
}
}
Button("Show What's New") {
settingsModel.settings.showWhatsNew = true
}
Toggle("Show completed Events in Home", isOn: $settingsModel.settings.showCompletedInHome)
.onChange(of: settingsModel.settings.showCompletedInHome) { _ in
settingsModel.saveSettings()
}
NavigationLink() {
List {
if !settingsModel.notifsGranted {
Text("\(Image(systemName: "xmark")) Notifications disabled for Near Future")
.foregroundStyle(.red)
Button("Request Notifications") {
Task.detached {
let requestNotifsResult = await requestNotifs()
await MainActor.run {
settingsModel.notifsGranted = requestNotifsResult
}
}
}
} else {
Text("\(Image(systemName: "checkmark")) Notifications enabled for Near Future")
.foregroundStyle(.green)
}
}
} label: {
Image(systemName: "bell.badge.fill")
Text("Notifications")
}
NavigationLink() {
iCloudSettingsView(
viewModel: viewModel,
settingsModel: settingsModel
)
} label: {
HStack {
Image(systemName: "icloud.fill")
Text("iCloud")
Spacer()
Circle()
.frame(width: 20, height: 20)
.foregroundStyle(viewModel.iCloudStatusColor)
}
}
.onAppear {
viewModel.sync()
viewModel.updateiCStatus()
}
NavigationLink() {
ImportView(viewModel: viewModel, importStr: $importStr)
} label: {
Label("Import Events", systemImage: "tray.and.arrow.down.fill")
.foregroundStyle(.one)
}
NavigationLink() {
ExportView(viewModel: viewModel)
} label: {
Label("Export Events", systemImage: "square.and.arrow.up")
.foregroundStyle(.one)
}
Text("Tip")
.font(.subheadline)
Text("Near Future has Widgets!")
Text("Danger Zone")
.foregroundStyle(.red)
.font(.subheadline)
Button("Delete local data", role: .destructive) {
viewModel.dangerClearLocalData()
}
Button("Delete iCloud data", role: .destructive) {
viewModel.dangerCleariCloudData()
}
Button("Delete all data", role: .destructive) {
viewModel.dangerClearLocalData()
viewModel.dangerCleariCloudData()
}
Text("Debug")
.foregroundStyle(.red)
.font(.subheadline)
Button("Reset UserDefaults", role: .destructive) {
viewModel.dangerResetLocalData()
}
Button("Reset iCloud", role: .destructive) {
viewModel.dangerResetiCloud()
}
// AboutView()
.modifier(navigationInlineLarge())
.scrollContentBackground(.hidden)
}
}
}
}
#Preview {
SettingsView(
viewModel: dummyEventViewModel(),
settingsModel: dummySettingsViewModel()
)
}

View File

@@ -14,10 +14,8 @@
A90D49442DDE1C7600781124 /* Tints.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A90D49432DDE1C1100781124 /* Tints.xcassets */; }; A90D49442DDE1C7600781124 /* Tints.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A90D49432DDE1C1100781124 /* Tints.xcassets */; };
A90D49452DDE1C7600781124 /* Tints.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A90D49432DDE1C1100781124 /* Tints.xcassets */; }; A90D49452DDE1C7600781124 /* Tints.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A90D49432DDE1C1100781124 /* Tints.xcassets */; };
A90D49462DDE1C7A00781124 /* Tints.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A90D49432DDE1C1100781124 /* Tints.xcassets */; }; A90D49462DDE1C7A00781124 /* Tints.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A90D49432DDE1C1100781124 /* Tints.xcassets */; };
A90D494B2DDE2C2900781124 /* AddEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F83C2DCAABE00064DCA0 /* AddEventView.swift */; };
A90D49522DDE2D0000781124 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D49512DDE2D0000781124 /* Extensions.swift */; }; A90D49522DDE2D0000781124 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D49512DDE2D0000781124 /* Extensions.swift */; };
A90D49532DDE2D0000781124 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D49512DDE2D0000781124 /* Extensions.swift */; }; A90D49532DDE2D0000781124 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D49512DDE2D0000781124 /* Extensions.swift */; };
A90D49562DDE2D5800781124 /* SFSymbolsPicker in Frameworks */ = {isa = PBXBuildFile; productRef = A90D49552DDE2D5800781124 /* SFSymbolsPicker */; };
A90D495B2DDE2EDB00781124 /* MacNearFutureApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D495A2DDE2EDB00781124 /* MacNearFutureApp.swift */; }; A90D495B2DDE2EDB00781124 /* MacNearFutureApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D495A2DDE2EDB00781124 /* MacNearFutureApp.swift */; };
A90D495E2DDE3C7400781124 /* NFCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D495D2DDE3C7400781124 /* NFCommands.swift */; }; A90D495E2DDE3C7400781124 /* NFCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D495D2DDE3C7400781124 /* NFCommands.swift */; };
A90D495F2DDE3C7400781124 /* NFCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D495D2DDE3C7400781124 /* NFCommands.swift */; }; A90D495F2DDE3C7400781124 /* NFCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D495D2DDE3C7400781124 /* NFCommands.swift */; };
@@ -27,11 +25,26 @@
A914FA4B2DD26C6800856265 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A914FA4A2DD26C0F00856265 /* HomeView.swift */; }; A914FA4B2DD26C6800856265 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A914FA4A2DD26C0F00856265 /* HomeView.swift */; };
A914FA4D2DD2768900856265 /* WhatsNewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A914FA4C2DD2768900856265 /* WhatsNewView.swift */; }; A914FA4D2DD2768900856265 /* WhatsNewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A914FA4C2DD2768900856265 /* WhatsNewView.swift */; };
A914FA4F2DD276D200856265 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A914FA4E2DD276D200856265 /* AboutView.swift */; }; A914FA4F2DD276D200856265 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A914FA4E2DD276D200856265 /* AboutView.swift */; };
A91EF8072DFC8B8B00B8463D /* ColorCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EF8062DFC8B8B00B8463D /* ColorCodable.swift */; };
A91EF8082DFC8B8B00B8463D /* ColorCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EF8062DFC8B8B00B8463D /* ColorCodable.swift */; };
A91EF8092DFC8B8B00B8463D /* ColorCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EF8062DFC8B8B00B8463D /* ColorCodable.swift */; };
A91EF80B2DFC910000B8463D /* ViewModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EF80A2DFC910000B8463D /* ViewModifiers.swift */; };
A91EF80C2DFC910000B8463D /* ViewModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EF80A2DFC910000B8463D /* ViewModifiers.swift */; };
A91EF80D2DFC910000B8463D /* ViewModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EF80A2DFC910000B8463D /* ViewModifiers.swift */; };
A91EF80E2DFC9A0C00B8463D /* WhatsNewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A914FA4C2DD2768900856265 /* WhatsNewView.swift */; };
A91EF8102DFCB66C00B8463D /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EF80F2DFCB66C00B8463D /* SettingsView.swift */; };
A91EF8132DFCC87D00B8463D /* EditEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F83D2DCAABE00064DCA0 /* EditEventView.swift */; };
A91EF8142DFCC87D00B8463D /* AddEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F83C2DCAABE00064DCA0 /* AddEventView.swift */; };
A91EF8182DFD77BF00B8463D /* SymbolsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EF8172DFD77BF00B8463D /* SymbolsLoader.swift */; };
A91EF8192DFD77BF00B8463D /* SymbolsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EF8172DFD77BF00B8463D /* SymbolsLoader.swift */; };
A91EF81A2DFD77BF00B8463D /* SymbolsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EF8172DFD77BF00B8463D /* SymbolsLoader.swift */; };
A91EF81C2DFD796600B8463D /* SymbolsPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EF81B2DFD796600B8463D /* SymbolsPicker.swift */; };
A91EF81D2DFD796600B8463D /* SymbolsPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EF81B2DFD796600B8463D /* SymbolsPicker.swift */; };
A91EF81E2DFD796600B8463D /* SymbolsPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EF81B2DFD796600B8463D /* SymbolsPicker.swift */; };
A920C2882D24011400E4F9B1 /* NearFutureApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C2872D24011400E4F9B1 /* NearFutureApp.swift */; }; A920C2882D24011400E4F9B1 /* NearFutureApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C2872D24011400E4F9B1 /* NearFutureApp.swift */; };
A920C28C2D24011400E4F9B1 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C28B2D24011400E4F9B1 /* Events.swift */; }; A920C28C2D24011400E4F9B1 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C28B2D24011400E4F9B1 /* Events.swift */; };
A920C28E2D24011A00E4F9B1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A920C28D2D24011A00E4F9B1 /* Assets.xcassets */; }; A920C28E2D24011A00E4F9B1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A920C28D2D24011A00E4F9B1 /* Assets.xcassets */; };
A920C2922D24011A00E4F9B1 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A920C2912D24011A00E4F9B1 /* Preview Assets.xcassets */; }; A920C2922D24011A00E4F9B1 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A920C2912D24011A00E4F9B1 /* Preview Assets.xcassets */; };
A920C2BE2D24021A00E4F9B1 /* SFSymbolsPicker in Frameworks */ = {isa = PBXBuildFile; productRef = A920C2BD2D24021A00E4F9B1 /* SFSymbolsPicker */; };
A949F8322DCAAA8A0064DCA0 /* NearFutureIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = A949F8312DCAAA8A0064DCA0 /* NearFutureIcon.png */; }; A949F8322DCAAA8A0064DCA0 /* NearFutureIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = A949F8312DCAAA8A0064DCA0 /* NearFutureIcon.png */; };
A949F84B2DCAABE00064DCA0 /* ArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F83A2DCAABE00064DCA0 /* ArchiveView.swift */; }; A949F84B2DCAABE00064DCA0 /* ArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F83A2DCAABE00064DCA0 /* ArchiveView.swift */; };
A949F84C2DCAABE00064DCA0 /* AddEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F83C2DCAABE00064DCA0 /* AddEventView.swift */; }; A949F84C2DCAABE00064DCA0 /* AddEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F83C2DCAABE00064DCA0 /* AddEventView.swift */; };
@@ -45,16 +58,24 @@
A949F8542DCAABE00064DCA0 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8462DCAABE00064DCA0 /* SettingsView.swift */; }; A949F8542DCAABE00064DCA0 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8462DCAABE00064DCA0 /* SettingsView.swift */; };
A949F8552DCAABE00064DCA0 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8482DCAABE00064DCA0 /* StatsView.swift */; }; A949F8552DCAABE00064DCA0 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8482DCAABE00064DCA0 /* StatsView.swift */; };
A949F85F2DCABB420064DCA0 /* Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F85D2DCABB420064DCA0 /* Buttons.swift */; }; A949F85F2DCABB420064DCA0 /* Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F85D2DCABB420064DCA0 /* Buttons.swift */; };
A95E9ED32DFC703200ED655F /* iCloudSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8442DCAABE00064DCA0 /* iCloudSettingsView.swift */; };
A95E9ED82DFC742B00ED655F /* AccentIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95E9ED72DFC742B00ED655F /* AccentIcon.swift */; };
A95E9ED92DFC742B00ED655F /* AccentIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95E9ED72DFC742B00ED655F /* AccentIcon.swift */; };
A95E9EDA2DFC742B00ED655F /* AccentIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95E9ED72DFC742B00ED655F /* AccentIcon.swift */; };
A95E9EE42DFC77D400ED655F /* ImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8452DCAABE00064DCA0 /* ImportView.swift */; };
A95E9EE52DFC77E200ED655F /* ExportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8432DCAABE00064DCA0 /* ExportView.swift */; };
A96609E72DFD800000DBFA78 /* HelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8412DCAABE00064DCA0 /* HelpView.swift */; };
A979F60A2D270AF00094C0B3 /* NearFutureWidgetsBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F6092D270AF00094C0B3 /* NearFutureWidgetsBundle.swift */; }; A979F60A2D270AF00094C0B3 /* NearFutureWidgetsBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F6092D270AF00094C0B3 /* NearFutureWidgetsBundle.swift */; };
A979F60C2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F60B2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift */; }; A979F60C2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F60B2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift */; };
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 */; };
A98C20CC2DE730740008D61C /* EditEventViewMac.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20C92DE730740008D61C /* EditEventViewMac.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 */; };
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 */; }; A9FC7EEA2D2823920020D75B /* NearFutureWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FC7EE92D28238A0020D75B /* NearFutureWidgets.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@@ -104,6 +125,11 @@
A914FA4A2DD26C0F00856265 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; }; A914FA4A2DD26C0F00856265 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
A914FA4C2DD2768900856265 /* WhatsNewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WhatsNewView.swift; path = NearFuture/Views/Settings/WhatsNewView.swift; sourceTree = SOURCE_ROOT; }; A914FA4C2DD2768900856265 /* WhatsNewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WhatsNewView.swift; path = NearFuture/Views/Settings/WhatsNewView.swift; sourceTree = SOURCE_ROOT; };
A914FA4E2DD276D200856265 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AboutView.swift; path = NearFuture/Views/Misc/AboutView.swift; sourceTree = SOURCE_ROOT; }; A914FA4E2DD276D200856265 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AboutView.swift; path = NearFuture/Views/Misc/AboutView.swift; sourceTree = SOURCE_ROOT; };
A91EF8062DFC8B8B00B8463D /* ColorCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorCodable.swift; sourceTree = "<group>"; };
A91EF80A2DFC910000B8463D /* ViewModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModifiers.swift; sourceTree = "<group>"; };
A91EF80F2DFCB66C00B8463D /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
A91EF8172DFD77BF00B8463D /* SymbolsLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymbolsLoader.swift; sourceTree = "<group>"; };
A91EF81B2DFD796600B8463D /* SymbolsPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymbolsPicker.swift; sourceTree = "<group>"; };
A920C2842D24011400E4F9B1 /* NearFuture.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NearFuture.app; sourceTree = BUILT_PRODUCTS_DIR; }; A920C2842D24011400E4F9B1 /* NearFuture.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NearFuture.app; sourceTree = BUILT_PRODUCTS_DIR; };
A920C2872D24011400E4F9B1 /* NearFutureApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureApp.swift; sourceTree = "<group>"; }; A920C2872D24011400E4F9B1 /* NearFutureApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureApp.swift; sourceTree = "<group>"; };
A920C28B2D24011400E4F9B1 /* Events.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Events.swift; sourceTree = "<group>"; }; A920C28B2D24011400E4F9B1 /* Events.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Events.swift; sourceTree = "<group>"; };
@@ -125,15 +151,15 @@
A949F8462DCAABE00064DCA0 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; }; A949F8462DCAABE00064DCA0 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
A949F8482DCAABE00064DCA0 /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = "<group>"; }; A949F8482DCAABE00064DCA0 /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = "<group>"; };
A949F85D2DCABB420064DCA0 /* Buttons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Buttons.swift; sourceTree = "<group>"; }; A949F85D2DCABB420064DCA0 /* Buttons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Buttons.swift; sourceTree = "<group>"; };
A95E9ED72DFC742B00ED655F /* AccentIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccentIcon.swift; sourceTree = "<group>"; };
A979F6022D270AF00094C0B3 /* NearFutureWidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NearFutureWidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; A979F6022D270AF00094C0B3 /* NearFutureWidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NearFutureWidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
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>"; };
A98C20C92DE730740008D61C /* EditEventViewMac.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditEventViewMac.swift; 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>"; };
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>"; }; A9FC7EE92D28238A0020D75B /* NearFutureWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgets.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@@ -142,7 +168,6 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A90D49562DDE2D5800781124 /* SFSymbolsPicker in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -150,7 +175,6 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A920C2BE2D24021A00E4F9B1 /* SFSymbolsPicker in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -169,6 +193,9 @@
children = ( children = (
A920C28B2D24011400E4F9B1 /* Events.swift */, A920C28B2D24011400E4F9B1 /* Events.swift */,
A90D49602DDE626300781124 /* Settings.swift */, A90D49602DDE626300781124 /* Settings.swift */,
A91EF8062DFC8B8B00B8463D /* ColorCodable.swift */,
A95E9ED72DFC742B00ED655F /* AccentIcon.swift */,
A91EF8162DFD77A500B8463D /* SymbolsPicker */,
); );
path = Model; path = Model;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -190,9 +217,8 @@
children = ( children = (
A90D49332DDE0FAF00781124 /* ContentViewMac.swift */, A90D49332DDE0FAF00781124 /* ContentViewMac.swift */,
A98C20CF2DE731BD0008D61C /* HomeView.swift */, A98C20CF2DE731BD0008D61C /* HomeView.swift */,
A98C20CA2DE730740008D61C /* EventListViewMac.swift */,
A98C20CD2DE7308E0008D61C /* ArchiveView.swift */, A98C20CD2DE7308E0008D61C /* ArchiveView.swift */,
A98C20C82DE730420008D61C /* Modification */, A91EF80F2DFCB66C00B8463D /* SettingsView.swift */,
); );
path = Views; path = Views;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -203,6 +229,8 @@
A920C2872D24011400E4F9B1 /* NearFutureApp.swift */, A920C2872D24011400E4F9B1 /* NearFutureApp.swift */,
A90D49512DDE2D0000781124 /* Extensions.swift */, A90D49512DDE2D0000781124 /* Extensions.swift */,
A90D49202DDE0A3B00781124 /* Model */, A90D49202DDE0A3B00781124 /* Model */,
A91EF80A2DFC910000B8463D /* ViewModifiers.swift */,
A9BAC6872DFF238100EC8E44 /* CompleteEventButton.swift */,
); );
path = Shared; path = Shared;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -214,6 +242,15 @@
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
A91EF8162DFD77A500B8463D /* SymbolsPicker */ = {
isa = PBXGroup;
children = (
A91EF8172DFD77BF00B8463D /* SymbolsLoader.swift */,
A91EF81B2DFD796600B8463D /* SymbolsPicker.swift */,
);
path = SymbolsPicker;
sourceTree = "<group>";
};
A920C27B2D24011300E4F9B1 = { A920C27B2D24011300E4F9B1 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -351,14 +388,6 @@
path = NearFutureWidgets; path = NearFutureWidgets;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
A98C20C82DE730420008D61C /* Modification */ = {
isa = PBXGroup;
children = (
A98C20C92DE730740008D61C /* EditEventViewMac.swift */,
);
path = Modification;
sourceTree = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
@@ -374,11 +403,9 @@
); );
dependencies = ( dependencies = (
A98C20D22DE732B10008D61C /* PBXTargetDependency */, A98C20D22DE732B10008D61C /* PBXTargetDependency */,
A90D494D2DDE2C6000781124 /* PBXTargetDependency */,
); );
name = MacNearFuture; name = MacNearFuture;
packageProductDependencies = ( packageProductDependencies = (
A90D49552DDE2D5800781124 /* SFSymbolsPicker */,
); );
productName = MacNearFuture; productName = MacNearFuture;
productReference = A90D49262DDE0FA400781124 /* Near Future.app */; productReference = A90D49262DDE0FA400781124 /* Near Future.app */;
@@ -400,7 +427,6 @@
); );
name = NearFuture; name = NearFuture;
packageProductDependencies = ( packageProductDependencies = (
A920C2BD2D24021A00E4F9B1 /* SFSymbolsPicker */,
); );
productName = NearFuture; productName = NearFuture;
productReference = A920C2842D24011400E4F9B1 /* NearFuture.app */; productReference = A920C2842D24011400E4F9B1 /* NearFuture.app */;
@@ -454,7 +480,6 @@
); );
mainGroup = A920C27B2D24011300E4F9B1; mainGroup = A920C27B2D24011300E4F9B1;
packageReferences = ( packageReferences = (
A920C2BC2D24021900E4F9B1 /* XCRemoteSwiftPackageReference "SFSymbolsPicker" */,
); );
productRefGroup = A920C2852D24011400E4F9B1 /* Products */; productRefGroup = A920C2852D24011400E4F9B1 /* Products */;
projectDirPath = ""; projectDirPath = "";
@@ -505,18 +530,30 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A98C20CB2DE730740008D61C /* EventListViewMac.swift in Sources */, A91EF80E2DFC9A0C00B8463D /* WhatsNewView.swift in Sources */,
A98C20CC2DE730740008D61C /* EditEventViewMac.swift in Sources */, A91EF8192DFD77BF00B8463D /* SymbolsLoader.swift in Sources */,
A90D494B2DDE2C2900781124 /* AddEventView.swift in Sources */, A95E9EE42DFC77D400ED655F /* ImportView.swift in Sources */,
A9BAC6892DFF242300EC8E44 /* CompleteEventButton.swift in Sources */,
A91EF80C2DFC910000B8463D /* ViewModifiers.swift in Sources */,
A95E9ED92DFC742B00ED655F /* AccentIcon.swift in Sources */,
A91EF8102DFCB66C00B8463D /* SettingsView.swift in Sources */,
A90D495E2DDE3C7400781124 /* NFCommands.swift in Sources */, A90D495E2DDE3C7400781124 /* NFCommands.swift in Sources */,
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 */, 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 */,
A90D495B2DDE2EDB00781124 /* MacNearFutureApp.swift in Sources */, A90D495B2DDE2EDB00781124 /* MacNearFutureApp.swift in Sources */,
A91EF8082DFC8B8B00B8463D /* ColorCodable.swift in Sources */,
A90D49522DDE2D0000781124 /* Extensions.swift in Sources */, A90D49522DDE2D0000781124 /* Extensions.swift in Sources */,
A91EF81D2DFD796600B8463D /* SymbolsPicker.swift in Sources */,
A90D49422DDE114100781124 /* Events.swift in Sources */, A90D49422DDE114100781124 /* Events.swift in Sources */,
A90D49382DDE0FAF00781124 /* ContentViewMac.swift in Sources */, A90D49382DDE0FAF00781124 /* ContentViewMac.swift in Sources */,
A90D49622DDE626300781124 /* Settings.swift in Sources */, A90D49622DDE626300781124 /* Settings.swift in Sources */,
A95E9ED32DFC703200ED655F /* iCloudSettingsView.swift in Sources */,
A96609E72DFD800000DBFA78 /* HelpView.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -534,15 +571,21 @@
A90D49612DDE626300781124 /* Settings.swift in Sources */, A90D49612DDE626300781124 /* Settings.swift in Sources */,
A90D495F2DDE3C7400781124 /* NFCommands.swift in Sources */, A90D495F2DDE3C7400781124 /* NFCommands.swift in Sources */,
A949F84F2DCAABE00064DCA0 /* EventListView.swift in Sources */, A949F84F2DCAABE00064DCA0 /* EventListView.swift in Sources */,
A91EF8092DFC8B8B00B8463D /* ColorCodable.swift in Sources */,
A949F8502DCAABE00064DCA0 /* HelpView.swift in Sources */, A949F8502DCAABE00064DCA0 /* HelpView.swift in Sources */,
A949F85F2DCABB420064DCA0 /* Buttons.swift in Sources */, A949F85F2DCABB420064DCA0 /* Buttons.swift in Sources */,
A914FA4D2DD2768900856265 /* WhatsNewView.swift in Sources */, A914FA4D2DD2768900856265 /* WhatsNewView.swift in Sources */,
A91EF80B2DFC910000B8463D /* ViewModifiers.swift in Sources */,
A949F8512DCAABE00064DCA0 /* ExportView.swift in Sources */, A949F8512DCAABE00064DCA0 /* ExportView.swift in Sources */,
A95E9ED82DFC742B00ED655F /* AccentIcon.swift in Sources */,
A90D49532DDE2D0000781124 /* Extensions.swift in Sources */, A90D49532DDE2D0000781124 /* Extensions.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 */,
A949F8552DCAABE00064DCA0 /* StatsView.swift in Sources */, A949F8552DCAABE00064DCA0 /* StatsView.swift in Sources */,
A91EF81A2DFD77BF00B8463D /* SymbolsLoader.swift in Sources */,
A920C2882D24011400E4F9B1 /* NearFutureApp.swift in Sources */, A920C2882D24011400E4F9B1 /* NearFutureApp.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@@ -552,7 +595,12 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A979F6182D2714310094C0B3 /* Events.swift in Sources */, A979F6182D2714310094C0B3 /* Events.swift in Sources */,
A91EF81C2DFD796600B8463D /* SymbolsPicker.swift in Sources */,
A979F60A2D270AF00094C0B3 /* NearFutureWidgetsBundle.swift in Sources */, A979F60A2D270AF00094C0B3 /* NearFutureWidgetsBundle.swift in Sources */,
A95E9EDA2DFC742B00ED655F /* AccentIcon.swift in Sources */,
A91EF80D2DFC910000B8463D /* ViewModifiers.swift in Sources */,
A91EF8182DFD77BF00B8463D /* SymbolsLoader.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 */,
A90D49632DDE626300781124 /* Settings.swift in Sources */, A90D49632DDE626300781124 /* Settings.swift in Sources */,
@@ -562,10 +610,6 @@
/* End PBXSourcesBuildPhase section */ /* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */ /* Begin PBXTargetDependency section */
A90D494D2DDE2C6000781124 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
productRef = A90D494C2DDE2C6000781124 /* SFSymbolsPicker */;
};
A979F6132D270AF90094C0B3 /* PBXTargetDependency */ = { A979F6132D270AF90094C0B3 /* PBXTargetDependency */ = {
isa = PBXTargetDependency; isa = PBXTargetDependency;
platformFilter = ios; platformFilter = ios;
@@ -994,35 +1038,6 @@
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
A920C2BC2D24021900E4F9B1 /* XCRemoteSwiftPackageReference "SFSymbolsPicker" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/alessiorubicini/SFSymbolsPicker";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.0.6;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
A90D494C2DDE2C6000781124 /* SFSymbolsPicker */ = {
isa = XCSwiftPackageProductDependency;
package = A920C2BC2D24021900E4F9B1 /* XCRemoteSwiftPackageReference "SFSymbolsPicker" */;
productName = SFSymbolsPicker;
};
A90D49552DDE2D5800781124 /* SFSymbolsPicker */ = {
isa = XCSwiftPackageProductDependency;
package = A920C2BC2D24021900E4F9B1 /* XCRemoteSwiftPackageReference "SFSymbolsPicker" */;
productName = SFSymbolsPicker;
};
A920C2BD2D24021A00E4F9B1 /* SFSymbolsPicker */ = {
isa = XCSwiftPackageProductDependency;
package = A920C2BC2D24021900E4F9B1 /* XCRemoteSwiftPackageReference "SFSymbolsPicker" */;
productName = SFSymbolsPicker;
};
/* End XCSwiftPackageProductDependency section */
}; };
rootObject = A920C27C2D24011300E4F9B1 /* Project object */; rootObject = A920C27C2D24011300E4F9B1 /* Project object */;
} }

View File

@@ -1,15 +0,0 @@
{
"originHash" : "19df39f99b22f4ef95b73ed292ffb0c8d7694dd4c9db2b96ea73b091b7b1a026",
"pins" : [
{
"identity" : "sfsymbolspicker",
"kind" : "remoteSourceControl",
"location" : "https://github.com/alessiorubicini/SFSymbolsPicker",
"state" : {
"revision" : "73c909b8a7fc77a30dd04208e33f759f8b52c4c8",
"version" : "1.0.6"
}
}
],
"version" : 3
}

View File

@@ -11,7 +11,8 @@ struct ArchiveView: View {
@ObservedObject var viewModel: EventViewModel @ObservedObject var viewModel: EventViewModel
@State var showAddEvent: Bool = false @State var showAddEvent: Bool = false
var filteredEvents: [Event] { var filteredEvents: [Event] {
return viewModel.events.filter() {$0.complete} let filteredEvents = viewModel.events.filter({$0.complete})
return filteredEvents.reversed()
} }
var body: some View { var body: some View {
NavigationStack { NavigationStack {
@@ -22,42 +23,40 @@ struct ArchiveView: View {
} else { } else {
ScrollView { ScrollView {
ForEach(filteredEvents) { event in ForEach(filteredEvents) { event in
EventListView(viewModel: viewModel, event: event) NavigationLink() {
.transition(.moveAndFadeReversed) EditEventView(
.id(event.complete) viewModel: viewModel,
event: Binding(
get: { event },
set: { newValue in
viewModel.editEvent(newValue)
}
)
)
} label: {
EventListView(viewModel: viewModel, event: event)
.id(event.complete)
}
.transition(.moveAndFadeReversed)
} }
.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)
} }
} }
.navigationTitle("Archive") .navigationTitle("Archive")
.apply { .modifier(navigationInlineLarge())
if #available(iOS 17, *) {
$0.toolbarTitleDisplayMode(.inlineLarge)
} else {
$0.navigationBarTitleDisplayMode(.inline)
}
}
} }
.sheet(isPresented: $showAddEvent) { .sheet(isPresented: $showAddEvent) {
AddEventView( AddEventView(
viewModel: viewModel, viewModel: viewModel
eventName: $viewModel.editableTemplate.name,
eventComplete: $viewModel.editableTemplate.complete,
eventCompleteDesc: $viewModel.editableTemplate.completeDesc,
eventSymbol: $viewModel.editableTemplate.symbol,
eventColor: $viewModel.editableTemplate.color.colorBind,
eventNotes: $viewModel.editableTemplate.notes,
eventDate: $viewModel.editableTemplate.date,
eventRecurrence: $viewModel.editableTemplate.recurrence,
adding: true
) )
} }
} }

View File

@@ -9,12 +9,10 @@ import SwiftUI
import UserNotifications import UserNotifications
import SwiftData import SwiftData
enum Field {
case Search
}
enum Tab { enum Tab {
case home case home
case archive case archive
case symbols
case stats case stats
case settings case settings
} }
@@ -22,10 +20,10 @@ enum Tab {
struct ContentView: View { struct ContentView: View {
@StateObject var viewModel: EventViewModel @StateObject var viewModel: EventViewModel
@StateObject var settingsModel: SettingsViewModel @StateObject var settingsModel: SettingsViewModel
@State var selection: Tab = .home @State var tabSelection: Tab = .home
var body: some View { var body: some View {
TabView(selection: $selection) { TabView(selection: $tabSelection) {
HomeView(viewModel: viewModel, settingsModel: settingsModel) HomeView(viewModel: viewModel, settingsModel: settingsModel)
.tabItem { .tabItem {
Label("Home", systemImage: "house") Label("Home", systemImage: "house")
@@ -36,7 +34,16 @@ 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()
.tabItem { .tabItem {
Label("Statistics", systemImage: "chart.pie") Label("Statistics", systemImage: "chart.pie")
} }
@@ -47,11 +54,7 @@ struct ContentView: View {
} }
.tag(Tab.settings) .tag(Tab.settings)
} }
.apply { .modifier(hapticHeavy(trigger: tabSelection))
if #available(iOS 17, *) {
$0.sensoryFeedback(.impact(weight: .heavy, intensity: 1), trigger: selection)
}
}
.sheet(isPresented: $settingsModel.settings.showWhatsNew) { .sheet(isPresented: $settingsModel.settings.showWhatsNew) {
WhatsNewView(settingsModel: settingsModel) WhatsNewView(settingsModel: settingsModel)
} }

View File

@@ -6,21 +6,13 @@
// //
import SwiftUI import SwiftUI
import SFSymbolsPicker
struct AddEventView: View { struct AddEventView: View {
@ObservedObject var viewModel: EventViewModel @ObservedObject var viewModel: EventViewModel
@Binding var eventName: String @State var event: Event = dummyEventViewModel().template
@Binding var eventComplete: Bool
@Binding var eventCompleteDesc: String
@Binding var eventSymbol: String
@Binding var eventColor: Color
@Binding var eventNotes: String
@Binding var eventDate: Date
@Binding var eventRecurrence: Event.RecurrenceType
@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
@@ -33,13 +25,21 @@ 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 {
backgroundGradient backgroundGradient
} }
NavigationStack { NavigationStack {
Form { List {
Section( Section(
header: header:
Text("Event Details") Text("Event Details")
@@ -51,66 +51,52 @@ struct AddEventView: View {
Button() { Button() {
isSymbolPickerPresented.toggle() isSymbolPickerPresented.toggle()
} label: { } label: {
Image(systemName: eventSymbol) Image(systemName: event.symbol)
.resizable() .resizable()
.scaledToFit() .scaledToFit()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.foregroundStyle(eventColor) .foregroundStyle(event.color.color)
} }
.frame(width: 20) .frame(width: 20)
.buttonStyle(.borderless) .buttonStyle(.borderless)
.sheet(isPresented: $isSymbolPickerPresented) { .sheet(isPresented: $isSymbolPickerPresented) {
SymbolsPicker( SymbolsPicker(
selection: $eventSymbol, selection: $event.symbol
title: "Choose a Symbol", )
searchLabel: "Search...",
autoDismiss: true)
.presentationDetents([.medium]) .presentationDetents([.medium])
.apply { .modifier(presentationSizeForm())
if #available(iOS 16.4, *) {
$0.presentationBackground(.ultraThinMaterial)
}
}
}
ColorPicker("", selection: $eventColor, supportsOpacity: false)
.fixedSize()
ZStack {
TextField("Event Name", text: $eventName)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding(.trailing, eventName.isEmpty ? 0 : 30)
.animation(.spring, value: eventName)
.focused($focusedField, equals: Field.Name)
.submitLabel(.next)
.onSubmit {
focusedField = .Notes
}
// MagicClearButton(text: $eventName)
} }
TextField("Event Name", text: $event.name)
.textFieldStyle(.roundedBorder)
} }
// dscription // dscription
ZStack { ZStack {
TextField("Event Notes", text: $eventNotes) TextField("Event Notes", text: $event.notes)
.textFieldStyle(RoundedBorderTextFieldStyle()) .textFieldStyle(RoundedBorderTextFieldStyle())
.padding(.trailing, eventNotes.isEmpty ? 0 : 30) .padding(.trailing, event.notes.isEmpty ? 0 : 30)
.animation(.spring, value: eventNotes) .animation(.spring, value: event.notes)
.focused($focusedField, equals: Field.Notes) .focused($focusedField, equals: Field.Notes)
.submitLabel(.done) .submitLabel(.done)
.onSubmit { .onSubmit {
focusedField = nil focusedField = nil
} }
// MagicClearButton(text: $eventNotes)
} }
ColorPicker("Event Color", selection: $event.color.colorBind)
// date picker // date picker
HStack { HStack {
Spacer() Spacer()
DatePicker("", selection: $eventDate, displayedComponents: .date) DatePicker("", selection: $event.date, displayedComponents: .date)
// .datePickerStyle(datepickersty) #if os(iOS)
.datePickerStyle(.wheel)
#else
.datePickerStyle(.graphical)
#endif
Spacer() Spacer()
Button() { Button() {
eventDate = Date() event.date = Date()
} label: { } label: {
Image(systemName: "arrow.uturn.left") Image(systemName: "arrow.uturn.left")
.resizable() .resizable()
@@ -122,12 +108,15 @@ struct AddEventView: View {
DatePicker( DatePicker(
"", "",
selection: $eventDate, selection: $event.date,
displayedComponents: .hourAndMinute displayedComponents: .hourAndMinute
) )
#if os(macOS)
.datePickerStyle(.stepperField)
#endif
// re-ocurrence Picker // re-ocurrence Picker
Picker("Recurrence", selection: $eventRecurrence) { Picker("Recurrence", selection: $event.recurrence) {
ForEach(Event.RecurrenceType.allCases, id: \.self) { recurrence in ForEach(Event.RecurrenceType.allCases, id: \.self) { recurrence in
Text(recurrence.rawValue.capitalized) Text(recurrence.rawValue.capitalized)
} }
@@ -135,61 +124,44 @@ struct AddEventView: View {
.pickerStyle(SegmentedPickerStyle()) .pickerStyle(SegmentedPickerStyle())
Text( Text(
describeOccurrence( describeOccurrence(
date: eventDate, date: event.date,
recurrence: eventRecurrence recurrence: event.recurrence
) )
) )
} }
} }
.scrollContentBackground(.hidden)
.navigationTitle("\(adding ? "Add Event" : "")") .navigationTitle("\(adding ? "Add Event" : "")")
// .navigationBarTitleDisplayMode(.inline) .modifier(navigationInlineLarge())
.toolbar { .toolbar {
ToolbarItem(/*placement: .topBarLeading*/) { ToolbarItem(placement: .cancellationAction) {
if adding { if adding {
Button() { Button() {
resetAddEventView() resetAddEventView()
dismiss() dismiss()
} label: { } label: {
Image(systemName: "xmark.circle.fill") Image(systemName: "xmark")
.symbolRenderingMode(.hierarchical)
.resizable() .resizable()
.scaledToFit() .scaledToFit()
.frame(width: 30) .tint(.one)
} }
} }
} }
ToolbarItem/*(placement: .topBarTrailing)*/ { ToolbarItem() {
if adding { if adding {
Button { Button {
viewModel.addEvent( viewModel.addEvent(
newEvent: Event( newEvent: event
name: eventName,
complete: eventComplete,
completeDesc: eventCompleteDesc,
symbol: eventSymbol,
color: ColorCodable(eventColor),
notes: eventNotes,
date: eventDate,
recurrence: eventRecurrence
)
) )
bye.toggle() bye.toggle()
resetAddEventView() resetAddEventView()
} label: { } label: {
Text("Save") Label("Save", systemImage: "checkmark")
.font(.headline)
.cornerRadius(10)
.buttonStyle(BorderedProminentButtonStyle())
} }
.apply { .tint(.accent)
if #available(iOS 17, *) { .modifier(hapticSuccess(trigger: bye))
$0.sensoryFeedback(.success, trigger: bye) .disabled(event.name.isEmpty)
}
}
.disabled(eventName.isEmpty)
.onTapGesture { .onTapGesture {
if eventName.isEmpty { if event.name.isEmpty {
showNeedsNameAlert.toggle() showNeedsNameAlert.toggle()
} }
} }
@@ -201,36 +173,29 @@ struct AddEventView: View {
} message: { } message: {
Text("Give your Event a name before saving.") Text("Give your Event a name before saving.")
} }
if eventName.isEmpty { }
HStack { }
Image(systemName: "exclamationmark") ToolbarItem(placement: .confirmationAction) {
.foregroundStyle(.red) if !adding {
Text("Give your event a name.") Button() {
} viewModel.editEvent(event)
dismiss()
} label: {
Label("Done", systemImage: "checkmark")
} }
.disabled(event.name == "")
} }
} }
} }
.navigationTitle("Editing \(event.name) - Ne")
} }
.scrollContentBackground(isMac ? .automatic : .hidden)
.presentationDragIndicator(.visible) .presentationDragIndicator(.visible)
.scrollContentBackground(.hidden)
}
.apply {
if #available(iOS 16.4, *) {
$0.presentationBackground(.ultraThinMaterial)
}
} }
} }
func resetAddEventView() { func resetAddEventView() {
//reset addeventView //reset addeventView
eventName = viewModel.template.name event = viewModel.template
eventComplete = viewModel.template.complete
eventCompleteDesc = viewModel.template.completeDesc
eventSymbol = viewModel.template.symbol
eventColor = randomColor()
eventNotes = viewModel.template.notes
eventDate = viewModel.template.date
eventRecurrence = viewModel.template.recurrence
dismiss() dismiss()
} }
@@ -243,14 +208,6 @@ struct AddEventView: View {
.sheet(isPresented: .constant(true)) { .sheet(isPresented: .constant(true)) {
AddEventView( AddEventView(
viewModel: vm, viewModel: vm,
eventName: .constant(vm.template.notes),
eventComplete: .constant(vm.template.complete),
eventCompleteDesc: .constant(vm.template.completeDesc),
eventSymbol: .constant(vm.template.symbol),
eventColor: .constant(vm.template.color.color),
eventNotes: .constant(vm.template.notes),
eventDate: .constant(vm.template.date),
eventRecurrence: .constant(vm.template.recurrence),
adding: true adding: true
) )
} }

View File

@@ -12,44 +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,
eventName: $event.name, event: event,
eventComplete: $event.complete,
eventCompleteDesc: $event.completeDesc,
eventSymbol: $event.symbol,
eventColor: $event.color.colorBind,
eventNotes: $event.notes,
eventDate: $event.date,
eventRecurrence: $event.recurrence,
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 == "")
}
}
} }
} }

View File

@@ -12,142 +12,185 @@ 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)
viewModel: viewModel, HStack {
event: $event RoundedRectangle(cornerRadius: 5)
) .frame(width: 7)
} label: { .foregroundStyle(
ZStack { event.color.color.opacity(
HStack { event.complete ? 0.5 : 1
RoundedRectangle(cornerRadius: 5)
.frame(width: 7)
.foregroundStyle(
event.color.color.opacity(
event.complete ? 0.5 : 1
)
) )
VStack(alignment: .leading) { )
HStack { VStack(alignment: .leading) {
Image(systemName: event.symbol) HStack {
.resizable() Image(systemName: event.symbol)
.scaledToFit() .resizable()
.frame(width: 20, height: 20) .scaledToFit()
.shadow(radius: 5) .frame(width: 20, height: 20)
.foregroundStyle( .shadow(radius: 5)
.one.opacity( .foregroundStyle(
event.complete ? 0.5 : 1 .one.opacity(
) event.complete ? 0.5 : 1
) )
Text("\(event.name)")
.font(.headline)
.foregroundStyle(.one)
.strikethrough(event.complete)
.multilineTextAlignment(.leading)
}
if !event.notes.isEmpty {
Text(event.notes)
.font(.subheadline)
.foregroundStyle(.one.opacity(0.8))
.multilineTextAlignment(.leading)
}
Text(
event.date.formatted(
date: .long,
time: .shortened
) )
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
) )
.font(.subheadline) )
.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: {
Label("Delete", systemImage: "trash")
}
}
}
#else
var body: some View {
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( .foregroundStyle(
.one.opacity( .one.opacity(
event.complete ? 0.5 : 1 event.complete ? 0.5 : 1
) )
) )
if event.recurrence != .none { Text("\(event.name)")
Text("Occurs \(event.recurrence.rawValue)") .font(.headline)
.font(.subheadline) .foregroundStyle(.one)
.foregroundStyle( .strikethrough(event.complete)
.one.opacity(event.complete ? 0.5 : 1)) .multilineTextAlignment(.leading)
}
}
Spacer()
VStack {
Text("\(daysUntilEvent(event.date).long)")
.font(.subheadline)
.foregroundStyle(event.date.timeIntervalSinceNow < 0 ? .red : .one)
.multilineTextAlignment(.trailing)
}
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)
}
}
.buttonStyle(.borderless)
.frame(maxWidth: 25, maxHeight: 25)
.shadow(radius: 5)
.padding(.trailing, 5)
.apply {
if #available(iOS 17, *) {
$0.sensoryFeedback(.success, trigger: event.complete)
}
}
} }
.transition(.opacity) if !event.notes.isEmpty {
.padding(.vertical, 5) Text(event.notes)
.background(.ultraThinMaterial) .font(.subheadline)
.overlay( .foregroundStyle(.one.opacity(0.8))
RoundedRectangle(cornerRadius: 10) .multilineTextAlignment(.leading)
.stroke( }
.one.opacity(0.5), Text(
lineWidth: 1 event.date.formatted(
) date: .long,
time: .shortened
)
) )
.clipShape( .font(.subheadline)
RoundedRectangle(cornerRadius: 10) .foregroundStyle(
.one.opacity(
event.complete ? 0.5 : 1
)
) )
.fixedSize(horizontal: false, vertical: true) if event.recurrence != .none {
Text("Occurs \(event.recurrence.rawValue)")
.font(.subheadline)
.foregroundStyle(
.one.opacity(event.complete ? 0.5 : 1))
}
} }
.contextMenu() { Spacer()
Button(role: .destructive) { VStack {
let eventToModify = viewModel.events.firstIndex() { currEvent in Text("\(daysUntilEvent(event.date).long)")
currEvent.id == event.id .font(.subheadline)
} .foregroundStyle(event.date.timeIntervalSinceNow < 0 ? .red : .one)
if let eventToModify = eventToModify { .multilineTextAlignment(.trailing)
viewModel.events.remove(at: eventToModify) }
viewModel.saveEvents() CompleteEventButton(
} viewModel: viewModel,
} label: { event: $event
Label("Delete", systemImage: "trash") )
}
.padding(.vertical, 5)
.overlay(
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
currEvent.id == event.id
} }
if let eventToModify = eventToModify {
viewModel.events.remove(at: eventToModify)
viewModel.saveEvents()
}
} label: {
Label("Delete", systemImage: "trash")
} }
} }
} }
#endif
} }
#Preview("EventListView") { #Preview("EventListView") {

View File

@@ -13,6 +13,10 @@ enum HelpType {
case Archive case Archive
} }
enum Field {
case Search
}
struct HelpView: View { struct HelpView: View {
/// initialises a Search HelpView /// initialises a Search HelpView
/// ///
@@ -74,7 +78,7 @@ struct HelpView: View {
var body: some View { var body: some View {
List { List {
ZStack { ZStack {
Color(.tintColor) Color(.accent)
.opacity(0.4) .opacity(0.4)
.padding(.horizontal, -15) .padding(.horizontal, -15)
.blur(radius: 5) .blur(radius: 5)

View File

@@ -5,19 +5,13 @@
// Created by neon443 on 12/05/2025. // Created by neon443 on 12/05/2025.
// //
import SwiftUI;import AppIntents import SwiftUI
import AppIntents
struct HomeView: View { struct HomeView: View {
@ObservedObject var viewModel: EventViewModel @ObservedObject var viewModel: EventViewModel
@ObservedObject var settingsModel: SettingsViewModel @ObservedObject var settingsModel: SettingsViewModel
@State private var eventName: String = ""
@State private var eventComplete: Bool = false
@State private var eventCompleteDesc: String = ""
@State private var eventSymbol: String = "star"
@State private var eventColor: Color = randomColor()
@State private var eventNotes: String = ""
@State private var eventDate = Date()
@State private var eventRecurrence: Event.RecurrenceType = .none
@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
@@ -47,36 +41,30 @@ struct HomeView: View {
ZStack { ZStack {
backgroundGradient backgroundGradient
VStack { VStack {
ZStack {
TextField(
"\(Image(systemName: "magnifyingglass")) Search",
text: $searchInput
)
.padding(.trailing, searchInput.isEmpty ? 0 : 30)
.animation(.spring, value: searchInput)
.textFieldStyle(RoundedBorderTextFieldStyle())
.submitLabel(.done)
.focused($focusedField, equals: Field.Search)
.onSubmit {
focusedField = nil
}
MagicClearButton(text: $searchInput)
.onTapGesture {
focusedField = nil
}
}
.padding(.horizontal)
if filteredEvents.isEmpty && !searchInput.isEmpty { if filteredEvents.isEmpty && !searchInput.isEmpty {
HelpView(searchInput: $searchInput, focusedField: focusedField) HelpView(searchInput: $searchInput, focusedField: focusedField)
} else { } else {
ScrollView { ScrollView {
ForEach(filteredEvents) { event in // LazyVStack {
EventListView(viewModel: viewModel, event: event) ForEach(filteredEvents) { event in
NavigationLink() {
EditEventView(
viewModel: viewModel,
event: Binding(
get: { event },
set: { newValue in
viewModel.editEvent(newValue)
}
)
)
} label: {
EventListView(viewModel: viewModel, event: event)
.id(event.complete)
}
.transition(.moveAndFade) .transition(.moveAndFade)
.id(event.complete) }
} .padding(.horizontal)
.padding(.horizontal) // }
if filteredEvents.isEmpty { if filteredEvents.isEmpty {
HelpView( HelpView(
searchInput: $searchInput, searchInput: $searchInput,
@@ -88,30 +76,16 @@ struct HomeView: View {
.animation(.default, value: filteredEvents) .animation(.default, value: filteredEvents)
} }
} }
.searchable(text: $searchInput)
.navigationTitle("Near Future") .navigationTitle("Near Future")
.apply { .modifier(navigationInlineLarge())
if #available(iOS 17, *) {
$0.toolbarTitleDisplayMode(.inlineLarge)
} else {
$0.navigationBarTitleDisplayMode(.inline)
}
}
.sheet(isPresented: $showingAddEventView) { .sheet(isPresented: $showingAddEventView) {
AddEventView( AddEventView(
viewModel: viewModel, viewModel: viewModel
eventName: $eventName,
eventComplete: $eventComplete,
eventCompleteDesc: $eventCompleteDesc,
eventSymbol: $eventSymbol,
eventColor: $eventColor,
eventNotes: $eventNotes,
eventDate: $eventDate,
eventRecurrence: $eventRecurrence,
adding: true //adding event
) )
} }
.toolbar { .toolbar {
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .primaryAction) {
AddEventButton(showingAddEventView: $showingAddEventView) AddEventButton(showingAddEventView: $showingAddEventView)
} }
} }

View File

@@ -34,17 +34,12 @@ struct AddEventButton: View {
Button() { Button() {
showingAddEventView.toggle() showingAddEventView.toggle()
} label: { } label: {
ZStack { Image(systemName: "plus")
Circle() .resizable()
.frame(width: 33) .scaledToFit()
.foregroundStyle(.one) .frame(width: 15)
Image(systemName: "plus") .bold()
.resizable() .foregroundStyle(.one)
.scaledToFit()
.frame(width: 15)
.bold()
.foregroundStyle(.two)
}
} }
} }
} }

View File

@@ -12,7 +12,11 @@ struct ExportView: View {
var body: some View { var body: some View {
List { List {
Button() { Button() {
#if canImport(UIKit)
UIPasteboard.general.string = viewModel.exportEvents() UIPasteboard.general.string = viewModel.exportEvents()
#else
NSPasteboard.general.setString(viewModel.exportEvents(), forType: .string)
#endif
} label: { } label: {
Label("Copy Events", systemImage: "document.on.clipboard") Label("Copy Events", systemImage: "document.on.clipboard")
} }

View File

@@ -75,7 +75,6 @@ struct ImportView: View {
.ignoresSafeArea() .ignoresSafeArea()
ZStack { ZStack {
Rectangle() Rectangle()
.background(.ultraThinMaterial)
.clipShape(RoundedRectangle(cornerRadius: 25)) .clipShape(RoundedRectangle(cornerRadius: 25))
VStack(alignment: .center) { VStack(alignment: .center) {
Text("Are you sure?") Text("Are you sure?")

View File

@@ -10,48 +10,21 @@ import SwiftUI
struct SettingsView: View { struct SettingsView: View {
@ObservedObject var viewModel: EventViewModel @ObservedObject var viewModel: EventViewModel
@ObservedObject var settingsModel: SettingsViewModel @ObservedObject var settingsModel: SettingsViewModel
@State private var hasUbiquitous: Bool = false
@State private var lastSyncWasSuccessful: Bool = false
@State private var lastSyncWasNormalAgo: Bool = false
@State private var localCountEqualToiCloud: Bool = false
@State private var icloudCountEqualToLocal: Bool = false
@State private var importStr: String = "" @State private var importStr: String = ""
func updateStatus() { func changeIcon(to toIcon: String) {
let vm = viewModel
hasUbiquitous = vm.hasUbiquitousKeyValueStore()
lastSyncWasSuccessful = vm.syncStatus.contains("Success")
lastSyncWasNormalAgo = vm.lastSync?.timeIntervalSinceNow.isNormal ?? false
localCountEqualToiCloud = vm.localEventCount == vm.icloudEventCount
icloudCountEqualToLocal = vm.icloudEventCount == vm.localEventCount
}
var iCloudStatusColor: Color {
let allTrue = hasUbiquitous && lastSyncWasSuccessful && lastSyncWasNormalAgo && localCountEqualToiCloud && icloudCountEqualToLocal
let someTrue = hasUbiquitous || lastSyncWasSuccessful || lastSyncWasNormalAgo || localCountEqualToiCloud || icloudCountEqualToLocal
if allTrue {
return .green
} else if someTrue {
return .orange
} else {
return .red
}
}
func changeIcon(to: String) {
guard UIApplication.shared.supportsAlternateIcons else { guard UIApplication.shared.supportsAlternateIcons else {
print("doesnt tsupport alternate icons") print("doesnt tsupport alternate icons")
return return
} }
guard to != "orange" else { guard toIcon != "orange" else {
UIApplication.shared.setAlternateIconName(nil) { error in UIApplication.shared.setAlternateIconName(nil) { error in
print(error as Any) print(error as Any)
} }
return return
} }
UIApplication.shared.setAlternateIconName(to) { error in UIApplication.shared.setAlternateIconName(toIcon) { error in
print(error as Any) print(error as Any)
} }
} }
@@ -74,6 +47,7 @@ struct SettingsView: View {
.foregroundStyle(color) .foregroundStyle(color)
.frame(width: 30) .frame(width: 30)
} }
.buttonStyle(.plain)
if ColorCodable(color) == settingsModel.settings.tint { if ColorCodable(color) == settingsModel.settings.tint {
let needContrast: Bool = ColorCodable(color) == settingsModel.settings.tint let needContrast: Bool = ColorCodable(color) == settingsModel.settings.tint
Circle() Circle()
@@ -94,6 +68,8 @@ struct SettingsView: View {
NavigationLink() { NavigationLink() {
List { List {
if !settingsModel.notifsGranted { if !settingsModel.notifsGranted {
Text("\(Image(systemName: "xmark")) Notifications disabled for Near Future")
.foregroundStyle(.red)
Button("Request Notifications") { Button("Request Notifications") {
Task.detached { Task.detached {
let requestNotifsResult = await requestNotifs() let requestNotifsResult = await requestNotifs()
@@ -102,8 +78,6 @@ struct SettingsView: View {
} }
} }
} }
Text("\(Image(systemName: "xmark")) Notifications disabled for Near Future")
.foregroundStyle(.red)
} else { } else {
Text("\(Image(systemName: "checkmark")) Notifications enabled for Near Future") Text("\(Image(systemName: "checkmark")) Notifications enabled for Near Future")
.foregroundStyle(.green) .foregroundStyle(.green)
@@ -116,13 +90,7 @@ struct SettingsView: View {
NavigationLink() { NavigationLink() {
iCloudSettingsView( iCloudSettingsView(
viewModel: viewModel, viewModel: viewModel,
settingsModel: settingsModel, settingsModel: settingsModel
hasUbiquitous: $hasUbiquitous,
lastSyncWasSuccessful: $lastSyncWasSuccessful,
lastSyncWasNormalAgo: $lastSyncWasNormalAgo,
localCountEqualToiCloud: $localCountEqualToiCloud,
icloudCountEqualToLocal: $icloudCountEqualToLocal,
updateStatus: updateStatus
) )
} label: { } label: {
HStack { HStack {
@@ -131,12 +99,12 @@ struct SettingsView: View {
Spacer() Spacer()
Circle() Circle()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.foregroundStyle(iCloudStatusColor) .foregroundStyle(viewModel.iCloudStatusColor)
} }
} }
.onAppear { .onAppear {
viewModel.sync() viewModel.sync()
updateStatus() viewModel.updateiCStatus()
} }
NavigationLink() { NavigationLink() {
ImportView(viewModel: viewModel, importStr: $importStr) ImportView(viewModel: viewModel, importStr: $importStr)
@@ -180,15 +148,9 @@ struct SettingsView: View {
} }
} }
} }
.scrollContentBackground(.hidden)
.navigationTitle("Settings") .navigationTitle("Settings")
.apply { .modifier(navigationInlineLarge())
if #available(iOS 17, *) { .scrollContentBackground(.hidden)
$0.toolbarTitleDisplayMode(.inlineLarge)
} else {
$0.navigationBarTitleDisplayMode(.inline)
}
}
} }
} }
} }

View File

@@ -19,6 +19,11 @@ struct WhatsNewView: View {
@State var bye: Bool = false @State var bye: Bool = false
var whatsNewChunks: [WhatsNewChunk] { var whatsNewChunks: [WhatsNewChunk] {
return [ return [
WhatsNewChunk(
symbol: "desktopcomputer",
title: "Mac Native App",
subtitle: "New Mac native app (Intel too!)"
),
WhatsNewChunk( WhatsNewChunk(
symbol: "iphone.radiowaves.left.and.right", symbol: "iphone.radiowaves.left.and.right",
title: "Haptic Feedback", title: "Haptic Feedback",
@@ -53,23 +58,22 @@ struct WhatsNewView: View {
} }
var body: some View { var body: some View {
NavigationStack { NavigationStack {
List { ScrollView {
VStack { Text("What's New")
Text("What's New") .font(.largeTitle)
.font(.largeTitle) .bold()
.bold() .padding(.vertical)
AboutView() VStack(alignment: .leading) {
Divider() ForEach(whatsNewChunks) { new in
VStack(alignment: .leading) { WhatsNewChunkView(
ForEach(whatsNewChunks) { new in symbol: new.symbol,
WhatsNewChunkView( title: new.title,
symbol: new.symbol, subtitle: new.subtitle
title: new.title, )
subtitle: new.subtitle
)
}
} }
Spacer()
} }
.padding(.horizontal, 10)
} }
Button() { Button() {
bye.toggle() bye.toggle()
@@ -79,24 +83,14 @@ struct WhatsNewView: View {
.font(.headline) .font(.headline)
.frame(height: 40) .frame(height: 40)
.bold() .bold()
.frame(maxWidth: .infinity) // .frame(maxWidth: .infinity)
}
.buttonStyle(BorderedProminentButtonStyle())
.clipShape(RoundedRectangle(cornerRadius: 15))
.padding().padding()
.apply {
if #available(iOS 17, *) {
$0.sensoryFeedback(.impact(weight: .heavy, intensity: 1), trigger: bye)
}
} }
.foregroundStyle(.orange)
.modifier(glassButton())
.modifier(hapticHeavy(trigger: bye))
} }
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.presentationDragIndicator(.visible) .presentationDragIndicator(.visible)
.apply {
if #available(iOS 16.4, *) {
$0.presentationBackground(.ultraThinMaterial)
}
}
.onDisappear { .onDisappear {
settingsModel.settings.prevAppVersion = getVersion()+getBuildID() settingsModel.settings.prevAppVersion = getVersion()+getBuildID()
settingsModel.saveSettings() settingsModel.saveSettings()
@@ -122,7 +116,7 @@ struct WhatsNewChunkView: View {
.resizable() .resizable()
.scaledToFit() .scaledToFit()
.frame(width: 30, height: 30) .frame(width: 30, height: 30)
.foregroundStyle(Color.accentColor) .foregroundStyle(Color.orange)
.padding(.trailing, 15) .padding(.trailing, 15)
VStack(alignment: .leading) { VStack(alignment: .leading) {
Text(title) Text(title)

View File

@@ -13,13 +13,13 @@ struct iCloudSettingsView: View {
@State var showPushAlert: Bool = false @State var showPushAlert: Bool = false
@State var showPullAlert: Bool = false @State var showPullAlert: Bool = false
@Binding var hasUbiquitous: Bool // @Binding var hasUbiquitous: Bool
@Binding var lastSyncWasSuccessful: Bool // @Binding var lastSyncWasSuccessful: Bool
@Binding var lastSyncWasNormalAgo: Bool // @Binding var lastSyncWasNormalAgo: Bool
@Binding var localCountEqualToiCloud: Bool // @Binding var localCountEqualToiCloud: Bool
@Binding var icloudCountEqualToLocal: Bool // @Binding var icloudCountEqualToLocal: Bool
//
var updateStatus: () -> Void // var updateStatus: () -> Void
var body: some View { var body: some View {
ZStack { ZStack {
@@ -55,7 +55,7 @@ struct iCloudSettingsView: View {
Button("OK", role: .destructive) { Button("OK", role: .destructive) {
viewModel.replaceiCloudWithLocalData() viewModel.replaceiCloudWithLocalData()
viewModel.sync() viewModel.sync()
updateStatus() viewModel.updateiCStatus()
} }
Button("Cancel", role: .cancel) {} Button("Cancel", role: .cancel) {}
} message: { } message: {
@@ -64,7 +64,7 @@ struct iCloudSettingsView: View {
Button() { Button() {
viewModel.sync() viewModel.sync()
updateStatus() viewModel.updateiCStatus()
} label: { } label: {
Image(systemName: "arrow.triangle.2.circlepath") Image(systemName: "arrow.triangle.2.circlepath")
.resizable() .resizable()
@@ -87,7 +87,7 @@ struct iCloudSettingsView: View {
Button("OK", role: .destructive) { Button("OK", role: .destructive) {
viewModel.replaceLocalWithiCloudData() viewModel.replaceLocalWithiCloudData()
viewModel.sync() viewModel.sync()
updateStatus() viewModel.updateiCStatus()
} }
Button("Cancel", role: .cancel) {} Button("Cancel", role: .cancel) {}
} message: { } message: {
@@ -112,23 +112,23 @@ struct iCloudSettingsView: View {
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
.onAppear { .onAppear {
viewModel.sync() viewModel.sync()
updateStatus() viewModel.updateiCStatus()
} }
HStack { HStack {
Circle() Circle()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.foregroundStyle(hasUbiquitous ? .green : .red) .foregroundStyle(viewModel.hasUbiquitous ? .green : .red)
Text("iCloud") Text("iCloud")
Spacer() Spacer()
Text("\(hasUbiquitous ? "" : "Not ")Working") Text("\(viewModel.hasUbiquitous ? "" : "Not ")Working")
.bold() .bold()
} }
HStack { HStack {
Circle() Circle()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.foregroundStyle(lastSyncWasSuccessful ? .green : .red) .foregroundStyle(viewModel.lastSyncWasSuccessful ? .green : .red)
Text("Sync Status") Text("Sync Status")
Spacer() Spacer()
Text("\(viewModel.syncStatus)") Text("\(viewModel.syncStatus)")
@@ -138,7 +138,7 @@ struct iCloudSettingsView: View {
HStack { HStack {
Circle() Circle()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.foregroundStyle(lastSyncWasNormalAgo ? .green : .red) .foregroundStyle(viewModel.lastSyncWasNormalAgo ? .green : .red)
Text("Last Sync") Text("Last Sync")
Spacer() Spacer()
Text("\(viewModel.lastSync?.formatted(date: .long, time: .standard) ?? "Never")") Text("\(viewModel.lastSync?.formatted(date: .long, time: .standard) ?? "Never")")
@@ -148,7 +148,7 @@ struct iCloudSettingsView: View {
HStack { HStack {
Circle() Circle()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.foregroundStyle(localCountEqualToiCloud ? .green : .red) .foregroundStyle(viewModel.localCountEqualToiCloud ? .green : .red)
Text("Local Events") Text("Local Events")
Spacer() Spacer()
Text("\(viewModel.localEventCount)") Text("\(viewModel.localEventCount)")
@@ -158,7 +158,7 @@ struct iCloudSettingsView: View {
HStack { HStack {
Circle() Circle()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.foregroundStyle(icloudCountEqualToLocal ? .green : .red) .foregroundStyle(viewModel.icloudCountEqualToLocal ? .green : .red)
Text("Events in iCloud") Text("Events in iCloud")
Spacer() Spacer()
Text("\(viewModel.icloudEventCount)") Text("\(viewModel.icloudEventCount)")
@@ -172,11 +172,10 @@ struct iCloudSettingsView: View {
} }
.refreshable { .refreshable {
viewModel.sync() viewModel.sync()
updateStatus() viewModel.updateiCStatus()
} }
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.navigationTitle("iCloud") .navigationTitle("iCloud")
.navigationBarTitleDisplayMode(.inline)
} }
} }
} }
@@ -184,12 +183,6 @@ struct iCloudSettingsView: View {
#Preview("iCloudSettingsView") { #Preview("iCloudSettingsView") {
iCloudSettingsView( iCloudSettingsView(
viewModel: dummyEventViewModel(), viewModel: dummyEventViewModel(),
settingsModel: dummySettingsViewModel(), settingsModel: dummySettingsViewModel()
hasUbiquitous: .constant(true),
lastSyncWasSuccessful: .constant(true),
lastSyncWasNormalAgo: .constant(true),
localCountEqualToiCloud: .constant(true),
icloudCountEqualToLocal: .constant(true),
updateStatus: {}
) )
} }

View File

@@ -58,13 +58,7 @@ struct StatsView: View {
} }
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.navigationTitle("Statistics") .navigationTitle("Statistics")
.apply { .modifier(navigationInlineLarge())
if #available(iOS 17, *) {
$0.toolbarTitleDisplayMode(.inlineLarge)
} else {
$0.navigationBarTitleDisplayMode(.inline)
}
}
} }
} }
} }

View File

@@ -11,17 +11,18 @@
- [x] Event colors - [x] Event colors
- [x] Recurrence - [x] Recurrence
- [x] Search - [x] Search
- [ ] Notifications - [x] Notifications
- [ ] Mac App
- [ ] Apple Watch App - [ ] Apple Watch App
- [x] Home Screen Widgets - [x] Home Screen Widgets
- [ ] Lock Screen Widgets - [ ] Lock Screen Widgets
- [ ] Later Box - [ ] Later Box
- [ ] Sort by - [ ] Sort by
- [ ] Reorder Events - [ ] Reorder Events
- [ ] Archive - [x] Archive
- [ ] Collaboration - [ ] Collaboration
- [ ] Autocomplete tasks - [ ] Autocomplete tasks
- [ ] Settings - [x] Settings
## Features ## Features
- **Event Creation**: Create events with a name, description, date, recurrence, and an icon - **Event Creation**: Create events with a name, description, date, recurrence, and an icon
@@ -53,4 +54,4 @@ Contributions are welcome! Just follow these steps:
## Used Tools/Frameworks ## Used Tools/Frameworks
- Swift & SwiftUI by Apple - Swift & SwiftUI by Apple
- **SFSymbolsPicker** by [alessiorubicini/SFSymbolsPickerForSwiftUI]. - **SFSymbolsPicker** by [alessiorubicini/SFSymbolsPickerForSwiftUI].

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

View File

@@ -0,0 +1,50 @@
{
"fill" : {
"linear-gradient" : [
"display-p3:0.12307,0.73184,0.43944,1.00000",
"display-p3:0.12142,0.28022,0.58006,1.00000"
]
},
"groups" : [
{
"layers" : [
{
"fill" : {
"automatic-gradient" : "display-p3:1.00000,0.30347,0.00000,1.00000"
},
"image-name" : "Rounded Rectangle.png",
"name" : "Rounded Rectangle",
"position" : {
"scale" : 1,
"translation-in-points" : [
-466.2265625,
37.9921875
]
}
},
{
"fill" : {
"automatic-gradient" : "display-p3:0.96611,0.36599,0.00000,1.00000"
},
"hidden" : false,
"image-name" : "Oval copy.png",
"name" : "Oval copy"
}
],
"shadow" : {
"kind" : "neutral",
"opacity" : 0.5
},
"translucency" : {
"enabled" : true,
"value" : 0.5
}
}
],
"supported-platforms" : {
"circles" : [
"watchOS"
],
"squares" : "shared"
}
}

View 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))
}

View File

@@ -17,8 +17,6 @@ extension View {
) )
.ignoresSafeArea(.all) .ignoresSafeArea(.all)
} }
func apply<V: View>(@ViewBuilder _ block: (Self) -> V) -> V { block(self) }
} }
extension AnyTransition { extension AnyTransition {

View File

@@ -0,0 +1,47 @@
//
// AccentIcon.swift
// NearFuture
//
// Created by neon443 on 13/06/2025.
//
import Foundation
import SwiftUI
#if canImport(AppKit)
import AppKit
#endif
class AccentIcon {
#if canImport(UIKit)
var icon: UIImage
#elseif canImport(AppKit)
var icon: NSImage
#endif
var color: Color
var name: String
init(_ colorName: String) {
#if canImport(UIKit)
self.icon = UIImage(named: "AppIcon")!
self.color = Color(uiColor: UIColor(named: "uiColors/\(colorName)")!)
#elseif canImport(AppKit)
self.icon = NSImage(imageLiteralResourceName: "AppIcon")
self.color = Color(nsColor: NSColor(named: "uiColors/\(colorName)")!)
#endif
self.name = colorName
if colorName != "orange" {
setSelfIcon(to: colorName)
}
}
func setSelfIcon(to name: String) {
#if canImport(UIKit)
self.icon = UIImage(named: name)!
#elseif canImport(AppKit)
self.icon = NSImage(imageLiteralResourceName: name)
#endif
}
}

View File

@@ -0,0 +1,83 @@
//
// ColorCodable.swift
// NearFuture
//
// Created by neon443 on 13/06/2025.
//
import Foundation
import SwiftUI
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
struct ColorCodable: Codable, Equatable, Hashable {
init(_ color: Color) {
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 1
#if canImport(UIKit)
let uiColor = UIColor(color)
uiColor.getRed(&r, green: &g, blue: &b, alpha: &a)
#elseif canImport(AppKit)
let nscolor = NSColor(color).usingColorSpace(.deviceRGB)
nscolor!.getRed(&r, green: &g, blue: &b, alpha: &a)
#endif
self = ColorCodable(
red: r,
green: g,
blue: b
)
}
#if canImport(UIKit)
init(uiColor: UIColor) {
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 1.0
uiColor.getRed(&r, green: &g, blue: &b, alpha: &a)
self = ColorCodable(
red: r,
green: g,
blue: b
)
}
#elseif canImport(AppKit)
init(nsColor: NSColor) {
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 1.0
let nsColor = nsColor.usingColorSpace(.deviceRGB)
nsColor!.getRed(&r, green: &g, blue: &b, alpha: &a)
self = ColorCodable(
red: r,
green: g,
blue: b
)
}
#endif
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
var red: Double
var green: Double
var blue: Double
var color: Color {
Color(red: red, green: green, blue: blue)
}
var colorBind: Color {
get {
return Color(
red: red,
green: green,
blue: blue
)
} set {
let cc = ColorCodable(newValue)
self.red = cc.red
self.green = cc.green
self.blue = cc.blue
}
}
}

View File

@@ -11,10 +11,8 @@ import SwiftUI
import WidgetKit import WidgetKit
import UserNotifications import UserNotifications
import AppIntents import AppIntents
import AudioToolbox
#if canImport(AppKit) #if canImport(AppKit)
import AppKit import AppKit
import IOKit
#endif #endif
//@Model //@Model
@@ -26,7 +24,7 @@ import IOKit
// } // }
//} //}
struct Event: Identifiable, Codable, Equatable, Animatable { struct Event: Identifiable, Codable, Equatable, Animatable, Hashable {
var id = UUID() var id = UUID()
var name: String var name: String
var complete: Bool var complete: Bool
@@ -42,75 +40,6 @@ struct Event: Identifiable, Codable, Equatable, Animatable {
} }
} }
struct ColorCodable: Codable, Equatable {
init(_ color: Color) {
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 1
#if canImport(UIKit)
let uiColor = UIColor(color)
uiColor.getRed(&r, green: &g, blue: &b, alpha: &a)
#elseif canImport(AppKit)
let nscolor = NSColor(color).usingColorSpace(.deviceRGB)
nscolor!.getRed(&r, green: &g, blue: &b, alpha: &a)
#endif
self = ColorCodable(
red: r,
green: g,
blue: b
)
}
#if canImport(UIKit)
init(uiColor: UIColor) {
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 1.0
uiColor.getRed(&r, green: &g, blue: &b, alpha: &a)
self = ColorCodable(
red: r,
green: g,
blue: b
)
}
#elseif canImport(AppKit)
init(nsColor: NSColor) {
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 1.0
let nsColor = nsColor.usingColorSpace(.deviceRGB)
nsColor!.getRed(&r, green: &g, blue: &b, alpha: &a)
self = ColorCodable(
red: r,
green: g,
blue: b
)
}
#endif
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
var red: Double
var green: Double
var blue: Double
var color: Color {
Color(red: red, green: green, blue: blue)
}
var colorBind: Color {
get {
return Color(
red: red,
green: green,
blue: blue
)
} set {
let cc = ColorCodable(newValue)
self.red = cc.red
self.green = cc.green
self.blue = cc.blue
}
}
}
func daysUntilEvent(_ eventDate: Date) -> (long: String, short: String) { func daysUntilEvent(_ eventDate: Date) -> (long: String, short: String) {
let calendar = Calendar.current let calendar = Calendar.current
let startOfDayNow = calendar.startOfDay(for: Date()) let startOfDayNow = calendar.startOfDay(for: Date())
@@ -164,6 +93,25 @@ class EventViewModel: ObservableObject, @unchecked Sendable {
@Published var localEventCount: Int = 0 @Published var localEventCount: Int = 0
@Published var syncStatus: String = "Not Synced" @Published var syncStatus: String = "Not Synced"
@Published var hasUbiquitous: Bool = false
@Published var lastSyncWasSuccessful: Bool = false
@Published var lastSyncWasNormalAgo: Bool = false
@Published var localCountEqualToiCloud: Bool = false
@Published var icloudCountEqualToLocal: Bool = false
var iCloudStatusColor: Color {
let allTrue = hasUbiquitous && lastSyncWasSuccessful && lastSyncWasNormalAgo && localCountEqualToiCloud && icloudCountEqualToLocal
let someTrue = hasUbiquitous || lastSyncWasSuccessful || lastSyncWasNormalAgo || localCountEqualToiCloud || icloudCountEqualToLocal
if allTrue {
return .green
} else if someTrue {
return .orange
} else {
return .red
}
}
init(load: Bool = true) { init(load: Bool = true) {
self.editableTemplate = template self.editableTemplate = template
if load { if load {
@@ -274,6 +222,24 @@ class EventViewModel: ObservableObject, @unchecked Sendable {
saveEvents() //sync with icloud 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) { func removeEvent(at index: IndexSet) {
events.remove(atOffsets: index) events.remove(atOffsets: index)
saveEvents() //sync local and icl saveEvents() //sync local and icl
@@ -343,6 +309,14 @@ class EventViewModel: ObservableObject, @unchecked Sendable {
} }
} }
func updateiCStatus() {
hasUbiquitous = hasUbiquitousKeyValueStore()
lastSyncWasSuccessful = syncStatus.contains("Success")
lastSyncWasNormalAgo = lastSync?.timeIntervalSinceNow.isNormal ?? false
localCountEqualToiCloud = localEventCount == icloudEventCount
icloudCountEqualToLocal = icloudEventCount == localEventCount
}
//MARK: Danger Zone //MARK: Danger Zone
func dangerClearLocalData() { func dangerClearLocalData() {
UserDefaults.standard.removeObject(forKey: "events") UserDefaults.standard.removeObject(forKey: "events")

View File

@@ -18,40 +18,6 @@ struct NFSettings: Codable, Equatable {
var prevAppVersion: String = getVersion()+getBuildID() var prevAppVersion: String = getVersion()+getBuildID()
} }
class AccentIcon {
#if canImport(UIKit)
var icon: UIImage
#elseif canImport(AppKit)
var icon: NSImage
#endif
var color: Color
var name: String
init(_ colorName: String) {
#if canImport(UIKit)
self.icon = UIImage(named: "AppIcon")!
self.color = Color(uiColor: UIColor(named: "uiColors/\(colorName)")!)
#elseif canImport(AppKit)
self.icon = NSImage(imageLiteralResourceName: "AppIcon")
self.color = Color(nsColor: NSColor(named: "uiColors/\(colorName)")!)
#endif
self.name = colorName
if colorName != "orange" {
setSelfIcon(to: colorName)
}
}
func setSelfIcon(to name: String) {
#if canImport(UIKit)
self.icon = UIImage(named: name)!
#elseif canImport(AppKit)
self.icon = NSImage(imageLiteralResourceName: name)
#endif
}
}
class SettingsViewModel: ObservableObject { class SettingsViewModel: ObservableObject {
@Published var settings: NFSettings = NFSettings() @Published var settings: NFSettings = NFSettings()

View File

@@ -0,0 +1,36 @@
//
// SymbolsLoader.swift
// NearFuture
//
// Created by neon443 on 14/06/2025.
//
import Foundation
class SymbolsLoader: ObservableObject {
@Published var allSymbols: [String] = []
init() {
self.allSymbols = getAllSymbols()
}
func getSymbols(_ searched: String) -> [String] {
if searched.isEmpty {
return allSymbols
} else {
return allSymbols.filter() { $0.localizedCaseInsensitiveContains(searched) }
}
}
func getAllSymbols() -> [String] {
var allSymbols = [String]()
if let bundle = Bundle(identifier: "com.apple.CoreGlyphs"),
let resPath = bundle.path(forResource: "name_availability", ofType: "plist"),
let plist = try? NSDictionary(contentsOf: URL(fileURLWithPath: resPath), error: ()),
let plistSymbols = plist["symbols"] as? [String: String]
{
allSymbols = Array(plistSymbols.keys)
}
return allSymbols
}
}

View File

@@ -0,0 +1,89 @@
//
// SymbolsPicker.swift
// NearFuture
//
// Created by neon443 on 14/06/2025.
//
import SwiftUI
struct SymbolsPicker: View {
@StateObject private var symbolsLoader = SymbolsLoader()
@Binding var selection: String
@FocusState var searchfocuesd: Bool
@State var searchInput: String = ""
@State var browsing: Bool = false
@Environment(\.dismiss) var dismiss
var symbols: [String] {
return symbolsLoader.getSymbols(searchInput)
}
private func gridLayout(forWidth geoSizeWidth: CGFloat) -> [GridItem] {
let gridItem = GridItem(.fixed(80), spacing: 20, alignment: .center)
let columns = Int(geoSizeWidth/100.rounded(.up))
return Array(repeating: gridItem, count: columns)
}
var body: some View {
NavigationStack {
GeometryReader { geo in
ScrollView {
if symbols.isEmpty {
HStack {
Image(systemName: "magnifyingglass")
.resizable().scaledToFit()
.frame(width: 30)
Text("You look lost")
.font(.title)
.bold()
}
.padding()
Text("The symbol picker search only works with exact matches, try a different search term.")
}
LazyVGrid(columns: gridLayout(forWidth: geo.size.width)) {
ForEach(symbols, id: \.self) { symbol in
Button() {
selection = symbol
searchInput = ""
dismiss()
} label: {
VStack {
Image(systemName: symbol)
.resizable()
.scaledToFit()
.symbolRenderingMode(.palette)
.foregroundStyle(.blue, .gray, .black)
Text(symbol)
.truncationMode(.middle)
.font(.footnote)
}
}
.frame(maxWidth: 80, maxHeight: 80)
.buttonStyle(.plain)
}
}
}
}
.searchable(text: $searchInput)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
if !browsing {
Button() {
searchInput = ""
dismiss()
} label: {
Label("Cancel", systemImage: "xmark")
}
}
}
}
}
}
}
#Preview {
SymbolsPicker(selection: .constant(""))
}

View File

@@ -0,0 +1,76 @@
//
// ViewModifiers.swift
// NearFuture
//
// Created by neon443 on 13/06/2025.
//
import Foundation
import SwiftUI
struct hapticHeavy: ViewModifier {
var trigger: any Equatable
init(trigger: any Equatable) {
self.trigger = trigger
}
func body(content: Content) -> some View {
if #available(iOS 17, *) {
content
.sensoryFeedback(.impact(weight: .heavy, intensity: 1), trigger: trigger)
} else {
content
}
}
}
struct glassButton: ViewModifier {
func body(content: Content) -> some View {
if #available(iOS 19, macOS 16, *) {
content.buttonStyle(.glass)
} else {
content.buttonStyle(.borderedProminent)
.clipShape(RoundedRectangle(cornerRadius: 15))
.tint(.two)
}
}
}
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
}
}
}
struct navigationInlineLarge: ViewModifier {
func body(content: Content) -> some View {
#if os(macOS)
content
.toolbarTitleDisplayMode(.inlineLarge)
#else
content
.navigationBarTitleDisplayMode(.inline)
#endif
}
}
struct presentationSizeForm: ViewModifier {
func body(content: Content) -> some View {
if #available(iOS 18, macOS 15, *) {
content.presentationSizing(.form)
} else {
content
}
}
}