15 Commits

Author SHA1 Message Date
neon443
169d51e6b9 improved app icon getting - now gets the actual app icon 2025-05-06 15:37:10 +01:00
neon443
afd6d23838 bump version 2025-05-06 10:09:16 +01:00
neon443
5877f3b525 fixed no color selected when having no settings
fix helpview always orange
2025-05-06 10:05:01 +01:00
neon443
4157520ff4 accent color picking
added about
added saveSettings()
backported to 16.0
new title bar
2025-05-06 09:37:03 +01:00
neon443
f03ea73813 basic settings done!!!
now can set accent color
2025-05-05 09:57:58 +01:00
neon443
dafd2ae0e8 Merge branch 'feat-accent' 2025-05-05 08:58:10 +01:00
neon443
1bbacd0caa added onappear reset to importVie 2025-05-05 08:57:45 +01:00
Nihaal Sharma
29be23efc2 Update README.md 2025-05-04 12:49:49 +01:00
neon443
ffbd17fad8 rerewrote daysUntillEvent, now acc can count days
inlineLarge titlebar, backwards compatible view extension
competed events go to archive
fix long event name and notes alignemnts
can refresh icloud settings to sync
2025-05-04 12:31:01 +01:00
neon443
c97d37711c Fix import/Export events
rewrote the daysTillEventt()
2025-05-03 10:36:17 +01:00
neon443
80b193f449 ajdshfjkas 2025-05-02 10:03:43 +01:00
neon443
48d086602e fix the negative time when using the long daystill 2025-05-02 09:50:26 +01:00
neon443
3450e8bf1c cleanup 2025-05-01 21:23:14 +01:00
neon443
a08642bcb5 fix day count being wrong, minor updates 2025-05-01 21:05:31 +01:00
neon443
8ea1eda8f4 add xcconfig vars to widget appex 2025-04-29 21:29:21 +01:00
37 changed files with 807 additions and 371 deletions

View File

@@ -10,7 +10,8 @@
TEAM_ID = 8JGND254B7
BUNDLE_ID = com.neon443.NearFuture
BUNDLE_ID_WIDGETS = com.neon443.NearFuture.widgets
GROUP_ID = group.NearFuture
VERSION = 3.0.1
VERSION = 3.3.1
NAME = Near Future
BUILD_NUMBER = 1
BUILD_NUMBER = 0

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

View File

Binary file not shown.

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 609 KiB

View File

Binary file not shown.

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 534 KiB

View File

Binary file not shown.

View File

