Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Match the surrounding code. It's consistent, and consistency beats personal pref
- **Design:** iOS 26 Liquid Glass. `.glassEffect()` on card surfaces, `Color(.systemBackground)` for page backgrounds. Don't nest glass in glass.
- **Motion:** Every animation respects `accessibilityReduceMotion`.
- **Comments:** Explain *why*, not *what*. The non-obvious constraint, the bug this guards against, the reason it isn't the simpler thing.
- **No third-party dependencies.** Apple frameworks only. This is intentional.
- **Almost no third-party dependencies.** Google Mobile Ads and its User Messaging Platform dependency support the ad-funded free tier; everything else is Apple frameworks. Adding a new third-party package needs a strong justification and an explicit decision.

## Commit messages

Expand Down
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@

<p align="center">
Built for iOS with SwiftUI, SwiftData, and WeatherKit.<br/>
No accounts. No tracking. No third-party dependencies.
No accounts. The free version shows banner ads; the optional SunHat Ad-Free
subscription removes them.
</p>

---
Expand Down Expand Up @@ -52,7 +53,7 @@ You describe the conditions you care about. SunHat checks the forecast in the ba
| **Spotlight indexing** | Find reminders from iOS search |
| **Data export and deletion** | Full GDPR-style export and deletion, covered by schema-parity tests |
| **Liquid Glass UI** | Native iOS 26 design with `.glassEffect()` surfaces |
| **Zero tracking** | No accounts, no analytics, no third-party SDKs |
| **Ad-free option** | The free version shows banner ads (Google AdMob); the SunHat Ad-Free subscription removes them. No accounts, and SunHat collects no analytics of its own |

## Screenshots

Expand All @@ -79,9 +80,11 @@ Captured on iOS 26 (iPhone). SunHat is iPhone-first. iPad, widget, and watch sur
| Xcode | 26+ |
| Swift | 6.2 |
| Minimum iOS | 26.0 |
| Dependencies | None. Apple frameworks only. |
| Dependencies | Google Mobile Ads + User Messaging Platform (SPM), for the ad-supported free tier. Everything else is Apple frameworks. |

No CocoaPods, no SPM packages, no Carthage. Every import is an Apple framework, and that's intentional.
No CocoaPods, no Carthage. The only SPM packages are Google Mobile Ads and its
User Messaging Platform dependency, which exist to support the ad-funded free
tier; every other import is an Apple framework, and that's intentional.

## Getting started

Expand Down
86 changes: 86 additions & 0 deletions SunHat.storekit
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{
"identifier" : "FDFD818F-1B0F-4818-8178-F54144221F17",
"nonRenewingSubscriptions" : [

],
"products" : [

],
"settings" : {
"_askToBuyEnabled" : false,
"_billingGracePeriodEnabled" : true,
"_developerTeamID" : "HD39MR492X",
"_failTransactionsEnabled" : false,
"_locale" : "en_US",
"_storefront" : "USA"
},
"subscriptionGroups" : [
{
"id" : "A0B1E0B0-E228-4BD8-B4E4-EEF9FC150D77",
"localizations" : [
{
"description" : "Removes all ads from SunHat.",
"displayName" : "SunHat Ad-Free",
"locale" : "en_US"
}
],
"name" : "SunHat Ad-Free",
"subscriptions" : [
{
"adHocOffers" : [

],
"codeOffers" : [

],
"displayPrice" : "1.00",
"familyShareable" : false,
"groupNumber" : 1,
"internalID" : "64420497-E937-44EC-A22A-785480ABDFDF",
"introductoryOffer" : null,
"localizations" : [
{
"description" : "Removes all ads from SunHat.",
"displayName" : "Ad-Free Monthly",
"locale" : "en_US"
}
],
"productID" : "org.wesley.sunhat.adfree.monthly",
"recurringSubscriptionPeriod" : "P1M",
"referenceName" : "Ad-Free Monthly",
"subscriptionGroupID" : "A0B1E0B0-E228-4BD8-B4E4-EEF9FC150D77",
"type" : "RecurringSubscription"
},
{
"adHocOffers" : [

],
"codeOffers" : [

],
"displayPrice" : "10.00",
"familyShareable" : false,
"groupNumber" : 1,
"internalID" : "B7A93E52-4C11-4E8D-9F30-2D6E8C41A9D0",
"introductoryOffer" : null,
"localizations" : [
{
"description" : "Best value — two months free vs. monthly.",
"displayName" : "Ad-Free Annual",
"locale" : "en_US"
}
],
"productID" : "org.wesley.sunhat.adfree.yearly",
"recurringSubscriptionPeriod" : "P1Y",
"referenceName" : "Ad-Free Annual",
"subscriptionGroupID" : "A0B1E0B0-E228-4BD8-B4E4-EEF9FC150D77",
"type" : "RecurringSubscription"
}
]
}
],
"version" : {
"major" : 3,
"minor" : 0
}
}
40 changes: 34 additions & 6 deletions SunHat.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,16 @@
};
/* End PBXFileSystemSynchronizedRootGroup section */

