mirror of
https://github.com/neon443/NearFuture.git
synced 2026-03-11 06:49:12 +00:00
tring to ad animations
This commit is contained in:
@@ -126,6 +126,7 @@ struct EventListView: View {
|
|||||||
)
|
)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
}
|
}
|
||||||
|
.transition(.opacity)
|
||||||
.contextMenu() {
|
.contextMenu() {
|
||||||
Button(role: .destructive) {
|
Button(role: .destructive) {
|
||||||
let eventToModify = viewModel.events.firstIndex() { currEvent in
|
let eventToModify = viewModel.events.firstIndex() { currEvent in
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import UserNotifications
|
|||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
|
||||||
struct Event: Identifiable, Codable {
|
struct Event: Identifiable, Codable, Equatable {
|
||||||
var id = UUID()
|
var id = UUID()
|
||||||
var name: String
|
var name: String
|
||||||
var complete: Bool
|
var complete: Bool
|
||||||
@@ -415,9 +415,20 @@ class EventViewModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class dummyEventViewModel: EventViewModel {
|
class dummyEventViewModel: EventViewModel {
|
||||||
|
var template2: Event
|
||||||
override init(load: Bool = false) {
|
override init(load: Bool = false) {
|
||||||
|
self.template2 = Event(
|
||||||
|
name: "template2",
|
||||||
|
complete: false,
|
||||||
|
completeDesc: "",
|
||||||
|
symbol: "hammer",
|
||||||
|
color: ColorCodable(randomColor()),
|
||||||
|
notes: "notes",
|
||||||
|
date: Date(),
|
||||||
|
recurrence: .none
|
||||||
|
)
|
||||||
super.init(load: false)
|
super.init(load: false)
|
||||||
self.events = [self.example, self.template, self.example, self.template]
|
self.events = [self.example, self.template, self.template2]
|
||||||
self.events[0].complete.toggle()
|
self.events[0].complete.toggle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,11 +22,12 @@ struct ArchiveView: View {
|
|||||||
ForEach(viewModel.events.filter({$0.complete})) { event in
|
ForEach(viewModel.events.filter({$0.complete})) { event in
|
||||||
EventListView(viewModel: viewModel, event: event)
|
EventListView(viewModel: viewModel, event: event)
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
}
|
||||||
.id(hey)
|
.transition(.opacity)
|
||||||
.onReceive(viewModel.objectWillChange) {
|
.padding(.horizontal)
|
||||||
hey = UUID()
|
.id(hey)
|
||||||
}
|
.onReceive(viewModel.objectWillChange) {
|
||||||
|
hey = UUID()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ struct AddEventView: View {
|
|||||||
@State var showNeedsNameAlert: Bool = false
|
@State var showNeedsNameAlert: Bool = false
|
||||||
@State var isSymbolPickerPresented = false
|
@State var isSymbolPickerPresented = false
|
||||||
|
|
||||||
|
@State private var bye: Bool = false
|
||||||
|
|
||||||
@FocusState private var focusedField: Field?
|
@FocusState private var focusedField: Field?
|
||||||
private enum Field {
|
private enum Field {
|
||||||
case Name, Notes
|
case Name, Notes
|
||||||
@@ -32,168 +34,177 @@ struct AddEventView: View {
|
|||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) var dismiss
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
ZStack {
|
||||||
Form {
|
backgroundGradient
|
||||||
Section(
|
NavigationStack {
|
||||||
header:
|
Form {
|
||||||
Text("Event Details")
|
Section(
|
||||||
.font(.headline)
|
header:
|
||||||
.foregroundColor(.accentColor)
|
Text("Event Details")
|
||||||
) {
|
.font(.headline)
|
||||||
// name & symbol
|
.foregroundColor(.accentColor)
|
||||||
HStack(spacing: 5) {
|
) {
|
||||||
Button() {
|
// name & symbol
|
||||||
isSymbolPickerPresented.toggle()
|
HStack(spacing: 5) {
|
||||||
} label: {
|
Button() {
|
||||||
Image(systemName: eventSymbol)
|
isSymbolPickerPresented.toggle()
|
||||||
.resizable()
|
} label: {
|
||||||
.scaledToFit()
|
Image(systemName: eventSymbol)
|
||||||
.frame(width: 20, height: 20)
|
.resizable()
|
||||||
.foregroundStyle(eventColor)
|
.scaledToFit()
|
||||||
}
|
.frame(width: 20, height: 20)
|
||||||
.frame(width: 20)
|
.foregroundStyle(eventColor)
|
||||||
.buttonStyle(.borderless)
|
}
|
||||||
.sheet(isPresented: $isSymbolPickerPresented) {
|
.frame(width: 20)
|
||||||
SymbolsPicker(
|
.buttonStyle(.borderless)
|
||||||
selection: $eventSymbol,
|
.sheet(isPresented: $isSymbolPickerPresented) {
|
||||||
title: "Choose a Symbol",
|
SymbolsPicker(
|
||||||
searchLabel: "Search...",
|
selection: $eventSymbol,
|
||||||
autoDismiss: true)
|
title: "Choose a Symbol",
|
||||||
.presentationDetents([.medium])
|
searchLabel: "Search...",
|
||||||
.apply {
|
autoDismiss: true)
|
||||||
if #available(iOS 16.4, *) {
|
.presentationDetents([.medium])
|
||||||
$0.presentationBackground(.ultraThinMaterial)
|
.apply {
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ColorPicker("", selection: $eventColor, supportsOpacity: false)
|
|
||||||
.fixedSize()
|
// dscription
|
||||||
ZStack {
|
ZStack {
|
||||||
TextField("Event Name", text: $eventName)
|
TextField("Event Notes", text: $eventNotes)
|
||||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||||
.padding(.trailing, eventName.isEmpty ? 0 : 30)
|
.padding(.trailing, eventNotes.isEmpty ? 0 : 30)
|
||||||
.animation(.spring, value: eventName)
|
.animation(.spring, value: eventNotes)
|
||||||
.focused($focusedField, equals: Field.Name)
|
.focused($focusedField, equals: Field.Notes)
|
||||||
.submitLabel(.next)
|
.submitLabel(.done)
|
||||||
.onSubmit {
|
.onSubmit {
|
||||||
focusedField = .Notes
|
focusedField = nil
|
||||||
}
|
}
|
||||||
MagicClearButton(text: $eventName)
|
MagicClearButton(text: $eventNotes)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// dscription
|
// date picker
|
||||||
ZStack {
|
HStack {
|
||||||
TextField("Event Notes", text: $eventNotes)
|
Spacer()
|
||||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
DatePicker("", selection: $eventDate, displayedComponents: .date)
|
||||||
.padding(.trailing, eventNotes.isEmpty ? 0 : 30)
|
.datePickerStyle(WheelDatePickerStyle())
|
||||||
.animation(.spring, value: eventNotes)
|
Spacer()
|
||||||
.focused($focusedField, equals: Field.Notes)
|
Button() {
|
||||||
.submitLabel(.done)
|
eventDate = Date()
|
||||||
.onSubmit {
|
} label: {
|
||||||
focusedField = nil
|
Image(systemName: "arrow.uturn.left")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
}
|
}
|
||||||
MagicClearButton(text: $eventNotes)
|
.buttonStyle(BorderlessButtonStyle())
|
||||||
}
|
.frame(width: 20)
|
||||||
|
|
||||||
|
|
||||||
// date picker
|
|
||||||
HStack {
|
|
||||||
Spacer()
|
|
||||||
DatePicker("", selection: $eventDate, displayedComponents: .date)
|
|
||||||
.datePickerStyle(WheelDatePickerStyle())
|
|
||||||
Spacer()
|
|
||||||
Button() {
|
|
||||||
eventDate = Date()
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "arrow.uturn.left")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
}
|
}
|
||||||
.buttonStyle(BorderlessButtonStyle())
|
|
||||||
.frame(width: 20)
|
|
||||||
}
|
|
||||||
|
|
||||||
DatePicker(
|
DatePicker(
|
||||||
"",
|
"",
|
||||||
selection: $eventDate,
|
selection: $eventDate,
|
||||||
displayedComponents: .hourAndMinute
|
displayedComponents: .hourAndMinute
|
||||||
)
|
)
|
||||||
|
|
||||||
// re-ocurrence Picker
|
// re-ocurrence Picker
|
||||||
Picker("Recurrence", selection: $eventRecurrence) {
|
Picker("Recurrence", selection: $eventRecurrence) {
|
||||||
ForEach(Event.RecurrenceType.allCases, id: \.self) { recurrence in
|
ForEach(Event.RecurrenceType.allCases, id: \.self) { recurrence in
|
||||||
Text(recurrence.rawValue.capitalized)
|
Text(recurrence.rawValue.capitalized)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.pickerStyle(SegmentedPickerStyle())
|
||||||
.pickerStyle(SegmentedPickerStyle())
|
Text(
|
||||||
Text(
|
describeOccurrence(
|
||||||
describeOccurrence(
|
date: eventDate,
|
||||||
date: eventDate,
|
recurrence: eventRecurrence
|
||||||
recurrence: eventRecurrence
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.scrollContentBackground(.hidden)
|
|
||||||
.navigationTitle("\(adding ? "Add Event" : "")")
|
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
|
||||||
.toolbar {
|
|
||||||
ToolbarItem(placement: .topBarLeading) {
|
|
||||||
if adding {
|
|
||||||
Button() {
|
|
||||||
resetAddEventView()
|
|
||||||
dismiss()
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "xmark.circle.fill")
|
|
||||||
.symbolRenderingMode(.hierarchical)
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 30)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ToolbarItem(placement: .topBarTrailing) {
|
|
||||||
if adding {
|
|
||||||
Button {
|
|
||||||
viewModel.addEvent(
|
|
||||||
newEvent: Event(
|
|
||||||
name: eventName,
|
|
||||||
complete: eventComplete,
|
|
||||||
completeDesc: eventCompleteDesc,
|
|
||||||
symbol: eventSymbol,
|
|
||||||
color: ColorCodable(eventColor),
|
|
||||||
notes: eventNotes,
|
|
||||||
date: eventDate,
|
|
||||||
recurrence: eventRecurrence
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
resetAddEventView()
|
)
|
||||||
} label: {
|
}
|
||||||
Text("Save")
|
}
|
||||||
.font(.headline)
|
.scrollContentBackground(.hidden)
|
||||||
.cornerRadius(10)
|
.navigationTitle("\(adding ? "Add Event" : "")")
|
||||||
.buttonStyle(BorderedProminentButtonStyle())
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .topBarLeading) {
|
||||||
|
if adding {
|
||||||
|
Button() {
|
||||||
|
resetAddEventView()
|
||||||
|
dismiss()
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "xmark.circle.fill")
|
||||||
|
.symbolRenderingMode(.hierarchical)
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 30)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.disabled(eventName.isEmpty)
|
}
|
||||||
.onTapGesture {
|
ToolbarItem(placement: .topBarTrailing) {
|
||||||
|
if adding {
|
||||||
|
Button {
|
||||||
|
viewModel.addEvent(
|
||||||
|
newEvent: Event(
|
||||||
|
name: eventName,
|
||||||
|
complete: eventComplete,
|
||||||
|
completeDesc: eventCompleteDesc,
|
||||||
|
symbol: eventSymbol,
|
||||||
|
color: ColorCodable(eventColor),
|
||||||
|
notes: eventNotes,
|
||||||
|
date: eventDate,
|
||||||
|
recurrence: eventRecurrence
|
||||||
|
)
|
||||||
|
)
|
||||||
|
bye.toggle()
|
||||||
|
resetAddEventView()
|
||||||
|
} label: {
|
||||||
|
Text("Save")
|
||||||
|
.font(.headline)
|
||||||
|
.cornerRadius(10)
|
||||||
|
.buttonStyle(BorderedProminentButtonStyle())
|
||||||
|
}
|
||||||
|
.apply {
|
||||||
|
if #available(iOS 17, *) {
|
||||||
|
$0.sensoryFeedback(.success, trigger: bye)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.disabled(eventName.isEmpty)
|
||||||
|
.onTapGesture {
|
||||||
|
if eventName.isEmpty {
|
||||||
|
showNeedsNameAlert.toggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.alert("Missing Name", isPresented: $showNeedsNameAlert) {
|
||||||
|
Button("OK", role: .cancel) {
|
||||||
|
showNeedsNameAlert.toggle()
|
||||||
|
focusedField = .Name
|
||||||
|
}
|
||||||
|
} message: {
|
||||||
|
Text("Give your Event a name before saving.")
|
||||||
|
}
|
||||||
if eventName.isEmpty {
|
if eventName.isEmpty {
|
||||||
showNeedsNameAlert.toggle()
|
HStack {
|
||||||
}
|
Image(systemName: "exclamationmark")
|
||||||
}
|
.foregroundStyle(.red)
|
||||||
.alert("Missing Name", isPresented: $showNeedsNameAlert) {
|
Text("Give your event a name.")
|
||||||
Button("OK", role: .cancel) {
|
}
|
||||||
showNeedsNameAlert.toggle()
|
|
||||||
focusedField = .Name
|
|
||||||
}
|
|
||||||
} message: {
|
|
||||||
Text("Give your Event a name before saving.")
|
|
||||||
}
|
|
||||||
if eventName.isEmpty {
|
|
||||||
HStack {
|
|
||||||
Image(systemName: "exclamationmark")
|
|
||||||
.foregroundStyle(.red)
|
|
||||||
Text("Give your event a name.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,16 @@ enum Field {
|
|||||||
case Search
|
case Search
|
||||||
}
|
}
|
||||||
enum Tab {
|
enum Tab {
|
||||||
case Home
|
case home
|
||||||
case Archive
|
case archive
|
||||||
case Statistics
|
case stats
|
||||||
case Settings
|
case settings
|
||||||
case Gradient
|
}
|
||||||
|
enum FilterCategory {
|
||||||
|
case Future
|
||||||
|
case Past
|
||||||
|
case Complete
|
||||||
|
case Incomplete
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@@ -36,7 +41,11 @@ struct ContentView: View {
|
|||||||
@State private var searchInput: String = ""
|
@State private var searchInput: String = ""
|
||||||
var filteredEvents: [Event] {
|
var filteredEvents: [Event] {
|
||||||
if searchInput.isEmpty {
|
if searchInput.isEmpty {
|
||||||
return viewModel.events.filter() {!$0.complete}
|
if settingsModel.settings.showCompletedInHome {
|
||||||
|
return viewModel.events
|
||||||
|
} else {
|
||||||
|
return viewModel.events.filter() {!$0.complete}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return viewModel.events.filter {
|
return viewModel.events.filter {
|
||||||
$0.name.localizedCaseInsensitiveContains(searchInput) ||
|
$0.name.localizedCaseInsensitiveContains(searchInput) ||
|
||||||
@@ -44,17 +53,9 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@State private var focusedTab: Tab = .home
|
||||||
var noEvents: Bool {
|
|
||||||
if viewModel.events.count == 0 {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@FocusState private var focusedField: Field?
|
@FocusState private var focusedField: Field?
|
||||||
@FocusState private var focusedTab: Tab?
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
TabView {
|
TabView {
|
||||||
@@ -85,17 +86,28 @@ struct ContentView: View {
|
|||||||
if filteredEvents.isEmpty && !searchInput.isEmpty {
|
if filteredEvents.isEmpty && !searchInput.isEmpty {
|
||||||
HelpView(searchInput: $searchInput, focusedField: focusedField)
|
HelpView(searchInput: $searchInput, focusedField: focusedField)
|
||||||
} else {
|
} else {
|
||||||
|
Button("hiiiiiiiiiiiiiii") {
|
||||||
|
withAnimation() {
|
||||||
|
settingsModel.settings.showCompletedInHome.toggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button("sort") {
|
||||||
|
withAnimation() {
|
||||||
|
viewModel.events.sort() { $0.date < $1.date }
|
||||||
|
}
|
||||||
|
}
|
||||||
ScrollView {
|
ScrollView {
|
||||||
ForEach(filteredEvents) { event in
|
ForEach(filteredEvents) { event in
|
||||||
EventListView(viewModel: viewModel, event: event)
|
EventListView(viewModel: viewModel, event: event)
|
||||||
}
|
}
|
||||||
.onDelete(perform: viewModel.removeEvent)
|
.animation(.default, value: filteredEvents)
|
||||||
|
.transition(.opacity)
|
||||||
.id(hey)
|
.id(hey)
|
||||||
.onReceive(viewModel.objectWillChange) {
|
.onReceive(viewModel.objectWillChange) {
|
||||||
hey = UUID()
|
hey = UUID()
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
if /*!searchInput.isEmpty && */filteredEvents.isEmpty {
|
if filteredEvents.isEmpty {
|
||||||
HelpView(
|
HelpView(
|
||||||
searchInput: $searchInput,
|
searchInput: $searchInput,
|
||||||
focusedField: focusedField
|
focusedField: focusedField
|
||||||
@@ -143,22 +155,22 @@ struct ContentView: View {
|
|||||||
.tabItem {
|
.tabItem {
|
||||||
Label("Home", systemImage: "house")
|
Label("Home", systemImage: "house")
|
||||||
}
|
}
|
||||||
.focused($focusedTab, equals: Tab.Home)
|
.tag(Tab.home)
|
||||||
ArchiveView(viewModel: viewModel)
|
ArchiveView(viewModel: viewModel)
|
||||||
.tabItem() {
|
.tabItem() {
|
||||||
Label("Archive", systemImage: "tray.full")
|
Label("Archive", systemImage: "tray.full")
|
||||||
}
|
}
|
||||||
.focused($focusedTab, equals: Tab.Archive)
|
.tag(Tab.archive)
|
||||||
StatsView(viewModel: viewModel)
|
StatsView(viewModel: viewModel)
|
||||||
.tabItem {
|
.tabItem {
|
||||||
Label("Statistics", systemImage: "chart.pie")
|
Label("Statistics", systemImage: "chart.pie")
|
||||||
}
|
}
|
||||||
.focused($focusedTab, equals: Tab.Statistics)
|
.tag(Tab.stats)
|
||||||
SettingsView(viewModel: viewModel, settingsModel: settingsModel)
|
SettingsView(viewModel: viewModel, settingsModel: settingsModel)
|
||||||
.tabItem {
|
.tabItem {
|
||||||
Label("Settings", systemImage: "gear")
|
Label("Settings", systemImage: "gear")
|
||||||
}
|
}
|
||||||
.focused($focusedTab, equals: Tab.Settings)
|
.tag(Tab.settings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ struct EventListView: View {
|
|||||||
VStack {
|
VStack {
|
||||||
Text("\(daysUntilEvent(event.date).long)")
|
Text("\(daysUntilEvent(event.date).long)")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundStyle(.one)
|
.foregroundStyle(event.date.timeIntervalSinceNow < 0 ? .red : .one)
|
||||||
}
|
}
|
||||||
Button() {
|
Button() {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
@@ -112,6 +112,7 @@ struct EventListView: View {
|
|||||||
.shadow(radius: 5)
|
.shadow(radius: 5)
|
||||||
.padding(.trailing, 5)
|
.padding(.trailing, 5)
|
||||||
}
|
}
|
||||||
|
.transition(.opacity)
|
||||||
.padding(.vertical, 5)
|
.padding(.vertical, 5)
|
||||||
.background(.ultraThinMaterial)
|
.background(.ultraThinMaterial)
|
||||||
.overlay(
|
.overlay(
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user