@@ -7,6 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
A9111B1B2DCA549600D4F793 /* NearFutureIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = A9111B1A2DCA549600D4F793 /* NearFutureIcon.png */; };
A9111B1C2DCA549600D4F793 /* NearFutureIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = A9111B1A2DCA549600D4F793 /* NearFutureIcon.png */; };
A920C2882D24011400E4F9B1 /* NearFutureApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C2872D24011400E4F9B1 /* NearFutureApp.swift */; };
A920C28C2D24011400E4F9B1 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C28B2D24011400E4F9B1 /* Item.swift */; };
A920C28E2D24011A00E4F9B1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A920C28D2D24011A00E4F9B1 /* Assets.xcassets */; };
@@ -16,6 +18,9 @@
A920C2BE2D24021A00E4F9B1 /* SFSymbolsPicker in Frameworks */ = {isa = PBXBuildFile; productRef = A920C2BD2D24021A00E4F9B1 /* SFSymbolsPicker */; };
A920C2C12D2403CA00E4F9B1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C2C02D2403CA00E4F9B1 /* ContentView.swift */; };
A93BC0942D2B18A3002E8BBD /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A93BC0932D2B18A3002E8BBD /* StatsView.swift */; };
A973B26C2DC551310028F8A2 /* ImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A973B26B2DC551310028F8A2 /* ImportView.swift */; };
A973B2702DC552EB0028F8A2 /* ExportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A973B26F2DC552EB0028F8A2 /* ExportView.swift */; };
A973B2712DC553050028F8A2 /* ExportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A973B26F2DC552EB0028F8A2 /* ExportView.swift */; };
A977CC922DBBB48000DED8C0 /* ArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A977CC912DBBB48000DED8C0 /* ArchiveView.swift */; };
A977CC9A2DBD74FE00DED8C0 /* HelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A977CC992DBD74FE00DED8C0 /* HelpView.swift */; };
A979F57F2D26B1300094C0B3 /* EditEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F57E2D26B1300094C0B3 /* EditEventView.swift */; };
@@ -64,6 +69,7 @@
/* Begin PBXFileReference section */
A90FDE222DC0D4310012790C /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
A9111B1A2DCA549600D4F793 /* NearFutureIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = NearFutureIcon.png; path = NearFuture/Assets.xcassets/AppIcon.appiconset/NearFutureIcon.png; sourceTree = SOURCE_ROOT; };
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>"; };
A920C28B2D24011400E4F9B1 /* Item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Item.swift; sourceTree = "<group>"; };
@@ -74,6 +80,8 @@
A920C2B72D2401A300E4F9B1 /* AddEventView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddEventView.swift; sourceTree = "<group>"; };
A920C2C02D2403CA00E4F9B1 /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
A93BC0932D2B18A3002E8BBD /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = "<group>"; };
A973B26B2DC551310028F8A2 /* ImportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportView.swift; sourceTree = "<group>"; };
A973B26F2DC552EB0028F8A2 /* ExportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportView.swift; sourceTree = "<group>"; };
A977CC912DBBB48000DED8C0 /* ArchiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveView.swift; sourceTree = "<group>"; };
A977CC992DBD74FE00DED8C0 /* HelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpView.swift; sourceTree = "<group>"; };
A979F57E2D26B1300094C0B3 /* EditEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditEventView.swift; sourceTree = "<group>"; };
@@ -130,10 +138,19 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
A9111B192DCA548800D4F793 /* Resources */ = {
isa = PBXGroup;
children = (
A9111B1A2DCA549600D4F793 /* NearFutureIcon.png */,
);
path = Resources;
sourceTree = "<group>";
};
A920C27B2D24011300E4F9B1 = {
isa = PBXGroup;
children = (
A90FDE222DC0D4310012790C /* Config.xcconfig */,
A9111B192DCA548800D4F793 /* Resources */,
A920C2862D24011400E4F9B1 /* NearFuture */,
A979F6082D270AF00094C0B3 /* NearFutureWidgets */,
A980FC382D93FB2B006A778F /* NearFutureTests */,
@@ -165,6 +182,8 @@
A93BC0932D2B18A3002E8BBD /* StatsView.swift */,
A977CC992DBD74FE00DED8C0 /* HelpView.swift */,
A920C2B42D2401A100E4F9B1 /* SettingsView.swift */,
A973B26B2DC551310028F8A2 /* ImportView.swift */,
A973B26F2DC552EB0028F8A2 /* ExportView.swift */,
A985104D2DB256430013D5FF /* iCloudSettingsView.swift */,
A980FC302D920097006A778F /* Info.plist */,
A920C28D2D24011A00E4F9B1 /* Assets.xcassets */,
@@ -334,6 +353,7 @@
buildActionMask = 2147483647;
files = (
A920C2922D24011A00E4F9B1 /* Preview Assets.xcassets in Resources */,
A9111B1C2DCA549600D4F793 /* NearFutureIcon.png in Resources */,
A920C28E2D24011A00E4F9B1 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -343,6 +363,7 @@
buildActionMask = 2147483647;
files = (
A979F6102D270AF90094C0B3 /* Assets.xcassets in Resources */,
A9111B1B2DCA549600D4F793 /* NearFutureIcon.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -361,8 +382,10 @@
buildActionMask = 2147483647;
files = (
A920C2BB2D2401A400E4F9B1 /* AddEventView.swift in Sources */,
A973B26C2DC551310028F8A2 /* ImportView.swift in Sources */,
A98510502DB263F00013D5FF /* EventListView.swift in Sources */,
A920C2C12D2403CA00E4F9B1 /* ContentView.swift in Sources */,
A973B2712DC553050028F8A2 /* ExportView.swift in Sources */,
A920C2B82D2401A300E4F9B1 /* SettingsView.swift in Sources */,
A977CC9A2DBD74FE00DED8C0 /* HelpView.swift in Sources */,
A920C28C2D24011400E4F9B1 /* Item.swift in Sources */,
@@ -389,6 +412,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A973B2702DC552EB0028F8A2 /* ExportView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -561,7 +585,7 @@
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 16.4;
IPHONEOS_DEPLOYMENT_TARGET = 16;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 13;
@@ -572,10 +596,10 @@
);
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID)";
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = NO;
REGISTER_APP_GROUPS = YES;
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -612,7 +636,7 @@
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 16.4;
IPHONEOS_DEPLOYMENT_TARGET = 16;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 13;
@@ -620,10 +644,10 @@
OTHER_LDFLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID)";
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = NO;
REGISTER_APP_GROUPS = YES;
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -640,7 +664,8 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = NearFutureWidgets/NearFutureWidgetsExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = "$(BUILD_NUMBER)";
DEVELOPMENT_TEAM = "$(TEAM_ID)";
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = NearFutureWidgets/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = NearFutureWidgets;
@@ -651,20 +676,23 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.0;
MACOSX_DEPLOYMENT_TARGET = 14;
MARKETING_VERSION = "$(VERSION)";
OTHER_LDFLAGS = (
"-Xlinker",
"-interposable",
);
PRODUCT_BUNDLE_IDENTIFIER = com.neon443.NearFuture.widgets;
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_WIDGETS)";
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MACCATALYST = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSION = "$(VERSION)";
};
name = Debug;
};
@@ -676,7 +704,8 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = NearFutureWidgets/NearFutureWidgetsExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = "$(BUILD_NUMBER)";
DEVELOPMENT_TEAM = "$(TEAM_ID)";
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = NearFutureWidgets/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = NearFutureWidgets;
@@ -687,17 +716,20 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.0;
PRODUCT_BUNDLE_IDENTIFIER = com.neon443.NearFuture.widgets;
MACOSX_DEPLOYMENT_TARGET = 14;
MARKETING_VERSION = "$(VERSION)";
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_WIDGETS)";
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MACCATALYST = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSION = "$(VERSION)";
};
name = Release;
};

View File

@@ -104,7 +104,6 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
askForAppToLaunch = "Yes"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">

View File

@@ -18,7 +18,6 @@ struct AddEventView: View {
@Binding var eventColor: Color
@Binding var eventNotes: String
@Binding var eventDate: Date
@Binding var eventTime: Bool
@Binding var eventRecurrence: Event.RecurrenceType
@State var adding: Bool
@@ -60,6 +59,11 @@ struct AddEventView: View {
title: "Choose a Symbol",
searchLabel: "Search...",
autoDismiss: true)
.apply {
if #available(iOS 16.4, *) {
$0.presentationBackground(.ultraThinMaterial)
}
}
}
ColorPicker("", selection: $eventColor, supportsOpacity: false)
.fixedSize()
@@ -94,8 +98,10 @@ struct AddEventView: View {
// date picker
HStack {
Spacer()
DatePicker("", selection: $eventDate, displayedComponents: .date)
.datePickerStyle(WheelDatePickerStyle())
Spacer()
Button() {
eventDate = Date()
} label: {
@@ -107,14 +113,11 @@ struct AddEventView: View {
.frame(width: 20)
}
Toggle("Schedule a Time", isOn: $eventTime)
if eventTime {
DatePicker(
"",
selection: $eventDate,
displayedComponents: .hourAndMinute
)
}
// re-ocurrence Picker
Picker("Recurrence", selection: $eventRecurrence) {
@@ -161,7 +164,6 @@ struct AddEventView: View {
color: ColorCodable(eventColor),
notes: eventNotes,
date: eventDate,
time: eventTime,
recurrence: eventRecurrence
)
)
@@ -207,7 +209,6 @@ struct AddEventView: View {
eventColor = randomColor()
eventNotes = viewModel.template.notes
eventDate = viewModel.template.date
eventTime = viewModel.template.time
eventRecurrence = viewModel.template.recurrence
dismiss()
}
@@ -248,11 +249,14 @@ struct MagicClearButton: View {
eventColor: .constant(vm.template.color.color),
eventNotes: .constant(vm.template.notes),
eventDate: .constant(vm.template.date),
eventTime: .constant(vm.template.time),
eventRecurrence: .constant(vm.template.recurrence),
adding: true
)
.presentationDragIndicator(.visible)
.presentationBackground(.ultraThinMaterial)
.apply {
if #available(iOS 16.4, *) {
$0.presentationBackground(.ultraThinMaterial)
}
}
}
}

View File

@@ -11,9 +11,6 @@ struct ArchiveView: View {
@ObservedObject var viewModel: EventViewModel
@State var showAddEvent: Bool = false
@State var hey: UUID = UUID()
init(viewModel: EventViewModel) {
self.viewModel = viewModel
}
var body: some View {
NavigationStack {
ZStack {
@@ -39,6 +36,14 @@ struct ArchiveView: View {
AddEventButton(showingAddEventView: $showAddEvent)
}
}
.navigationTitle("Archive")
.apply {
if #available(iOS 17, *) {
$0.toolbarTitleDisplayMode(.inlineLarge)
} else {
$0.navigationBarTitleDisplayMode(.inline)
}
}
}
.sheet(isPresented: $showAddEvent) {
AddEventView(
@@ -50,11 +55,15 @@ struct ArchiveView: View {
eventColor: $viewModel.editableTemplate.color.colorBind,
eventNotes: $viewModel.editableTemplate.notes,
eventDate: $viewModel.editableTemplate.date,
eventTime: $viewModel.editableTemplate.time,
eventRecurrence: $viewModel.editableTemplate.recurrence,
adding: true
)
.presentationDragIndicator(.visible)
.apply {
if #available(iOS 16.4, *) {
$0.presentationBackground(.ultraThinMaterial)
}
}
}
}
}

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 609 KiB

After

Width:  |  Height:  |  Size: 729 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 534 KiB

After

Width:  |  Height:  |  Size: 632 KiB

View File

@@ -0,0 +1,9 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"provides-namespace" : true
}
}

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.000",
"green" : "0.000",
"red" : "0.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.961",
"green" : "0.510",
"red" : "0.227"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.216",
"green" : "0.737",
"red" : "0.337"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.871",
"green" : "0.329",
"red" : "0.345"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "extended-srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.000",
"green" : "0.376",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.349",
"green" : "0.000",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.242",
"green" : "0.197",
"red" : "0.910"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.239",
"green" : "0.690",
"red" : "0.933"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -21,6 +21,7 @@ enum Tab {
struct ContentView: View {
@StateObject var viewModel: EventViewModel
@StateObject var settingsModel: SettingsViewModel
@State private var eventName = ""
@State private var eventComplete = false
@State private var eventCompleteDesc = ""
@@ -28,14 +29,13 @@ struct ContentView: View {
@State private var eventColor: Color = randomColor()
@State private var eventNotes = ""
@State private var eventDate = Date()
@State private var eventTime = false
@State private var eventRecurrence: Event.RecurrenceType = .none
@State var hey: UUID = UUID()
@State private var showingAddEventView = false
@State private var searchInput: String = ""
var filteredEvents: [Event] {
if searchInput.isEmpty {
return viewModel.events
return viewModel.events.filter() {!$0.complete}
} else {
return viewModel.events.filter {
$0.name.localizedCaseInsensitiveContains(searchInput) ||
@@ -87,14 +87,23 @@ struct ContentView: View {
}
.padding(.horizontal)
if /*!searchInput.isEmpty && */filteredEvents.isEmpty {
HelpView(searchInput: $searchInput, focusedField: focusedField)
HelpView(
searchInput: $searchInput,
focusedField: focusedField
)
}
Spacer()
}
}
}
.navigationTitle("Near Future")
.navigationBarTitleDisplayMode(.inline)
.apply {
if #available(iOS 17, *) {
$0.toolbarTitleDisplayMode(.inlineLarge)
} else {
$0.navigationBarTitleDisplayMode(.inline)
}
}
.sheet(isPresented: $showingAddEventView) {
AddEventView(
viewModel: viewModel,
@@ -105,12 +114,15 @@ struct ContentView: View {
eventColor: $eventColor,
eventNotes: $eventNotes,
eventDate: $eventDate,
eventTime: $eventTime,
eventRecurrence: $eventRecurrence,
adding: true //adding event
)
.presentationDragIndicator(.visible)
.presentationBackground(.ultraThinMaterial)
.apply {
if #available(iOS 16.4, *) {
$0.presentationBackground(.ultraThinMaterial)
}
}
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
@@ -133,7 +145,7 @@ struct ContentView: View {
Label("Statistics", systemImage: "chart.pie")
}
.focused($focusedTab, equals: Tab.Statistics)
SettingsView(viewModel: viewModel)
SettingsView(viewModel: viewModel, settingsModel: settingsModel)
.tabItem {
Label("Settings", systemImage: "gear")
}
@@ -143,7 +155,10 @@ struct ContentView: View {
}
#Preview {
ContentView(viewModel: dummyEventViewModel())
ContentView(
viewModel: dummyEventViewModel(),
settingsModel: dummySettingsViewModel()
)
}
struct SearchBar: View {
@@ -195,3 +210,7 @@ extension View {
.ignoresSafeArea(.all)
}
}
extension View {
func apply<V: View>(@ViewBuilder _ block: (Self) -> V) -> V { block(self) }
}