/* Begin PBXBuildFile section */
ADF0000000000000000000C3 /* GoogleMobileAds in Frameworks */ = {isa = PBXBuildFile; productRef = ADF0000000000000000000C2 /* GoogleMobileAds */; };
/* End PBXBuildFile section */

/* Begin PBXFrameworksBuildPhase section */
64120D192E2D64CB0034CB20 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
ADF0000000000000000000C3 /* GoogleMobileAds in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -148,6 +153,7 @@
);
name = SunHat;
packageProductDependencies = (
ADF0000000000000000000C2 /* GoogleMobileAds */,
);
productName = SunHat;
productReference = 64120D1C2E2D64CB0034CB20 /* SunHat.app */;
Expand Down Expand Up @@ -232,6 +238,9 @@
);
mainGroup = 64120D132E2D64CA0034CB20;
minimizedProjectReferenceProxies = 1;
packageReferences = (
ADF0000000000000000000C1 /* XCRemoteSwiftPackageReference "swift-package-manager-google-mobile-ads" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = 64120D1D2E2D64CB0034CB20 /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -325,7 +334,7 @@
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 26.4;
IPHONEOS_DEPLOYMENT_TARGET = 26.5;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down Expand Up @@ -362,7 +371,7 @@
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 26.4;
IPHONEOS_DEPLOYMENT_TARGET = 26.5;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down Expand Up @@ -432,7 +441,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.4;
IPHONEOS_DEPLOYMENT_TARGET = 26.5;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
Expand Down Expand Up @@ -491,7 +500,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.4;
IPHONEOS_DEPLOYMENT_TARGET = 26.5;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
Expand All @@ -510,7 +519,7 @@
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = HD39MR492X;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.4;
IPHONEOS_DEPLOYMENT_TARGET = 26.5;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.wesley.sunhatTests;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -531,7 +540,7 @@
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = HD39MR492X;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.4;
IPHONEOS_DEPLOYMENT_TARGET = 26.5;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.wesley.sunhatTests;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down Expand Up @@ -622,6 +631,25 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */

/* Begin XCRemoteSwiftPackageReference section */
ADF0000000000000000000C1 /* XCRemoteSwiftPackageReference "swift-package-manager-google-mobile-ads" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/googleads/swift-package-manager-google-mobile-ads.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 12.0.0;
};
};
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
ADF0000000000000000000C2 /* GoogleMobileAds */ = {
isa = XCSwiftPackageProductDependency;
package = ADF0000000000000000000C1 /* XCRemoteSwiftPackageReference "swift-package-manager-google-mobile-ads" */;
productName = GoogleMobileAds;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 64120D142E2D64CA0034CB20 /* Project object */;
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions SunHat.xcodeproj/xcshareddata/xcschemes/SunHat.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<StoreKitConfigurationFileReference
identifier = "../SunHat.storekit">
</StoreKitConfigurationFileReference>
<Testables>
<TestableReference
skipped = "NO"
Expand Down Expand Up @@ -101,6 +104,9 @@
ReferencedContainer = "container:SunHat.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<StoreKitConfigurationFileReference
identifier = "../SunHat.storekit">
</StoreKitConfigurationFileReference>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
Expand Down
Loading