diff --git a/DolphinController.xcodeproj/project.pbxproj b/DolphinController.xcodeproj/project.pbxproj index 36c835c..86f67e1 100644 --- a/DolphinController.xcodeproj/project.pbxproj +++ b/DolphinController.xcodeproj/project.pbxproj @@ -63,6 +63,12 @@ C59D538E28D6776B005AFEC8 /* IdleManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C59D538D28D6776B005AFEC8 /* IdleManager.swift */; }; C59D539028D69A9E005AFEC8 /* HelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C59D538F28D69A9E005AFEC8 /* HelpView.swift */; }; C59D539228D781C9005AFEC8 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = C59D539128D781C9005AFEC8 /* README.md */; }; + C5F515452D46A2140015180F /* Skin.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5F515442D46A2130015180F /* Skin.swift */; }; + C5F515462D46A3180015180F /* Skin.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5F515442D46A2130015180F /* Skin.swift */; }; + C5F515492D46B3860015180F /* SkinSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5F515482D46B3840015180F /* SkinSelector.swift */; }; + C5F5154B2D46BF980015180F /* DolphinController.storekit in Resources */ = {isa = PBXBuildFile; fileRef = C5F5154A2D46BF980015180F /* DolphinController.storekit */; }; + C5F515502D46D86B0015180F /* ClearSkinView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5F5154F2D46D8670015180F /* ClearSkinView.swift */; }; + C5F515522D47116C0015180F /* SupportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5F515512D4711690015180F /* SupportView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -159,6 +165,11 @@ C59D538F28D69A9E005AFEC8 /* HelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpView.swift; sourceTree = ""; }; C59D539128D781C9005AFEC8 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; C59D539328D781DA005AFEC8 /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = ""; }; + C5F515442D46A2130015180F /* Skin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Skin.swift; sourceTree = ""; }; + C5F515482D46B3840015180F /* SkinSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkinSelector.swift; sourceTree = ""; }; + C5F5154A2D46BF980015180F /* DolphinController.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = DolphinController.storekit; sourceTree = ""; }; + C5F5154F2D46D8670015180F /* ClearSkinView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearSkinView.swift; sourceTree = ""; }; + C5F515512D4711690015180F /* SupportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -205,6 +216,9 @@ C50E761926B7CA8100FB8C2C /* Views */ = { isa = PBXGroup; children = ( + C5F515512D4711690015180F /* SupportView.swift */, + C5F5154F2D46D8670015180F /* ClearSkinView.swift */, + C5F515482D46B3840015180F /* SkinSelector.swift */, C52CD4C326C80B850081ABF5 /* LightView.swift */, C52A64C026DC02A1001A39C7 /* PingView.swift */, C52A64C426DC1965001A39C7 /* GCButton.swift */, @@ -274,9 +288,11 @@ C53D554A26B43D4E00E1F75B /* iOS */ = { isa = PBXGroup; children = ( + C5F515442D46A2130015180F /* Skin.swift */, C50E761926B7CA8100FB8C2C /* Views */, C53D554126B43D4C00E1F75B /* DolphinControllerApp.swift */, C51271BE26D2F9AF00820249 /* Storage.swift */, + C5F5154A2D46BF980015180F /* DolphinController.storekit */, C50E761B26B83F2400FB8C2C /* Haptics.swift */, C53D55AA26B5362700E1F75B /* Client.swift */, C59D538D28D6776B005AFEC8 /* IdleManager.swift */, @@ -507,6 +523,7 @@ files = ( C59D539228D781C9005AFEC8 /* README.md in Resources */, C53D556E26B43D4F00E1F75B /* Assets.xcassets in Resources */, + C5F5154B2D46BF980015180F /* DolphinController.storekit in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -552,6 +569,7 @@ C52A64C326DC1910001A39C7 /* ControllerView.swift in Sources */, C53D559026B443D500E1F75B /* Networking.swift in Sources */, C50E761C26B83F2400FB8C2C /* Haptics.swift in Sources */, + C5F515492D46B3860015180F /* SkinSelector.swift in Sources */, C53D558D26B4402E00E1F75B /* ContentView.swift in Sources */, C55E1CD12B23CD30005A98F5 /* ActivityManager.swift in Sources */, C54BE4AA26D495D500530927 /* PolygonShape.swift in Sources */, @@ -563,14 +581,17 @@ C52A64C726DC199A001A39C7 /* PressAction.swift in Sources */, C52528B326DAEFB000902B87 /* SettingsView.swift in Sources */, C52A64C126DC02A1001A39C7 /* PingView.swift in Sources */, + C5F515502D46D86B0015180F /* ClearSkinView.swift in Sources */, C51271B726D2B14300820249 /* Theme.swift in Sources */, C51271C026D2F9B200820249 /* Storage.swift in Sources */, C59D539028D69A9E005AFEC8 /* HelpView.swift in Sources */, C53D558726B43F3C00E1F75B /* ObservableObject.swift in Sources */, + C5F515452D46A2140015180F /* Skin.swift in Sources */, C59D538E28D6776B005AFEC8 /* IdleManager.swift in Sources */, C52CD4BA26C44C970081ABF5 /* ServerBrowserView.swift in Sources */, C55E1CF32B269A6B005A98F5 /* ActivityAttributes.swift in Sources */, C50E761726B7CA6B00FB8C2C /* JoystickView.swift in Sources */, + C5F515522D47116C0015180F /* SupportView.swift in Sources */, C53D55AB26B5362700E1F75B /* Client.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -593,6 +614,7 @@ C53D559126B443D500E1F75B /* Networking.swift in Sources */, C518B0D426D5E8480042A931 /* ControllerPlugView.swift in Sources */, C52CD4C226C7F6DB0081ABF5 /* ControllerConnection.swift in Sources */, + C5F515462D46A3180015180F /* Skin.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -767,8 +789,10 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = ""; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 6D9DLTQJSN; ENABLE_PREVIEWS = YES; @@ -790,8 +814,10 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = ""; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 6D9DLTQJSN; ENABLE_PREVIEWS = YES; diff --git a/DolphinController.xcodeproj/xcshareddata/xcschemes/DolphinController (iOS).xcscheme b/DolphinController.xcodeproj/xcshareddata/xcschemes/DolphinController (iOS).xcscheme new file mode 100644 index 0000000..e0216fd --- /dev/null +++ b/DolphinController.xcodeproj/xcshareddata/xcschemes/DolphinController (iOS).xcscheme @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DolphinController.xcodeproj/xcshareddata/xcschemes/WidgetExtensionExtension.xcscheme b/DolphinController.xcodeproj/xcshareddata/xcschemes/WidgetExtensionExtension.xcscheme new file mode 100644 index 0000000..2d63f39 --- /dev/null +++ b/DolphinController.xcodeproj/xcshareddata/xcschemes/WidgetExtensionExtension.xcscheme @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Shared/Assets.xcassets/AppIcon Clear.appiconset/App_store_1024_1x.png b/Shared/Assets.xcassets/AppIcon Clear.appiconset/App_store_1024_1x.png new file mode 100644 index 0000000..51cce77 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon Clear.appiconset/App_store_1024_1x.png differ diff --git a/Shared/Assets.xcassets/AppIcon Clear.appiconset/Contents.json b/Shared/Assets.xcassets/AppIcon Clear.appiconset/Contents.json new file mode 100644 index 0000000..35026eb --- /dev/null +++ b/Shared/Assets.xcassets/AppIcon Clear.appiconset/Contents.json @@ -0,0 +1,36 @@ +{ + "images" : [ + { + "filename" : "App_store_1024_1x.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/AppIcon Clear.imageset/App_store_1024_1x.png b/Shared/Assets.xcassets/AppIcon Clear.imageset/App_store_1024_1x.png new file mode 100644 index 0000000..51cce77 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon Clear.imageset/App_store_1024_1x.png differ diff --git a/Shared/Assets.xcassets/AppIcon Clear.imageset/Contents.json b/Shared/Assets.xcassets/AppIcon Clear.imageset/Contents.json new file mode 100644 index 0000000..7660558 --- /dev/null +++ b/Shared/Assets.xcassets/AppIcon Clear.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "App_store_1024_1x.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/AppIcon Emerald Blue.appiconset/App_store_1024_1x.png b/Shared/Assets.xcassets/AppIcon Emerald Blue.appiconset/App_store_1024_1x.png new file mode 100644 index 0000000..4079084 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon Emerald Blue.appiconset/App_store_1024_1x.png differ diff --git a/Shared/Assets.xcassets/AppIcon Emerald Blue.appiconset/Contents.json b/Shared/Assets.xcassets/AppIcon Emerald Blue.appiconset/Contents.json new file mode 100644 index 0000000..35026eb --- /dev/null +++ b/Shared/Assets.xcassets/AppIcon Emerald Blue.appiconset/Contents.json @@ -0,0 +1,36 @@ +{ + "images" : [ + { + "filename" : "App_store_1024_1x.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/AppIcon Emerald Blue.imageset/App_store_1024_1x.png b/Shared/Assets.xcassets/AppIcon Emerald Blue.imageset/App_store_1024_1x.png new file mode 100644 index 0000000..4079084 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon Emerald Blue.imageset/App_store_1024_1x.png differ diff --git a/Shared/Assets.xcassets/AppIcon Emerald Blue.imageset/Contents.json b/Shared/Assets.xcassets/AppIcon Emerald Blue.imageset/Contents.json new file mode 100644 index 0000000..7660558 --- /dev/null +++ b/Shared/Assets.xcassets/AppIcon Emerald Blue.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "App_store_1024_1x.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/AppIcon Jet Black.appiconset/App_store_1024_1x.png b/Shared/Assets.xcassets/AppIcon Jet Black.appiconset/App_store_1024_1x.png new file mode 100644 index 0000000..fe3bdb8 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon Jet Black.appiconset/App_store_1024_1x.png differ diff --git a/Shared/Assets.xcassets/AppIcon Jet Black.appiconset/Contents.json b/Shared/Assets.xcassets/AppIcon Jet Black.appiconset/Contents.json new file mode 100644 index 0000000..35026eb --- /dev/null +++ b/Shared/Assets.xcassets/AppIcon Jet Black.appiconset/Contents.json @@ -0,0 +1,36 @@ +{ + "images" : [ + { + "filename" : "App_store_1024_1x.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/AppIcon Jet Black.imageset/App_store_1024_1x.png b/Shared/Assets.xcassets/AppIcon Jet Black.imageset/App_store_1024_1x.png new file mode 100644 index 0000000..fe3bdb8 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon Jet Black.imageset/App_store_1024_1x.png differ diff --git a/Shared/Assets.xcassets/AppIcon Jet Black.imageset/Contents.json b/Shared/Assets.xcassets/AppIcon Jet Black.imageset/Contents.json new file mode 100644 index 0000000..7660558 --- /dev/null +++ b/Shared/Assets.xcassets/AppIcon Jet Black.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "App_store_1024_1x.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/AppIcon Pearl White.appiconset/App_store_1024_1x.png b/Shared/Assets.xcassets/AppIcon Pearl White.appiconset/App_store_1024_1x.png new file mode 100644 index 0000000..126f272 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon Pearl White.appiconset/App_store_1024_1x.png differ diff --git a/Shared/Assets.xcassets/AppIcon Pearl White.appiconset/Contents.json b/Shared/Assets.xcassets/AppIcon Pearl White.appiconset/Contents.json new file mode 100644 index 0000000..35026eb --- /dev/null +++ b/Shared/Assets.xcassets/AppIcon Pearl White.appiconset/Contents.json @@ -0,0 +1,36 @@ +{ + "images" : [ + { + "filename" : "App_store_1024_1x.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/AppIcon Pearl White.imageset/App_store_1024_1x.png b/Shared/Assets.xcassets/AppIcon Pearl White.imageset/App_store_1024_1x.png new file mode 100644 index 0000000..126f272 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon Pearl White.imageset/App_store_1024_1x.png differ diff --git a/Shared/Assets.xcassets/AppIcon Pearl White.imageset/Contents.json b/Shared/Assets.xcassets/AppIcon Pearl White.imageset/Contents.json new file mode 100644 index 0000000..7660558 --- /dev/null +++ b/Shared/Assets.xcassets/AppIcon Pearl White.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "App_store_1024_1x.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/AppIcon Platinum.appiconset/App_store_1024_1x.png b/Shared/Assets.xcassets/AppIcon Platinum.appiconset/App_store_1024_1x.png new file mode 100644 index 0000000..cdd32d2 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon Platinum.appiconset/App_store_1024_1x.png differ diff --git a/Shared/Assets.xcassets/AppIcon Platinum.appiconset/Contents.json b/Shared/Assets.xcassets/AppIcon Platinum.appiconset/Contents.json new file mode 100644 index 0000000..35026eb --- /dev/null +++ b/Shared/Assets.xcassets/AppIcon Platinum.appiconset/Contents.json @@ -0,0 +1,36 @@ +{ + "images" : [ + { + "filename" : "App_store_1024_1x.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/AppIcon Platinum.imageset/App_store_1024_1x.png b/Shared/Assets.xcassets/AppIcon Platinum.imageset/App_store_1024_1x.png new file mode 100644 index 0000000..cdd32d2 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon Platinum.imageset/App_store_1024_1x.png differ diff --git a/Shared/Assets.xcassets/AppIcon Platinum.imageset/Contents.json b/Shared/Assets.xcassets/AppIcon Platinum.imageset/Contents.json new file mode 100644 index 0000000..7660558 --- /dev/null +++ b/Shared/Assets.xcassets/AppIcon Platinum.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "App_store_1024_1x.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/AppIcon Spice Orange.appiconset/App_store_1024_1x.png b/Shared/Assets.xcassets/AppIcon Spice Orange.appiconset/App_store_1024_1x.png new file mode 100644 index 0000000..3e3afae Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon Spice Orange.appiconset/App_store_1024_1x.png differ diff --git a/Shared/Assets.xcassets/AppIcon Spice Orange.appiconset/Contents.json b/Shared/Assets.xcassets/AppIcon Spice Orange.appiconset/Contents.json new file mode 100644 index 0000000..35026eb --- /dev/null +++ b/Shared/Assets.xcassets/AppIcon Spice Orange.appiconset/Contents.json @@ -0,0 +1,36 @@ +{ + "images" : [ + { + "filename" : "App_store_1024_1x.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/AppIcon Spice Orange.imageset/App_store_1024_1x.png b/Shared/Assets.xcassets/AppIcon Spice Orange.imageset/App_store_1024_1x.png new file mode 100644 index 0000000..3e3afae Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon Spice Orange.imageset/App_store_1024_1x.png differ diff --git a/Shared/Assets.xcassets/AppIcon Spice Orange.imageset/Contents.json b/Shared/Assets.xcassets/AppIcon Spice Orange.imageset/Contents.json new file mode 100644 index 0000000..7660558 --- /dev/null +++ b/Shared/Assets.xcassets/AppIcon Spice Orange.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "App_store_1024_1x.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/AppIcon Starlight Gold.appiconset/App_store_1024_1x.png b/Shared/Assets.xcassets/AppIcon Starlight Gold.appiconset/App_store_1024_1x.png new file mode 100644 index 0000000..2b371a2 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon Starlight Gold.appiconset/App_store_1024_1x.png differ diff --git a/Shared/Assets.xcassets/AppIcon Starlight Gold.appiconset/Contents.json b/Shared/Assets.xcassets/AppIcon Starlight Gold.appiconset/Contents.json new file mode 100644 index 0000000..35026eb --- /dev/null +++ b/Shared/Assets.xcassets/AppIcon Starlight Gold.appiconset/Contents.json @@ -0,0 +1,36 @@ +{ + "images" : [ + { + "filename" : "App_store_1024_1x.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/AppIcon Starlight Gold.imageset/App_store_1024_1x.png b/Shared/Assets.xcassets/AppIcon Starlight Gold.imageset/App_store_1024_1x.png new file mode 100644 index 0000000..2b371a2 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon Starlight Gold.imageset/App_store_1024_1x.png differ diff --git a/Shared/Assets.xcassets/AppIcon Starlight Gold.imageset/Contents.json b/Shared/Assets.xcassets/AppIcon Starlight Gold.imageset/Contents.json new file mode 100644 index 0000000..7660558 --- /dev/null +++ b/Shared/Assets.xcassets/AppIcon Starlight Gold.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "App_store_1024_1x.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/AppIcon Symphonic Green.appiconset/App_store_1024_1x.png b/Shared/Assets.xcassets/AppIcon Symphonic Green.appiconset/App_store_1024_1x.png new file mode 100644 index 0000000..87a7207 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon Symphonic Green.appiconset/App_store_1024_1x.png differ diff --git a/Shared/Assets.xcassets/AppIcon Symphonic Green.appiconset/Contents.json b/Shared/Assets.xcassets/AppIcon Symphonic Green.appiconset/Contents.json new file mode 100644 index 0000000..35026eb --- /dev/null +++ b/Shared/Assets.xcassets/AppIcon Symphonic Green.appiconset/Contents.json @@ -0,0 +1,36 @@ +{ + "images" : [ + { + "filename" : "App_store_1024_1x.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/AppIcon Symphonic Green.imageset/App_store_1024_1x.png b/Shared/Assets.xcassets/AppIcon Symphonic Green.imageset/App_store_1024_1x.png new file mode 100644 index 0000000..87a7207 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon Symphonic Green.imageset/App_store_1024_1x.png differ diff --git a/Shared/Assets.xcassets/AppIcon Symphonic Green.imageset/Contents.json b/Shared/Assets.xcassets/AppIcon Symphonic Green.imageset/Contents.json new file mode 100644 index 0000000..7660558 --- /dev/null +++ b/Shared/Assets.xcassets/AppIcon Symphonic Green.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "App_store_1024_1x.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/AppIcon White.appiconset/App_store_1024_1x.png b/Shared/Assets.xcassets/AppIcon White.appiconset/App_store_1024_1x.png new file mode 100644 index 0000000..982e87e Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon White.appiconset/App_store_1024_1x.png differ diff --git a/Shared/Assets.xcassets/AppIcon White.appiconset/Contents.json b/Shared/Assets.xcassets/AppIcon White.appiconset/Contents.json new file mode 100644 index 0000000..35026eb --- /dev/null +++ b/Shared/Assets.xcassets/AppIcon White.appiconset/Contents.json @@ -0,0 +1,36 @@ +{ + "images" : [ + { + "filename" : "App_store_1024_1x.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/AppIcon White.imageset/App_store_1024_1x.png b/Shared/Assets.xcassets/AppIcon White.imageset/App_store_1024_1x.png new file mode 100644 index 0000000..982e87e Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon White.imageset/App_store_1024_1x.png differ diff --git a/Shared/Assets.xcassets/AppIcon White.imageset/Contents.json b/Shared/Assets.xcassets/AppIcon White.imageset/Contents.json new file mode 100644 index 0000000..7660558 --- /dev/null +++ b/Shared/Assets.xcassets/AppIcon White.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "App_store_1024_1x.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/AppIcon.imageset/App_store_1024_1x.png b/Shared/Assets.xcassets/AppIcon.imageset/App_store_1024_1x.png new file mode 100644 index 0000000..059d218 Binary files /dev/null and b/Shared/Assets.xcassets/AppIcon.imageset/App_store_1024_1x.png differ diff --git a/Shared/Assets.xcassets/AppIcon.imageset/Contents.json b/Shared/Assets.xcassets/AppIcon.imageset/Contents.json new file mode 100644 index 0000000..a5becd4 --- /dev/null +++ b/Shared/Assets.xcassets/AppIcon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "App_store_1024_1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/LaunchScreenBackground.colorset/Contents.json b/Shared/Assets.xcassets/LaunchScreenBackground.colorset/Contents.json index ab20316..0425637 100644 --- a/Shared/Assets.xcassets/LaunchScreenBackground.colorset/Contents.json +++ b/Shared/Assets.xcassets/LaunchScreenBackground.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0xBC", - "green" : "0x73", - "red" : "0x6A" + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" } }, "idiom" : "universal" @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0xBC", - "green" : "0x73", - "red" : "0x6A" + "blue" : "0.000", + "green" : "0.000", + "red" : "0.000" } }, "idiom" : "universal" diff --git a/Shared/Assets.xcassets/iPhoneTeardown.imageset/Contents.json b/Shared/Assets.xcassets/iPhoneTeardown.imageset/Contents.json new file mode 100644 index 0000000..77e9a9f --- /dev/null +++ b/Shared/Assets.xcassets/iPhoneTeardown.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "iPhone-14-Pro-wallpaper-1.jpg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/iPhoneTeardown.imageset/iPhone-14-Pro-wallpaper-1.jpg b/Shared/Assets.xcassets/iPhoneTeardown.imageset/iPhone-14-Pro-wallpaper-1.jpg new file mode 100644 index 0000000..1f50504 Binary files /dev/null and b/Shared/Assets.xcassets/iPhoneTeardown.imageset/iPhone-14-Pro-wallpaper-1.jpg differ diff --git a/Shared/Theme.swift b/Shared/Theme.swift index ae42a43..63ae324 100644 --- a/Shared/Theme.swift +++ b/Shared/Theme.swift @@ -3,7 +3,7 @@ import SwiftUI final class GameCubeColors { static let lightGray = Color(red: 221/256, green: 218/256, blue: 231/256) static let zColor = Color(red: 72/256, green: 100/256, blue: 226/256) - static let green = Color(red: 55/256, green: 199/256, blue: 195/256) + static let green = Color(red: 47/256, green: 214/256, blue: 172/256) static let red = Color(red: 232/256, green: 16/256, blue: 39/256) static let yellow = Color(red: 254/256, green: 217/256, blue: 39/256) static let purple = Color(red: 106/256, green: 115/256, blue: 188/256) diff --git a/iOS/DolphinController.storekit b/iOS/DolphinController.storekit new file mode 100644 index 0000000..4c7e565 --- /dev/null +++ b/iOS/DolphinController.storekit @@ -0,0 +1,167 @@ +{ + "appPolicies" : { + "eula" : "", + "policies" : [ + { + "locale" : "en_US", + "policyText" : "", + "policyURL" : "" + } + ] + }, + "identifier" : "72A49B88", + "nonRenewingSubscriptions" : [ + + ], + "products" : [ + { + "displayPrice" : "5.00", + "familyShareable" : false, + "internalID" : "727664EF", + "localizations" : [ + { + "description" : "A five dollar tip!", + "displayName" : "T2", + "locale" : "en_US" + } + ], + "productID" : "tip5", + "referenceName" : "Tip5", + "type" : "Consumable" + }, + { + "displayPrice" : "10.00", + "familyShareable" : false, + "internalID" : "DDD7B3AC", + "localizations" : [ + { + "description" : "A three dollar tip!", + "displayName" : "T3", + "locale" : "en_US" + } + ], + "productID" : "tip10", + "referenceName" : "Tip10", + "type" : "Consumable" + }, + { + "displayPrice" : "1.00", + "familyShareable" : false, + "internalID" : "593BE86C", + "localizations" : [ + { + "description" : "A one dollar tip!", + "displayName" : "T1", + "locale" : "en_US" + } + ], + "productID" : "tip1", + "referenceName" : "Tip1", + "type" : "Consumable" + }, + { + "displayPrice" : "1.00", + "familyShareable" : false, + "internalID" : "6EB1D2BC", + "localizations" : [ + { + "description" : "", + "displayName" : "", + "locale" : "en_US" + } + ], + "productID" : "support1", + "referenceName" : "Support1", + "type" : "NonConsumable" + }, + { + "displayPrice" : "5.00", + "familyShareable" : false, + "internalID" : "BF018C86", + "localizations" : [ + { + "description" : "", + "displayName" : "T2", + "locale" : "en_US" + } + ], + "productID" : "support5", + "referenceName" : "Support5", + "type" : "NonConsumable" + }, + { + "displayPrice" : "10.00", + "familyShareable" : false, + "internalID" : "280D2231", + "localizations" : [ + { + "description" : "", + "displayName" : "T3", + "locale" : "en_US" + } + ], + "productID" : "support10", + "referenceName" : "Support10", + "type" : "NonConsumable" + } + ], + "settings" : { + "_failTransactionsEnabled" : false, + "_locale" : "en_US", + "_storefront" : "USA", + "_storeKitErrors" : [ + { + "current" : null, + "enabled" : false, + "name" : "Load Products" + }, + { + "current" : null, + "enabled" : false, + "name" : "Purchase" + }, + { + "current" : null, + "enabled" : false, + "name" : "Verification" + }, + { + "current" : null, + "enabled" : false, + "name" : "App Store Sync" + }, + { + "current" : null, + "enabled" : false, + "name" : "Subscription Status" + }, + { + "current" : null, + "enabled" : false, + "name" : "App Transaction" + }, + { + "current" : null, + "enabled" : false, + "name" : "Manage Subscriptions Sheet" + }, + { + "current" : null, + "enabled" : false, + "name" : "Refund Request Sheet" + }, + { + "current" : null, + "enabled" : false, + "name" : "Offer Code Redeem Sheet" + } + ] + }, + "subscriptionGroups" : [ + + ], + "version" : { + "major" : 4, + "minor" : 0 + } +} diff --git a/iOS/DolphinControllerApp.swift b/iOS/DolphinControllerApp.swift index dd80111..e9198ce 100644 --- a/iOS/DolphinControllerApp.swift +++ b/iOS/DolphinControllerApp.swift @@ -9,13 +9,16 @@ import NetworkExtension struct DolphinControllerApp: App { @ObservedObject var client = Client() @State var shouldAutoReconnect: Bool = true - + + @AppStorage("skin") private var skin = Skin.indigo + var body: some Scene { WindowGroup { ZStack { - GameCubeColors.purple.ignoresSafeArea() + skin.view.ignoresSafeArea() ContentView( - shouldAutoReconnect: $shouldAutoReconnect + shouldAutoReconnect: $shouldAutoReconnect, + skin: $skin ) .environmentObject(client) .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in @@ -24,7 +27,6 @@ struct DolphinControllerApp: App { } } .onReceive(NotificationCenter.default.publisher(for: AIntentNotificationName), perform: { _ in - print("press A intent handler") client.send("PRESS A") DispatchQueue.main.async { let t = Timer.scheduledTimer( @@ -37,7 +39,6 @@ struct DolphinControllerApp: App { } }) .onReceive(NotificationCenter.default.publisher(for: BIntentNotificationName), perform: { _ in - print("press B intent handler") client.send("PRESS B") DispatchQueue.main.async { let t = Timer.scheduledTimer( @@ -51,6 +52,7 @@ struct DolphinControllerApp: App { }) } .ignoresSafeArea(edges: .top) + .environment(\.skin, skin) } } } diff --git a/iOS/Skin.swift b/iOS/Skin.swift new file mode 100644 index 0000000..74a162c --- /dev/null +++ b/iOS/Skin.swift @@ -0,0 +1,117 @@ +import Foundation +import SwiftUI + +extension EnvironmentValues { + @Entry var skin = Skin.indigo +} + +// https://gccontrollerlibrary.com/controllers/wired-gamecube-controller/ +enum Skin: String, CaseIterable, Identifiable { + case indigo + case black + case orange + case platinum + case emerald + case white + case clear + case starlight + case pearl + case symphonic + + var id: Self { self } + + var name: String { + switch self { + case .indigo: return "Indigo" + case .black: return "Jet Black" + case .orange: return "Spice Orange" + case .platinum: return "Platinum" + case .emerald: return "Emerald Blue" + case .white: return "White" + case .clear: return "Clear" + case .starlight: return "Starlight Gold" + case .pearl: return "Pearl White" + case .symphonic: return "Symphonic Green" + } + } + + var requiresSupport: Bool { + switch self { + case .indigo, .clear: + return false + default: + return true + } + } + + var view: some View { + switch self { + case .clear: + return AnyView(ClearSkinView()) + default: + return AnyView(color) + } + } + + var color: Color { + switch self { + case .indigo: + return Color(red: 106/256, green: 115/256, blue: 188/256) + case .black: + return Color(red: 45/256, green: 45/256, blue: 45/256) + case .orange: + return Color(red: 0.941, green: 0.569, blue: 0.239) + case .platinum: + return Color(red: 220/256, green: 220/256, blue: 220/256) + case .emerald: + return Color(red: 43/256, green: 199/256, blue: 199/256) + case .white: + return Color(red: 245/256, green: 245/256, blue: 245/256) + case .clear: + return .clear + case .starlight: + return Color(red: 238/256, green: 228/256, blue: 196/256) + case .pearl: + return Color(red: 250/256, green: 249/256, blue: 240/256) + case .symphonic: + return Color(red: 204/256, green: 231/256, blue: 200/256) + } + } + + enum Rarity: Hashable, CaseIterable, Comparable, Identifiable { + case common + case uncommon + case rare + + var id: String { description } + } + + var rarity: Rarity { + switch self { + case .indigo: return .common + case .black: return .common + case .orange: return .common + case .platinum: return .common + case .emerald: return .uncommon + case .white: return .uncommon + case .clear: return .uncommon + case .starlight: return .rare + case .pearl: return .uncommon + case .symphonic: return .rare + } + } +} + +extension Skin: CustomStringConvertible { + var description: String { name } +} + +extension Skin.Rarity: CustomStringConvertible { + var description: String { + switch self { + case .common: return "Common" + case .uncommon: return "Uncommon" + case .rare: return "Rare" + } + } +} diff --git a/iOS/Views/ClearSkinView.swift b/iOS/Views/ClearSkinView.swift new file mode 100644 index 0000000..133f50a --- /dev/null +++ b/iOS/Views/ClearSkinView.swift @@ -0,0 +1,50 @@ +import SwiftUI + +struct ClearSkinView: View { + @State var orientation: UIInterfaceOrientation = .unknown + + var image: UIImage { + let image = UIImage(named: "iPhoneTeardown")! + let newOrientation: UIImage.Orientation + switch orientation { + case .landscapeRight: newOrientation = .left + case .landscapeLeft: newOrientation = .right + case .portraitUpsideDown: newOrientation = .down + default: + return image + } +// let image = UIImage(named: "ControllerBoard")! +// let newOrientation: UIImage.Orientation +// switch orientation { +// case .landscapeLeft: newOrientation = .down +// case .portrait: newOrientation = .right +// case .portraitUpsideDown: newOrientation = .left +// default: +// return image +// } + return UIImage( + cgImage: image.cgImage!, + scale: image.scale, + orientation: newOrientation + ) + } + + var body: some View { + Image(uiImage: image) + .resizable() + .aspectRatio(contentMode: .fill) + .ignoresSafeArea() + .onReceive(NotificationCenter.default.publisher(for: UIApplication.didChangeStatusBarFrameNotification)) { _ in + orientation = UIApplication.shared.statusBarOrientation + } + .onAppear { + orientation = UIApplication.shared.statusBarOrientation + } + .opacity(0.6) + .blur(radius: 4) + } +} + +#Preview { + ClearSkinView() +} diff --git a/iOS/Views/ContentView.swift b/iOS/Views/ContentView.swift index 40c243d..b20e731 100644 --- a/iOS/Views/ContentView.swift +++ b/iOS/Views/ContentView.swift @@ -14,6 +14,8 @@ struct ContentView: View { @State private var ping: TimeInterval? = nil @AppStorage("showPing") private var showPing = false + @Binding var skin: Skin + var body: some View { ZStack { if showPing { @@ -105,7 +107,7 @@ struct ContentView: View { .environmentObject(client) } .sheet(isPresented: $showingSettings) { - SettingsView() + SettingsView(skin: $skin) } .alert(isPresented: Binding(get: { self.error != nil diff --git a/iOS/Views/HelpView.swift b/iOS/Views/HelpView.swift index 74531cd..bca0c67 100644 --- a/iOS/Views/HelpView.swift +++ b/iOS/Views/HelpView.swift @@ -5,6 +5,26 @@ let serverInstallURL = URL(string: "https://github.com/apexskier/dolphin-control let serverInstallURLDescription = "Server Installation Instructions" let appURL = URL(string: "https://apps.apple.com/us/app/id1584272645")! +struct CustomLink: View { + var item: URL + var subject: String + var message: String + @ViewBuilder var label: () -> Label + + var body: some View { + if #available(iOS 16.0, *) { + ShareLink( + item: item, + subject: Text(subject), + message: Text(message), + label: label + ) + } else { + Link(subject, destination: item) + } + } +} + struct HelpView: View { var body: some View { Section( @@ -12,26 +32,14 @@ struct HelpView: View { footer: Text("You'll need to install and run the server alongside Dolphin on your Mac.") ) { Link(serverInstallURLDescription, destination: serverInstallURL) - if #available(iOS 16.0, *) { - ShareLink( - item: serverInstallURL, - subject: Text("Dolphin Controller Server"), - message: Text("Follow this link to install the Dolphin Controller server on your Mac."), - label: { - Text("\(Image(systemName: "square.and.arrow.up")) Share \(serverInstallURLDescription)") - } - ) - ShareLink( - item: appURL, - subject: Text("Dolphin Controller App"), - message: Text("Follow this link to install the Dolphin Controller app on your iOS device."), - label: { - Text("\(Image(systemName: "square.and.arrow.up")) Share iOS App") - } - ) - } else { - Link("iOS App", destination: appURL) - } + CustomLink( + item: serverInstallURL, + subject: "Dolphin Controller Server", + message: "Follow this link to install the Dolphin Controller server on your Mac.", + label: { + Text("\(Image(systemName: "square.and.arrow.up")) Share \(serverInstallURLDescription)") + } + ) } } } diff --git a/iOS/Views/SettingsView.swift b/iOS/Views/SettingsView.swift index b04d3c7..c3d58e3 100644 --- a/iOS/Views/SettingsView.swift +++ b/iOS/Views/SettingsView.swift @@ -1,32 +1,276 @@ +import StoreKit import SwiftUI +struct HelpTextModifier: ViewModifier { + func body(content: Content) -> some View { + if #available(iOS 16.0, *) { + content + .font(.caption) + .foregroundStyle(.secondary) + } else { + content + .font(.caption) + } + } +} + +extension Text { + func helpText() -> some View { + modifier(HelpTextModifier()) + } +} + +enum SupportState { + case loading + case supported + case notYet +} + +@available(iOS 17.0, *) +struct SupportModifier: ViewModifier { + @State var support1Entitlement: + StoreKit.EntitlementTaskState< + StoreKit.VerificationResult? + > = .loading + @State var support5Entitlement: + StoreKit.EntitlementTaskState< + StoreKit.VerificationResult? + > = .loading + @State var support10Entitlement: + StoreKit.EntitlementTaskState< + StoreKit.VerificationResult? + > = .loading + + var action: + ( + StoreKit.EntitlementTaskState< + StoreKit.VerificationResult? + > + ) -> Void + + private func test() { + // if any entitlement has a transaction, call the action, otherwise return the first which will be loading or failed + for entitlement in [ + support1Entitlement, support5Entitlement, support10Entitlement, + ] { + if entitlement.transaction != nil { + action(entitlement) + return + } + } + action(support1Entitlement) + } + + func body(content: Content) -> some View { + content + .currentEntitlementTask(for: "support1") { state in + support1Entitlement = state + test() + } + .currentEntitlementTask(for: "support5") { state in + support1Entitlement = state + test() + } + .currentEntitlementTask(for: "support10") { state in + support1Entitlement = state + test() + } + } +} + +enum SupportPageConf { + case hidden + case visible(Skin?) +} + struct SettingsView: View { @EnvironmentObject private var client: Client @Environment(\.presentationMode) private var presentationMode @AppStorage("keepScreenAwake") private var keepScreenAwake = true - @AppStorage("joystickHapticsEnabled") private var joystickHapticsEnabled = true + @AppStorage("joystickHapticsEnabled") private var joystickHapticsEnabled = + true @AppStorage("showPing") private var showPing = false + @State private var hasSupported: SupportState = .loading + + @Binding var skin: Skin + @State private var showSupportAlert: SupportPageConf = .hidden + @State private var showSupportPage: SupportPageConf = .hidden var body: some View { NavigationView { List { - Toggle("Keep Screen Awake (when connected to server)", isOn: $keepScreenAwake) - .onChange(of: keepScreenAwake) { newValue in - client.idleManager?.update() + VStack(alignment: .leading) { + Toggle("Keep Screen Awake", isOn: $keepScreenAwake) + .onChange(of: keepScreenAwake) { newValue in + client.idleManager?.update() + } + Text( + "Prevent the screen from dimming or turning off while connected to a server." + ) + .helpText() + } + VStack(alignment: .leading) { + Toggle( + "Continuous Joystick Haptics", + isOn: $joystickHapticsEnabled) + Text("Increase rumble as you move a joystick to its edge.") + .helpText() + } + Toggle("Display Server Ping", isOn: $showPing) + + if #available(iOS 17.0, *) { + Section( + header: Text("Supporter Features"), + footer: Text( + "Support the development of this app and unlock special customization options!" + ) + ) { + NavigationLink( + isActive: .init( + get: { + switch showSupportPage { + case .hidden: + return false + case .visible: + return true + } + }, + set: { isActive in + showSupportPage = + isActive ? .visible(nil) : .hidden + }) + ) { + switch hasSupported { + case .loading: + ProgressView() + case .supported: + SupportView(hasSupported: true, postSupport: changeSkinAfterSupport) + case .notYet: + SupportView(hasSupported: false, postSupport: changeSkinAfterSupport) + } + } label: { + Text("Support the App") + } + + NavigationLink { + SkinSelectorView(skin: $skin, select: changeSkinWithValidation) + .navigationTitle("Choose Appearance") + } label: { + Text("Change Appearance") + } } - Toggle("Continuous Joystick Haptics", isOn: $joystickHapticsEnabled) - Toggle("Display Ping", isOn: $showPing) + .alert( + "Support the App!", + isPresented: .init( + get: { + switch showSupportAlert { + case .hidden: + return false + case .visible: + return true + } + }, + set: { isPresented in + showSupportAlert = + isPresented ? .visible(nil) : .hidden + }) + ) { + Button { + showSupportPage = showSupportAlert + } label: { + Text("Support") + } + Button("Cancel", role: .cancel) {} + } message: { + Text("Support the app to use this skin.") + } + .modifier( + SupportModifier(action: { state in + switch state { + case .loading: + hasSupported = .loading + default: + hasSupported = + state.transaction != nil + ? .supported : .notYet + } + })) + } HelpView() + + CustomLink( + item: appURL, + subject: "Dolphin Controller App", + message: + "Follow this link to install the Dolphin Controller app on your iOS device.", + label: { + Text( + "\(Image(systemName: "square.and.arrow.up")) Share iOS App" + ) + } + ) + + NavigationLink("Attribution") { + Text( + "Clear background provided by iFixit under the [Creative Commons BY-NC-SA 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/)." + ) + } } - .navigationBarTitle("Settings") - .navigationBarItems(trailing: Button("Close", action: { - self.presentationMode.wrappedValue.dismiss() - })) + .navigationBarTitle("Settings") + .navigationBarItems( + trailing: Button( + "Close", + action: { + self.presentationMode.wrappedValue.dismiss() + }) + ) + } + } + + func postSupportSkin() -> Skin? { + switch showSupportPage { + case .hidden: + return nil + case .visible(let skin): + return skin + } + } + + func changeSkinAfterSupport() { + guard let newSkin = postSupportSkin() else { + return + } + + changeSkin(skin: newSkin) + } + + func changeSkinWithValidation(skin: Skin) { + if skin.requiresSupport + && hasSupported != .supported + { + showSupportAlert = .visible(skin) + return + } + + changeSkin(skin: skin) + } + + func changeSkin(skin: Skin) { + self.skin = skin + + let current = UIApplication.shared + .alternateIconName + let new = + skin == .indigo + ? nil : "AppIcon \(skin.name)" + if current == new { + return } + UIApplication.shared.setAlternateIconName(new) } } #Preview { - SettingsView() + SettingsView(skin: .constant(.indigo)) } diff --git a/iOS/Views/SkinSelector.swift b/iOS/Views/SkinSelector.swift new file mode 100644 index 0000000..433c7fe --- /dev/null +++ b/iOS/Views/SkinSelector.swift @@ -0,0 +1,71 @@ +import SwiftUI + +struct ButtonTintViewModifier: ViewModifier { + var color: Color + + func body(content: Content) -> some View { + if #available(iOS 16.0, *) { + content + .tint(color) + } else { + content + } + } +} + +struct SkinSelectorView: View { + @Binding var skin: Skin + + var select: (Skin) -> Void + + var body: some View { + List { + ForEach(Skin.Rarity.allCases) { rarity in + Section(header: Text(rarity.description)) { + ForEach(Skin.allCases.filter({ $0.rarity == rarity })) { + skin in + Button(action: { + select(skin) + }) { + HStack { + HStack(spacing: 20) { + if UIApplication.shared + .supportsAlternateIcons, + let image = UIImage( + named: skin == .indigo + ? "AppIcon" + : "AppIcon \(skin.name)") + { + Image(uiImage: image) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 60, height: 60) + .clipShape( + RoundedRectangle( + cornerRadius: 14, + style: .continuous)) + } + + Text(skin.name) + } + + Spacer() + + if self.skin == skin { + Image(systemName: "checkmark") + } + } + } + .modifier(ButtonTintViewModifier(color: .primary)) + } + } + } + } + } +} + +@available(iOS 18, *) +#Preview { + @Previewable @State var skin = Skin.indigo + SkinSelectorView(skin: $skin, select: { _ in }) +} diff --git a/iOS/Views/SupportView.swift b/iOS/Views/SupportView.swift new file mode 100644 index 0000000..b56e678 --- /dev/null +++ b/iOS/Views/SupportView.swift @@ -0,0 +1,90 @@ +import SwiftUI +import StoreKit + +struct TipButtonStyleButton: ButtonStyle { + func makeBody(configuration: Configuration) -> some View { + configuration.label + .padding() + .background(Color(red: 0, green: 0, blue: 0.5)) + // .foregroundStyle(.white) + .clipShape(Capsule()) + } +} + +@available(iOS 17.0, *) +struct SupportView: View { + @Environment(\.purchase) private var purchase: PurchaseAction + @State private var initialProducts: [Product]? = nil + @State private var supports: [Product]? = nil + @Environment(\.dismiss) private var dismiss + + var hasSupported: Bool + var postSupport: () -> Void + + var products: [Product]? { + hasSupported ? initialProducts : supports + } + + @State var error: VerificationResult.VerificationError? = nil + + var body: some View { + VStack(alignment: .center, spacing: 8) { + Spacer() + if hasSupported { + Text("Thanks for supporting this app! If you want to contribute more, thank you so much!") + .font(.title2) + .multilineTextAlignment(.center) + } else { + Text("Thanks for using this app! By supporting development, you'll unlock special features and help keep this app running.") + .font(.title2) + .multilineTextAlignment(.center) + } + Spacer() + HStack { + if let products { + ForEach(products) { product in + Button { + Task { + switch try? await purchase(product) { + case .success(let verificationResult): + switch verificationResult { + case .verified(let transaction): + await transaction.finish() + dismiss() + postSupport() + case .unverified(_, let error): + self.error = error + } + default: + break + } + } + } label: { + Text(product.displayPrice) + } + } + } else { + ProgressView() + } + } + .buttonStyle(GCCButton( + color: GameCubeColors.green, + width: 100, + height: 42, + shape: RoundedRectangle(cornerRadius: 4, style: .continuous) + )) + Spacer() + } + .storeProductsTask(for: ["tip1", "tip5", "tip10"]) { states in + initialProducts = states.products + } + .storeProductsTask(for: ["support1", "support5", "support10"]) { states in + supports = states.products + } + } +} + +@available(iOS 17.0, *) +#Preview { + SupportView(hasSupported: true) { } +} diff --git a/macOS/DolphinControllerApp.swift b/macOS/DolphinControllerApp.swift index 49579ac..01ed964 100644 --- a/macOS/DolphinControllerApp.swift +++ b/macOS/DolphinControllerApp.swift @@ -10,7 +10,7 @@ struct DolphinControllerApp: App { var body: some Scene { WindowGroup { ZStack { - GameCubeColors.purple.ignoresSafeArea() + Skin.indigo.color.ignoresSafeArea() ContentView() .padding([.horizontal, .bottom]) } diff --git a/macOS/Views/ControllerPlugView.swift b/macOS/Views/ControllerPlugView.swift index 2e5bc75..2df24ac 100644 --- a/macOS/Views/ControllerPlugView.swift +++ b/macOS/Views/ControllerPlugView.swift @@ -52,7 +52,7 @@ struct ControllerPlugView: View { .frame(width: 36, height: 36) if connected { Circle() - .fill(GameCubeColors.purple) + .fill(Skin.indigo.color) .frame(width: 30, height: 30) ZStack { // this pulls the "cord" (rounded rect) out of the layout flow RoundedRectangle(cornerRadius: 12)