View File

@@ -35,7 +35,6 @@ struct EditEventView: View {
eventColor: $event.color.colorBind,
eventNotes: $event.notes,
eventDate: $event.date,
eventTime: $event.time,
eventRecurrence: $event.recurrence,
adding: false //bc we editing existing event
)

View File

@@ -44,16 +44,18 @@ struct EventListView: View {
.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: event.time ? .shortened : .omitted
time: .shortened
)
)
.font(.subheadline)
@@ -71,7 +73,7 @@ struct EventListView: View {
}
Spacer()
VStack {
Text("\(daysUntilEvent(event.date, short: false))")
Text("\(daysUntilEvent(event.date).long)")
.font(.subheadline)
.foregroundStyle(.one)
}

View File

@@ -0,0 +1,27 @@
//
// ExportView.swift
// NearFuture
//
// Created by neon443 on 02/05/2025.
//
import SwiftUI
struct ExportView: View {
@ObservedObject var viewModel: EventViewModel
var body: some View {
List {
Button() {
UIPasteboard.general.string = viewModel.exportEvents()
} label: {
Label("Copy Events", systemImage: "document.on.clipboard")
}
Text(viewModel.exportEvents())
.textSelection(.enabled)
}
}
}
#Preview {
ExportView(viewModel: dummyEventViewModel())
}

View File

@@ -74,7 +74,7 @@ struct HelpView: View {
var body: some View {
List {
ZStack {
Color(.accent)
Color(.tintColor)
.opacity(0.4)
.padding(.horizontal, -15)
.blur(radius: 5)

View File

@@ -0,0 +1,67 @@
//
// ImportView.swift
// NearFuture
//
// Created by neon443 on 02/05/2025.
//
import SwiftUI
struct ImportView: View {
@ObservedObject var viewModel: EventViewModel
@Binding var importStr: String
@State private var image: String = "clock.fill"
@State private var text: String = "Ready..."
@State private var fgColor: Color = .yellow
var body: some View {
List {
Section("Status") {
Label(text, systemImage: image)
.contentTransition(.numericText())
.foregroundStyle(fgColor)
}
TextField("", text: $importStr)
Button() {
do throws {
try viewModel.importEvents(importStr)
withAnimation {
image = "checkmark.circle.fill"
text = "Complete"
fgColor = .green
}
} catch importError.invalidB64 {
withAnimation {
image = "xmark.app.fill"
text = "Invalid base64 input."
fgColor = .red
}
} catch {
withAnimation {
image = "xmark.app.fill"
text = error.localizedDescription
fgColor = .red
}
}
} label: {
Label("Import", systemImage: "tray.and.arrow.down.fill")
}
.disabled(importStr.isEmpty)
.onAppear() {
importStr = ""
image = "clock.fill"
text = "Ready..."
fgColor = .yellow
}
}
}
}
#Preview {
ImportView(
viewModel: dummyEventViewModel(),
importStr: .constant("kljadfskljafdlkj;==")
)
}

View File

@@ -28,7 +28,6 @@ struct Event: Identifiable, Codable {
var color: ColorCodable
var notes: String
var date: Date
var time: Bool
var recurrence: RecurrenceType
enum RecurrenceType: String, Codable, CaseIterable {
@@ -36,7 +35,29 @@ struct Event: Identifiable, Codable {
}
}
struct ColorCodable: Codable {
struct ColorCodable: Codable, Equatable {
init(_ color: Color) {
let uiColor = UIColor(color)
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 1.0
uiColor.getRed(&r, green: &g, blue: &b, alpha: &a)
self.red = Double(r)
self.green = Double(g)
self.blue = Double(b)
}
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.red = Double(r)
self.green = Double(g)
self.blue = Double(b)
}
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
@@ -58,42 +79,81 @@ struct ColorCodable: Codable {
self.blue = cc.blue
}
}
}
init(_ color: Color) {
let uiColor = UIColor(color)
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 1.0
uiColor.getRed(&r, green: &g, blue: &b, alpha: &a)
self.red = Double(r)
self.green = Double(g)
self.blue = Double(b)
}
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
func daysUntilEvent(_ eventDate: Date) -> (long: String, short: String) {
let calendar = Calendar.current
let startOfDayNow = calendar.startOfDay(for: Date())
let startOfDayEvent = calendar.startOfDay(for: eventDate)
let components = calendar.dateComponents([.day], from: startOfDayNow, to: startOfDayEvent)
guard let days = components.day else { return ("N/A", "N/A") }
guard days != 0 else { return ("Today", "Today") }
if days < 0 {
//past
return (
"\(-days) day\(plu(days)) ago",
"\(days)d"
)
} else {
//future
return (
"\(days) day\(plu(days))",
"\(days)d"
)
}
}
func daysUntilEvent(_ eventDate: Date, short: Bool, sepLines: Bool = false) -> String {
let calendar = Calendar.current
let currentDate = Date()
let components = calendar.dateComponents([.day], from: currentDate, to: eventDate)
guard let days = components.day else { return "N/A" }
guard days >= 0 else {
if short {
return "\(days)d"
} else {
return "\(-days)\(sepLines ? "\n" : " ")day\(-days == 1 ? "" : "s") ago"
struct Settings: Codable, Equatable {
var showCompletedInHome: Bool
var tint: ColorCodable
}
class SettingsViewModel: ObservableObject {
@Published var settings: Settings = Settings(
showCompletedInHome: false,
tint: ColorCodable(uiColor: UIColor(named: "AccentColor")!)
)
@Published var accentChoices: [Color] = [
Color(UIColor(named: "uiColors/red")!),
Color(UIColor(named: "uiColors/orange")!),
Color(UIColor(named: "uiColors/yellow")!),
Color(UIColor(named: "uiColors/green")!),
Color(UIColor(named: "uiColors/blue")!),
Color(UIColor(named: "uiColors/indigo")!),
Color(UIColor(named: "uiColors/basic")!)
]
init(load: Bool = true) {
if load {
loadSettings()
}
}
guard days != 0 else {
return "Today"
let appGroupSettingsStore = UserDefaults(suiteName: "group.NearFuture") ?? UserDefaults.standard
let icSettStore = NSUbiquitousKeyValueStore.default
func loadSettings() {
let decoder = JSONDecoder()
if let icSettings = icSettStore.data(forKey: "settings") {
if let decodedSetts = try? decoder.decode(Settings.self, from: icSettings) {
self.settings = decodedSetts
}
} else if let savedData = appGroupSettingsStore.data(forKey: "settings") {
if let decodedSetts = try? decoder.decode(Settings.self, from: savedData) {
self.settings = decodedSetts
}
}
}
if short {
return "\(days)d"
} else {
return "\(days)\(sepLines ? "\n" : " ")day\(days == 1 ? "" : "s")"
func saveSettings() {
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(settings) {
appGroupSettingsStore.set(encoded, forKey: "settings")
icSettStore.set(encoded, forKey: "settings")
icSettStore.synchronize()
loadSettings()
}
}
}
@@ -109,20 +169,9 @@ class EventViewModel: ObservableObject {
color: ColorCodable(randomColor()),
notes: "",
date: Date(),
time: false,
recurrence: .none
)
@Published var editableTemplate: Event = Event(
name: "",
complete: false,
completeDesc: "",
symbol: "star",
color: ColorCodable(randomColor()),
notes: "",
date: Date(),
time: false,
recurrence: .none
)
@Published var editableTemplate: Event
@Published var example: Event = Event(
name: "event",
complete: false,
@@ -131,7 +180,6 @@ class EventViewModel: ObservableObject {
color: ColorCodable(.orange),
notes: "lksdjfakdflkasjlkjl",
date: Date(),
time: true,
recurrence: .daily
)
@@ -141,6 +189,7 @@ class EventViewModel: ObservableObject {
@Published var syncStatus: String = "Not Synced"
init(load: Bool = true) {
self.editableTemplate = template
if load {
loadEvents()
}
@@ -249,57 +298,25 @@ class EventViewModel: ObservableObject {
saveEvents()
}
func exportEvents() -> String? {
func exportEvents() -> String {
let encoder = JSONEncoder()
// Custom date encoding strategy to handle date formatting
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
encoder.dateEncodingStrategy = .formatted(dateFormatter)
do {
// Encode the events array to JSON data
let encodedData = try encoder.encode(events)
// Convert the JSON data to a string
if let jsonString = String(data: encodedData, encoding: .utf8) {
return jsonString
} else {
print("Failed to convert encoded data to string")
return nil
}
} catch {
print("Failed to encode events: \(error.localizedDescription)")
return nil
if let json = try? encoder.encode(self.events) {
return "\(json.base64EncodedString())"
}
return ""
}
func importEvents(_ imp: String) {
guard let impData = imp.data(using: .utf8) else {
print("Failed to convert string to data")
return
func importEvents(_ imported: String) throws {
guard let data = Data(base64Encoded: imported) else {
throw importError.invalidB64
}
// Create a JSONDecoder
let decoder = JSONDecoder()
// Add a custom date formatter for decoding the date string
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ" // Adjust this to the date format you're using
decoder.dateDecodingStrategy = .formatted(dateFormatter)
do {
// Attempt to decode the events from the provided data
let decoded = try decoder.decode([Event].self, from: impData)
print("Successfully decoded events: \(decoded)")
// Save and reload after importing events
let decoded = try decoder.decode([Event].self, from: data)
self.events = decoded
saveEvents()
loadEvents()
} catch {
// Print error if decoding fails
print("Failed to decode events: \(error.localizedDescription)")
throw error
}
}
@@ -352,6 +369,12 @@ class dummyEventViewModel: EventViewModel {
}
}
class dummySettingsViewModel: SettingsViewModel {
override init(load: Bool = false) {
super.init(load: false)
}
}
func describeOccurrence(date: Date, recurrence: Event.RecurrenceType) -> String {
let dateString = date.formatted(date: .long, time: .omitted)
let recurrenceDescription: String
@@ -390,3 +413,13 @@ func randomColor() -> Color {
let b = Double.random(in: 0...1)
return Color(red: r, green: g, blue: b)
}
func plu(_ inp: Int) -> String {
var input = inp
if inp < 0 { input.negate() }
return "\(input == 1 ? "" : "s")"
}
public enum importError: Error {
case invalidB64
}

View File

@@ -22,10 +22,14 @@ struct NearFutureApp: App {
// fatalError("Could not create ModelContainer: \(error)")
// }
// }()
@StateObject var settingsModel: SettingsViewModel = SettingsViewModel()
var body: some Scene {
WindowGroup {
ContentView(viewModel: EventViewModel())
ContentView(
viewModel: EventViewModel(),
settingsModel: settingsModel
)
.tint(settingsModel.settings.tint.color)
}
// .modelContainer(sharedModelContainer)
}

View File

@@ -8,7 +8,8 @@
import SwiftUI
struct SettingsView: View {
@State var viewModel: EventViewModel
@ObservedObject var viewModel: EventViewModel
@ObservedObject var settingsModel: SettingsViewModel
@State private var hasUbiquitous: Bool = false
@State private var lastSyncWasSuccessful: Bool = false
@@ -44,6 +45,28 @@ struct SettingsView: View {
ZStack {
backgroundGradient
List {
ScrollView(.horizontal) {
HStack {
ForEach(settingsModel.accentChoices, id: \.self) { color in
ZStack {
Button() {
settingsModel.settings.tint.colorBind = color
settingsModel.saveSettings()
} label: {
Circle()
.foregroundStyle(color)
.frame(width: 30)
}
if ColorCodable(color) == settingsModel.settings.tint {
let needContrast: Bool = ColorCodable(color) == settingsModel.settings.tint
Circle()
.foregroundStyle(needContrast ? .two : .one)
.frame(width: 10)
}
}
}
}
}
NavigationLink() {
iCloudSettingsView(
viewModel: viewModel,
@@ -68,47 +91,17 @@ struct SettingsView: View {
viewModel.sync()
updateStatus()
}
NavigationLink() {
NavigationStack() {
Button() {
UIPasteboard.general.string = "\(viewModel.exportEvents() ?? "")"
print(viewModel.exportEvents() as Any)
} label: {
Text("copy")
}
Text("\(viewModel.exportEvents() ?? "")")
}
ImportView(viewModel: viewModel, importStr: $importStr)
} label: {
Image(systemName: "list.bullet.rectangle")
Text("Export events")
Label("Import Events", systemImage: "tray.and.arrow.down.fill")
.foregroundStyle(.one)
}
NavigationLink() {
NavigationStack() {
VStack {
TextEditor(text: $importStr)
.foregroundStyle(.foreground, .gray)
.background(.gray)
.frame(width: 200, height: 400)
.shadow(radius: 5)
Button() {
viewModel.importEvents(importStr)
} label: {
Text("import events")
}
.buttonStyle(BorderedProminentButtonStyle())
Button() {
if let pb = UIPasteboard.general.string {
print(pb)
}
} label: {
Text("print pb")
}
}
}
ExportView(viewModel: viewModel)
} label: {
Image(systemName: "square.and.arrow.down")
Text("Import events")
Label("Export Events", systemImage: "square.and.arrow.up")
.foregroundStyle(.one)
}
Section("Tip") {
@@ -135,19 +128,54 @@ struct SettingsView: View {
viewModel.dangerResetiCloud()
}
}
Section("About") {
VStack {
Image(uiImage: #imageLiteral(resourceName: "NearFutureIcon.png"))
.resizable()
.scaledToFit()
.frame(width: 100)
.clipShape(RoundedRectangle(cornerRadius: 25))
Text("Near Future")
.bold()
.monospaced()
.font(.title)
.frame(maxWidth: .infinity)
Text("Version " + getVersion() + " (\(getBuildID()))")
.frame(maxWidth: .infinity)
}
}
}
}
.scrollContentBackground(.hidden)
.navigationTitle("Settings")
.apply {
if #available(iOS 17, *) {
$0.toolbarTitleDisplayMode(.inlineLarge)
} else {
$0.navigationBarTitleDisplayMode(.inline)
}
.scrollContentBackground(.hidden)
.navigationTitle("Settings")
.navigationBarTitleDisplayMode(.inline)
}
}
}
}
#Preview {
SettingsView(viewModel: dummyEventViewModel())
SettingsView(
viewModel: dummyEventViewModel(),
settingsModel: dummySettingsViewModel()
)
}
func test() -> Void {
func getVersion() -> String {
guard let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] else {
fatalError("no bundle id wtf lol")
}
return "\(version)"
}
func getBuildID() -> String {
guard let build = Bundle.main.infoDictionary?["CFBundleVersion"] else {
fatalError("wtf did u do w the build number")
}
return "\(build)"
}

View File

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

View File

@@ -37,145 +37,155 @@ struct iCloudSettingsView: View {
ZStack {
backgroundGradient
List {
HStack {
Spacer()
VStack {
ZStack {
Image(systemName: "icloud")
.resizable()
.scaledToFit()
.frame(width: 75, height: 55)
.symbolRenderingMode(.multicolor)
Text("\(viewModel.icloudEventCount)")
.font(.title2)
.monospaced()
.bold()
}
Text("iCloud")
HStack {
Button(role: .destructive) {
showPushAlert.toggle()
} label: {
Image(systemName: "arrow.up")
Section {
HStack {
Spacer()
VStack {
ZStack {
Image(systemName: "icloud")
.resizable()
.scaledToFit()
.frame(width: 30, height: 40)
.frame(width: 75, height: 55)
.symbolRenderingMode(.multicolor)
Text("\(viewModel.icloudEventCount)")
.font(.title2)
.monospaced()
.bold()
}
.buttonStyle(BorderedButtonStyle())
.alert("Warning", isPresented: $showPushAlert) {
Button("OK", role: .destructive) {
viewModel.replaceiCloudWithLocalData()
Text("iCloud")
HStack {
Button(role: .destructive) {
showPushAlert.toggle()
} label: {
Image(systemName: "arrow.up")
.resizable()
.scaledToFit()
.frame(width: 30, height: 40)
}
.buttonStyle(BorderedButtonStyle())
.alert("Warning", isPresented: $showPushAlert) {
Button("OK", role: .destructive) {
viewModel.replaceiCloudWithLocalData()
viewModel.sync()
updateStatus()
}
Button("Cancel", role: .cancel) {}
} message: {
Text("This will replace Events stored in iCloud with Events stored locally.")
}
Button() {
viewModel.sync()
updateStatus()
} label: {
Image(systemName: "arrow.triangle.2.circlepath")
.resizable()
.scaledToFit()
.frame(width: 30, height: 40)
.foregroundStyle(Color.accentColor)
}
.buttonStyle(BorderedButtonStyle())
Button(role: .destructive) {
showPullAlert.toggle()
} label: {
Image(systemName: "arrow.down")
.resizable()
.scaledToFit()
.frame(width: 30, height: 40)
}
.buttonStyle(BorderedButtonStyle())
.alert("Warning", isPresented: $showPullAlert) {
Button("OK", role: .destructive) {
viewModel.replaceLocalWithiCloudData()
viewModel.sync()
updateStatus()
}
Button("Cancel", role: .cancel) {}
} message: {
Text("This will replace Events stored locally with Events stored in iCloud.")
}
Button("Cancel", role: .cancel) {}
} message: {
Text("This will replace Events stored in iCloud with Events stored locally.")
}
Button() {
viewModel.sync()
updateStatus()
} label: {
Image(systemName: "arrow.triangle.2.circlepath")
ZStack {
Image(systemName: device.sf)
.resizable()
.scaledToFit()
.frame(width: 30, height: 40)
.foregroundStyle(Color.accentColor)
}
.buttonStyle(BorderedButtonStyle())
Button(role: .destructive) {
showPullAlert.toggle()
} label: {
Image(systemName: "arrow.down")
.resizable()
.scaledToFit()
.frame(width: 30, height: 40)
}
.buttonStyle(BorderedButtonStyle())
.alert("Warning", isPresented: $showPullAlert) {
Button("OK", role: .destructive) {
viewModel.replaceLocalWithiCloudData()
viewModel.sync()
updateStatus()
}
Button("Cancel", role: .cancel) {}
} message: {
Text("This will replace Events stored locally with Events stored in iCloud.")
.frame(width: 75, height: 75)
.symbolRenderingMode(.monochrome)
Text("\(viewModel.localEventCount)")
.font(.title2)
.monospaced()
.bold()
}
Text(device.label)
}
ZStack {
Image(systemName: device.sf)
.resizable()
.scaledToFit()
.frame(width: 75, height: 75)
.symbolRenderingMode(.monochrome)
Text("\(viewModel.localEventCount)")
.font(.title2)
.monospaced()
.bold()
}
Text(device.label)
Spacer()
}
Spacer()
}
.listRowSeparator(.hidden)
.onAppear {
viewModel.sync()
updateStatus()
}
HStack {
Circle()
.frame(width: 20, height: 20)
.foregroundStyle(hasUbiquitous ? .green : .red)
Text("iCloud")
Spacer()
Text("\(hasUbiquitous ? "" : "Not ")Working")
.bold()
}
HStack {
Circle()
.frame(width: 20, height: 20)
.foregroundStyle(lastSyncWasSuccessful ? .green : .red)
.listRowSeparator(.hidden)
.onAppear {
viewModel.sync()
updateStatus()
}
HStack {
Circle()
.frame(width: 20, height: 20)
.foregroundStyle(hasUbiquitous ? .green : .red)
Text("iCloud")
Spacer()
Text("\(hasUbiquitous ? "" : "Not ")Working")
.bold()
}
HStack {
Circle()
.frame(width: 20, height: 20)
.foregroundStyle(lastSyncWasSuccessful ? .green : .red)
Text("Sync Status")
Spacer()
Text("\(viewModel.syncStatus)")
.bold()
}
HStack {
Circle()
.frame(width: 20, height: 20)
.foregroundStyle(lastSyncWasNormalAgo ? .green : .red)
Text("Last Sync")
Spacer()
Text("\(viewModel.lastSync?.formatted(date: .long, time: .standard) ?? "Never")")
.bold()
}
HStack {
Circle()
.frame(width: 20, height: 20)
.foregroundStyle(localCountEqualToiCloud ? .green : .red)
Text("Local Events")
Spacer()
Text("\(viewModel.localEventCount)")
.bold()
}
HStack {
Circle()
.frame(width: 20, height: 20)
.foregroundStyle(icloudCountEqualToLocal ? .green : .red)
Text("Events in iCloud")
Spacer()
Text("\(viewModel.icloudEventCount)")
.bold()
}
} header: {
Text("Sync Status")
Spacer()
Text("\(viewModel.syncStatus)")
.bold()
}
HStack {
Circle()
.frame(width: 20, height: 20)
.foregroundStyle(lastSyncWasNormalAgo ? .green : .red)
Text("Last Sync")
Spacer()
Text("\(viewModel.lastSync?.formatted() ?? "Never")")
.bold()
}
HStack {
Circle()
.frame(width: 20, height: 20)
.foregroundStyle(localCountEqualToiCloud ? .green : .red)
Text("Local Events")
Spacer()
Text("\(viewModel.localEventCount)")
.bold()
}
HStack {
Circle()
.frame(width: 20, height: 20)
.foregroundStyle(icloudCountEqualToLocal ? .green : .red)
Text("Events in iCloud")
Spacer()
Text("\(viewModel.icloudEventCount)")
.bold()
} footer: {
Text("Pull to sync\nOr use the arrows to force push/pull")
}
}
.refreshable {
viewModel.sync()
updateStatus()
}
.scrollContentBackground(.hidden)
.navigationTitle("iCloud")
.navigationBarTitleDisplayMode(.inline)
@@ -191,6 +201,6 @@ struct iCloudSettingsView: View {
lastSyncWasNormalAgo: .constant(true),
localCountEqualToiCloud: .constant(true),
icloudCountEqualToLocal: .constant(true),
updateStatus: test
updateStatus: {}
)
}

View File

@@ -128,11 +128,19 @@ struct EventWidgetView: View {
Spacer()
//short days till if not large widget
Text(daysUntilEvent(event.date, short: !isLarge, sepLines: true))
.font(.caption)
.multilineTextAlignment(.trailing)
.foregroundColor(event.color.color)
.padding(.trailing, -12)
if isLarge {
Text(daysUntilEvent(event.date).long)
.font(.caption)
.multilineTextAlignment(.trailing)
.foregroundColor(event.color.color)
.padding(.trailing, -12)
} else {
Text(daysUntilEvent(event.date).short)
.font(.caption)
.multilineTextAlignment(.trailing)
.foregroundColor(event.color.color)
.padding(.trailing, -12)
}
}
}
Spacer()

View File

@@ -5,76 +5,76 @@
// Created by Nihaal Sharma on 02/01/2025.
//
import ActivityKit
//import ActivityKit
import WidgetKit
import SwiftUI
struct NearFutureWidgetsAttributes: ActivityAttributes {
public struct ContentState: Codable, Hashable {
// Dynamic stateful properties about your activity go here!
var emoji: String
}
//struct NearFutureWidgetsAttributes: ActivityAttributes {
// public struct ContentState: Codable, Hashable {
// // Dynamic stateful properties about your activity go here!
// var emoji: String
// }
//
// // Fixed non-changing properties about your activity go here!
// var name: String
//}
// Fixed non-changing properties about your activity go here!
var name: String
}
//struct NearFutureWidgetsLiveActivity: Widget {
// var body: some WidgetConfiguration {
// ActivityConfiguration(for: NearFutureWidgetsAttributes.self) { context in
// // Lock screen/banner UI goes here
// VStack {
// Text("Hello \(context.state.emoji)")
// }
// .activityBackgroundTint(Color.cyan)
// .activitySystemActionForegroundColor(Color.black)
//
// } dynamicIsland: { context in
// DynamicIsland {
// // Expanded UI goes here. Compose the expanded UI through
// // various regions, like leading/trailing/center/bottom
// DynamicIslandExpandedRegion(.leading) {
// Text("Leading")
// }
// DynamicIslandExpandedRegion(.trailing) {
// Text("Trailing")
// }
// DynamicIslandExpandedRegion(.bottom) {
// Text("Bottom \(context.state.emoji)")
// // more content
// }
// } compactLeading: {
// Text("L")
// } compactTrailing: {
// Text("T \(context.state.emoji)")
// } minimal: {
// Text(context.state.emoji)
// }
// .widgetURL(URL(string: "http://www.apple.com"))
// .keylineTint(Color.red)
// }
// }
//}
//
//extension NearFutureWidgetsAttributes {
// fileprivate static var preview: NearFutureWidgetsAttributes {
// NearFutureWidgetsAttributes(name: "World")
// }
//}
struct NearFutureWidgetsLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: NearFutureWidgetsAttributes.self) { context in
// Lock screen/banner UI goes here
VStack {
Text("Hello \(context.state.emoji)")
}
.activityBackgroundTint(Color.cyan)
.activitySystemActionForegroundColor(Color.black)
} dynamicIsland: { context in
DynamicIsland {
// Expanded UI goes here. Compose the expanded UI through
// various regions, like leading/trailing/center/bottom
DynamicIslandExpandedRegion(.leading) {
Text("Leading")
}
DynamicIslandExpandedRegion(.trailing) {
Text("Trailing")
}
DynamicIslandExpandedRegion(.bottom) {
Text("Bottom \(context.state.emoji)")
// more content
}
} compactLeading: {
Text("L")
} compactTrailing: {
Text("T \(context.state.emoji)")
} minimal: {
Text(context.state.emoji)
}
.widgetURL(URL(string: "http://www.apple.com"))
.keylineTint(Color.red)
}
}
}
extension NearFutureWidgetsAttributes {
fileprivate static var preview: NearFutureWidgetsAttributes {
NearFutureWidgetsAttributes(name: "World")
}
}
extension NearFutureWidgetsAttributes.ContentState {
fileprivate static var smiley: NearFutureWidgetsAttributes.ContentState {
NearFutureWidgetsAttributes.ContentState(emoji: "😀")
}
fileprivate static var starEyes: NearFutureWidgetsAttributes.ContentState {
NearFutureWidgetsAttributes.ContentState(emoji: "🤩")
}
}
#Preview("Notification", as: .content, using: NearFutureWidgetsAttributes.preview) {
NearFutureWidgetsLiveActivity()
} contentStates: {
NearFutureWidgetsAttributes.ContentState.smiley
NearFutureWidgetsAttributes.ContentState.starEyes
}
//extension NearFutureWidgetsAttributes.ContentState {
// fileprivate static var smiley: NearFutureWidgetsAttributes.ContentState {
// NearFutureWidgetsAttributes.ContentState(emoji: "😀")
// }
//
// fileprivate static var starEyes: NearFutureWidgetsAttributes.ContentState {
// NearFutureWidgetsAttributes.ContentState(emoji: "🤩")
// }
//}
//
//#Preview("Notification", as: .content, using: NearFutureWidgetsAttributes.preview) {
// NearFutureWidgetsLiveActivity()
//} contentStates: {
// NearFutureWidgetsAttributes.ContentState.smiley
// NearFutureWidgetsAttributes.ContentState.starEyes
//}

View File

@@ -1,5 +1,6 @@
# NearFuture
[![AppStore Link](https://github.com/neon443/NearFuture/blob/main/NearFuture/Images/appstore.png?raw=true)](https://apps.apple.com/us/app/near-future-event-tracker/id6744963429)
[App Store](https://apps.apple.com/us/app/near-future-event-tracker/id6744963429)
**Near Future** is a SwiftUI App to help people track upcoming events - Holidays, Trips, Birthdays, Weddings, Anniversaries.