commit 2ff6aac218c85714cd6a884cdbd319810de1cedb
Author: Balackburn <93828569+Balackburn@users.noreply.github.com>
Date: Tue Jun 27 09:54:41 2023 +0200
added files via upload
diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml
new file mode 100644
index 0000000..8650bc3
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug.yaml
@@ -0,0 +1,86 @@
+name: Bug
+description: Make sure you complete the template. Otherwise, it will be closed without further explanation!
+title: "[Bug] Replace this with your title"
+labels: bug
+body:
+- type: checkboxes
+ attributes:
+ label: Is there an existing issue for this?
+ description: _Please search to see if an issue already exists for the bug you encountered_
+ options:
+ - label: I have searched the existing issues
+ required: true
+
+- type: checkboxes
+ attributes:
+ label: Have you read the FAQ?
+ description: _Make sure you visit the [**FAQ**](https://github.com/qnblackcat/CercubePlus/wiki/FAQ) page first!_
+ options:
+ - label: Yes, I read the FAQ
+ required: true
+
+- type: textarea
+ attributes:
+ label: Device info
+ description:
+ value: |
+ - iOS/iPadOS version:
+ - Device model:
+ - Sideload tool (AltStore, Sideloadly, TrollStore,...):
+ - The specific version of CercubePlus (**latest** or **newest** is **NOT** a version number!):
+ validations:
+ required: true
+
+- type: textarea
+ attributes:
+ label: Describe the bug
+ description: _Please attach videos or screenshots if possible_
+ validations:
+ required: true
+
+- type: textarea
+ attributes:
+ label: Steps to reproduce the issue
+ description: _Please attach videos or screenshots if possible_
+ value: |
+ 1.
+ 2.
+ 3.
+ validations:
+ required: true
+
+- type: textarea
+ id: logs
+ attributes:
+ label: Crash log (if the app crashes)
+ description: _If somehow the app crashes, **you must provide the crash log**. It can be found in your device's Settings App > Privacy > Analytics & Improvements > Analytics Data > Youtube-xxx-xxx.ips_
+ render: shell
+
+- type: dropdown
+ attributes:
+ label: Are you using the newest version of CercubePlus? If not, why?
+ description: _Developers spend time and effort to fix bugs & add improvements with every release. Why don't you update to the [latest version](https://github.com/Balackburn/CercubePlusExtra/releases/latest) before reporting about an issue?_
+ multiple: false
+ options:
+ - ✅ Yes, I'm using the latest version of CercubePlus right now
+ - ❌ No, I'll explain at the end of the template
+ validations:
+ required: true
+
+- type: dropdown
+ attributes:
+ label: Does the issue happen with the official YouTube from AppStore?
+ description: _Well, YouTube itself is buggy sometimes..._
+ multiple: false
+ options:
+ - ❌ No, It doesn't
+ - ✅ Yes, It does
+ validations:
+ required: true
+
+- type: textarea
+ attributes:
+ label: Additional context
+ description: _Um, anything else you want to say?_
+ validations:
+ required: false
diff --git a/.github/ISSUE_TEMPLATE/question-help.md b/.github/ISSUE_TEMPLATE/question-help.md
new file mode 100644
index 0000000..aa0410a
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/question-help.md
@@ -0,0 +1,37 @@
+name: Question
+description: Have question(s)?
+title: "[Questions] Replace with your question"
+labels: question
+body:
+- type: checkboxes
+ attributes:
+ label: Is there an existing issue/question for this?
+ description: _Please search to see if an issue already exists for the bug you encountered. **I DON\'T MAKE THIS TICK BOX FOR COSMETIC.**_
+ options:
+ - label: I have searched the existing issues
+ required: true
+
+- type: dropdown
+ attributes:
+ label: Do you think this is a bug?
+ description: _If you think this is a bug, please open a new issue with the bug template_
+ multiple: false
+ options:
+ - ✅ Yes, I believe this is a bug. I will open a new issue with the bug template
+ - ❌ No, I don't think this is a bug. I will explain below
+ validations:
+ required: true
+
+- type: textarea
+ attributes:
+ label: My question
+ description: _Please enter your question here_
+ validations:
+ required: true
+
+- type: textarea
+ attributes:
+ label: Additional context
+ description: _Um, anything else you want to say?_
+ validations:
+ required: false
diff --git a/.github/RELEASE_TEMPLATE/Release.md b/.github/RELEASE_TEMPLATE/Release.md
new file mode 100644
index 0000000..8eb035a
--- /dev/null
+++ b/.github/RELEASE_TEMPLATE/Release.md
@@ -0,0 +1,42 @@
+# [YOUR_TWEAK_NAME] Features
+CercubePlus but adds Additional Features that should’ve been added to the native CercubePlus which were probably not added.
+
+**LowContrastMode:** This tweak helps remove the new contrasty looking UI that was first introducted way back in 2020 August/September. (Tweak made by arichorn)
+
+More Features...
+
+**LowContrastModeColors:** for users who doesn’t use gray will get a set of colors to switch which changes and improves the UI Customization. (Add-on added for LowContrastMode)
+
+**YTNoHeatwaves:** Turns off the Heatwaves Feature in the video player. `(CercubePlus/VideoPlayerOverlayControls)`
+
+**YTNoUpgradeDialog:** Disables the Upgrade Dialog so you won’t be prompt to update the app.
+
+**YouMute:** Mute/unmute videos in YouTube directly.
+
+**iPadLayout:** Gives iPhone users the ability to use the iPad’s Interface and the ability to use the some of the YouTube features that are not on iPhone.
+
+**iPhoneLayout:** Gives iPad users the ability to use Community Posts, to create Shorts and the ability to use the buggy iPhone layout. but using it in split view mode fixes the ui.
+
+**HideSponsorBlockButton:** Hide the SponsorBlock Button shown on the Nav Bar. Added by Dayanch96
+
+**DisableWifiRelatedOptions:** want to remove sections that are only shown when internet is on? You can toggle this to remove all of those sections. Well not all but toggling the option will remove some of the annoying sections that may not be used.
+
+**HideShadowOverlayButtons:** want to remove shadow overlay on the buttons used in the video player? Then toggle this to remove the Shadow Overlay on the buttons Previous, Next, Rewind, Forward.
+
+**etc..**
+
+
+# [YOUR_TWEAK_NAME] Release Information
+Current YouTube IPA: `TEMPLATE`
+Current Cercube Version: `v5.3.13`
+Current App Compatibility: `iOS/iPadOS 14.0` or later
+
+**RELEASE F1:**
+[THIS IS WHERE YOU PUT YOUR CHANGES BELOW, also F1 indicates First Release on the same YouTube Version in case you needed to know]
+- [<-- this subtract symbol makes the changelog look cool]
+
+- [this right here shows you added additonal changes on a certain tweak like this for example, below]
+- Improvements
+ - Fixed Sign-in Issue
+ - Fixed Ads Not Working Issue
+[THIS IS HOW YOU DO IT! YOU CAN DELETE ALL OF THE TEXT I'VE SAID!]
diff --git a/.github/workflows/buildapp.yml b/.github/workflows/buildapp.yml
new file mode 100644
index 0000000..57574ea
--- /dev/null
+++ b/.github/workflows/buildapp.yml
@@ -0,0 +1,159 @@
+# Original idea by @ISnackable. Many thanks to him for handling the most hardest parts!
+# https://github.com/ISnackable/CercubePlus/blob/main/.github/workflows/Build.yml
+
+name: Build and Release YTLitePlus
+
+on:
+ workflow_dispatch:
+ inputs:
+ decrypted_youtube_url:
+ description: "The direct URL to the decrypted YouTube ipa"
+ default: ""
+ required: true
+ type: string
+ youtube_version:
+ description: "The version of YouTube"
+ default: ""
+ required: true
+ type: string
+ ytliteplus_version:
+ description: "The version of YTLitePlus"
+ default: "2.0"
+ required: true
+ type: string
+ bundle_id:
+ description: "Modify the bundle ID. Not recommended"
+ default: "com.google.ios.youtube"
+ required: true
+ type: string
+ app_name:
+ description: "Modify the name of the app on the Home Screen. Not recommended"
+ default: "YouTube"
+ required: true
+ type: string
+ create_release:
+ description: "Create a draft release"
+ default: true
+ required: false
+ type: boolean
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ build:
+ name: Build YTLitePlus
+ runs-on: macos-latest
+ permissions:
+ contents: write
+
+ steps:
+ - name: Checkout Main
+ uses: actions/checkout@v3.5.2
+ with:
+ path: main
+ submodules: recursive
+
+ - name: Install Dependencies
+ run: brew install ldid dpkg make
+
+ - name: Setup Theos
+ uses: actions/checkout@v3.5.2
+ with:
+ repository: theos/theos
+ ref: master
+ path: theos
+ submodules: recursive
+
+ - name: Caching
+ id: SDK
+ uses: actions/cache@v3.3.1
+ env:
+ cache-name: iOS-15.5-SDK
+ with:
+ path: theos/sdks/
+ key: ${{ env.cache-name }}
+
+ - name: Download iOS 15.5 SDK
+ if: steps.SDK.outputs.cache-hit != 'true'
+ run: |
+ svn checkout -q https://github.com/chrisharper22/sdks/trunk/iPhoneOS15.5.sdk
+ mv *.sdk $THEOS/sdks
+ env:
+ THEOS: ${{ github.workspace }}/theos
+
+ - name: Setup Theos Jailed
+ uses: actions/checkout@v3.5.2
+ with:
+ repository: qnblackcat/theos-jailed
+ ref: master
+ path: theos-jailed
+ submodules: recursive
+
+ - name: Install Theos Jailed
+ run: |
+ ./theos-jailed/install
+ env:
+ THEOS: ${{ github.workspace }}/theos
+
+ - name: Prepare YouTube iPA
+ run: |
+ wget "$YOUTUBE_URL" --no-verbose -O main/YouTube.ipa
+ echo -e "==> \033[1mYouTube v${{ inputs.youtube_version }} downloaded! \033[0m"
+ unzip -q main/YouTube.ipa -d main/tmp
+ rm -rf main/tmp/Payload/YouTube.app/_CodeSignature/CodeResources
+ rm -rf main/tmp/Payload/YouTube.app/PlugIns/*
+ cp -R main/Extensions/*.appex main/tmp/Payload/YouTube.app/PlugIns
+ echo -e "==> \033[1mYouTube v${{ inputs.youtube_version }} unpacked! \033[0m"
+
+ env:
+ THEOS: ${{ github.workspace }}/theos
+ YOUTUBE_VERSION: ${{ inputs.youtube_version }}
+ YOUTUBE_URL: ${{ inputs.decrypted_youtube_url }}
+
+ - name: Fix Compiling & Build Package
+ id: build_package
+ run: |
+ (echo PATH=\"$(brew --prefix make)/libexec/gnubin:\$PATH\" >> ~/.zprofile)
+ cd ${{ github.workspace }}/main
+ sed -i '' "12s#.*#BUNDLE_ID = ${{ env.BUNDLE_ID }}#g" Makefile
+ sed -i '' "11s#.*#DISPLAY_NAME = ${{ env.APP_NAME }}#g" Makefile
+ make package FINALPACKAGE=1
+ (mv "packages/$(ls -t packages | head -n1)" "packages/YTLitePlus_${{ env.YOUTUBE_VERSION }}.ipa")
+ echo "package=$(ls -t packages | head -n1)" >>$GITHUB_OUTPUT
+ echo -e "==> \033[1mSHASUM256: $(shasum -a 256 packages/*.ipa | cut -f1 -d' ')\033[0m"
+ echo -e "==> \033[1mBundle ID: ${{ env.BUNDLE_ID }}\033[0m"
+ env:
+ THEOS: ${{ github.workspace }}/theos
+ ytliteplus_version: ${{ inputs.ytliteplus_version }}
+ YOUTUBE_VERSION: ${{ inputs.youtube_version }}
+ BUNDLE_ID: ${{ inputs.bundle_id }}
+ APP_NAME: ${{ inputs.app_name }}
+
+ - name: Upload Artifact
+ uses: actions/upload-artifact@v3.1.2
+ env:
+ ytliteplus_version: ${{ inputs.ytliteplus_version }}
+ YOUTUBE_VERSION: ${{ inputs.youtube_version }}
+ with:
+ name: YTLitePlus_${{ env.YOUTUBE_VERSION }}_${{ env.ytliteplus_version }}
+ path: ${{ github.workspace }}/main/packages/${{ steps.build_package.outputs.package }}
+ if-no-files-found: error
+
+ - name: Create Release
+ id: create_release
+ uses: softprops/action-gh-release@v0.1.15
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ ytliteplus_version: ${{ inputs.ytliteplus_version }}
+ YOUTUBE_VERSION: ${{ inputs.youtube_version }}
+ DRAFT: ${{ inputs.create_release }}
+ with:
+ tag_name: v${{ env.YOUTUBE_VERSION }}-${{ env.ytliteplus_version }}-(${{ github.run_number }})
+ name: v${{ env.YOUTUBE_VERSION }}-${{ env.ytliteplus_version }}-(${{ github.run_number }})
+ files: main/packages/*.ipa
+ draft: ${{ env.DRAFT }}
+
+
+
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..37c52c5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+.DS_Store
+.theos/
+packages/
+tmp/
+Tweaks/Cercube/*
+!Tweaks/Cercube/.gitkeep
+Resources/
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..acf322b
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,45 @@
+[submodule "Tweaks/YTUHD"]
+ path = Tweaks/YTUHD
+ url = https://github.com/PoomSmart/YTUHD.git
+[submodule "Tweaks/YouPiP"]
+ path = Tweaks/YouPiP
+ url = https://github.com/PoomSmart/YouPiP.git
+[submodule "Tweaks/Return-YouTube-Dislikes"]
+ path = Tweaks/Return-YouTube-Dislikes
+ url = https://github.com/PoomSmart/Return-YouTube-Dislikes.git
+[submodule "Tweaks/YouTubeHeader"]
+ path = Tweaks/YouTubeHeader
+ url = https://github.com/PoomSmart/YouTubeHeader.git
+[submodule "Tweaks/Alderis"]
+ path = Tweaks/Alderis
+ url = https://github.com/qnblackcat/Alderis.git
+[submodule "Tweaks/PSHeader"]
+ path = Tweaks/PSHeader
+ url = https://github.com/PoomSmart/PSHeader.git
+[submodule "Tweaks/YTABConfig"]
+ path = Tweaks/YTABConfig
+ url = https://github.com/PoomSmart/YTABConfig.git
+[submodule "Tweaks/YouMute"]
+ path = Tweaks/YouMute
+ url = https://github.com/PoomSmart/YouMute.git
+[submodule "Tweaks/RemoteLog"]
+ path = Tweaks/RemoteLog
+ url = https://github.com/Muirey03/RemoteLog.git
+[submodule "Tweaks/FLEX"]
+ path = Tweaks/FLEX
+ url = https://github.com/qnblackcat/FLEX-Classes.git
+[submodule "Tweaks/iSponsorBlock"]
+ path = Tweaks/iSponsorBlock
+ url = https://github.com/Galactic-Dev/iSponsorBlock.git
+[submodule "Extensions"]
+ path = Extensions
+ url = https://github.com/CokePokes/YoutubeExtensions.git
+[submodule "Tweaks/YTHoldForSpeed"]
+ path = Tweaks/YTHoldForSpeed
+ url = https://github.com/arichorn/YTHoldForSpeed.git
+[submodule "Tweaks/DontEatMyContent"]
+ path = Tweaks/DontEatMyContent
+ url = https://github.com/therealFoxster/DontEatMyContent.git
+[submodule "Tweaks/YTLite"]
+ path = Tweaks/YTLite
+ url = https://github.com/dayanch96/YTLite.git
diff --git a/Extensions/.gitattributes b/Extensions/.gitattributes
new file mode 100644
index 0000000..dfe0770
--- /dev/null
+++ b/Extensions/.gitattributes
@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text=auto
diff --git a/Extensions/.gitignore b/Extensions/.gitignore
new file mode 100644
index 0000000..496ee2c
--- /dev/null
+++ b/Extensions/.gitignore
@@ -0,0 +1 @@
+.DS_Store
\ No newline at end of file
diff --git a/Extensions/OpenYoutubeSafariExtension.appex/Info.plist b/Extensions/OpenYoutubeSafariExtension.appex/Info.plist
new file mode 100644
index 0000000..5d7d778
Binary files /dev/null and b/Extensions/OpenYoutubeSafariExtension.appex/Info.plist differ
diff --git a/Extensions/OpenYoutubeSafariExtension.appex/OpenYoutube b/Extensions/OpenYoutubeSafariExtension.appex/OpenYoutube
new file mode 100755
index 0000000..2555c7a
Binary files /dev/null and b/Extensions/OpenYoutubeSafariExtension.appex/OpenYoutube differ
diff --git a/Extensions/OpenYoutubeSafariExtension.appex/_CodeSignature/CodeResources b/Extensions/OpenYoutubeSafariExtension.appex/_CodeSignature/CodeResources
new file mode 100644
index 0000000..b9dc07e
--- /dev/null
+++ b/Extensions/OpenYoutubeSafariExtension.appex/_CodeSignature/CodeResources
@@ -0,0 +1,333 @@
+
+
+
+
+ files
+
+ Info.plist
+
+ 5KaPBsMVTGZfcc05Od8nGW6/EyQ=
+
+ _locales/en/messages.json
+
+ 8cPkszxzYFSjhRWsf34A0AiHaPM=
+
+ background.js
+
+ tClFxbnUm0L65Qj+Qu8Fk9asL2o=
+
+ content.js
+
+ a6o4B4PMlrIOBXlniU2NS4D9SJE=
+
+ embedded.mobileprovision
+
+ dVGLS8Qpu/eCI1J5VCu9IfYq3Z4=
+
+ images/cat_1.0.gif
+
+ DtSTnvB7wRt647kM1b38KLfDfXA=
+
+ images/icon-128.png
+
+ rYa1lv1LgkO3fkP9h6gRSjlQgqk=
+
+ images/icon-256.png
+
+ 05xbekvTg28KsH9RRIKQRL8tqQs=
+
+ images/icon-48.png
+
+ BWUZe8raKzOzA2cDWIYFoBPxxw8=
+
+ images/icon-512.png
+
+ shzpVHJMsAu4q820Fb+YzByiFhc=
+
+ images/icon-64.png
+
+ HZC4YIb4ZLg4lpjNAKFqSaka6r0=
+
+ images/icon-96.png
+
+ IPAOd1HfCtCI7ybhVhwA5TU/BZ8=
+
+ images/toolbar-icon-16.png
+
+ Bkb2Cy8VYIcWl7wFZ2HcWZcCoyA=
+
+ images/toolbar-icon-19.png
+
+ XLkdODJ73CVbwn/1LCAsfB6scPg=
+
+ images/toolbar-icon-32.png
+
+ 0cQgf/z3urGdzsmtmc2Wnim1+lg=
+
+ images/toolbar-icon-38.png
+
+ eas7Tl3/r2JMZf6nK84ZjoBJrZo=
+
+ images/toolbar-icon-48.png
+
+ fxyz1xgkIR877Cf/7uYNUqXrxoM=
+
+ images/toolbar-icon-72.png
+
+ pHL3CJwjPY30T9sFvvcA2qlraBw=
+
+ manifest.json
+
+ b37QyqMYHlvlfOjbvJLOKeAM5Y4=
+
+ popup.css
+
+ mXR2qQxu+ceMOItNpc4aImux5wY=
+
+ popup.html
+
+ zTk6QImmvcZjMYLQY8ek+cqSEWg=
+
+ popup.js
+
+ grmSQBg1aRbGzHHjd3TWHS/HbOY=
+
+
+ files2
+
+ _locales/en/messages.json
+
+ hash2
+
+ 4pOaVgBrOGGIoI81XdEOZidIyW9peYtz85HIYX7d1QE=
+
+
+ background.js
+
+ hash2
+
+ 2S7ONYvdkGsryEavyrR7nMCTwsjNGqMV2BSWIxWLSYg=
+
+
+ content.js
+
+ hash2
+
+ 9CYz6yZ8bRI2JRzRwnn4DOGuPuy2GSsOudXSZU6s1po=
+
+
+ embedded.mobileprovision
+
+ hash2
+
+ 4TQyzTTz0VYUTM0IXf2PMSufVEkzd4WlPWbwKMyfKfQ=
+
+
+ images/cat_1.0.gif
+
+ hash2
+
+ 65Ql3FhfivRMoMifbuY5KK8I/1rZx/Zmmwsx1La4OyI=
+
+
+ images/icon-128.png
+
+ hash2
+
+ TV8I8bcVP9rAnVA4AJRAWtkJveIenMpYsd/7+rsMLzo=
+
+
+ images/icon-256.png
+
+ hash2
+
+ kksdsW0Y1svz0bT0i3UzQGBmBDxJGr2eTCZmaqTS+XM=
+
+
+ images/icon-48.png
+
+ hash2
+
+ mBNPpZ78p0eH4WsNdF/EXw/sFHq5f8PqERARlLulYbY=
+
+
+ images/icon-512.png
+
+ hash2
+
+ waQn18oFc3GbhXXm2HEn6xFQk3HKehp9crGljWAMsGQ=
+
+
+ images/icon-64.png
+
+ hash2
+
+ CJwxajQk49Cd0RfR22yEPO6pjDbg+Aa4Ocxi+812CiM=
+
+
+ images/icon-96.png
+
+ hash2
+
+ zuzRjCepizxOWXh9ftCPth38GUxy1Ea/4zS3dw19OXo=
+
+
+ images/toolbar-icon-16.png
+
+ hash2
+
+ xmXH4YHb+Pq/YuC1e8JLIun93LuM3+/8MvB3r8oTKls=
+
+
+ images/toolbar-icon-19.png
+
+ hash2
+
+ qSX/1Ux4BOUIhixJK5NkvyTomLwp0yISfsQ97uF8/5g=
+
+
+ images/toolbar-icon-32.png
+
+ hash2
+
+ pGDg4NcSdD5EQaZgaiwxNosbSEXmZnpRc79RfitKc1c=
+
+
+ images/toolbar-icon-38.png
+
+ hash2
+
+ BUU3dwrCSvNOiFkBQWdPkrwASjUvxSxbizW+Izt3LBA=
+
+
+ images/toolbar-icon-48.png
+
+ hash2
+
+ JLZV5da+fZkfw0m2KfMqHSixWBCJvZoUNlt3la7gm54=
+
+
+ images/toolbar-icon-72.png
+
+ hash2
+
+ TwM7A1Bkuv4C3SWorFNwG+w38mWo3TaMtglLdDXxcFw=
+
+
+ manifest.json
+
+ hash2
+
+ +nWdvS9ap7YzpjqKSWsGUHP5K2irkTGozQX/tI4WrYc=
+
+
+ popup.css
+
+ hash2
+
+ LIfF3an8mzKIMD8PDhKILnrZBhxxSTzoZQKyRiyWuB0=
+
+
+ popup.html
+
+ hash2
+
+ 9Ro1fCC5Meq0eSNrYab7V3uc4tNvmwRqMsCb1757M6Q=
+
+
+ popup.js
+
+ hash2
+
+ ruLXAORE+vDoLKPGBSkl7hnXv8V+ZexCoX3KvJyHxBk=
+
+
+
+ rules
+
+ ^.*
+
+ ^.*\.lproj/
+
+ optional
+
+ weight
+ 1000
+
+ ^.*\.lproj/locversion.plist$
+
+ omit
+
+ weight
+ 1100
+
+ ^Base\.lproj/
+
+ weight
+ 1010
+
+ ^version.plist$
+
+
+ rules2
+
+ .*\.dSYM($|/)
+
+ weight
+ 11
+
+ ^(.*/)?\.DS_Store$
+
+ omit
+
+ weight
+ 2000
+
+ ^.*
+
+ ^.*\.lproj/
+
+ optional
+
+ weight
+ 1000
+
+ ^.*\.lproj/locversion.plist$
+
+ omit
+
+ weight
+ 1100
+
+ ^Base\.lproj/
+
+ weight
+ 1010
+
+ ^Info\.plist$
+
+ omit
+
+ weight
+ 20
+
+ ^PkgInfo$
+
+ omit
+
+ weight
+ 20
+
+ ^embedded\.provisionprofile$
+
+ weight
+ 20
+
+ ^version\.plist$
+
+ weight
+ 20
+
+
+
+
diff --git a/Extensions/OpenYoutubeSafariExtension.appex/_locales/en/messages.json b/Extensions/OpenYoutubeSafariExtension.appex/_locales/en/messages.json
new file mode 100644
index 0000000..12ab694
--- /dev/null
+++ b/Extensions/OpenYoutubeSafariExtension.appex/_locales/en/messages.json
@@ -0,0 +1,10 @@
+{
+ "extension_name": {
+ "message": "OpenYoutube",
+ "description": "The display name for the extension."
+ },
+ "extension_description": {
+ "message": "Displays an Open in Youtube alert for sideloaded YT",
+ "description": "Description of what the extension does."
+ }
+}
diff --git a/Extensions/OpenYoutubeSafariExtension.appex/background.js b/Extensions/OpenYoutubeSafariExtension.appex/background.js
new file mode 100644
index 0000000..ff39b3b
--- /dev/null
+++ b/Extensions/OpenYoutubeSafariExtension.appex/background.js
@@ -0,0 +1,6 @@
+browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
+ console.log("Received request: ", request);
+
+ if (request.greeting === "hello")
+ sendResponse({ farewell: "goodbye" });
+});
diff --git a/Extensions/OpenYoutubeSafariExtension.appex/content.js b/Extensions/OpenYoutubeSafariExtension.appex/content.js
new file mode 100644
index 0000000..8c80064
--- /dev/null
+++ b/Extensions/OpenYoutubeSafariExtension.appex/content.js
@@ -0,0 +1,25 @@
+browser.runtime.sendMessage({ greeting: "hello" }).then((response) => {
+ console.log("Received response: ", response);
+});
+
+browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
+ console.log("Received request: ", request);
+});
+
+function afterNavigate() {
+ if ('/watch' === location.pathname) {
+ window.location.href = `youtube://${window.location.pathname.slice(1)}${
+ window.location.search
+ }${window.location.hash}`;
+ }
+}
+(document.body || document.documentElement).addEventListener('transitionend',
+ function(/*TransitionEvent*/ event) {
+ if (event.propertyName === 'width' && event.target.id === 'progress') {
+ afterNavigate();
+ }
+}, true);
+// After page load
+afterNavigate();
+
+
diff --git a/Extensions/OpenYoutubeSafariExtension.appex/images/cat_1.0.gif b/Extensions/OpenYoutubeSafariExtension.appex/images/cat_1.0.gif
new file mode 100644
index 0000000..ce1e88b
Binary files /dev/null and b/Extensions/OpenYoutubeSafariExtension.appex/images/cat_1.0.gif differ
diff --git a/Extensions/OpenYoutubeSafariExtension.appex/images/icon-128.png b/Extensions/OpenYoutubeSafariExtension.appex/images/icon-128.png
new file mode 100644
index 0000000..c919eb0
Binary files /dev/null and b/Extensions/OpenYoutubeSafariExtension.appex/images/icon-128.png differ
diff --git a/Extensions/OpenYoutubeSafariExtension.appex/images/icon-256.png b/Extensions/OpenYoutubeSafariExtension.appex/images/icon-256.png
new file mode 100644
index 0000000..6bd3d20
Binary files /dev/null and b/Extensions/OpenYoutubeSafariExtension.appex/images/icon-256.png differ
diff --git a/Extensions/OpenYoutubeSafariExtension.appex/images/icon-48.png b/Extensions/OpenYoutubeSafariExtension.appex/images/icon-48.png
new file mode 100644
index 0000000..353e8fb
Binary files /dev/null and b/Extensions/OpenYoutubeSafariExtension.appex/images/icon-48.png differ
diff --git a/Extensions/OpenYoutubeSafariExtension.appex/images/icon-512.png b/Extensions/OpenYoutubeSafariExtension.appex/images/icon-512.png
new file mode 100644
index 0000000..2200828
Binary files /dev/null and b/Extensions/OpenYoutubeSafariExtension.appex/images/icon-512.png differ
diff --git a/Extensions/OpenYoutubeSafariExtension.appex/images/icon-64.png b/Extensions/OpenYoutubeSafariExtension.appex/images/icon-64.png
new file mode 100644
index 0000000..995689f
Binary files /dev/null and b/Extensions/OpenYoutubeSafariExtension.appex/images/icon-64.png differ
diff --git a/Extensions/OpenYoutubeSafariExtension.appex/images/icon-96.png b/Extensions/OpenYoutubeSafariExtension.appex/images/icon-96.png
new file mode 100644
index 0000000..cb079d2
Binary files /dev/null and b/Extensions/OpenYoutubeSafariExtension.appex/images/icon-96.png differ
diff --git a/Extensions/OpenYoutubeSafariExtension.appex/images/toolbar-icon-16.png b/Extensions/OpenYoutubeSafariExtension.appex/images/toolbar-icon-16.png
new file mode 100644
index 0000000..ad014f6
Binary files /dev/null and b/Extensions/OpenYoutubeSafariExtension.appex/images/toolbar-icon-16.png differ
diff --git a/Extensions/OpenYoutubeSafariExtension.appex/images/toolbar-icon-19.png b/Extensions/OpenYoutubeSafariExtension.appex/images/toolbar-icon-19.png
new file mode 100644
index 0000000..33eb01e
Binary files /dev/null and b/Extensions/OpenYoutubeSafariExtension.appex/images/toolbar-icon-19.png differ
diff --git a/Extensions/OpenYoutubeSafariExtension.appex/images/toolbar-icon-32.png b/Extensions/OpenYoutubeSafariExtension.appex/images/toolbar-icon-32.png
new file mode 100644
index 0000000..a71914b
Binary files /dev/null and b/Extensions/OpenYoutubeSafariExtension.appex/images/toolbar-icon-32.png differ
diff --git a/Extensions/OpenYoutubeSafariExtension.appex/images/toolbar-icon-38.png b/Extensions/OpenYoutubeSafariExtension.appex/images/toolbar-icon-38.png
new file mode 100644
index 0000000..990e7f4
Binary files /dev/null and b/Extensions/OpenYoutubeSafariExtension.appex/images/toolbar-icon-38.png differ
diff --git a/Extensions/OpenYoutubeSafariExtension.appex/images/toolbar-icon-48.png b/Extensions/OpenYoutubeSafariExtension.appex/images/toolbar-icon-48.png
new file mode 100644
index 0000000..f4c70ad
Binary files /dev/null and b/Extensions/OpenYoutubeSafariExtension.appex/images/toolbar-icon-48.png differ
diff --git a/Extensions/OpenYoutubeSafariExtension.appex/images/toolbar-icon-72.png b/Extensions/OpenYoutubeSafariExtension.appex/images/toolbar-icon-72.png
new file mode 100644
index 0000000..9bf6d4e
Binary files /dev/null and b/Extensions/OpenYoutubeSafariExtension.appex/images/toolbar-icon-72.png differ
diff --git a/Extensions/OpenYoutubeSafariExtension.appex/manifest.json b/Extensions/OpenYoutubeSafariExtension.appex/manifest.json
new file mode 100644
index 0000000..a7c520e
--- /dev/null
+++ b/Extensions/OpenYoutubeSafariExtension.appex/manifest.json
@@ -0,0 +1,40 @@
+{
+ "manifest_version": 2,
+ "default_locale": "en",
+
+ "name": "__MSG_extension_name__",
+ "description": "__MSG_extension_description__",
+ "version": "1.0",
+
+ "icons": {
+ "48": "images/icon-48.png",
+ "96": "images/icon-96.png",
+ "128": "images/icon-128.png",
+ "256": "images/icon-256.png",
+ "512": "images/icon-512.png"
+ },
+
+ "background": {
+ "scripts": [ "background.js" ],
+ "persistent": false
+ },
+
+ "content_scripts": [{
+ "js": [ "content.js" ],
+ "matches": [ "*://*.youtube.com/*" ]
+ }],
+
+ "browser_action": {
+ "default_popup": "popup.html",
+ "default_icon": {
+ "16": "images/toolbar-icon-16.png",
+ "19": "images/toolbar-icon-19.png",
+ "32": "images/toolbar-icon-32.png",
+ "38": "images/toolbar-icon-38.png",
+ "48": "images/toolbar-icon-48.png",
+ "72": "images/toolbar-icon-72.png"
+ }
+ },
+
+ "permissions": [ ]
+}
diff --git a/Extensions/OpenYoutubeSafariExtension.appex/popup.css b/Extensions/OpenYoutubeSafariExtension.appex/popup.css
new file mode 100644
index 0000000..5b149b9
--- /dev/null
+++ b/Extensions/OpenYoutubeSafariExtension.appex/popup.css
@@ -0,0 +1,15 @@
+:root {
+ color-scheme: light dark;
+}
+
+body {
+ width: 100px;
+ padding: 10px;
+
+ font-family: system-ui;
+ text-align: center;
+}
+
+@media (prefers-color-scheme: dark) {
+ /* Dark Mode styles go here. */
+}
diff --git a/Extensions/OpenYoutubeSafariExtension.appex/popup.html b/Extensions/OpenYoutubeSafariExtension.appex/popup.html
new file mode 100644
index 0000000..0287388
--- /dev/null
+++ b/Extensions/OpenYoutubeSafariExtension.appex/popup.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+ Extension made by @CokePokes
+
+
diff --git a/Extensions/OpenYoutubeSafariExtension.appex/popup.js b/Extensions/OpenYoutubeSafariExtension.appex/popup.js
new file mode 100644
index 0000000..5c1aa86
--- /dev/null
+++ b/Extensions/OpenYoutubeSafariExtension.appex/popup.js
@@ -0,0 +1 @@
+console.log("Hello World!", browser);
diff --git a/Extensions/README.md b/Extensions/README.md
new file mode 100644
index 0000000..5b68abf
--- /dev/null
+++ b/Extensions/README.md
@@ -0,0 +1,20 @@
+# YoutubeExtensions
+ appex extensions for sideloaded YT
+
+
+ What is this?
+
+ These plugins enable "Open In" for sideloaded Youtube without any need to download other Opener apps /Shortcuts.
+
+
+ How to install:
+
+ 1. Download the .appex files here
+ 2. Unzip Youtube.ipa & copy them to /Payload/Youtube.app/Plugins
+ 3. Compress Payload folder & rename .zip to .ipa
+ 4. Sign & Install onto device
+ 5. Open YouTube once & then open Safari to enable the iOS 15 safari plugin [prompted always allow - do it]
+ 6. Press the Share icon in Safari scroll app list > More > Enable Youtube
+
+ Done!
+
diff --git a/Extensions/ShareServiceExtension.appex/Base.lproj/MainInterface.storyboardc/Info.plist b/Extensions/ShareServiceExtension.appex/Base.lproj/MainInterface.storyboardc/Info.plist
new file mode 100644
index 0000000..c231977
Binary files /dev/null and b/Extensions/ShareServiceExtension.appex/Base.lproj/MainInterface.storyboardc/Info.plist differ
diff --git a/Extensions/ShareServiceExtension.appex/Base.lproj/MainInterface.storyboardc/UIViewController-j1y-V4-xli.nib b/Extensions/ShareServiceExtension.appex/Base.lproj/MainInterface.storyboardc/UIViewController-j1y-V4-xli.nib
new file mode 100644
index 0000000..8dac7fd
Binary files /dev/null and b/Extensions/ShareServiceExtension.appex/Base.lproj/MainInterface.storyboardc/UIViewController-j1y-V4-xli.nib differ
diff --git a/Extensions/ShareServiceExtension.appex/Base.lproj/MainInterface.storyboardc/j1y-V4-xli-view-wbc-yd-nQP.nib b/Extensions/ShareServiceExtension.appex/Base.lproj/MainInterface.storyboardc/j1y-V4-xli-view-wbc-yd-nQP.nib
new file mode 100644
index 0000000..a0da51e
Binary files /dev/null and b/Extensions/ShareServiceExtension.appex/Base.lproj/MainInterface.storyboardc/j1y-V4-xli-view-wbc-yd-nQP.nib differ
diff --git a/Extensions/ShareServiceExtension.appex/Info.plist b/Extensions/ShareServiceExtension.appex/Info.plist
new file mode 100644
index 0000000..427b324
Binary files /dev/null and b/Extensions/ShareServiceExtension.appex/Info.plist differ
diff --git a/Extensions/ShareServiceExtension.appex/Youtube b/Extensions/ShareServiceExtension.appex/Youtube
new file mode 100755
index 0000000..f903ca4
Binary files /dev/null and b/Extensions/ShareServiceExtension.appex/Youtube differ
diff --git a/Extensions/ShareServiceExtension.appex/Youtube@2x.png b/Extensions/ShareServiceExtension.appex/Youtube@2x.png
new file mode 100644
index 0000000..36773c5
Binary files /dev/null and b/Extensions/ShareServiceExtension.appex/Youtube@2x.png differ
diff --git a/Extensions/ShareServiceExtension.appex/_CodeSignature/CodeResources b/Extensions/ShareServiceExtension.appex/_CodeSignature/CodeResources
new file mode 100644
index 0000000..d2950b5
--- /dev/null
+++ b/Extensions/ShareServiceExtension.appex/_CodeSignature/CodeResources
@@ -0,0 +1,157 @@
+
+
+
+
+ files
+
+ Base.lproj/MainInterface.storyboardc/Info.plist
+
+ Bgt43YuQd48Y7NBkKAH/I1Nd+7Q=
+
+ Base.lproj/MainInterface.storyboardc/UIViewController-j1y-V4-xli.nib
+
+ t/rVTMy59WLCl0e0ydlT2JlJKIs=
+
+ Base.lproj/MainInterface.storyboardc/j1y-V4-xli-view-wbc-yd-nQP.nib
+
+ 56s4vIzFNrq5HEvB3Mr6qnqVRFg=
+
+ Info.plist
+
+ 7+n2S5sS/Ngeoe21gJMc6iC6+ac=
+
+ embedded.mobileprovision
+
+ dVGLS8Qpu/eCI1J5VCu9IfYq3Z4=
+
+ icon.png
+
+ /pip+c1EETgjhxNcOhgiyQxxzyU=
+
+
+ files2
+
+ Base.lproj/MainInterface.storyboardc/Info.plist
+
+ hash2
+
+ Qnp5pVqL/lRCSEISDYZs1VBLxfcEnfpOzHg6WgcgLgk=
+
+
+ Base.lproj/MainInterface.storyboardc/UIViewController-j1y-V4-xli.nib
+
+ hash2
+
+ VpQww1vK2BoM0RIaiw/B/8JZ3kp5Pks5bM8Sjtjju9w=
+
+
+ Base.lproj/MainInterface.storyboardc/j1y-V4-xli-view-wbc-yd-nQP.nib
+
+ hash2
+
+ C87FO0vFlvhG8vp7+3v+EcR9kbOeu9v+mDLy/iU+6e8=
+
+
+ embedded.mobileprovision
+
+ hash2
+
+ 4TQyzTTz0VYUTM0IXf2PMSufVEkzd4WlPWbwKMyfKfQ=
+
+
+ icon.png
+
+ hash2
+
+ mfufJ0CJyFLUpRnLC8q8awHkGqmWeQtRDgF5EBXKUQs=
+
+
+
+ rules
+
+ ^.*
+
+ ^.*\.lproj/
+
+ optional
+
+ weight
+ 1000
+
+ ^.*\.lproj/locversion.plist$
+
+ omit
+
+ weight
+ 1100
+
+ ^Base\.lproj/
+
+ weight
+ 1010
+
+ ^version.plist$
+
+
+ rules2
+
+ .*\.dSYM($|/)
+
+ weight
+ 11
+
+ ^(.*/)?\.DS_Store$
+
+ omit
+
+ weight
+ 2000
+
+ ^.*
+
+ ^.*\.lproj/
+
+ optional
+
+ weight
+ 1000
+
+ ^.*\.lproj/locversion.plist$
+
+ omit
+
+ weight
+ 1100
+
+ ^Base\.lproj/
+
+ weight
+ 1010
+
+ ^Info\.plist$
+
+ omit
+
+ weight
+ 20
+
+ ^PkgInfo$
+
+ omit
+
+ weight
+ 20
+
+ ^embedded\.provisionprofile$
+
+ weight
+ 20
+
+ ^version\.plist$
+
+ weight
+ 20
+
+
+
+
diff --git a/Extensions/ShareServiceExtension.appex/test.json b/Extensions/ShareServiceExtension.appex/test.json
new file mode 100644
index 0000000..06c02da
--- /dev/null
+++ b/Extensions/ShareServiceExtension.appex/test.json
@@ -0,0 +1,7 @@
+var GetURL = function() {};
+GetURL.prototype = {
+run: function(arguments) {
+ arguments.completionFunction({"URL": document.URL});
+}
+};
+var ExtensionPreprocessingJS = new GetURL;
diff --git a/Header.h b/Header.h
new file mode 100644
index 0000000..fc38ccd
--- /dev/null
+++ b/Header.h
@@ -0,0 +1,165 @@
+#import "Tweaks/YouTubeHeader/YTPlayerViewController.h" // Header.h
+#import "Tweaks/YouTubeHeader/YTQTMButton.h" // Header.h
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import "Tweaks/FLEX/FLEX.h"
+#import "Tweaks/YouTubeHeader/YTVideoQualitySwitchOriginalController.h"
+#import "Tweaks/YouTubeHeader/YTPlayerViewController.h"
+#import "Tweaks/YouTubeHeader/YTWatchController.h"
+#import "Tweaks/YouTubeHeader/YTIGuideResponse.h"
+#import "Tweaks/YouTubeHeader/YTIGuideResponseSupportedRenderers.h"
+#import "Tweaks/YouTubeHeader/YTIPivotBarSupportedRenderers.h"
+#import "Tweaks/YouTubeHeader/YTIPivotBarRenderer.h"
+#import "Tweaks/YouTubeHeader/YTIBrowseRequest.h"
+#import "Tweaks/YouTubeHeader/YTCommonColorPalette.h"
+#import "Tweaks/YouTubeHeader/ASCollectionView.h"
+#import "Tweaks/YouTubeHeader/YTPlayerOverlay.h"
+#import "Tweaks/YouTubeHeader/YTPlayerOverlayProvider.h"
+#import "Tweaks/YouTubeHeader/YTReelWatchPlaybackOverlayView.h"
+#import "Tweaks/YouTubeHeader/YTReelPlayerBottomButton.h"
+#import "Tweaks/YouTubeHeader/YTReelPlayerViewController.h"
+#import "Tweaks/YouTubeHeader/YTAlertView.h"
+#import "Tweaks/YouTubeHeader/YTISectionListRenderer.h"
+#import "Tweaks/YouTubeHeader/YTPivotBarItemView.h"
+
+#define LOC(x) [tweakBundle localizedStringForKey:x value:nil table:nil]
+#define YT_BUNDLE_ID @"com.google.ios.youtube"
+#define YT_NAME @"YouTube"
+
+// YTSpeed
+@interface YTVarispeedSwitchControllerOption : NSObject
+- (id)initWithTitle:(id)title rate:(float)rate;
+@end
+
+@interface MLHAMQueuePlayer : NSObject
+@property id playerEventCenter;
+@property id delegate;
+- (void)setRate:(float)rate;
+- (void)internalSetRate;
+@end
+
+@interface MLPlayerStickySettings : NSObject
+- (void)setRate:(float)rate;
+@end
+
+@interface MLPlayerEventCenter : NSObject
+- (void)broadcastRateChange:(float)rate;
+@end
+
+@interface HAMPlayerInternal : NSObject
+- (void)setRate:(float)rate;
+@end
+
+// YTLitePlus
+@interface YTChipCloudCell : UIView
+@end
+
+@interface YTPlayabilityResolutionUserActionUIController : NSObject // Skips content warning before playing *some videos - @PoomSmart
+- (void)confirmAlertDidPressConfirm;
+@end
+
+@interface YTMainAppControlsOverlayView: UIView
+@end
+
+@interface YTTransportControlsButtonView : UIView
+@end
+
+@interface YTPlaybackButton : UIControl
+@end
+
+@interface YTSegmentableInlinePlayerBarView
+@property (nonatomic, assign, readwrite) BOOL enableSnapToChapter;
+@end
+
+// Cercube button in Nav bar
+@interface MDCButton : UIButton
+@end
+
+@interface YTRightNavigationButtons : UIView
+@property (nonatomic, strong, readwrite) MDCButton *cercubeButton;
+@property YTQTMButton *notificationButton;
+@property YTQTMButton *sponsorBlockButton;
+@end
+
+// IAmYouTube
+@interface SSOConfiguration : NSObject
+@end
+
+// BigYTMiniPlayer
+@interface YTMainAppVideoPlayerOverlayView : UIView
+- (UIViewController *)_viewControllerForAncestor;
+@end
+
+@interface YTWatchMiniBarView : UIView
+@end
+
+// YTAutoFullscreen
+@interface YTPlayerViewController (YTAFS)
+- (void)autoFullscreen;
+@end
+
+// YTNoShorts
+@interface ELMCellNode
+@end
+
+@interface _ASCollectionViewCell : UICollectionViewCell
+- (id)node;
+@end
+
+@interface YTAsyncCollectionView : UICollectionView
+- (void)removeShortsCellAtIndexPath:(NSIndexPath *)indexPath;
+@end
+
+@interface YTPlayerView : UIView
+- (void)downloadVideo;
+@end
+
+
+// App Theme
+@interface YCHLiveChatView : UIView
+@end
+
+@interface YTFullscreenEngagementOverlayView : UIView
+@end
+
+@interface YTRelatedVideosView : UIView
+@end
+
+@interface ELMView: UIView
+@end
+
+@interface ASWAppSwitcherCollectionViewCell: UIView
+@end
+
+@interface ASScrollView : UIView
+@end
+
+@interface UIKeyboardLayoutStar : UIView
+@end
+
+@interface UIKeyboardDockView : UIView
+@end
+
+@interface _ASDisplayView : UIView
+@end
+
+@interface YTCommentDetailHeaderCell : UIView
+@end
+
+@interface SponsorBlockSettingsController : UITableViewController
+@end
+
+@interface SponsorBlockViewController : UIViewController
+@end
+
+@interface UICandidateViewController : UIViewController
+@end
+
+@interface UIPredictionViewController : UIViewController
+@end
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..6166cc1
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,42 @@
+TARGET = iphone:clang:15.5:14.0
+YTLitePlus_USE_FISHHOOK = 0
+ARCHS = arm64
+MODULES = jailed
+FINALPACKAGE = 1
+CODESIGN_IPA = 0
+PACKAGE_VERSION = 18.23.3-2.0
+
+TWEAK_NAME = YTLitePlus
+DISPLAY_NAME = YouTube
+BUNDLE_ID = com.google.ios.youtube
+
+EXTRA_CFLAGS := $(addprefix -I,$(shell find Tweaks/FLEX -name '*.h' -exec dirname {} \;))
+
+YTLitePlus_INJECT_DYLIBS = .theos/obj/libcolorpicker.dylib .theos/obj/iSponsorBlock.dylib .theos/obj/YTUHD.dylib .theos/obj/YouPiP.dylib .theos/obj/YouTubeDislikesReturn.dylib .theos/obj/YTABConfig.dylib .theos/obj/YouMute.dylib .theos/obj/DontEatMyContent.dylib .theos/obj/YTHoldForSpeed.dylib .theos/obj/YTLite.dylib
+YTLitePlus_FILES = YTLitePlus.xm $(shell find Source -name '*.xm' -o -name '*.x' -o -name '*.m') $(shell find Tweaks/FLEX -type f \( -iname \*.c -o -iname \*.m -o -iname \*.mm \))
+YTLitePlus_IPA = ./tmp/Payload/YouTube.app
+YTLitePlus_CFLAGS = -fobjc-arc -Wno-deprecated-declarations -Wno-unsupported-availability-guard -Wno-unused-but-set-variable -DTWEAK_VERSION=$(PACKAGE_VERSION) $(EXTRA_CFLAGS)
+YTLitePlus_FRAMEWORKS = UIKit Security
+
+include $(THEOS)/makefiles/common.mk
+include $(THEOS_MAKE_PATH)/tweak.mk
+SUBPROJECTS += Tweaks/Alderis Tweaks/iSponsorBlock Tweaks/YTUHD Tweaks/YouPiP Tweaks/Return-YouTube-Dislikes Tweaks/YTABConfig Tweaks/YouMute Tweaks/DontEatMyContent Tweaks/YTLite Tweaks/YTHoldForSpeed
+include $(THEOS_MAKE_PATH)/aggregate.mk
+
+before-package::
+ @echo -e "==> \033[1mMoving tweak's bundle to Resources/...\033[0m"
+ @mkdir -p Resources/Frameworks/Alderis.framework && find .theos/obj/install/Library/Frameworks/Alderis.framework -maxdepth 1 -type f -exec cp {} Resources/Frameworks/Alderis.framework/ \;
+ @cp -R Tweaks/YTLite/layout/Library/Application\ Support/YTLite.bundle Resources/
+ @cp -R Tweaks/YTUHD/layout/Library/Application\ Support/YTUHD.bundle Resources/
+ @cp -R Tweaks/YouPiP/layout/Library/Application\ Support/YouPiP.bundle Resources/
+ @cp -R Tweaks/Return-YouTube-Dislikes/layout/Library/Application\ Support/RYD.bundle Resources/
+ @cp -R Tweaks/YTABConfig/layout/Library/Application\ Support/YTABC.bundle Resources/
+ @cp -R Tweaks/YouMute/layout/Library/Application\ Support/YouMute.bundle Resources/
+ @cp -R Tweaks/DontEatMyContent/layout/Library/Application\ Support/DontEatMyContent.bundle Resources/
+ @cp -R Tweaks/YTHoldForSpeed/layout/Library/Application\ Support/YTHoldForSpeed.bundle Resources/
+ @cp -R Tweaks/iSponsorBlock/layout/Library/Application\ Support/iSponsorBlock.bundle Resources/
+ @cp -R lang/YTLitePlus.bundle Resources/
+ @echo -e "==> \033[1mChanging the installation path of dylibs...\033[0m"
+ @ldid -r .theos/obj/iSponsorBlock.dylib && install_name_tool -change /usr/lib/libcolorpicker.dylib @rpath/libcolorpicker.dylib .theos/obj/iSponsorBlock.dylib
+ @codesign --remove-signature .theos/obj/libcolorpicker.dylib && install_name_tool -change /Library/Frameworks/Alderis.framework/Alderis @rpath/Alderis.framework/Alderis .theos/obj/libcolorpicker.dylib
+
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..77b0fc4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,302 @@
+## Cercube with extra features!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# Table of Contents
+
+* [Credits](#credits)
+* [Features](#features)
+* [Known issues](#known-issues)
+* [Download (IPA)](#download-ipa)
+* [Support the developers!](#support-the-developers)
+* [Building (optional)](#building-optional)
+
+# Credits
+
+
+Special thanks to these developer(s) for maintaining and improving CercubePlus (@qnblackcat is the OG dev)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Special thanks to all the developers who have contributed to CercubePlus/CercubePlusExtra!
+
+(Cercube is an original tweak by Majd Alfhaily @majd)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# Features
+
+1. **Cercube:**
+
+- Block all advertisements & Enable background playback.
+
+- Set default player quality on WiFi & Celullar.
+
+- Save videos in high resolution, save videos as audio-only, save public playlists (beta).
+
+- And many more...
+
+2. **iSponsorBlock:** Skips annoying sponsor ads inside videos. iSponsorBlock is based on [SponsorBlock engine](https://sponsor.ajay.app/). Basically, this is the iOS version of the SponsorBlock extension.
+
+3. **YouPiP:** enable YouTube's **native PiP**. More options are in YouTube Settings => General.
+
+4. **YTUHD:** unlock VP9 codec and in effect, enables video quality of 2K and 4K. You can configure YTUHD in YouTube's Settings - Video quality preferences.
+
+5. **YouTube Dislike Return:** brings back Dislike counts under YouTube videos using **ReturnYoutubeDislike**'s API.
+
+
+ and many more...!
+
+6. **YTClassicVideoQuality:** since YouTube v16.xx, you need one more step to change the video quality. YTClassicVideoQuality brings back the old video quality selector, which is a lot better than the new one.
+
+7. **YTNoHoverCards:** offer an option to enable/disable the annoying suggested videos show up at the end of the videos.
+
+8. **YTABGoodies:** allow you to disable some YouTube A/B testing features. It is a combination of several tweaks, such as:
+
+- YouAreThere: disable "Video paused. Continue watching?" popup in the YouTube app when you play a long video.
+
+- YouRememberCaption: make YouTube remember your video caption setting (if not already).
+
+- YTNoCheckLocalNetwork: block the Local Network permission popup.
+
+9. **NOYTPremium:** remove YouTube Premium upsell alerts.
+
+10. **YTSpeed**: a toggleable tweak to add 2.25x, 2.5x, 2.75x, 3x, 3.25x, 3.5x, 3.75x, 4x & 5x playback speed options in the video player.
+
+11. **YTMiniplayerEnabler**: enable Miniplayer for all YouTube videos.
+
+12. **DontEatMyContent**: prevent the notch/Dynamic Island from munching on 2:1 video content in YouTube.
+
+13. **YTShortsProgress**: always enable progress bar and scrubbing in YouTube Shorts (iPhone only).
+
+14. **YTABConfig**: allow user to control over YouTube A/B testing flags.
+
+15. **YouMute**: Mute/unmute videos in the YouTube Video Player directly.
+
+16. **LowContrastMode**: makes the YouTube Interface Low Contrast as possible to make it easier on the eyes.
+
+any many more...
+
+
+# Known issues
+
+1. **Cercube**:
+
+- Hide Cast button is not working. (Workaround: Hide cast button in CercubePlus settings)
+
+- The Updated Dark Mode in the YouTube App is not Present or Working in Cercube v5.3.13 & older versions
+
+2. **LowContrastMode**: this tweak doesn't work everywhere with every ui element on the YouTube App.
+
+3. **YTUHD**: [Stuttering on 4K videos](https://github.com/qnblackcat/uYouPlus/issues/6).
+
+4. **YouPiP** (iOS 14.0 - 14.4.2): due to Apple's fault, you may encounter the *speedup-bug* as described [here](https://drive.google.com/file/d/1NKdv1fr_KRWgD8nhkMDfG2eLBnbdeVtX/view?usp=sharing). The bug also happens when you try to play multi-sources of sound at the same time. Enable **LegacyPiP** is a workaround. Keep in mind that LegacyPiP also removes UHD quality and breaks YouTube Autoplay next. Use it at your own risk!
+
+5. **Not a bug**:
+
+- The app won't be able to receive push notifications if you use a free developer account to sideload it.
+
+- It's impossible to fix deep-link (a.k.a Open in the YouTube app). However, you can use this [Shortcuts](https://shortcutsgallery.com/shortcuts/open-in-youtube/) as a workaround (tested on iOS 14). **Credit:** RandomAccessMemories#5025
+
+# Download (IPA)
+
+- **CercubePlus** (or you can call it Cercube+) requires iOS & iPadOS 14.0 and later. The latest version of **CercubePlus** can be found in the [Release tab](https://github.com/Balackburn/CercubePlusExtra/releases/latest).
+
+- For AltStore user: [Open in AltStore (v18.18.2-5.3.11)](https://tinyurl.com/5a5jn7ra) - It will take a while to install because AltStore needs to download the IPA first.
+
+
+ Alternative Download
+
+[Open in AltStore (v18.14.1-5.3.11-F2)](https://tinyurl.com/4exxknn8) - This is the **v18.14.1-5.3.11-F2** release. only use this to resolve the OLED/OLD Dark Mode Problem.
+
+
+
+- Version info: _(May 9 2023)_
+
+
+ Expand!
+
+| **Tweaks/App** | **Developer** | **Version** | **Open source** |
+| - | - | :-: | :-: |
+| **YouTube** | Google Inc | 18.21.3 | ✖︎ |
+| **Cercube** | Majd Alfhaily | 5.3.11 | ✖︎ |
+| **Open in YouTube** | [CokePokes](https://github.com/CokePokes/) | 1.2 | ✖︎ |
+| **YTNoShorts** | [MiRO92](https://twitter.com/miro92) | 1.0.2 | [✔︎](https://github.com/MiRO92/YTNoShorts) |
+| **iSponsorBlock** | [Galactic-Dev](https://github.com/Galactic-Dev) | 1.0-15 | [✔︎](https://github.com/Galactic-Dev/iSponsorBlock) |
+| **BigYTMiniPlayer** | [Galactic-Dev](https://github.com/Galactic-Dev) | 1.0-1 | [✔︎](https://github.com/Galactic-Dev/BigYTMiniPlayer) |
+| **YTNoHoverCards** | [level3tjg](https://twitter.com/level3tjg) | 0.0.3 | [✔︎](https://github.com/level3tjg/YTNoHoverCards) |
+| **YTMiniplayerEnabler** | [level3tjg](https://twitter.com/level3tjg) | 0.0.2 | [✔︎](https://github.com/level3tjg/YTMiniplayerEnabler) |
+| **DontEatMyContent** | [therealFoxster](https://github.com/therealFoxster) | 1.0.6 | [✔︎](https://github.com/therealFoxster/DontEatMyContent) |
+| **LowContrastMode** | arichorn | 1.2.3 | [✔︎](https://github.com/arichorn/LowContrastMode) |
+| **YTUHD** | PoomSmart | 1.4.0 | [✔︎](https://github.com/PoomSmart/YTUHD) |
+| **YouPiP** | PoomSmart | 1.7.19-2 | [✔︎](https://github.com/PoomSmart/YouPiP) |
+| **YouMute** | PoomSmart | 1.1.1-1 | [✔︎](https://github.com/PoomSmart/YouMute) |
+| **YTABConfig** | PoomSmart | 1.5.0-1 | [✔︎](https://github.com/PoomSmart/YTABConfig) |
+| **IAmYouTube** | PoomSmart | 1.2.0 | [✔︎](https://github.com/PoomSmart/IAmYouTube) |
+| **YTReExplore** | PoomSmart | 1.0.2 | [✔︎](https://github.com/PoomSmart/YTReExplore) |
+| **NoYTPremium** | PoomSmart | 1.0.4 | [✔︎](https://github.com/PoomSmart/NoYTPremium) |
+| **YTNoPaidPromo** | PoomSmart | 1.0.0 | [✔︎](https://github.com/PoomSmart/YTNoPaidPromo) |
+| **YTAutoFullScreen** | PoomSmart | 1.0.3 | [✔︎](https://github.com/PoomSmart/YTAutoFullScreen) |
+| **YTShortsProgress** | PoomSmart | 1.0.2 | [✔︎](https://github.com/PoomSmart/YTShortsProgress) |
+| **Return YouTube Dislike** | PoomSmart | 1.11.3 | [✔︎](https://github.com/PoomSmart/Return-YouTube-Dislikes) |
+
+
+
+# Support the developers
+- [**MiRO92**](https://twitter.com/miro92): https://github.com/MiRO92/uYou-for-YouTube#support
+- [**level3tjg**](https://twitter.com/level3tjg): https://ko-fi.com/level3tjg
+- [**BandarHL**](https://twitter.com/bandarhl): https://www.paypal.com/paypalme/BandarHL
+- [**julioverne**](https://twitter.com/ijulioverne): https://www.patreon.com/julioverne
+- [**Galactic-dev**](https://twitter.com/dev_galactic):
+ - Paypal: https://www.paypal.com/paypalme/DBrett684
+ - Venmo: https://venmo.com/u/DavidBrett
+
+# Building(s) (optional)
+See [CercubePlusExtra/Building - Wiki](https://github.com/Balackburn/CercubePlusExtra/wiki/Building)
+or Another Version [uYouPlus/Building - Wiki](https://github.com/qnblackcat/uYouPlus/wiki/Building)
diff --git a/Source/Download.xm.bak b/Source/Download.xm.bak
new file mode 100644
index 0000000..7adb0d0
--- /dev/null
+++ b/Source/Download.xm.bak
@@ -0,0 +1,42 @@
+// Code has been disabled due to some compiling errors.
+
+#import "../Header.h"
+
+%ctor {
+ void $YTPlayerView_downloadVideo_register();
+ void $YTPlayerView_layoutSubviews$_register();
+}
+
+#ifndef YTPLAYERVIEW_DOWNLOADVIDEO_REGISTER
+#ifndef YTPLAYERVIEW_LAYOUTSUBVIEWS_REGISTER
+#define YTPLAYERVIEW_DOWNLOADVIDEO_REGISTER
+#define YTPLAYERVIEW_LAYOUTSUBVIEWS_REGISTER
+
+void $YTPlayerView_downloadVideo_register();
+void $YTPlayerView_layoutSubviews$_register();
+
+#endif
+
+// YouTube Video Downloading
+CHDeclareClass(YTPlayerView);
+
+CHOptimizedMethod1(self, void, YTPlayerView, layoutSubviews, BOOL, arg1)
+{
+ CHSuper1(YTPlayerView, layoutSubviews, arg1);
+ CGRect downloadButtonFrame = CGRectMake(0, 0, 100, 50);
+ UIButton *downloadButton = [[UIButton alloc] initWithFrame:downloadButtonFrame];
+ [downloadButton setTitle:@"Download" forState:UIControlStateNormal];
+ [downloadButton addTarget:self action:@selector(downloadVideo) forControlEvents:UIControlEventTouchUpInside];
+ [self addSubview:downloadButton];
+}
+
+CHOptimizedMethod0(self, void, YTPlayerView, downloadVideo)
+{
+ NSString *videoUrlString = @"[Insert YouTube video URL here]";
+ NSURL *videoUrl = [NSURL URLWithString:videoUrlString];
+ NSData *videoData = [NSData dataWithContentsOfURL:videoUrl];
+
+ // Save the video data to a file
+ NSString *filePath = @"[Insert file path here]";
+ [videoData writeToFile:filePath atomically:YES];
+}
diff --git a/Source/LowContrastMode.xm b/Source/LowContrastMode.xm
new file mode 100644
index 0000000..0fce450
--- /dev/null
+++ b/Source/LowContrastMode.xm
@@ -0,0 +1,1160 @@
+#import "../Header.h"
+
+//
+static BOOL IsEnabled(NSString *key) {
+ return [[NSUserDefaults standardUserDefaults] boolForKey:key];
+}
+static BOOL isDarkMode() {
+ return ([[NSUserDefaults standardUserDefaults] integerForKey:@"page_style"] == 1);
+}
+static int colorContrastMode() {
+ return [[NSUserDefaults standardUserDefaults] integerForKey:@"lcmColor"];
+}
+static BOOL defaultContrastMode() {
+ return IsEnabled(@"lowContrastMode_enabled") && colorContrastMode() == 0;
+}
+static BOOL redContrastMode() {
+ return IsEnabled(@"lowContrastMode_enabled") && colorContrastMode() == 1;
+}
+static BOOL blueContrastMode() {
+ return IsEnabled(@"lowContrastMode_enabled") && colorContrastMode() == 2;
+}
+static BOOL greenContrastMode() {
+ return IsEnabled(@"lowContrastMode_enabled") && colorContrastMode() == 3;
+}
+static BOOL yellowContrastMode() {
+ return IsEnabled(@"lowContrastMode_enabled") && colorContrastMode() == 4;
+}
+static BOOL orangeContrastMode() {
+ return IsEnabled(@"lowContrastMode_enabled") && colorContrastMode() == 5;
+}
+static BOOL purpleContrastMode() {
+ return IsEnabled(@"lowContrastMode_enabled") && colorContrastMode() == 6;
+}
+static BOOL violetContrastMode() {
+ return IsEnabled(@"lowContrastMode_enabled") && colorContrastMode() == 7;
+}
+static BOOL pinkContrastMode() {
+ return IsEnabled(@"lowContrastMode_enabled") && colorContrastMode() == 8;
+}
+
+%group gLowContrastMode // Low Contrast Mode v1.3.0 (Compatible with only v15.02.1-present)
+%hook UIColor
++ (UIColor *)whiteColor { // Dark Theme Color
+ return [UIColor colorWithRed: 0.56 green: 0.56 blue: 0.56 alpha: 1.00];
+}
++ (UIColor *)textColor {
+ return [UIColor colorWithRed: 0.56 green: 0.56 blue: 0.56 alpha: 1.00];
+}
+%end
+
+%hook UILabel
++ (void)load {
+ @autoreleasepool {
+ [[UILabel appearance] setTextColor:[UIColor whiteColor]];
+ }
+}
+%end
+
+%hook YTCommonColorPalette
+- (UIColor *)textPrimary {
+ if (self.pageStyle == 1) {
+ return [UIColor whiteColor]; // Dark Theme
+ }
+ return [UIColor colorWithRed: 0.38 green: 0.38 blue: 0.38 alpha: 1.00]; // Light Theme
+}
+- (UIColor *)textSecondary {
+ if (self.pageStyle == 1) {
+ return [UIColor whiteColor]; // Dark Theme
+ }
+ return [UIColor colorWithRed: 0.38 green: 0.38 blue: 0.38 alpha: 1.00]; // Light Theme
+}
+%end
+
+%hook YTCollectionView
+ - (void)setTintColor:(UIColor *)color {
+ return isDarkMode() ? %orig([UIColor whiteColor]) : %orig;
+}
+%end
+
+%hook LOTAnimationView
+- (void) setTintColor:(UIColor *)tintColor {
+ tintColor = [UIColor whiteColor];
+ %orig(tintColor);
+}
+%end
+
+%hook ASTextNode
+- (NSAttributedString *)attributedString {
+ NSAttributedString *originalAttributedString = %orig;
+
+ NSMutableAttributedString *newAttributedString = [originalAttributedString mutableCopy];
+ [newAttributedString addAttribute:NSForegroundColorAttributeName value:[UIColor whiteColor] range:NSMakeRange(0, newAttributedString.length)];
+
+ return newAttributedString;
+}
+%end
+
+%hook ASTextFieldNode
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook ASTextView
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook ASButtonNode
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook UIButton
+- (void)setTitleColor:(UIColor *)color forState:(UIControlState)state {
+ %log;
+ color = [UIColor whiteColor];
+ %orig(color, state);
+}
+%end
+
+%hook UILabel
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook UITextField
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook UITextView
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook VideoTitleLabel
+- (void)setTextColor:(UIColor *)textColor {
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook _ASDisplayView
+- (UIColor *)textColor {
+ return [UIColor whiteColor];
+}
+%end
+%end
+
+%group gRedContrastMode // Red Contrast Mode
+%hook UIColor
++ (UIColor *)whiteColor {
+ return [UIColor colorWithRed: 1.00 green: 0.31 blue: 0.27 alpha: 1.00];
+}
++ (UIColor *)textColor {
+ return [UIColor colorWithRed: 1.00 green: 0.31 blue: 0.27 alpha: 1.00];
+}
+%end
+
+%hook UILabel
++ (void)load {
+ @autoreleasepool {
+ [[UILabel appearance] setTextColor:[UIColor whiteColor]];
+ }
+}
+%end
+
+%hook YTCommonColorPalette
+- (UIColor *)textPrimary {
+ if (self.pageStyle == 1) {
+ return [UIColor whiteColor]; // Dark Theme
+ }
+ return [UIColor colorWithRed: 0.84 green: 0.25 blue: 0.23 alpha: 1.00]; // Light Theme
+ }
+- (UIColor *)textSecondary {
+ if (self.pageStyle == 1) {
+ return [UIColor whiteColor]; // Dark Theme
+ }
+ return [UIColor colorWithRed: 0.84 green: 0.25 blue: 0.23 alpha: 1.00]; // Light Theme
+ }
+%end
+
+%hook YTCollectionView
+ - (void)setTintColor:(UIColor *)color {
+ return isDarkMode() ? %orig([UIColor whiteColor]) : %orig;
+}
+%end
+
+%hook LOTAnimationView
+- (void) setTintColor:(UIColor *)tintColor {
+ tintColor = [UIColor whiteColor];
+ %orig(tintColor);
+}
+%end
+
+%hook ASTextNode
+- (NSAttributedString *)attributedString {
+ NSAttributedString *originalAttributedString = %orig;
+
+ NSMutableAttributedString *newAttributedString = [originalAttributedString mutableCopy];
+ [newAttributedString addAttribute:NSForegroundColorAttributeName value:[UIColor whiteColor] range:NSMakeRange(0, newAttributedString.length)];
+
+ return newAttributedString;
+}
+%end
+
+%hook ASTextFieldNode
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook ASTextView
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook ASButtonNode
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook UIButton
+- (void)setTitleColor:(UIColor *)color forState:(UIControlState)state {
+ %log;
+ color = [UIColor whiteColor];
+ %orig(color, state);
+}
+%end
+
+%hook UILabel
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook UITextField
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook UITextView
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook VideoTitleLabel
+- (void)setTextColor:(UIColor *)textColor {
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook _ASDisplayView
+- (UIColor *)textColor {
+ return [UIColor whiteColor];
+}
+%end
+%end
+
+%group gBlueContrastMode // Blue Contrast Mode
+%hook UIColor
++ (UIColor *)whiteColor {
+ return [UIColor colorWithRed: 0.04 green: 0.47 blue: 0.72 alpha: 1.00];
+}
++ (UIColor *)textColor {
+ return [UIColor colorWithRed: 0.04 green: 0.47 blue: 0.72 alpha: 1.00];
+}
+%end
+
+%hook UILabel
++ (void)load {
+ @autoreleasepool {
+ [[UILabel appearance] setTextColor:[UIColor whiteColor]];
+ }
+}
+%end
+
+%hook YTCommonColorPalette
+- (UIColor *)textPrimary {
+ if (self.pageStyle == 1) {
+ return [UIColor whiteColor]; // Dark Theme
+ }
+ return [UIColor colorWithRed: 0.04 green: 0.41 blue: 0.62 alpha: 1.00]; // Light Theme
+ }
+- (UIColor *)textSecondary {
+ if (self.pageStyle == 1) {
+ return [UIColor whiteColor]; // Dark Theme
+ }
+ return [UIColor colorWithRed: 0.04 green: 0.41 blue: 0.62 alpha: 1.00]; // Light Theme
+ }
+%end
+
+%hook YTCollectionView
+ - (void)setTintColor:(UIColor *)color {
+ return isDarkMode() ? %orig([UIColor whiteColor]) : %orig;
+}
+%end
+
+%hook LOTAnimationView
+- (void) setTintColor:(UIColor *)tintColor {
+ tintColor = [UIColor whiteColor];
+ %orig(tintColor);
+}
+%end
+
+%hook ASTextNode
+- (NSAttributedString *)attributedString {
+ NSAttributedString *originalAttributedString = %orig;
+
+ NSMutableAttributedString *newAttributedString = [originalAttributedString mutableCopy];
+ [newAttributedString addAttribute:NSForegroundColorAttributeName value:[UIColor whiteColor] range:NSMakeRange(0, newAttributedString.length)];
+
+ return newAttributedString;
+}
+%end
+
+%hook ASTextFieldNode
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook ASTextView
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook ASButtonNode
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook UIButton
+- (void)setTitleColor:(UIColor *)color forState:(UIControlState)state {
+ %log;
+ color = [UIColor whiteColor];
+ %orig(color, state);
+}
+%end
+
+%hook UILabel
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook UITextField
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook UITextView
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook VideoTitleLabel
+- (void)setTextColor:(UIColor *)textColor {
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook _ASDisplayView
+- (UIColor *)textColor {
+ return [UIColor whiteColor];
+}
+%end
+%end
+
+%group gGreenContrastMode // Green Contrast Mode
+%hook UIColor
++ (UIColor *)whiteColor {
+ return [UIColor colorWithRed: 0.01 green: 0.66 blue: 0.18 alpha: 1.00];
+}
++ (UIColor *)textColor {
+ return [UIColor colorWithRed: 0.01 green: 0.66 blue: 0.18 alpha: 1.00];
+}
+%end
+
+%hook UILabel
++ (void)load {
+ @autoreleasepool {
+ [[UILabel appearance] setTextColor:[UIColor whiteColor]];
+ }
+}
+%end
+
+%hook YTCommonColorPalette
+- (UIColor *)textPrimary {
+ if (self.pageStyle == 1) {
+ return [UIColor whiteColor]; // Dark Theme
+ }
+ return [UIColor colorWithRed: 0.00 green: 0.50 blue: 0.13 alpha: 1.00]; // Light Theme
+ }
+- (UIColor *)textSecondary {
+ if (self.pageStyle == 1) {
+ return [UIColor whiteColor]; // Dark Theme
+ }
+ return [UIColor colorWithRed: 0.00 green: 0.50 blue: 0.13 alpha: 1.00]; // Light Theme
+ }
+%end
+
+%hook YTCollectionView
+ - (void)setTintColor:(UIColor *)color {
+ return isDarkMode() ? %orig([UIColor whiteColor]) : %orig;
+}
+%end
+
+%hook LOTAnimationView
+- (void) setTintColor:(UIColor *)tintColor {
+ tintColor = [UIColor whiteColor];
+ %orig(tintColor);
+}
+%end
+
+%hook ASTextNode
+- (NSAttributedString *)attributedString {
+ NSAttributedString *originalAttributedString = %orig;
+
+ NSMutableAttributedString *newAttributedString = [originalAttributedString mutableCopy];
+ [newAttributedString addAttribute:NSForegroundColorAttributeName value:[UIColor whiteColor] range:NSMakeRange(0, newAttributedString.length)];
+
+ return newAttributedString;
+}
+%end
+
+%hook ASTextFieldNode
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook ASTextView
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook ASButtonNode
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook UIButton
+- (void)setTitleColor:(UIColor *)color forState:(UIControlState)state {
+ %log;
+ color = [UIColor whiteColor];
+ %orig(color, state);
+}
+%end
+
+%hook UILabel
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook UITextField
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook UITextView
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook VideoTitleLabel
+- (void)setTextColor:(UIColor *)textColor {
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook _ASDisplayView
+- (UIColor *)textColor {
+ return [UIColor whiteColor];
+}
+%end
+%end
+
+%group gYellowContrastMode // Yellow Contrast Mode
+%hook UIColor
++ (UIColor *)whiteColor {
+ return [UIColor colorWithRed: 0.89 green: 0.82 blue: 0.20 alpha: 1.00];
+}
++ (UIColor *)textColor {
+ return [UIColor colorWithRed: 0.89 green: 0.82 blue: 0.20 alpha: 1.00];
+}
+%end
+
+%hook UILabel
++ (void)load {
+ @autoreleasepool {
+ [[UILabel appearance] setTextColor:[UIColor whiteColor]];
+ }
+}
+%end
+
+%hook YTCommonColorPalette
+- (UIColor *)textPrimary {
+ if (self.pageStyle == 1) {
+ return [UIColor whiteColor]; // Dark Theme
+ }
+ return [UIColor colorWithRed: 0.77 green: 0.71 blue: 0.14 alpha: 1.00]; // Light Theme
+ }
+- (UIColor *)textSecondary {
+ if (self.pageStyle == 1) {
+ return [UIColor whiteColor]; // Dark Theme
+ }
+ return [UIColor colorWithRed: 0.77 green: 0.71 blue: 0.14 alpha: 1.00]; // Light Theme
+ }
+%end
+
+%hook YTCollectionView
+ - (void)setTintColor:(UIColor *)color {
+ return isDarkMode() ? %orig([UIColor whiteColor]) : %orig;
+}
+%end
+
+%hook LOTAnimationView
+- (void) setTintColor:(UIColor *)tintColor {
+ tintColor = [UIColor whiteColor];
+ %orig(tintColor);
+}
+%end
+
+%hook ASTextNode
+- (NSAttributedString *)attributedString {
+ NSAttributedString *originalAttributedString = %orig;
+
+ NSMutableAttributedString *newAttributedString = [originalAttributedString mutableCopy];
+ [newAttributedString addAttribute:NSForegroundColorAttributeName value:[UIColor whiteColor] range:NSMakeRange(0, newAttributedString.length)];
+
+ return newAttributedString;
+}
+%end
+
+%hook ASTextFieldNode
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook ASTextView
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook ASButtonNode
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook UIButton
+- (void)setTitleColor:(UIColor *)color forState:(UIControlState)state {
+ %log;
+ color = [UIColor whiteColor];
+ %orig(color, state);
+}
+%end
+
+%hook UILabel
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook UITextField
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook UITextView
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook VideoTitleLabel
+- (void)setTextColor:(UIColor *)textColor {
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook _ASDisplayView
+- (UIColor *)textColor {
+ return [UIColor whiteColor];
+}
+%end
+%end
+
+%group gOrangeContrastMode // Orange Contrast Mode
+%hook UIColor
++ (UIColor *)whiteColor {
+ return [UIColor colorWithRed: 0.73 green: 0.45 blue: 0.05 alpha: 1.00];
+}
++ (UIColor *)textColor {
+ return [UIColor colorWithRed: 0.73 green: 0.45 blue: 0.05 alpha: 1.00];
+}
+%end
+
+%hook UILabel
++ (void)load {
+ @autoreleasepool {
+ [[UILabel appearance] setTextColor:[UIColor whiteColor]];
+ }
+}
+%end
+
+%hook YTCommonColorPalette
+- (UIColor *)textPrimary {
+ if (self.pageStyle == 1) {
+ return [UIColor whiteColor]; // Dark Theme
+ }
+ return [UIColor colorWithRed: 0.80 green: 0.49 blue: 0.05 alpha: 1.00]; // Light Theme
+ }
+- (UIColor *)textSecondary {
+ if (self.pageStyle == 1) {
+ return [UIColor whiteColor]; // Dark Theme
+ }
+ return [UIColor colorWithRed: 0.80 green: 0.49 blue: 0.05 alpha: 1.00]; // Light Theme
+ }
+%end
+
+%hook YTCollectionView
+ - (void)setTintColor:(UIColor *)color {
+ return isDarkMode() ? %orig([UIColor whiteColor]) : %orig;
+}
+%end
+
+%hook LOTAnimationView
+- (void) setTintColor:(UIColor *)tintColor {
+ tintColor = [UIColor whiteColor];
+ %orig(tintColor);
+}
+%end
+
+%hook ASTextNode
+- (NSAttributedString *)attributedString {
+ NSAttributedString *originalAttributedString = %orig;
+
+ NSMutableAttributedString *newAttributedString = [originalAttributedString mutableCopy];
+ [newAttributedString addAttribute:NSForegroundColorAttributeName value:[UIColor whiteColor] range:NSMakeRange(0, newAttributedString.length)];
+
+ return newAttributedString;
+}
+%end
+
+%hook ASTextFieldNode
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook ASTextView
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook ASButtonNode
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook UIButton
+- (void)setTitleColor:(UIColor *)color forState:(UIControlState)state {
+ %log;
+ color = [UIColor whiteColor];
+ %orig(color, state);
+}
+%end
+
+%hook UILabel
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook UITextField
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook UITextView
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook VideoTitleLabel
+- (void)setTextColor:(UIColor *)textColor {
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook _ASDisplayView
+- (UIColor *)textColor {
+ return [UIColor whiteColor];
+}
+%end
+%end
+
+%group gPurpleContrastMode // Purple Contrast Mode
+%hook UIColor
++ (UIColor *)whiteColor {
+ return [UIColor colorWithRed: 0.42 green: 0.18 blue: 0.68 alpha: 1.00];
+}
++ (UIColor *)textColor {
+ return [UIColor colorWithRed: 0.42 green: 0.18 blue: 0.68 alpha: 1.00];
+}
+%end
+
+%hook UILabel
++ (void)load {
+ @autoreleasepool {
+ [[UILabel appearance] setTextColor:[UIColor whiteColor]];
+ }
+}
+%end
+
+%hook YTCommonColorPalette
+- (UIColor *)textPrimary {
+ if (self.pageStyle == 1) {
+ return [UIColor whiteColor]; // Dark Theme
+ }
+ return [UIColor colorWithRed: 0.42 green: 0.05 blue: 0.68 alpha: 1.00]; // Light Theme
+ }
+- (UIColor *)textSecondary {
+ if (self.pageStyle == 1) {
+ return [UIColor whiteColor]; // Dark Theme
+ }
+ return [UIColor colorWithRed: 0.42 green: 0.05 blue: 0.68 alpha: 1.00]; // Light Theme
+ }
+%end
+
+%hook YTCollectionView
+ - (void)setTintColor:(UIColor *)color {
+ return isDarkMode() ? %orig([UIColor whiteColor]) : %orig;
+}
+%end
+
+%hook LOTAnimationView
+- (void) setTintColor:(UIColor *)tintColor {
+ tintColor = [UIColor whiteColor];
+ %orig(tintColor);
+}
+%end
+
+%hook ASTextNode
+- (NSAttributedString *)attributedString {
+ NSAttributedString *originalAttributedString = %orig;
+
+ NSMutableAttributedString *newAttributedString = [originalAttributedString mutableCopy];
+ [newAttributedString addAttribute:NSForegroundColorAttributeName value:[UIColor whiteColor] range:NSMakeRange(0, newAttributedString.length)];
+
+ return newAttributedString;
+}
+%end
+
+%hook ASTextFieldNode
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook ASTextView
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook ASButtonNode
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook UIButton
+- (void)setTitleColor:(UIColor *)color forState:(UIControlState)state {
+ %log;
+ color = [UIColor whiteColor];
+ %orig(color, state);
+}
+%end
+
+%hook UILabel
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook UITextField
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook UITextView
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook VideoTitleLabel
+- (void)setTextColor:(UIColor *)textColor {
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook _ASDisplayView
+- (UIColor *)textColor {
+ return [UIColor whiteColor];
+}
+%end
+%end
+
+%group gVioletContrastMode // Violet Contrast Mode
+%hook UIColor
++ (UIColor *)whiteColor {
+ return [UIColor colorWithRed: 0.50 green: 0.00 blue: 1.00 alpha: 1.00];
+}
++ (UIColor *)textColor {
+ return [UIColor colorWithRed: 0.50 green: 0.00 blue: 1.00 alpha: 1.00];
+}
+%end
+
+%hook UILabel
++ (void)load {
+ @autoreleasepool {
+ [[UILabel appearance] setTextColor:[UIColor whiteColor]];
+ }
+}
+%end
+
+%hook YTCommonColorPalette
+- (UIColor *)textPrimary {
+ if (self.pageStyle == 1) {
+ return [UIColor whiteColor]; // Dark Theme
+ }
+ return [UIColor colorWithRed: 0.29 green: 0.00 blue: 0.51 alpha: 1.00]; // Light Theme
+ }
+- (UIColor *)textSecondary {
+ if (self.pageStyle == 1) {
+ return [UIColor whiteColor]; // Dark Theme
+ }
+ return [UIColor colorWithRed: 0.29 green: 0.00 blue: 0.51 alpha: 1.00]; // Light Theme
+ }
+%end
+
+%hook YTCollectionView
+ - (void)setTintColor:(UIColor *)color {
+ return isDarkMode() ? %orig([UIColor whiteColor]) : %orig;
+}
+%end
+
+%hook LOTAnimationView
+- (void) setTintColor:(UIColor *)tintColor {
+ tintColor = [UIColor whiteColor];
+ %orig(tintColor);
+}
+%end
+
+%hook ASTextNode
+- (NSAttributedString *)attributedString {
+ NSAttributedString *originalAttributedString = %orig;
+
+ NSMutableAttributedString *newAttributedString = [originalAttributedString mutableCopy];
+ [newAttributedString addAttribute:NSForegroundColorAttributeName value:[UIColor whiteColor] range:NSMakeRange(0, newAttributedString.length)];
+
+ return newAttributedString;
+}
+%end
+
+%hook ASTextFieldNode
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook ASTextView
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook ASButtonNode
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook UIButton
+- (void)setTitleColor:(UIColor *)color forState:(UIControlState)state {
+ %log;
+ color = [UIColor whiteColor];
+ %orig(color, state);
+}
+%end
+
+%hook UILabel
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook UITextField
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook UITextView
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook VideoTitleLabel
+- (void)setTextColor:(UIColor *)textColor {
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook _ASDisplayView
+- (UIColor *)textColor {
+ return [UIColor whiteColor];
+}
+%end
+%end
+
+%group gPinkContrastMode // Pink Contrast Mode
+%hook UIColor
++ (UIColor *)whiteColor {
+ return [UIColor colorWithRed: 0.74 green: 0.02 blue: 0.46 alpha: 1.00];
+}
++ (UIColor *)textColor {
+ return [UIColor colorWithRed: 0.74 green: 0.02 blue: 0.46 alpha: 1.00];
+}
+%end
+
+%hook UILabel
++ (void)load {
+ @autoreleasepool {
+ [[UILabel appearance] setTextColor:[UIColor whiteColor]];
+ }
+}
+%end
+
+%hook YTCommonColorPalette
+- (UIColor *)textPrimary {
+ if (self.pageStyle == 1) {
+ return [UIColor whiteColor]; // Dark Theme
+ }
+ return [UIColor colorWithRed: 0.81 green: 0.56 blue: 0.71 alpha: 1.00]; // Light Theme
+ }
+- (UIColor *)textSecondary {
+ if (self.pageStyle == 1) {
+ return [UIColor whiteColor]; // Dark Theme
+ }
+ return [UIColor colorWithRed: 0.81 green: 0.56 blue: 0.71 alpha: 1.00]; // Light Theme
+ }
+%end
+
+%hook YTCollectionView
+ - (void)setTintColor:(UIColor *)color {
+ return isDarkMode() ? %orig([UIColor whiteColor]) : %orig;
+}
+%end
+
+%hook LOTAnimationView
+- (void) setTintColor:(UIColor *)tintColor {
+ tintColor = [UIColor whiteColor];
+ %orig(tintColor);
+}
+%end
+
+%hook ASTextNode
+- (NSAttributedString *)attributedString {
+ NSAttributedString *originalAttributedString = %orig;
+
+ NSMutableAttributedString *newAttributedString = [originalAttributedString mutableCopy];
+ [newAttributedString addAttribute:NSForegroundColorAttributeName value:[UIColor whiteColor] range:NSMakeRange(0, newAttributedString.length)];
+
+ return newAttributedString;
+}
+%end
+
+%hook ASTextFieldNode
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook ASTextView
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook ASButtonNode
+- (void)setTextColor:(UIColor *)textColor {
+ %orig([UIColor whiteColor]);
+}
+%end
+
+%hook UIButton
+- (void)setTitleColor:(UIColor *)color forState:(UIControlState)state {
+ %log;
+ color = [UIColor whiteColor];
+ %orig(color, state);
+}
+%end
+
+%hook UILabel
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook UITextField
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook UITextView
+- (void)setTextColor:(UIColor *)textColor {
+ %log;
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook VideoTitleLabel
+- (void)setTextColor:(UIColor *)textColor {
+ textColor = [UIColor whiteColor];
+ %orig(textColor);
+}
+%end
+
+%hook _ASDisplayView
+- (UIColor *)textColor {
+ return [UIColor whiteColor];
+}
+%end
+%end
+
+# pragma mark - ctor
+%ctor {
+ %init;
+ if (defaultContrastMode()) {
+ %init(gLowContrastMode);
+ }
+ if (redContrastMode()) {
+ %init(gRedContrastMode);
+ }
+ if (blueContrastMode()) {
+ %init(gBlueContrastMode);
+ }
+ if (greenContrastMode()) {
+ %init(gGreenContrastMode);
+ }
+ if (yellowContrastMode()) {
+ %init(gYellowContrastMode);
+ }
+ if (orangeContrastMode()) {
+ %init(gOrangeContrastMode);
+ }
+ if (purpleContrastMode()) {
+ %init(gPurpleContrastMode);
+ }
+ if (violetContrastMode()) {
+ %init(gVioletContrastMode);
+ }
+ if (pinkContrastMode()) {
+ %init(gPinkContrastMode);
+ }
+}
diff --git a/Source/Settings.xm b/Source/Settings.xm
new file mode 100644
index 0000000..1c14456
--- /dev/null
+++ b/Source/Settings.xm
@@ -0,0 +1,746 @@
+#import "../Tweaks/YouTubeHeader/YTSettingsViewController.h"
+#import "../Tweaks/YouTubeHeader/YTSearchableSettingsViewController.h"
+#import "../Tweaks/YouTubeHeader/YTSettingsSectionItem.h"
+#import "../Tweaks/YouTubeHeader/YTSettingsSectionItemManager.h"
+#import "../Tweaks/YouTubeHeader/YTUIUtils.h"
+#import "../Tweaks/YouTubeHeader/YTSettingsPickerViewController.h"
+#import "../Header.h"
+
+static BOOL IsEnabled(NSString *key) {
+ return [[NSUserDefaults standardUserDefaults] boolForKey:key];
+}
+static int GetSelection(NSString *key) {
+ return [[NSUserDefaults standardUserDefaults] integerForKey:key];
+}
+static int colorContrastMode() {
+ return [[NSUserDefaults standardUserDefaults] integerForKey:@"lcmColor"];
+}
+static const NSInteger YTLitePlusSection = 500;
+
+@interface YTSettingsSectionItemManager (YTLitePlus)
+- (void)updateYTLitePlusSectionWithEntry:(id)entry;
+@end
+
+extern NSBundle *YTLitePlusBundle();
+
+// Settings
+%hook YTAppSettingsPresentationData
++ (NSArray *)settingsCategoryOrder {
+ NSArray *order = %orig;
+ NSMutableArray *mutableOrder = [order mutableCopy];
+ NSUInteger insertIndex = [order indexOfObject:@(1)];
+ if (insertIndex != NSNotFound)
+ [mutableOrder insertObject:@(YTLitePlusSection) atIndex:insertIndex + 1];
+ return mutableOrder;
+}
+%end
+
+%hook YTSettingsSectionController
+
+- (void)setSelectedItem:(NSUInteger)selectedItem {
+ if (selectedItem != NSNotFound) %orig;
+}
+
+%end
+
+%hook YTSettingsSectionItemManager
+%new(v@:@)
+- (void)updateYTLitePlusSectionWithEntry:(id)entry {
+ NSMutableArray *sectionItems = [NSMutableArray array];
+ NSBundle *tweakBundle = YTLitePlusBundle();
+ Class YTSettingsSectionItemClass = %c(YTSettingsSectionItem);
+ YTSettingsViewController *settingsViewController = [self valueForKey:@"_settingsViewControllerDelegate"];
+
+ YTSettingsSectionItem *main = [%c(YTSettingsSectionItem)
+ itemWithTitle:[NSString stringWithFormat:LOC(@"VERSION"), @(OS_STRINGIFY(TWEAK_VERSION))]
+ titleDescription:LOC(@"VERSION_CHECK")
+ accessibilityIdentifier:nil
+ detailTextBlock:nil
+ selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) {
+ return [%c(YTUIUtils) openURL:[NSURL URLWithString:@"https://github.com/Balackburn/YTLitePlusExtra/releases/latest"]];
+ }];
+ [sectionItems addObject:main];
+
+# pragma mark - VideoPlayer
+ YTSettingsSectionItem *videoPlayerGroup = [YTSettingsSectionItemClass itemWithTitle:LOC(@"VIDEO_PLAYER_OPTIONS") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) {
+ NSArray *rows = @[
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"AUTO_FULLSCREEN")
+ titleDescription:LOC(@"AUTO_FULLSCREEN_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"autoFull_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"autoFull_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"TAP_TO_SKIP")
+ titleDescription:LOC(@"TAP_TO_SKIP_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"tapToSkip_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"tapToSkip_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"SNAP_TO_CHAPTER")
+ titleDescription:LOC(@"SNAP_TO_CHAPTER_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"snapToChapter_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"snapToChapter_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"PINCH_TO_ZOOM")
+ titleDescription:LOC(@"PINCH_TO_ZOOM_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"pinchToZoom_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"pinchToZoom_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"YT_MINIPLAYER")
+ titleDescription:LOC(@"YT_MINIPLAYER_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"ytMiniPlayer_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"ytMiniPlayer_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"STOCK_VOLUME_HUD")
+ titleDescription:LOC(@"STOCK_VOLUME_HUD_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"stockVolumeHUD_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"stockVolumeHUD_enabled"];
+ return YES;
+ }
+ settingItemId:0]
+ ];
+ YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"VIDEO_PLAYER_OPTIONS") pickerSectionTitle:nil rows:rows selectedItemIndex:NSNotFound parentResponder:[self parentResponder]];
+ [settingsViewController pushViewController:picker];
+ return YES;
+ }];
+ [sectionItems addObject:videoPlayerGroup];
+
+# pragma mark - Video Controls Overlay Options
+ YTSettingsSectionItem *videoControlOverlayGroup = [YTSettingsSectionItemClass itemWithTitle:LOC(@"VIDEO_CONTROLS_OVERLAY_OPTIONS") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) {
+ NSArray *rows = @[
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"ENABLE_SHARE_BUTTON")
+ titleDescription:LOC(@"ENABLE_SHARE_BUTTON_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"enableShareButton_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"enableShareButton_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"ENABLE_SAVE_TO_PLAYLIST_BUTTON")
+ titleDescription:LOC(@"ENABLE_SAVE_TO_PLAYLIST_BUTTON_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"enableSaveToButton_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"enableSaveToButton_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_AUTOPLAY_SWITCH")
+ titleDescription:LOC(@"HIDE_AUTOPLAY_SWITCH_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hideAutoplaySwitch_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hideAutoplaySwitch_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_SUBTITLES_BUTTON")
+ titleDescription:LOC(@"HIDE_SUBTITLES_BUTTON_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hideCC_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hideCC_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_HUD_MESSAGES")
+ titleDescription:LOC(@"HIDE_HUD_MESSAGES_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hideHUD_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hideHUD_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_PAID_PROMOTION_CARDS")
+ titleDescription:LOC(@"HIDE_PAID_PROMOTION_CARDS_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hidePaidPromotionCard_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hidePaidPromotionCard_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_CHANNEL_WATERMARK")
+ titleDescription:LOC(@"HIDE_CHANNEL_WATERMARK_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hideChannelWatermark_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hideChannelWatermark_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_SHADOW_OVERLAY_BUTTONS")
+ titleDescription:LOC(@"HIDE_SHADOW_OVERLAY_BUTTONS_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hideVideoPlayerShadowOverlayButtons_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hideVideoPlayerShadowOverlayButtons_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_PREVIOUS_AND_NEXT_BUTTON")
+ titleDescription:LOC(@"HIDE_PREVIOUS_AND_NEXT_BUTTON_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hidePreviousAndNextButton_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hidePreviousAndNextButton_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"REPLACE_PREVIOUS_NEXT_BUTTON")
+ titleDescription:LOC(@"REPLACE_PREVIOUS_NEXT_BUTTON_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"replacePreviousAndNextButton_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"replacePreviousAndNextButton_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"RED_PROGRESS_BAR")
+ titleDescription:LOC(@"RED_PROGRESS_BAR_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"redProgressBar_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"redProgressBar_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"DISABLE_VIDEO_PLAYER_ZOOM")
+ titleDescription:LOC(@"DISABLE_VIDEO_PLAYER_ZOOM")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"disableVideoPlayerZoom_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"disableVideoPlayerZoom_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_HOVER_CARD")
+ titleDescription:LOC(@"HIDE_HOVER_CARD_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hideHoverCards_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hideHoverCards_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_RIGHT_PANEL")
+ titleDescription:LOC(@"HIDE_RIGHT_PANEL_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hideRightPanel_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hideRightPanel_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_HEATWAVES")
+ titleDescription:LOC(@"HIDE_HEATWAVES_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hideHeatwaves_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hideHeatwaves_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_DARK_OVERLAY_BACKGROUND")
+ titleDescription:LOC(@"HIDE_DARK_OVERLAY_BACKGROUND_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hideOverlayDarkBackground_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hideOverlayDarkBackground_enabled"];
+ return YES;
+ }
+ settingItemId:0]
+ ];
+ YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"VIDEO_CONTROLS_OVERLAY_OPTIONS") pickerSectionTitle:nil rows:rows selectedItemIndex:NSNotFound parentResponder:[self parentResponder]];
+ [settingsViewController pushViewController:picker];
+ return YES;
+ }];
+ [sectionItems addObject:videoControlOverlayGroup];
+
+# pragma mark - Shorts Controls Overlay Options
+ YTSettingsSectionItem *shortsControlOverlayGroup = [YTSettingsSectionItemClass itemWithTitle:LOC(@"SHORTS_CONTROLS_OVERLAY_OPTIONS") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) {
+ NSArray *rows = @[
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_SHORTS_VIDEOS")
+ titleDescription:LOC(@"HIDE_SHORTS_VIDEOS_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hideShorts_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hideShorts_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_SHORTS_CHANNEL_AVATAR")
+ titleDescription:LOC(@"HIDE_SHORTS_CHANNEL_AVATAR_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hideShortsChannelAvatar_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hideShortsChannelAvatar_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_SHORTS_LIKE_BUTTON")
+ titleDescription:LOC(@"HIDE_SHORTS_LIKE_BUTTON_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hideShortsLikeButton_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hideShortsLikeButton_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_SHORTS_DISLIKE_BUTTON")
+ titleDescription:LOC(@"HIDE_SHORTS_DISLIKE_BUTTON_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hideShortsDislikeButton_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hideShortsDislikeButton_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_SHORTS_COMMENT_BUTTON")
+ titleDescription:LOC(@"HIDE_SHORTS_COMMENT_BUTTON_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hideShortsCommentButton_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hideShortsCommentButton_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_SHORTS_REMIX_BUTTON")
+ titleDescription:LOC(@"HIDE_SHORTS_REMIX_BUTTON_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hideShortsRemixButton_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hideShortsRemixButton_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_SHORTS_SHARE_BUTTON")
+ titleDescription:LOC(@"HIDE_SHORTS_SHARE_BUTTON_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hideShortsShareButton_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hideShortsShareButton_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_SUPER_THANKS")
+ titleDescription:LOC(@"HIDE_SUPER_THANKS_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hideBuySuperThanks_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hideBuySuperThanks_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_SUBSCRIPTIONS")
+ titleDescription:LOC(@"HIDE_SUBSCRIPTIONS_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hideSubscriptions_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hideSubscriptions_enabled"];
+ return YES;
+ }
+ settingItemId:0]
+ ];
+ YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"SHORTS_CONTROLS_OVERLAY_OPTIONS") pickerSectionTitle:nil rows:rows selectedItemIndex:NSNotFound parentResponder:[self parentResponder]];
+ [settingsViewController pushViewController:picker];
+ return YES;
+ }];
+ [sectionItems addObject:shortsControlOverlayGroup];
+
+# pragma mark - LowContrastMode
+ YTSettingsSectionItem *lowContrastModeSection = [YTSettingsSectionItemClass itemWithTitle:LOC(@"LCM_CHOOSE_COLOR")
+ accessibilityIdentifier:nil
+ detailTextBlock:^NSString *() {
+ switch (colorContrastMode()) {
+ case 1:
+ return LOC(@"RED_UI");
+ case 2:
+ return LOC(@"BLUE_UI");
+ case 3:
+ return LOC(@"GREEN_UI");
+ case 4:
+ return LOC(@"YELLOW_UI");
+ case 5:
+ return LOC(@"ORANGE_UI");
+ case 6:
+ return LOC(@"PURPLE_UI");
+ case 7:
+ return LOC(@"VIOLET_UI");
+ case 8:
+ return LOC(@"PINK_UI");
+ case 0:
+ default:
+ return LOC(@"DEFAULT_UI");
+ }
+ }
+ selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) {
+ NSArray *rows = @[
+ [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"DEFAULT_UI") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) {
+ [[NSUserDefaults standardUserDefaults] setInteger:0 forKey:@"lcmColor"];
+ [settingsViewController reloadData];
+ return YES;
+ }],
+ [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"RED_UI") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) {
+ [[NSUserDefaults standardUserDefaults] setInteger:1 forKey:@"lcmColor"];
+ [settingsViewController reloadData];
+ return YES;
+ }],
+ [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"BLUE_UI") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) {
+ [[NSUserDefaults standardUserDefaults] setInteger:2 forKey:@"lcmColor"];
+ [settingsViewController reloadData];
+ return YES;
+ }],
+ [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"GREEN_UI") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) {
+ [[NSUserDefaults standardUserDefaults] setInteger:3 forKey:@"lcmColor"];
+ [settingsViewController reloadData];
+ return YES;
+ }],
+ [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"YELLOW_UI") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) {
+ [[NSUserDefaults standardUserDefaults] setInteger:4 forKey:@"lcmColor"];
+ [settingsViewController reloadData];
+ return YES;
+ }],
+ [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"ORANGE_UI") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) {
+ [[NSUserDefaults standardUserDefaults] setInteger:5 forKey:@"lcmColor"];
+ [settingsViewController reloadData];
+ return YES;
+ }],
+ [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"PURPLE_UI") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) {
+ [[NSUserDefaults standardUserDefaults] setInteger:6 forKey:@"lcmColor"];
+ [settingsViewController reloadData];
+ return YES;
+ }],
+ [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"VIOLET_UI") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) {
+ [[NSUserDefaults standardUserDefaults] setInteger:7 forKey:@"lcmColor"];
+ [settingsViewController reloadData];
+ return YES;
+ }],
+ [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"PINK_UI") titleDescription:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) {
+ [[NSUserDefaults standardUserDefaults] setInteger:8 forKey:@"lcmColor"];
+ [settingsViewController reloadData];
+ return YES;
+ }]
+ ];
+ YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"LCM_CHOOSE_COLOR") pickerSectionTitle:nil rows:rows selectedItemIndex:colorContrastMode() parentResponder:[self parentResponder]];
+ [settingsViewController pushViewController:picker];
+ return YES;
+ }];
+
+# pragma mark - Theme
+ YTSettingsSectionItem *themeGroup = [YTSettingsSectionItemClass itemWithTitle:LOC(@"THEME_OPTIONS")
+ accessibilityIdentifier:nil
+ detailTextBlock:^NSString *() {
+ switch (GetSelection(@"appTheme")) {
+ case 1:
+ return LOC(@"OLED_DARK_THEME_2");
+ case 2:
+ return LOC(@"OLD_DARK_THEME");
+ case 0:
+ default:
+ return LOC(@"DEFAULT_THEME");
+ }
+ }
+ selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) {
+ NSArray *rows = @[
+ [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"DEFAULT_THEME") titleDescription:LOC(@"DEFAULT_THEME_DESC") selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) {
+ [[NSUserDefaults standardUserDefaults] setInteger:0 forKey:@"appTheme"];
+ [settingsViewController reloadData];
+ return YES;
+ }],
+ [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"OLED_DARK_THEME") titleDescription:LOC(@"OLED_DARK_THEME_DESC") selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) {
+ [[NSUserDefaults standardUserDefaults] setInteger:1 forKey:@"appTheme"];
+ [settingsViewController reloadData];
+ return YES;
+ }],
+ [YTSettingsSectionItemClass checkmarkItemWithTitle:LOC(@"OLD_DARK_THEME") titleDescription:LOC(@"OLD_DARK_THEME_DESC") selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) {
+ [[NSUserDefaults standardUserDefaults] setInteger:2 forKey:@"appTheme"];
+ [settingsViewController reloadData];
+ return YES;
+ }],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"OLED_KEYBOARD")
+ titleDescription:LOC(@"OLED_KEYBOARD_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"oledKeyBoard_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"oledKeyBoard_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"LOW_CONTRAST_MODE")
+ titleDescription:LOC(@"LOW_CONTRAST_MODE_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"lowContrastMode_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"lowContrastMode_enabled"];
+ return YES;
+ }
+ settingItemId:0], lowContrastModeSection];
+ YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"THEME_OPTIONS") pickerSectionTitle:nil rows:rows selectedItemIndex:GetSelection(@"appTheme") parentResponder:[self parentResponder]];
+ [settingsViewController pushViewController:picker];
+ return YES;
+ }];
+ [sectionItems addObject:themeGroup];
+
+# pragma mark - Miscellaneous
+ YTSettingsSectionItem *miscellaneousGroup = [YTSettingsSectionItemClass itemWithTitle:LOC(@"MISCELLANEOUS") accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) {
+ NSArray *rows = @[
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"ENABLE_YT_STARTUP_ANIMATION")
+ titleDescription:LOC(@"ENABLE_YT_STARTUP_ANIMATION_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"ytStartupAnimation_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"ytStartupAnimation_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_YOUTUBE_LOGO")
+ titleDescription:LOC(@"HIDE_YOUTUBE_LOGO_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hideYouTubeLogo_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hideYouTubeLogo_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_CHIP_BAR")
+ titleDescription:LOC(@"HIDE_CHIP_BAR_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hideChipBar_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hideChipBar_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_MODERN_INTERFACE")
+ titleDescription:LOC(@"HIDE_MODERN_INTERFACE_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"ytNoModernUI_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"ytNoModernUI_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"IPAD_LAYOUT")
+ titleDescription:LOC(@"IPAD_LAYOUT_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"iPadLayout_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"iPadLayout_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"IPHONE_LAYOUT")
+ titleDescription:LOC(@"IPHONE_LAYOUT_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"iPhoneLayout_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"iPhoneLayout_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"CAST_CONFIRM")
+ titleDescription:LOC(@"CAST_CONFIRM_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"castConfirm_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"castConfirm_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"DISABLE_HINTS")
+ titleDescription:LOC(@"DISABLE_HINTS_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"disableHints_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"disableHints_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"NEW_MINIPLAYER_STYLE")
+ titleDescription:LOC(@"NEW_MINIPLAYER_STYLE_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"bigYTMiniPlayer_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"bigYTMiniPlayer_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_CAST_BUTTON")
+ titleDescription:LOC(@"HIDE_CAST_BUTTON_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hideCastButton_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hideCastButton_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_NOTIFICATION_BUTTON")
+ titleDescription:LOC(@"HIDE_NOTIFICATION_BUTTON_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hideNotificationButton_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hideNotificationButton_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_SPONSORBLOCK_BUTTON")
+ titleDescription:LOC(@"HIDE_SPONSORBLOCK_BUTTON_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hideSponsorBlockButton_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hideSponsorBlockButton_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_CERCUBE_BUTTON")
+ titleDescription:LOC(@"")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hideCercubeButton_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hideCercubeButton_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_CERCUBE_PIP_BUTTON")
+ titleDescription:LOC(@"HIDE_CERCUBE_PIP_BUTTON_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hideCercubePiP_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"hideCercubePiP_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"HIDE_CERCUBE_DOWNLOAD_BUTTON")
+ titleDescription:LOC(@"HIDE_CERCUBE_DOWNLOAD_BUTTON_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"hideCercubeDownload_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL disabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"hideCercubeDownload_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"DISABLE_WIFI_RELATED_SETTINGS")
+ titleDescription:LOC(@"DISABLE_WIFI_RELATED_SETTINGS_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"disableWifiRelatedSettings_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"disableWifiRelatedSettings_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"YT_RE_EXPLORE")
+ titleDescription:LOC(@"YT_RE_EXPLORE_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"reExplore_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"reExplore_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"YT_SPEED")
+ titleDescription:LOC(@"YT_SPEED_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"ytSpeed_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"ytSpeed_enabled"];
+ return YES;
+ }
+ settingItemId:0],
+
+ [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"ENABLE_FLEX")
+ titleDescription:LOC(@"ENABLE_FLEX_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IsEnabled(@"flex_enabled")
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"flex_enabled"];
+ return YES;
+ }
+ settingItemId:0]
+ ];
+ YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"MISCELLANEOUS") pickerSectionTitle:nil rows:rows selectedItemIndex:NSNotFound parentResponder:[self parentResponder]];
+ [settingsViewController pushViewController:picker];
+ return YES;
+ }];
+ [sectionItems addObject:miscellaneousGroup];
+
+ [settingsViewController setSectionItems:sectionItems forCategory:YTLitePlusSection title:@"YTLitePlus" titleDescription:LOC(@"TITLE DESCRIPTION") headerHidden:YES];
+}
+
+- (void)updateSectionForCategory:(NSUInteger)category withEntry:(id)entry {
+ if (category == YTLitePlusSection) {
+ [self updateYTLitePlusSectionWithEntry:entry];
+ return;
+ }
+ %orig;
+}
+%end
diff --git a/Source/Themes.xm b/Source/Themes.xm
new file mode 100644
index 0000000..aab0624
--- /dev/null
+++ b/Source/Themes.xm
@@ -0,0 +1,466 @@
+#import "../Header.h"
+
+static BOOL IsEnabled(NSString *key) {
+ return [[NSUserDefaults standardUserDefaults] boolForKey:key];
+}
+static BOOL isDarkMode() {
+ return ([[NSUserDefaults standardUserDefaults] integerForKey:@"page_style"] == 1);
+}
+static BOOL oledDarkTheme() {
+ return ([[NSUserDefaults standardUserDefaults] integerForKey:@"appTheme"] == 1);
+}
+static BOOL oldDarkTheme() {
+ return ([[NSUserDefaults standardUserDefaults] integerForKey:@"appTheme"] == 2);
+}
+
+// Themes.xm - Theme Options
+// Old dark theme (gray)
+%group gOldDarkTheme
+%hook YTAsyncCollectionView
+- (UIColor *)backgroundColor:(NSInteger)pageStyle {
+ return pageStyle == 1 ? [UIColor colorWithRed:0.129 green:0.129 blue:0.129 alpha:1.0] : %orig;
+}
+- (UIColor *)darkBackgroundColor {
+ return [UIColor colorWithRed:0.129 green:0.129 blue:0.129 alpha:1.0];
+}
+%end
+
+%hook YTAppView
+- (void)setBackgroundColor:(UIColor *)color {
+ %orig([UIColor colorWithRed:0.129 green:0.129 blue:0.129 alpha:1.0]);
+}
+%end
+
+%hook YTPivotBarView
+- (void)setBackgroundColor:(UIColor *)color {
+ %orig([UIColor colorWithRed:0.129 green:0.129 blue:0.129 alpha:1.0]);
+}
+%end
+
+%hook YTAsyncCollectionView
+- (void)setBackgroundColor:(UIColor *)color {
+ %orig([UIColor colorWithRed:0.129 green:0.129 blue:0.129 alpha:1.0]);
+}
+%end
+
+%hook YTAppViewController
+- (UIColor *)backgroundColor:(NSInteger)pageStyle {
+ return pageStyle == 1 ? [UIColor colorWithRed:0.129 green:0.129 blue:0.129 alpha:1.0] : %orig;
+}
+%end
+
+%hook YTNavigationBar
+- (UIColor *)backgroundColor:(NSInteger)pageStyle {
+ return pageStyle == 1 ? [UIColor colorWithRed:0.129 green:0.129 blue:0.129 alpha:1.0] : %orig;
+}
+%end
+
+%hook YTInnerTubeCollectionViewController
+- (UIColor *)backgroundColor:(NSInteger)pageStyle {
+ return pageStyle == 1 ? [UIColor colorWithRed:0.129 green:0.129 blue:0.129 alpha:1.0] : %orig;
+}
+%end
+
+%hook YTColdConfig
+- (BOOL)uiSystemsClientGlobalConfigUseDarkerPaletteBgColorForNative { return NO; }
+- (BOOL)uiSystemsClientGlobalConfigUseDarkerPaletteTextColorForNative { return NO; }
+- (BOOL)enableCinematicContainerOnClient { return NO; }
+%end
+
+%hook _ASDisplayView
+- (void)didMoveToWindow {
+ %orig;
+ if ([self.accessibilityIdentifier isEqualToString:@"id.elements.components.comment_composer"]) { self.backgroundColor = [UIColor clearColor]; }
+ if ([self.accessibilityIdentifier isEqualToString:@"id.elements.components.video_list_entry"]) { self.backgroundColor = [UIColor clearColor]; }
+}
+%end
+
+%hook ASCollectionView
+- (void)didMoveToWindow {
+ %orig;
+ self.superview.backgroundColor = [UIColor colorWithRed:0.129 green:0.129 blue:0.129 alpha:1.0];
+}
+%end
+
+%hook YTFullscreenEngagementOverlayView
+- (void)didMoveToWindow {
+ %orig;
+ self.subviews[0].backgroundColor = [UIColor clearColor];
+}
+%end
+
+%hook YTRelatedVideosView
+- (void)didMoveToWindow {
+ %orig;
+ self.subviews[0].backgroundColor = [UIColor clearColor];
+}
+%end
+%end
+
+// OLED dark mode by BandarHL
+UIColor* raisedColor = [UIColor colorWithRed:0.035 green:0.035 blue:0.035 alpha:1.0];
+%group gOLED
+%hook YTCommonColorPalette
+- (UIColor *)brandBackgroundSolid {
+ return self.pageStyle == 1 ? [UIColor blackColor] : %orig;
+}
+- (UIColor *)brandBackgroundPrimary {
+ return self.pageStyle == 1 ? [UIColor blackColor] : %orig;
+}
+- (UIColor *)brandBackgroundSecondary {
+ return self.pageStyle == 1 ? [[UIColor blackColor] colorWithAlphaComponent:0.9] : %orig;
+}
+- (UIColor *)raisedBackground {
+ return self.pageStyle == 1 ? [UIColor blackColor] : %orig;
+}
+- (UIColor *)staticBrandBlack {
+ return self.pageStyle == 1 ? [UIColor blackColor] : %orig;
+}
+- (UIColor *)generalBackgroundA {
+ return self.pageStyle == 1 ? [UIColor blackColor] : %orig;
+}
+%end
+
+%hook YTAppView
+- (void)setBackgroundColor:(UIColor *)color {
+ %orig([UIColor blackColor]);
+}
+%end
+
+%hook YTPivotBarView
+- (void)setBackgroundColor:(UIColor *)color {
+ %orig([UIColor blackColor]);
+}
+%end
+
+%hook YTAsyncCollectionView
+- (void)setBackgroundColor:(UIColor *)color {
+ %orig([UIColor blackColor]);
+}
+%end
+
+%hook YTAppViewController
+- (UIColor *)backgroundColor:(NSInteger)pageStyle {
+ return pageStyle == 1 ? [UIColor blackColor] : %orig;
+}
+%end
+
+%hook YTNavigationBar
+- (UIColor *)backgroundColor:(NSInteger)pageStyle {
+ return pageStyle == 1 ? [UIColor blackColor] : %orig;
+}
+%end
+
+BOOL areColorsEqual(UIColor *color1, UIColor *color2, CGFloat tolerance) {
+ CGFloat r1, g1, b1, a1, r2, g2, b2, a2;
+ [color1 getRed:&r1 green:&g1 blue:&b1 alpha:&a1];
+ [color2 getRed:&r2 green:&g2 blue:&b2 alpha:&a2];
+
+ return (fabs(r1 - r2) <= tolerance) &&
+ (fabs(g1 - g2) <= tolerance) &&
+ (fabs(b1 - b2) <= tolerance) &&
+ (fabs(a1 - a2) <= tolerance);
+}
+
+%hook UIView
+- (void)setBackgroundColor:(UIColor *)color {
+ UIColor *targetColor = [UIColor colorWithRed:0.0588235 green:0.0588235 blue:0.0588235 alpha:1];
+ CGFloat tolerance = 0.01; // Adjust this value as needed
+
+ if (areColorsEqual(color, targetColor, tolerance)) {
+ color = [UIColor blackColor];
+ }
+ %orig(color);
+}
+%end
+
+%hook YTCollectionViewController
+- (UIColor *)backgroundColor:(NSInteger)pageStyle {
+ return pageStyle == 1 ? [UIColor blackColor] : %orig;
+}
+%end
+
+%hook YTChannelMobileHeaderViewController
+- (UIColor *)backgroundColor:(NSInteger)pageStyle {
+ return pageStyle == 1 ? [UIColor blackColor] : %orig;
+}
+%end
+
+%hook YTELMView
+- (UIColor *)backgroundColor {
+ return [UIColor blackColor];
+}
+%end
+
+%hook YTHeaderViewController
+- (UIColor *)backgroundColor:(NSInteger)pageStyle {
+ return pageStyle == 1 ? [UIColor blackColor] : %orig;
+}
+- (UIColor *)barTintColor:(NSInteger)pageStyle {
+ return pageStyle == 1 ? [UIColor blackColor] : %orig;
+}
+%end
+
+%hook YTInnerTubeCollectionViewController
+- (UIColor *)backgroundColor:(NSInteger)pageStyle {
+ return pageStyle == 1 ? [UIColor blackColor] : %orig;
+}
+%end
+
+%hook YTSettingsCell
+- (UIColor *)backgroundColor:(NSInteger)pageStyle {
+ return pageStyle == 1 ? [UIColor blackColor] : %orig;
+}
+%end
+
+%hook YTSearchViewController
+- (UIColor *)backgroundColor:(NSInteger)pageStyle {
+ return pageStyle == 1 ? [UIColor blackColor] : %orig;
+}
+%end
+
+%hook YTSectionListViewController
+- (UIColor *)backgroundColor:(NSInteger)pageStyle {
+ return pageStyle == 1 ? [UIColor blackColor] : %orig;
+}
+%end
+
+%hook YTWatchMiniBarViewController
+- (UIColor *)backgroundColor:(NSInteger)pageStyle {
+ return pageStyle == 1 ? [UIColor blackColor] : %orig;
+}
+%end
+
+%hook YTWrapperSplitViewController
+- (UIColor *)backgroundColor:(NSInteger)pageStyle {
+ return pageStyle == 1 ? [UIColor blackColor] : %orig;
+}
+%end
+
+// Explore
+%hook ASScrollView
+- (void)didMoveToWindow {
+ %orig;
+ if (isDarkMode()) {
+ self.backgroundColor = [UIColor clearColor];
+ }
+}
+%end
+
+// Your videos
+%hook ASCollectionView
+- (void)didMoveToWindow {
+ %orig;
+ if (isDarkMode() && [self.nextResponder isKindOfClass:%c(_ASDisplayView)]) {
+ self.superview.backgroundColor = [UIColor blackColor];
+ }
+}
+%end
+
+// Sub?
+%hook ELMView
+- (void)didMoveToWindow {
+ %orig;
+ if (isDarkMode()) {
+ self.subviews[0].backgroundColor = [UIColor blackColor];
+ }
+}
+%end
+
+// iSponsorBlock
+%hook SponsorBlockSettingsController
+- (void)viewDidLoad {
+ if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
+ %orig;
+ self.tableView.backgroundColor = [UIColor blackColor];
+ } else { return %orig; }
+}
+%end
+
+%hook SponsorBlockViewController
+- (void)viewDidLoad {
+ if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
+ %orig;
+ self.view.backgroundColor = [UIColor blackColor];
+ } else { return %orig; }
+}
+%end
+
+// Search View
+%hook YTSearchBarView
+- (void)setBackgroundColor:(UIColor *)color {
+ return isDarkMode() ? %orig([UIColor blackColor]) : %orig;
+}
+%end
+
+// History Search view
+%hook YTSearchBoxView
+- (void)setBackgroundColor:(UIColor *)color {
+ return isDarkMode() ? %orig([UIColor blackColor]) : %orig;
+}
+%end
+
+// Comment view
+%hook YTCommentView
+- (void)setBackgroundColor:(UIColor *)color {
+ return isDarkMode() ? %orig([UIColor blackColor]) : %orig;
+}
+%end
+
+%hook YTCreateCommentAccessoryView
+- (void)setBackgroundColor:(UIColor *)color {
+ return isDarkMode() ? %orig([UIColor blackColor]) : %orig;
+}
+%end
+
+%hook YTCreateCommentTextView
+- (void)setBackgroundColor:(UIColor *)color {
+ return isDarkMode() ? %orig([UIColor blackColor]) : %orig;
+}
+- (void)setTextColor:(UIColor *)color { // fix black text in #Shorts video's comment
+ return isDarkMode() ? %orig([UIColor whiteColor]) : %orig;
+}
+%end
+
+%hook YTCommentDetailHeaderCell
+- (void)didMoveToWindow {
+ %orig;
+ if (isDarkMode()) {
+ self.subviews[2].backgroundColor = [UIColor blackColor];
+ }
+}
+%end
+
+%hook YTFormattedStringLabel // YT is werid...
+- (void)setBackgroundColor:(UIColor *)color {
+ return isDarkMode() ? %orig([UIColor clearColor]) : %orig;
+}
+%end
+
+// Live chat comment
+%hook YCHLiveChatActionPanelView
+- (void)setBackgroundColor:(UIColor *)color {
+ return isDarkMode() ? %orig([UIColor blackColor]) : %orig;
+}
+%end
+
+%hook YTEmojiTextView
+- (void)setBackgroundColor:(UIColor *)color {
+ return isDarkMode() ? %orig([UIColor blackColor]) : %orig;
+}
+%end
+
+%hook YCHLiveChatView
+- (void)didMoveToWindow {
+ %orig;
+ if (isDarkMode()) {
+ self.subviews[1].backgroundColor = [UIColor blackColor];
+ }
+}
+%end
+
+//
+%hook YTBackstageCreateRepostDetailView
+- (void)setBackgroundColor:(UIColor *)color {
+ return isDarkMode() ? %orig([UIColor blackColor]) : %orig;
+}
+%end
+
+// Others
+%hook _ASDisplayView
+- (void)didMoveToWindow {
+ %orig;
+ if (isDarkMode()) {
+ if ([self.nextResponder isKindOfClass:%c(ASScrollView)]) { self.backgroundColor = [UIColor clearColor]; }
+ if ([self.accessibilityIdentifier isEqualToString:@"eml.cvr"]) { self.backgroundColor = [UIColor blackColor]; }
+ if ([self.accessibilityIdentifier isEqualToString:@"rich_header"]) { self.backgroundColor = [UIColor blackColor]; }
+ if ([self.accessibilityIdentifier isEqualToString:@"id.ui.comment_cell"]) { self.backgroundColor = [UIColor blackColor]; }
+ if ([self.accessibilityIdentifier isEqualToString:@"id.ui.cancel.button"]) { self.superview.backgroundColor = [UIColor clearColor]; }
+ if ([self.accessibilityIdentifier isEqualToString:@"id.elements.components.comment_composer"]) { self.backgroundColor = [UIColor blackColor]; }
+ if ([self.accessibilityIdentifier isEqualToString:@"id.elements.components.video_list_entry"]) { self.backgroundColor = [UIColor blackColor]; }
+ if ([self.accessibilityIdentifier isEqualToString:@"id.comment.guidelines_text"]) { self.superview.backgroundColor = [UIColor blackColor]; }
+ if ([self.accessibilityIdentifier isEqualToString:@"id.comment.channel_guidelines_bottom_sheet_container"]) { self.backgroundColor = [UIColor blackColor]; }
+ if ([self.accessibilityIdentifier isEqualToString:@"id.comment.channel_guidelines_entry_banner_container"]) { self.backgroundColor = [UIColor blackColor]; }
+ if ([self.accessibilityIdentifier isEqualToString:@"id.comment.comment_group_detail_container"]) { self.backgroundColor = [UIColor clearColor]; }
+ }
+}
+%end
+
+// Open link with...
+%hook ASWAppSwitchingSheetHeaderView
+- (void)setBackgroundColor:(UIColor *)color {
+ return isDarkMode() ? %orig(raisedColor) : %orig;
+}
+%end
+
+%hook ASWAppSwitchingSheetFooterView
+- (void)setBackgroundColor:(UIColor *)color {
+ return isDarkMode() ? %orig(raisedColor) : %orig;
+}
+%end
+
+%hook ASWAppSwitcherCollectionViewCell
+- (void)didMoveToWindow {
+ %orig;
+ if (isDarkMode()) {
+ self.backgroundColor = raisedColor;
+ self.subviews[1].backgroundColor = raisedColor;
+ self.superview.backgroundColor = raisedColor;
+ }
+}
+%end
+
+// Incompatibility with the new YT Dark theme
+%hook YTColdConfig
+- (BOOL)uiSystemsClientGlobalConfigUseDarkerPaletteBgColorForNative { return NO; }
+%end
+%end
+
+// OLED keyboard by @ichitaso <3 - http://gist.github.com/ichitaso/935100fd53a26f18a9060f7195a1be0e
+%group gOLEDKB
+%hook UIPredictionViewController
+- (void)loadView {
+ %orig;
+ [self.view setBackgroundColor:[UIColor blackColor]];
+}
+%end
+
+%hook UICandidateViewController
+- (void)loadView {
+ %orig;
+ [self.view setBackgroundColor:[UIColor blackColor]];
+}
+%end
+
+%hook UIKeyboardDockView
+- (void)didMoveToWindow {
+ %orig;
+ self.backgroundColor = [UIColor blackColor];
+}
+%end
+
+%hook UIKeyboardLayoutStar
+- (void)didMoveToWindow {
+ %orig;
+ self.backgroundColor = [UIColor blackColor];
+}
+%end
+
+%hook UIKBRenderConfig // Prediction text color
+- (void)setLightKeyboard:(BOOL)arg1 { %orig(NO); }
+%end
+%end
+
+# pragma mark - ctor
+%ctor {
+ %init;
+ if (IsEnabled(@"oledKeyBoard_enabled")) {
+ %init(gOLEDKB);
+ }
+ if (oledDarkTheme()) {
+ %init(gOLED);
+ }
+ if (oldDarkTheme()) {
+ %init(gOldDarkTheme);
+ }
+}
diff --git a/Tweaks/Alderis/.gitignore b/Tweaks/Alderis/.gitignore
new file mode 100644
index 0000000..5b863e9
--- /dev/null
+++ b/Tweaks/Alderis/.gitignore
@@ -0,0 +1,16 @@
+# Crap
+.DS_Store
+
+# Xcode
+*.pbxuser
+!default.pbxuser
+xcuserdata
+*.xccheckout
+*.xcuserstate
+
+# Carthage
+Carthage/
+
+# Theos
+.theos
+packages/
diff --git a/Tweaks/Alderis/.jazzy.yaml b/Tweaks/Alderis/.jazzy.yaml
new file mode 100644
index 0000000..fedf7e6
--- /dev/null
+++ b/Tweaks/Alderis/.jazzy.yaml
@@ -0,0 +1,36 @@
+clean: true
+author: HASHBANG Productions
+author_url: https://hbang.github.io/
+github_url: https://github.com/hbang/Alderis
+root_url: https://hbang.github.io/Alderis/
+dash_url: https://hbang.github.io/Alderis/docsets/Alderis.xml
+documentation: info/*.md
+hide_documentation_coverage: true
+
+custom_categories:
+ - name: "Guides"
+ children:
+ - "Preference Bundles"
+ - "Migrating to 1.1"
+
+ - name: "Color Picker"
+ children:
+ - ColorPickerViewController
+ - ColorPickerConfiguration
+ - ColorPickerDelegate
+ - ColorPickerTab
+
+ - name: "UI Components"
+ children:
+ - ColorWell
+
+ - name: "Extensions"
+ children:
+ - UIColor
+ - ColorPropertyListValue
+ - String
+ - Array
+
+ - name: "Deprecated"
+ children:
+ - ColorPickerCircleView
diff --git a/Tweaks/Alderis/.swiftlint.yml b/Tweaks/Alderis/.swiftlint.yml
new file mode 100644
index 0000000..8e390d9
--- /dev/null
+++ b/Tweaks/Alderis/.swiftlint.yml
@@ -0,0 +1,61 @@
+disabled_rules:
+ - trailing_comma
+ - nesting
+ - fallthrough
+ - shorthand_operator
+ - todo
+ - large_tuple
+ - identifier_name
+ - type_name
+ - type_body_length
+ # TODO: Why is vertical_parameter_alignment giving false positives?
+ - vertical_parameter_alignment
+ - vertical_parameter_alignment_on_call
+ # TODO: Enable when removing support for older Swift versions (<5.6)
+ - unavailable_condition
+opt_in_rules:
+ - closure_end_indentation
+ - closure_spacing
+ - contains_over_first_not_nil
+ - empty_count
+ - explicit_init
+ - fatal_error_message
+ - first_where
+ - joined_default_parameter
+ - literal_expression_end_indentation
+ - overridden_super_call
+ - prohibited_super_call
+ - sorted_first_last
+ - unneeded_parentheses_in_closure_argument
+ - vertical_parameter_alignment_on_call
+ - yoda_condition
+ - nslocalizedstring_key
+ - unused_setter_value
+custom_rules:
+ comment_whitespace:
+ name: "Comment Whitespace"
+ regex: //\S
+ match_kinds: comment
+ message: "Comments must begin with a whitespace character"
+ spaces_not_tabs:
+ name: "Tabs not Spaces"
+ regex: ^( )
+ message: "Use tabs instead of spaces"
+ point_zero:
+ name: "Point Zero"
+ regex: '(? "https://github.com/hbang/Alderis.git", :tag => "#{spec.version}" }
+ spec.requires_arc = true
+ spec.source_files = [ "Alderis/*.swift", "Alderis/*.h" ]
+ spec.resource_bundles = { "Alderis" => "Alderis/Assets-ios12.xcassets" }
+end
diff --git a/Tweaks/Alderis/Alderis.xcodeproj/project.pbxproj b/Tweaks/Alderis/Alderis.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..a2db244
--- /dev/null
+++ b/Tweaks/Alderis/Alderis.xcodeproj/project.pbxproj
@@ -0,0 +1,482 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 52;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 4E1C741328266C5900227EC3 /* UIFontDescriptorAdditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E1C741228266C5900227EC3 /* UIFontDescriptorAdditions.swift */; };
+ 4E1C74172826C1F100227EC3 /* ColorPickerAccessibilityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E1C74162826C1F100227EC3 /* ColorPickerAccessibilityViewController.swift */; };
+ 4E1C741928276D5600227EC3 /* TextViewLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E1C741828276D5600227EC3 /* TextViewLabel.swift */; };
+ 4E1C741B2827829E00227EC3 /* AccessibilityComplianceLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E1C741A2827829E00227EC3 /* AccessibilityComplianceLabel.swift */; };
+ 4E1C741D2827882600227EC3 /* AccessibilityContrastSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E1C741C2827882600227EC3 /* AccessibilityContrastSelector.swift */; };
+ 4E1C741F2827B3C800227EC3 /* UIFloat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E1C741E2827B3C800227EC3 /* UIFloat.swift */; };
+ 4E1C74212827B8F800227EC3 /* NSBeep.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E1C74202827B8F800227EC3 /* NSBeep.swift */; };
+ 4E2E6C06282BD5990089E4FB /* GradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E2E6C05282BD5990089E4FB /* GradientView.swift */; };
+ 569C25522427F57000022C60 /* ColorPickerTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569C25512427F57000022C60 /* ColorPickerTabViewController.swift */; };
+ 569C25582428346900022C60 /* ColorPickerMapSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569C25572428346900022C60 /* ColorPickerMapSlider.swift */; };
+ 56C74667242F722A003ED00A /* ColorPickerSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56C74666242F722A003ED00A /* ColorPickerSlider.swift */; };
+ 56C74669242F75E3003ED00A /* ColorPickerNumericSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56C74668242F75E3003ED00A /* ColorPickerNumericSlider.swift */; };
+ 94A2368C252B5951002B5D0B /* UIColorAdditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94A2368B252B5951002B5D0B /* UIColorAdditions.swift */; };
+ CF73D33A241F9C23000B1B10 /* Alderis.h in Headers */ = {isa = PBXBuildFile; fileRef = CF73D338241F9C23000B1B10 /* Alderis.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ CF73D344241F9C31000B1B10 /* ColorWell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF73D32D241E49EE000B1B10 /* ColorWell.swift */; };
+ CF73D345241F9C31000B1B10 /* ColorPickerMapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFE70280241CBDE700083903 /* ColorPickerMapViewController.swift */; };
+ CF73D346241F9C31000B1B10 /* ColorPickerSlidersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFE70282241CC88D00083903 /* ColorPickerSlidersViewController.swift */; };
+ CF73D347241F9C31000B1B10 /* ColorPickerWheelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFE70288241D0E7300083903 /* ColorPickerWheelView.swift */; };
+ CF73D348241F9C31000B1B10 /* ColorPickerInnerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFE7027B241A62ED00083903 /* ColorPickerInnerViewController.swift */; };
+ CF73D34A241F9C31000B1B10 /* ColorPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFE70273241A4C5500083903 /* ColorPickerViewController.swift */; };
+ CF73D34B241F9C31000B1B10 /* ColorPickerSwatchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFE7027E241B984600083903 /* ColorPickerSwatchViewController.swift */; };
+ CF73D34C241F9C4D000B1B10 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF73D32B241E4720000B1B10 /* Color.swift */; };
+ CF73D351241F9FB3000B1B10 /* ColorPickerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF73D350241F9FB3000B1B10 /* ColorPickerDelegate.swift */; };
+ CF7750B72520D3B50069CC57 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF7750B62520D3B50069CC57 /* Assets.swift */; };
+ CF7750BE252203630069CC57 /* DialogButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF7750BD252203630069CC57 /* DialogButton.swift */; };
+ CF7750CF252433680069CC57 /* ColorPickerConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF7750CE252433680069CC57 /* ColorPickerConfiguration.swift */; };
+ CF7750D62524615D0069CC57 /* SeparatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF7750D52524615D0069CC57 /* SeparatorView.swift */; };
+ CF775122252852460069CC57 /* AlderisSDKCompatibility.h in Headers */ = {isa = PBXBuildFile; fileRef = CF775121252852110069CC57 /* AlderisSDKCompatibility.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ CF79DA31251723C500F17BCB /* BottomSheetTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF79DA30251723C500F17BCB /* BottomSheetTransition.swift */; };
+ CFAFFC9124277CEE005AD4C1 /* Assets-ios12.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CFAFFC9024277CEE005AD4C1 /* Assets-ios12.xcassets */; platformFilter = ios; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 4E1C741228266C5900227EC3 /* UIFontDescriptorAdditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIFontDescriptorAdditions.swift; sourceTree = ""; };
+ 4E1C74162826C1F100227EC3 /* ColorPickerAccessibilityViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerAccessibilityViewController.swift; sourceTree = ""; };
+ 4E1C741828276D5600227EC3 /* TextViewLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewLabel.swift; sourceTree = ""; };
+ 4E1C741A2827829E00227EC3 /* AccessibilityComplianceLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityComplianceLabel.swift; sourceTree = ""; };
+ 4E1C741C2827882600227EC3 /* AccessibilityContrastSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityContrastSelector.swift; sourceTree = ""; };
+ 4E1C741E2827B3C800227EC3 /* UIFloat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIFloat.swift; sourceTree = ""; };
+ 4E1C74202827B8F800227EC3 /* NSBeep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSBeep.swift; sourceTree = ""; };
+ 4E2E6C05282BD5990089E4FB /* GradientView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientView.swift; sourceTree = ""; };
+ 569C25512427F57000022C60 /* ColorPickerTabViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerTabViewController.swift; sourceTree = ""; };
+ 569C25572428346900022C60 /* ColorPickerMapSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerMapSlider.swift; sourceTree = ""; };
+ 56C74666242F722A003ED00A /* ColorPickerSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerSlider.swift; sourceTree = ""; };
+ 56C74668242F75E3003ED00A /* ColorPickerNumericSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerNumericSlider.swift; sourceTree = ""; };
+ 94A2368B252B5951002B5D0B /* UIColorAdditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColorAdditions.swift; sourceTree = ""; };
+ CF73D32B241E4720000B1B10 /* Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = ""; };
+ CF73D32D241E49EE000B1B10 /* ColorWell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorWell.swift; sourceTree = ""; };
+ CF73D336241F9C23000B1B10 /* Alderis.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Alderis.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ CF73D338241F9C23000B1B10 /* Alderis.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Alderis.h; sourceTree = ""; };
+ CF73D339241F9C23000B1B10 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ CF73D350241F9FB3000B1B10 /* ColorPickerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerDelegate.swift; sourceTree = ""; };
+ CF7750B62520D3B50069CC57 /* Assets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Assets.swift; sourceTree = ""; };
+ CF7750BD252203630069CC57 /* DialogButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DialogButton.swift; sourceTree = ""; };
+ CF7750CE252433680069CC57 /* ColorPickerConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorPickerConfiguration.swift; sourceTree = ""; };
+ CF7750D52524615D0069CC57 /* SeparatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeparatorView.swift; sourceTree = ""; };
+ CF775121252852110069CC57 /* AlderisSDKCompatibility.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AlderisSDKCompatibility.h; sourceTree = ""; };
+ CF79DA30251723C500F17BCB /* BottomSheetTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomSheetTransition.swift; sourceTree = ""; };
+ CFAFFC9024277CEE005AD4C1 /* Assets-ios12.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Assets-ios12.xcassets"; sourceTree = ""; };
+ CFE70273241A4C5500083903 /* ColorPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerViewController.swift; sourceTree = ""; };
+ CFE7027B241A62ED00083903 /* ColorPickerInnerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerInnerViewController.swift; sourceTree = ""; };
+ CFE7027E241B984600083903 /* ColorPickerSwatchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerSwatchViewController.swift; sourceTree = ""; };
+ CFE70280241CBDE700083903 /* ColorPickerMapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerMapViewController.swift; sourceTree = ""; };
+ CFE70282241CC88D00083903 /* ColorPickerSlidersViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerSlidersViewController.swift; sourceTree = ""; };
+ CFE70288241D0E7300083903 /* ColorPickerWheelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerWheelView.swift; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ CF73D333241F9C23000B1B10 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ CF39D435222BC07C001EF57F = {
+ isa = PBXGroup;
+ children = (
+ CF73D337241F9C23000B1B10 /* Alderis */,
+ CF39D43F222BC07C001EF57F /* Products */,
+ );
+ indentWidth = 2;
+ sourceTree = "";
+ tabWidth = 2;
+ usesTabs = 1;
+ };
+ CF39D43F222BC07C001EF57F /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ CF73D336241F9C23000B1B10 /* Alderis.framework */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ CF73D337241F9C23000B1B10 /* Alderis */ = {
+ isa = PBXGroup;
+ children = (
+ CF73D338241F9C23000B1B10 /* Alderis.h */,
+ CF775121252852110069CC57 /* AlderisSDKCompatibility.h */,
+ CF7750B62520D3B50069CC57 /* Assets.swift */,
+ CF79DA30251723C500F17BCB /* BottomSheetTransition.swift */,
+ CF73D32B241E4720000B1B10 /* Color.swift */,
+ CFE70273241A4C5500083903 /* ColorPickerViewController.swift */,
+ CF7750CE252433680069CC57 /* ColorPickerConfiguration.swift */,
+ CF73D350241F9FB3000B1B10 /* ColorPickerDelegate.swift */,
+ CFE7027B241A62ED00083903 /* ColorPickerInnerViewController.swift */,
+ 569C25512427F57000022C60 /* ColorPickerTabViewController.swift */,
+ 56C74666242F722A003ED00A /* ColorPickerSlider.swift */,
+ CFE7027E241B984600083903 /* ColorPickerSwatchViewController.swift */,
+ 569C25572428346900022C60 /* ColorPickerMapSlider.swift */,
+ CFE70280241CBDE700083903 /* ColorPickerMapViewController.swift */,
+ CFE70288241D0E7300083903 /* ColorPickerWheelView.swift */,
+ CFE70282241CC88D00083903 /* ColorPickerSlidersViewController.swift */,
+ 56C74668242F75E3003ED00A /* ColorPickerNumericSlider.swift */,
+ 4E1C74162826C1F100227EC3 /* ColorPickerAccessibilityViewController.swift */,
+ 4E1C741A2827829E00227EC3 /* AccessibilityComplianceLabel.swift */,
+ 4E1C741C2827882600227EC3 /* AccessibilityContrastSelector.swift */,
+ CF73D32D241E49EE000B1B10 /* ColorWell.swift */,
+ CF7750BD252203630069CC57 /* DialogButton.swift */,
+ CF7750D52524615D0069CC57 /* SeparatorView.swift */,
+ 4E2E6C05282BD5990089E4FB /* GradientView.swift */,
+ 94A2368B252B5951002B5D0B /* UIColorAdditions.swift */,
+ 4E1C741228266C5900227EC3 /* UIFontDescriptorAdditions.swift */,
+ 4E1C741E2827B3C800227EC3 /* UIFloat.swift */,
+ 4E1C741828276D5600227EC3 /* TextViewLabel.swift */,
+ 4E1C74202827B8F800227EC3 /* NSBeep.swift */,
+ CF73D339241F9C23000B1B10 /* Info.plist */,
+ CFAFFC9024277CEE005AD4C1 /* Assets-ios12.xcassets */,
+ );
+ path = Alderis;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+ CF73D331241F9C23000B1B10 /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ CF73D33A241F9C23000B1B10 /* Alderis.h in Headers */,
+ CF775122252852460069CC57 /* AlderisSDKCompatibility.h in Headers */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+ CF73D335241F9C23000B1B10 /* Alderis */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = CF73D340241F9C23000B1B10 /* Build configuration list for PBXNativeTarget "Alderis" */;
+ buildPhases = (
+ CF73D331241F9C23000B1B10 /* Headers */,
+ CF73D332241F9C23000B1B10 /* Sources */,
+ CF73D333241F9C23000B1B10 /* Frameworks */,
+ CF73D334241F9C23000B1B10 /* Resources */,
+ CF77511225281F7E0069CC57 /* SwiftLint */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Alderis;
+ productName = Alderis;
+ productReference = CF73D336241F9C23000B1B10 /* Alderis.framework */;
+ productType = "com.apple.product-type.framework";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ CF39D436222BC07C001EF57F /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastUpgradeCheck = 1330;
+ ORGANIZATIONNAME = "HASHBANG Productions";
+ TargetAttributes = {
+ CF73D335241F9C23000B1B10 = {
+ CreatedOnToolsVersion = 11.3.1;
+ };
+ };
+ };
+ buildConfigurationList = CF39D439222BC07C001EF57F /* Build configuration list for PBXProject "Alderis" */;
+ compatibilityVersion = "Xcode 9.3";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = CF39D435222BC07C001EF57F;
+ productRefGroup = CF39D43F222BC07C001EF57F /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ CF73D335241F9C23000B1B10 /* Alderis */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ CF73D334241F9C23000B1B10 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ CFAFFC9124277CEE005AD4C1 /* Assets-ios12.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ CF77511225281F7E0069CC57 /* SwiftLint */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ );
+ name = SwiftLint;
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "export PATH=$PATH:/opt/homebrew/bin\nif which swiftlint >/dev/null; then\n\tswiftlint\nelse\n\techo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ CF73D332241F9C23000B1B10 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ CF73D34B241F9C31000B1B10 /* ColorPickerSwatchViewController.swift in Sources */,
+ CF7750BE252203630069CC57 /* DialogButton.swift in Sources */,
+ 4E1C741928276D5600227EC3 /* TextViewLabel.swift in Sources */,
+ CF73D347241F9C31000B1B10 /* ColorPickerWheelView.swift in Sources */,
+ CF7750D62524615D0069CC57 /* SeparatorView.swift in Sources */,
+ CF73D34C241F9C4D000B1B10 /* Color.swift in Sources */,
+ 94A2368C252B5951002B5D0B /* UIColorAdditions.swift in Sources */,
+ 569C25582428346900022C60 /* ColorPickerMapSlider.swift in Sources */,
+ 4E2E6C06282BD5990089E4FB /* GradientView.swift in Sources */,
+ CF73D344241F9C31000B1B10 /* ColorWell.swift in Sources */,
+ CF7750B72520D3B50069CC57 /* Assets.swift in Sources */,
+ CF73D348241F9C31000B1B10 /* ColorPickerInnerViewController.swift in Sources */,
+ 56C74667242F722A003ED00A /* ColorPickerSlider.swift in Sources */,
+ 4E1C74212827B8F800227EC3 /* NSBeep.swift in Sources */,
+ 4E1C74172826C1F100227EC3 /* ColorPickerAccessibilityViewController.swift in Sources */,
+ 4E1C741D2827882600227EC3 /* AccessibilityContrastSelector.swift in Sources */,
+ CF73D345241F9C31000B1B10 /* ColorPickerMapViewController.swift in Sources */,
+ CF79DA31251723C500F17BCB /* BottomSheetTransition.swift in Sources */,
+ 4E1C741328266C5900227EC3 /* UIFontDescriptorAdditions.swift in Sources */,
+ CF73D34A241F9C31000B1B10 /* ColorPickerViewController.swift in Sources */,
+ CF7750CF252433680069CC57 /* ColorPickerConfiguration.swift in Sources */,
+ 56C74669242F75E3003ED00A /* ColorPickerNumericSlider.swift in Sources */,
+ 569C25522427F57000022C60 /* ColorPickerTabViewController.swift in Sources */,
+ 4E1C741F2827B3C800227EC3 /* UIFloat.swift in Sources */,
+ CF73D351241F9FB3000B1B10 /* ColorPickerDelegate.swift in Sources */,
+ CF73D346241F9C31000B1B10 /* ColorPickerSlidersViewController.swift in Sources */,
+ 4E1C741B2827829E00227EC3 /* AccessibilityComplianceLabel.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ CF39D452222BC07E001EF57F /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ };
+ name = Debug;
+ };
+ CF39D453222BC07E001EF57F /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ CF73D341241F9C23000B1B10 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ APPLICATION_EXTENSION_API_ONLY = YES;
+ BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
+ CURRENT_PROJECT_VERSION = 3;
+ DEFINES_MODULE = YES;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ INFOPLIST_FILE = Alderis/Info.plist;
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.2;
+ PRODUCT_BUNDLE_IDENTIFIER = ws.hbang.Alderis;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = NO;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Debug;
+ };
+ CF73D342241F9C23000B1B10 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ APPLICATION_EXTENSION_API_ONLY = YES;
+ BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
+ CURRENT_PROJECT_VERSION = 3;
+ DEFINES_MODULE = YES;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ INFOPLIST_FILE = Alderis/Info.plist;
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.2;
+ PRODUCT_BUNDLE_IDENTIFIER = ws.hbang.Alderis;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = NO;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ CF39D439222BC07C001EF57F /* Build configuration list for PBXProject "Alderis" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ CF39D452222BC07E001EF57F /* Debug */,
+ CF39D453222BC07E001EF57F /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ CF73D340241F9C23000B1B10 /* Build configuration list for PBXNativeTarget "Alderis" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ CF73D341241F9C23000B1B10 /* Debug */,
+ CF73D342241F9C23000B1B10 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = CF39D436222BC07C001EF57F /* Project object */;
+}
diff --git a/Tweaks/Alderis/Alderis.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Tweaks/Alderis/Alderis.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..e210b76
--- /dev/null
+++ b/Tweaks/Alderis/Alderis.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/Tweaks/Alderis/Alderis.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Tweaks/Alderis/Alderis.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/Tweaks/Alderis/Alderis.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/Tweaks/Alderis/Alderis.xcodeproj/xcshareddata/xcschemes/Alderis.xcscheme b/Tweaks/Alderis/Alderis.xcodeproj/xcshareddata/xcschemes/Alderis.xcscheme
new file mode 100644
index 0000000..76b6914
--- /dev/null
+++ b/Tweaks/Alderis/Alderis.xcodeproj/xcshareddata/xcschemes/Alderis.xcscheme
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tweaks/Alderis/Alderis/AccessibilityComplianceLabel.swift b/Tweaks/Alderis/Alderis/AccessibilityComplianceLabel.swift
new file mode 100644
index 0000000..e54d569
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/AccessibilityComplianceLabel.swift
@@ -0,0 +1,77 @@
+//
+// AccessibilityComplianceLabel.swift
+// Alderis
+//
+// Created by Adam Demasi on 8/5/2022.
+// Copyright © 2022 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+
+internal class AccessibilityComplianceLabel: UIView {
+
+ var text: String {
+ get { label.text! }
+ set { label.text = newValue }
+ }
+
+ var isCompliant = false {
+ didSet { updateState() }
+ }
+
+ private let tickImage = Assets.systemImage(named: "checkmark.circle.fill")
+ private let crossImage = Assets.systemImage(named: "xmark.circle.fill")
+
+ private var imageView: UIImageView!
+ private var label: UILabel!
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+
+ let font = UIFont.systemFont(ofSize: UIFloat(16), weight: .medium)
+
+ imageView = UIImageView()
+ if #available(iOS 13, *) {
+ imageView.preferredSymbolConfiguration = UIImage.SymbolConfiguration(font: font, scale: .small)
+ }
+
+ label = UILabel()
+ label.font = font
+
+ let stackView = UIStackView(arrangedSubviews: [imageView, label])
+ stackView.translatesAutoresizingMaskIntoConstraints = false
+ stackView.alignment = .center
+ stackView.spacing = UIFloat(6)
+ addSubview(stackView)
+
+ NSLayoutConstraint.activate([
+ stackView.topAnchor.constraint(equalTo: self.topAnchor),
+ stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
+ stackView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
+ stackView.trailingAnchor.constraint(equalTo: self.trailingAnchor)
+ ])
+ }
+
+ convenience init(text: String) {
+ self.init(frame: .zero)
+ self.text = text
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func updateState() {
+ let color = isCompliant ? Assets.green : Assets.red
+
+ tintColor = color
+ accessibilityLabel = "\(text): \(isCompliant ? "Compliant" : "Not compliant")"
+ imageView.image = isCompliant ? tickImage : crossImage
+ }
+
+ override func tintColorDidChange() {
+ super.tintColorDidChange()
+ label.textColor = tintColor
+ }
+
+}
diff --git a/Tweaks/Alderis/Alderis/AccessibilityContrastSelector.swift b/Tweaks/Alderis/Alderis/AccessibilityContrastSelector.swift
new file mode 100644
index 0000000..3ef0773
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/AccessibilityContrastSelector.swift
@@ -0,0 +1,91 @@
+//
+// AccessibilityContrastSelector.swift
+// Alderis
+//
+// Created by Adam Demasi on 8/5/2022.
+// Copyright © 2022 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+
+internal class AccessibilityContrastSelector: UIView {
+
+ enum Mode: Int, CaseIterable {
+ case color, black, white
+
+ var label: String {
+ switch self {
+ case .color: return "Color"
+ case .black: return "Black"
+ case .white: return "White"
+ }
+ }
+
+ func color(withColor color: Color) -> Color {
+ switch self {
+ case .color: return color
+ case .black: return .black
+ case .white: return .white
+ }
+ }
+ }
+
+ var text: String {
+ get { label.text! }
+ set { label.text = newValue }
+ }
+
+ var value: Mode = .white {
+ didSet {
+ if segmentedControl.selectedSegmentIndex != value.rawValue {
+ segmentedControl.selectedSegmentIndex = value.rawValue
+ handleChange?(value)
+ }
+ }
+ }
+
+ var handleChange: ((Mode) -> Void)?
+
+ private var label: UILabel!
+ private var segmentedControl: UISegmentedControl!
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+
+ label = UILabel()
+ label.font = UIFont.systemFont(ofSize: UIFloat(16), weight: .medium)
+
+ segmentedControl = UISegmentedControl(items: Mode.allCases.map(\.label))
+ segmentedControl.addTarget(self, action: #selector(handleValueChanged), for: .valueChanged)
+
+ let stackView = UIStackView(arrangedSubviews: [label, UIView(), segmentedControl])
+ stackView.translatesAutoresizingMaskIntoConstraints = false
+ stackView.alignment = .center
+ stackView.distribution = .fill
+ stackView.spacing = UIFloat(5)
+ addSubview(stackView)
+
+ NSLayoutConstraint.activate([
+ stackView.topAnchor.constraint(equalTo: self.topAnchor),
+ stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
+ stackView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
+ stackView.trailingAnchor.constraint(equalTo: self.trailingAnchor)
+ ])
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ convenience init(text: String, value: Mode) {
+ self.init(frame: .zero)
+ self.text = text
+ self.value = value
+ }
+
+ @objc private func handleValueChanged() {
+ value = Mode(rawValue: segmentedControl.selectedSegmentIndex)!
+ handleChange?(value)
+ }
+
+}
diff --git a/Tweaks/Alderis/Alderis/Alderis.h b/Tweaks/Alderis/Alderis/Alderis.h
new file mode 100644
index 0000000..b801478
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/Alderis.h
@@ -0,0 +1,11 @@
+//
+// Alderis.h
+// Alderis
+//
+// Created by Adam Demasi on 16/3/20.
+// Copyright © 2020 HASHBANG Productions. All rights reserved.
+//
+
+@import UIKit;
+
+#import "AlderisSDKCompatibility.h"
diff --git a/Tweaks/Alderis/Alderis/AlderisSDKCompatibility.h b/Tweaks/Alderis/Alderis/AlderisSDKCompatibility.h
new file mode 100644
index 0000000..4fbe860
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/AlderisSDKCompatibility.h
@@ -0,0 +1,21 @@
+//
+// AlderisSDKCompatibility.h
+// Alderis
+//
+// Created by Adam Demasi on 3/10/20.
+// Copyright © 2020 HASHBANG Productions. All rights reserved.
+//
+
+@import UIKit;
+
+#ifndef __IPHONE_14_0
+// Allows building with the iOS 13 SDK while retaining iOS 14 compatibility.
+
+@interface UIControl ()
+
+- (void)addAction:(UIAction *)action forControlEvents:(UIControlEvents)controlEvents NS_SWIFT_NAME(addAction(_:for:)) API_AVAILABLE(ios(14.0));
+- (void)removeAction:(UIAction *)action forControlEvents:(UIControlEvents)controlEvents NS_SWIFT_NAME(removeAction(_:for:)) API_AVAILABLE(ios(14.0));
+- (void)removeActionForIdentifier:(UIActionIdentifier)actionIdentifier forControlEvents:(UIControlEvents)controlEvents NS_SWIFT_NAME(removeAction(identifiedBy:for:)) API_AVAILABLE(ios(14.0));
+
+@end
+#endif
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/Contents.json b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/sun.max.imageset/Contents.json b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/sun.max.imageset/Contents.json
new file mode 100644
index 0000000..5a6c8c8
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/sun.max.imageset/Contents.json
@@ -0,0 +1,26 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "sun.max.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "sun.max@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "sun.max@3x.png",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+}
\ No newline at end of file
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/sun.max.imageset/sun.max.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/sun.max.imageset/sun.max.png
new file mode 100644
index 0000000..c5e742d
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/sun.max.imageset/sun.max.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/sun.max.imageset/sun.max@2x.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/sun.max.imageset/sun.max@2x.png
new file mode 100644
index 0000000..63e276f
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/sun.max.imageset/sun.max@2x.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/sun.max.imageset/sun.max@3x.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/sun.max.imageset/sun.max@3x.png
new file mode 100644
index 0000000..9ac5d14
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/sun.max.imageset/sun.max@3x.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/sun.min.imageset/Contents.json b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/sun.min.imageset/Contents.json
new file mode 100644
index 0000000..9abdbb5
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/sun.min.imageset/Contents.json
@@ -0,0 +1,26 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "sun.min.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "sun.min@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "sun.min@3x.png",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+}
\ No newline at end of file
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/sun.min.imageset/sun.min.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/sun.min.imageset/sun.min.png
new file mode 100644
index 0000000..8e3b0dc
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/sun.min.imageset/sun.min.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/sun.min.imageset/sun.min@2x.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/sun.min.imageset/sun.min@2x.png
new file mode 100644
index 0000000..52d0b0b
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/sun.min.imageset/sun.min@2x.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/sun.min.imageset/sun.min@3x.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/sun.min.imageset/sun.min@3x.png
new file mode 100644
index 0000000..5ebf1c5
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Brightness Slider/sun.min.imageset/sun.min@3x.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contents.json b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/Contents.json b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/checkmark.circle.fill.imageset/Contents.json b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/checkmark.circle.fill.imageset/Contents.json
new file mode 100644
index 0000000..57a752a
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/checkmark.circle.fill.imageset/Contents.json
@@ -0,0 +1,26 @@
+{
+ "images" : [
+ {
+ "filename" : "checkmark.circle.fill.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "checkmark.circle.fill@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "checkmark.circle.fill@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/checkmark.circle.fill.imageset/checkmark.circle.fill.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/checkmark.circle.fill.imageset/checkmark.circle.fill.png
new file mode 100644
index 0000000..866f762
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/checkmark.circle.fill.imageset/checkmark.circle.fill.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/checkmark.circle.fill.imageset/checkmark.circle.fill@2x.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/checkmark.circle.fill.imageset/checkmark.circle.fill@2x.png
new file mode 100644
index 0000000..bd37650
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/checkmark.circle.fill.imageset/checkmark.circle.fill@2x.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/checkmark.circle.fill.imageset/checkmark.circle.fill@3x.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/checkmark.circle.fill.imageset/checkmark.circle.fill@3x.png
new file mode 100644
index 0000000..b765c07
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/checkmark.circle.fill.imageset/checkmark.circle.fill@3x.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/sparkles.imageset/Contents.json b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/sparkles.imageset/Contents.json
new file mode 100644
index 0000000..c23f0a8
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/sparkles.imageset/Contents.json
@@ -0,0 +1,26 @@
+{
+ "images" : [
+ {
+ "filename" : "sparkles.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "sparkles@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "sparkles@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/sparkles.imageset/sparkles.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/sparkles.imageset/sparkles.png
new file mode 100644
index 0000000..ca6719b
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/sparkles.imageset/sparkles.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/sparkles.imageset/sparkles@2x.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/sparkles.imageset/sparkles@2x.png
new file mode 100644
index 0000000..7e52803
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/sparkles.imageset/sparkles@2x.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/sparkles.imageset/sparkles@3x.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/sparkles.imageset/sparkles@3x.png
new file mode 100644
index 0000000..1b85e58
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/sparkles.imageset/sparkles@3x.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/xmark.circle.fill.imageset/Contents.json b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/xmark.circle.fill.imageset/Contents.json
new file mode 100644
index 0000000..c569aa6
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/xmark.circle.fill.imageset/Contents.json
@@ -0,0 +1,26 @@
+{
+ "images" : [
+ {
+ "filename" : "xmark.circle.fill.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "xmark.circle.fill@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "xmark.circle.fill@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/xmark.circle.fill.imageset/xmark.circle.fill.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/xmark.circle.fill.imageset/xmark.circle.fill.png
new file mode 100644
index 0000000..5e3ce94
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/xmark.circle.fill.imageset/xmark.circle.fill.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/xmark.circle.fill.imageset/xmark.circle.fill@2x.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/xmark.circle.fill.imageset/xmark.circle.fill@2x.png
new file mode 100644
index 0000000..3f5c29c
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/xmark.circle.fill.imageset/xmark.circle.fill@2x.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/xmark.circle.fill.imageset/xmark.circle.fill@3x.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/xmark.circle.fill.imageset/xmark.circle.fill@3x.png
new file mode 100644
index 0000000..2d96119
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Contrast Checker/xmark.circle.fill.imageset/xmark.circle.fill@3x.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/Contents.json b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/circle.righthalf.fill.imageset/Contents.json b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/circle.righthalf.fill.imageset/Contents.json
new file mode 100644
index 0000000..818d8fc
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/circle.righthalf.fill.imageset/Contents.json
@@ -0,0 +1,26 @@
+{
+ "images" : [
+ {
+ "filename" : "circle.righthalf.fill.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "circle.righthalf.fill@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "circle.righthalf.fill@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/circle.righthalf.fill.imageset/circle.righthalf.fill.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/circle.righthalf.fill.imageset/circle.righthalf.fill.png
new file mode 100644
index 0000000..32d27c9
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/circle.righthalf.fill.imageset/circle.righthalf.fill.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/circle.righthalf.fill.imageset/circle.righthalf.fill@2x.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/circle.righthalf.fill.imageset/circle.righthalf.fill@2x.png
new file mode 100644
index 0000000..b7926fc
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/circle.righthalf.fill.imageset/circle.righthalf.fill@2x.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/circle.righthalf.fill.imageset/circle.righthalf.fill@3x.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/circle.righthalf.fill.imageset/circle.righthalf.fill@3x.png
new file mode 100644
index 0000000..23d85a8
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/circle.righthalf.fill.imageset/circle.righthalf.fill@3x.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/slider.horizontal.3.imageset/Contents.json b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/slider.horizontal.3.imageset/Contents.json
new file mode 100644
index 0000000..a5c3941
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/slider.horizontal.3.imageset/Contents.json
@@ -0,0 +1,26 @@
+{
+ "images" : [
+ {
+ "filename" : "slider.horizontal.3.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "slider.horizontal.3@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "slider.horizontal.3@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/slider.horizontal.3.imageset/slider.horizontal.3.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/slider.horizontal.3.imageset/slider.horizontal.3.png
new file mode 100644
index 0000000..f0f741f
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/slider.horizontal.3.imageset/slider.horizontal.3.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/slider.horizontal.3.imageset/slider.horizontal.3@2x.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/slider.horizontal.3.imageset/slider.horizontal.3@2x.png
new file mode 100644
index 0000000..c48ecea
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/slider.horizontal.3.imageset/slider.horizontal.3@2x.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/slider.horizontal.3.imageset/slider.horizontal.3@3x.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/slider.horizontal.3.imageset/slider.horizontal.3@3x.png
new file mode 100644
index 0000000..324a185
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/slider.horizontal.3.imageset/slider.horizontal.3@3x.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/slider.horizontal.below.rectangle.imageset/Contents.json b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/slider.horizontal.below.rectangle.imageset/Contents.json
new file mode 100644
index 0000000..2f4068e
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/slider.horizontal.below.rectangle.imageset/Contents.json
@@ -0,0 +1,26 @@
+{
+ "images" : [
+ {
+ "filename" : "slider.horizontal.below.rectangle.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "slider.horizontal.below.rectangle@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "slider.horizontal.below.rectangle@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/slider.horizontal.below.rectangle.imageset/slider.horizontal.below.rectangle.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/slider.horizontal.below.rectangle.imageset/slider.horizontal.below.rectangle.png
new file mode 100644
index 0000000..aa95cf6
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/slider.horizontal.below.rectangle.imageset/slider.horizontal.below.rectangle.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/slider.horizontal.below.rectangle.imageset/slider.horizontal.below.rectangle@2x.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/slider.horizontal.below.rectangle.imageset/slider.horizontal.below.rectangle@2x.png
new file mode 100644
index 0000000..f35abf8
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/slider.horizontal.below.rectangle.imageset/slider.horizontal.below.rectangle@2x.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/slider.horizontal.below.rectangle.imageset/slider.horizontal.below.rectangle@3x.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/slider.horizontal.below.rectangle.imageset/slider.horizontal.below.rectangle@3x.png
new file mode 100644
index 0000000..aba0cd5
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/slider.horizontal.below.rectangle.imageset/slider.horizontal.below.rectangle@3x.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/square.grid.4x3.fill.imageset/Contents.json b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/square.grid.4x3.fill.imageset/Contents.json
new file mode 100644
index 0000000..86e632b
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/square.grid.4x3.fill.imageset/Contents.json
@@ -0,0 +1,26 @@
+{
+ "images" : [
+ {
+ "filename" : "square.grid.4x3.fill.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "square.grid.4x3.fill@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "square.grid.4x3.fill@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/square.grid.4x3.fill.imageset/square.grid.4x3.fill.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/square.grid.4x3.fill.imageset/square.grid.4x3.fill.png
new file mode 100644
index 0000000..11f84fa
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/square.grid.4x3.fill.imageset/square.grid.4x3.fill.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/square.grid.4x3.fill.imageset/square.grid.4x3.fill@2x.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/square.grid.4x3.fill.imageset/square.grid.4x3.fill@2x.png
new file mode 100644
index 0000000..635183e
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/square.grid.4x3.fill.imageset/square.grid.4x3.fill@2x.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/square.grid.4x3.fill.imageset/square.grid.4x3.fill@3x.png b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/square.grid.4x3.fill.imageset/square.grid.4x3.fill@3x.png
new file mode 100644
index 0000000..714ab9a
Binary files /dev/null and b/Tweaks/Alderis/Alderis/Assets-ios12.xcassets/Tabs/square.grid.4x3.fill.imageset/square.grid.4x3.fill@3x.png differ
diff --git a/Tweaks/Alderis/Alderis/Assets.swift b/Tweaks/Alderis/Alderis/Assets.swift
new file mode 100644
index 0000000..7289a9c
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/Assets.swift
@@ -0,0 +1,135 @@
+//
+// Assets.swift
+// Alderis
+//
+// Created by Adam Demasi on 27/9/20.
+// Copyright © 2020 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+
+internal struct Assets {
+
+ internal enum SymbolScale: Int {
+ case `default` = -1
+ case unspecified, small, medium, large
+
+ @available(iOS 13, *)
+ var uiImageSymbolScale: UIImage.SymbolScale { UIImage.SymbolScale(rawValue: rawValue)! }
+ }
+
+ private static let bundle: Bundle = {
+ let myBundle = Bundle(for: ColorPickerViewController.self)
+ if let resourcesURL = myBundle.url(forResource: "Alderis", withExtension: "bundle"),
+ let resourcesBundle = Bundle(url: resourcesURL) {
+ return resourcesBundle
+ }
+ return myBundle
+ }()
+ private static let uikitBundle = Bundle(for: UIView.self)
+
+ // MARK: - Localization
+
+ static func uikitLocalize(_ key: String) -> String {
+ uikitBundle.localizedString(forKey: key, value: nil, table: nil)
+ }
+
+ // MARK: - Images
+
+ static func systemImage(named name: String, font: UIFont? = nil, scale: SymbolScale = .default) -> UIImage? {
+ if #available(iOS 13, *) {
+ var configuration: UIImage.SymbolConfiguration?
+ if let font = font {
+ configuration = UIImage.SymbolConfiguration(font: font, scale: scale.uiImageSymbolScale)
+ }
+ return UIImage(systemName: name, withConfiguration: configuration)
+ }
+ return UIImage(named: name, in: bundle, compatibleWith: nil)
+ }
+
+ // MARK: - Fonts
+
+ static func niceMonospaceDigitFont(ofSize size: CGFloat) -> UIFont {
+ // Take the monospace digit font and enable stylistic alternative 6, which provides a
+ // high-legibility, monospace-looking style of the system font.
+ let font = UIFont.monospacedDigitSystemFont(ofSize: size, weight: .regular)
+ let fontDescriptor = font.fontDescriptor.addingAttributes([
+ .featureSettings: [
+ [
+ .alderisFeature: kStylisticAlternativesType,
+ .alderisSelector: kStylisticAltSixOnSelector
+ ]
+ ] as [[UIFontDescriptor.FeatureKey: Int]]
+ ])
+ return UIFont(descriptor: fontDescriptor, size: 0)
+ }
+
+ // MARK: - Colors
+
+ private static func color(userInterfaceStyles colors: [UIUserInterfaceStyle: UIColor], fallback: UIUserInterfaceStyle = .light) -> UIColor {
+ if #available(iOS 13, *) {
+ return UIColor { colors[$0.userInterfaceStyle] ?? colors[fallback] ?? colors.values.first! }
+ }
+ return colors[fallback] ?? colors.values.first!
+ }
+
+ static let backdropColor = UIColor(white: 0, alpha: 0.2)
+ static let separatorColor = UIColor(white: 1, alpha: 0.15)
+
+ static let labelColor: UIColor = {
+ if #available(iOS 13, *) {
+ return .label
+ }
+ return .black
+ }()
+
+ static let secondaryLabelColor: UIColor = {
+ if #available(iOS 13, *) {
+ return .secondaryLabel
+ }
+ return UIColor(white: 60 / 255, alpha: 0.6)
+ }()
+
+ static let borderColor: UIColor = {
+ if #available(iOS 13, *) {
+ return .separator
+ }
+ return UIColor(white: 1, alpha: 0.35)
+ }()
+
+ @available(iOS 13, *)
+ static let macTabBarSelectionColor = color(userInterfaceStyles: [
+ .light: .black,
+ .dark: .label
+ ])
+
+ static let red = UIColor.systemRed
+
+ // .systemGreen adjusted to be more consistent / easier to read
+ static let green = color(userInterfaceStyles: [
+ .light: UIColor(red: 15 / 255, green: 189 / 255, blue: 59 / 255, alpha: 1),
+ // swiftlint:disable:next colon
+ .dark: UIColor(red: 41 / 255, green: 179 / 255, blue: 76 / 255, alpha: 1)
+ ])
+
+ static let checkerboardPatternColor = color(userInterfaceStyles: [
+ .light: renderCheckerboardPattern(colors: (UIColor(white: 200 / 255, alpha: 1),
+ UIColor(white: 255 / 255, alpha: 1))),
+ // swiftlint:disable:next colon
+ .dark: renderCheckerboardPattern(colors: (UIColor(white: 140 / 255, alpha: 1),
+ UIColor(white: 186 / 255, alpha: 1)))
+ ])
+
+ private static func renderCheckerboardPattern(colors: (dark: UIColor, light: UIColor)) -> UIColor {
+ let size = 11
+ let image = UIGraphicsImageRenderer(size: CGSize(width: size * 2, height: size * 2)).image { context in
+ colors.dark.setFill()
+ context.fill(CGRect(x: 0, y: 0, width: size * 2, height: size * 2))
+ colors.light.setFill()
+ context.fill(CGRect(x: size, y: 0, width: size, height: size))
+ context.fill(CGRect(x: 0, y: size, width: size, height: size))
+ }
+ return UIColor(patternImage: image)
+ }
+
+}
diff --git a/Tweaks/Alderis/Alderis/BottomSheetTransition.swift b/Tweaks/Alderis/Alderis/BottomSheetTransition.swift
new file mode 100644
index 0000000..f131fbf
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/BottomSheetTransition.swift
@@ -0,0 +1,48 @@
+//
+// BottomSheetTransition.swift
+// Alderis
+//
+// Created by Adam Demasi on 20/9/20.
+// Copyright © 2020 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+
+internal class BottomSheetTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
+
+ func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
+ return BottomSheetTransition(direction: true)
+ }
+
+ func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
+ return BottomSheetTransition(direction: false)
+ }
+
+}
+
+internal class BottomSheetTransition: NSObject, UIViewControllerAnimatedTransitioning {
+
+ // Opening: true
+ // Closing: false
+ let direction: Bool
+
+ init(direction: Bool) {
+ self.direction = direction
+ }
+
+ func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
+ 0.4
+ }
+
+ func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
+ if direction {
+ let to = transitionContext.viewController(forKey: .to)!
+ transitionContext.containerView.addSubview(to.view)
+ }
+
+ Timer.scheduledTimer(withTimeInterval: transitionDuration(using: transitionContext), repeats: false) { _ in
+ transitionContext.completeTransition(true)
+ }
+ }
+
+}
diff --git a/Tweaks/Alderis/Alderis/Color.swift b/Tweaks/Alderis/Alderis/Color.swift
new file mode 100644
index 0000000..a7ebc0f
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/Color.swift
@@ -0,0 +1,342 @@
+//
+// Color.swift
+// Alderis
+//
+// Created by Adam Demasi on 15/3/20.
+// Copyright © 2020 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+
+internal struct Color: Equatable, Hashable {
+ static let black = Color(white: 0, alpha: 1)
+ static let white = Color(white: 1, alpha: 1)
+
+ var red: CGFloat = 0 {
+ didSet {
+ self = Color(red: red, green: green, blue: blue, alpha: alpha)
+ }
+ }
+ var green: CGFloat = 0 {
+ didSet {
+ self = Color(red: red, green: green, blue: blue, alpha: alpha)
+ }
+ }
+ var blue: CGFloat = 0 {
+ didSet {
+ self = Color(red: red, green: green, blue: blue, alpha: alpha)
+ }
+ }
+
+ var hue: CGFloat = 0 {
+ didSet {
+ self = Color(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha)
+ }
+ }
+ var saturation: CGFloat = 0 {
+ didSet {
+ self = Color(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha)
+ }
+ }
+ var brightness: CGFloat = 0 {
+ didSet {
+ self = Color(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha)
+ }
+ }
+ var hslSaturation: CGFloat = 0 {
+ didSet {
+ self = Color(hue: hue, saturation: hslSaturation, lightness: lightness, alpha: alpha)
+ }
+ }
+ var lightness: CGFloat = 0 {
+ didSet {
+ self = Color(hue: hue, saturation: hslSaturation, lightness: lightness, alpha: alpha)
+ }
+ }
+
+ var white: CGFloat = 0 {
+ didSet {
+ self = Color(white: white, alpha: alpha)
+ }
+ }
+
+ var alpha: CGFloat = 0
+
+ static func == (lhs: Color, rhs: Color) -> Bool {
+ lhs.red == rhs.red &&
+ lhs.green == rhs.green &&
+ lhs.blue == rhs.blue &&
+ lhs.alpha == rhs.alpha
+ }
+
+ func hash(into hasher: inout Hasher) {
+ hasher.combine(red)
+ hasher.combine(green)
+ hasher.combine(blue)
+ hasher.combine(alpha)
+ }
+
+ var uiColor: UIColor { .init(red: red, green: green, blue: blue, alpha: alpha) }
+
+ init(uiColor: UIColor) {
+ uiColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
+ uiColor.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: nil)
+ self.white = brightness
+ (self.hslSaturation, self.lightness) = hslValue
+ }
+
+ init(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
+ self.red = red
+ self.green = green
+ self.blue = blue
+ self.alpha = alpha
+ let uiColor = UIColor(red: red, green: green, blue: blue, alpha: alpha)
+ uiColor.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: nil)
+ self.white = brightness
+ (self.hslSaturation, self.lightness) = hslValue
+ }
+
+ init(white: CGFloat, alpha: CGFloat) {
+ self.init(red: white, green: white, blue: white, alpha: alpha)
+ }
+
+ init(hue: CGFloat, saturation: CGFloat, brightness: CGFloat, alpha: CGFloat) {
+ self.hue = hue
+ self.saturation = saturation
+ self.brightness = brightness
+ self.white = brightness
+ self.alpha = alpha
+ let uiColor = UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha)
+ uiColor.getRed(&red, green: &green, blue: &blue, alpha: nil)
+ (self.hslSaturation, self.lightness) = hslValue
+ }
+
+ init(hue: CGFloat, saturation: CGFloat, lightness: CGFloat, alpha: CGFloat) {
+ self.hue = hue
+ self.hslSaturation = saturation
+ self.lightness = lightness
+ self.alpha = alpha
+ (self.saturation, self.brightness) = hsbValue
+ let uiColor = UIColor(hue: hue, saturation: self.saturation, brightness: self.brightness, alpha: alpha)
+ uiColor.getRed(&red, green: &green, blue: &blue, alpha: nil)
+ self.white = brightness
+ }
+}
+
+extension Color {
+ static var brightnessThreshold: CGFloat {
+ // Accessibility enabled: conforms to WCAG 2.1 AAA
+ // Accessibility disabled: conforms to WCAG 2.1 AA
+ UIAccessibility.isDarkerSystemColorsEnabled ? 7 : 4.5
+ }
+
+ var relativeLuminanceValues: (red: CGFloat, green: CGFloat, blue: CGFloat) {
+ // https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
+ let values = [red, green, blue]
+ .map { $0 <= 0.03928 ? $0 / 12.92 : pow((($0 + 0.055) / 1.055), 2.4) }
+ return (values[0], values[1], values[2])
+ }
+
+ var relativeLuminance: CGFloat {
+ // https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
+ let (r, g, b) = relativeLuminanceValues
+ return (r * 0.2126) + (g * 0.7152) + (b * 0.0722)
+ }
+
+ func perceivedBrightness(onBackgroundColor background: Color) -> CGFloat {
+ // https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio - between 0-21
+ let a = relativeLuminance + 0.05
+ let b = background.relativeLuminance + 0.05
+ return a > b ? a / b : b / a
+ }
+
+ var isDark: Bool { perceivedBrightness(onBackgroundColor: .white) > Self.brightnessThreshold && alpha > 0.5 }
+}
+
+extension Color {
+ struct HexOptions: OptionSet {
+ let rawValue: Int
+ static let allowShorthand = Self(rawValue: 1 << 0)
+ static let forceAlpha = Self(rawValue: 1 << 1)
+ }
+
+ // if the character in `value` is repeated, `repeatedValue` is a single copy of that character. If
+ // `value` consists of two unique characters, `repeatedValue` is nil
+ // e.g. valid return values are `("AA", "A")` and `("AB", nil)`
+ private func hex(_ val: CGFloat) -> (value: String, repeatedValue: Character?) {
+ let byte = Int(val * 255) & 0xFF
+ let isRepeated = (byte & 0xF) == (byte >> 4)
+ let value = String(format: "%02X", byte)
+ return (value, isRepeated ? value.first : nil)
+ }
+
+ func hexString(with options: HexOptions = []) -> String {
+ let (r, rRep) = hex(red)
+ let (g, gRep) = hex(green)
+ let (b, bRep) = hex(blue)
+ let (a, aRep) = hex(alpha)
+ let showAlpha = options.contains(.forceAlpha) || alpha != 1
+ if options.contains(.allowShorthand),
+ let rRep = rRep, let gRep = gRep, let bRep = bRep, let aRep = aRep {
+ return "#\(rRep)\(gRep)\(bRep)\(showAlpha ? "\(aRep)" : "")"
+ } else {
+ return "#\(r)\(g)\(b)\(showAlpha ? a : "")"
+ }
+ }
+
+ var hexString: String { hexString() }
+
+ var hslValue: (saturation: CGFloat, lightness: CGFloat) {
+ let lightness = brightness - (brightness * (saturation / 2))
+ var saturation = min(lightness, 1 - lightness)
+ saturation = saturation == 0 ? 0 : (brightness - lightness) / saturation
+ return (saturation, lightness)
+ }
+
+ var hsbValue: (saturation: CGFloat, brightness: CGFloat) {
+ let brightness = hslSaturation * min(lightness, 1 - lightness) + lightness
+ let saturation = brightness == 0 ? 0 : 2 - 2 * lightness / brightness
+ return (saturation, brightness)
+ }
+
+ private func cssString(function: String, params: [String?]) -> String {
+ let filteredParams = params.compactMap { $0 }
+ return "\(function)\(filteredParams.count == 4 ? "a" : "")(\(filteredParams.joined(separator: ", ")))"
+ }
+
+ var rgbString: String {
+ cssString(function: "rgb", params: [
+ "\(Int(red * 255))",
+ "\(Int(green * 255))",
+ "\(Int(blue * 255))",
+ alpha == 1 ? nil : String(format: "%.2f", alpha)
+ ])
+ }
+
+ var hslString: String {
+ cssString(function: "hsl", params: [
+ "\(Int(hue * 360))",
+ "\(Int(hslSaturation * 100))%",
+ "\(Int(lightness * 100))%",
+ alpha == 1 ? nil : String(format: "%.2f", alpha)
+ ])
+ }
+
+ var objcString: String {
+ red == green && green == blue
+ ? String(format: "[UIColor colorWithWhite:%.3f alpha:%.2f]", white, alpha)
+ : String(format: "[UIColor colorWithRed:%.3f green:%.3f blue:%.3f alpha:%.2f]", red, green, blue, alpha)
+ }
+
+ var swiftString: String {
+ red == green && green == blue
+ ? String(format: "UIColor(white: %.3f, alpha: %.3f", white, alpha)
+ // swiftlint:disable:next color_init
+ : String(format: "UIColor(red: %.3f, green: %.3f, blue: %.3f, alpha: %.2f)", red, green, blue, alpha)
+ }
+}
+
+extension Color {
+ struct Component {
+ let keyPath: WritableKeyPath
+ let limit: CGFloat
+ let title: String
+ private let sliderTintColorForColor: (Color) -> [Color]
+
+ init(
+ keyPath: WritableKeyPath,
+ limit: CGFloat,
+ title: String,
+ sliderTintColorForColor: @escaping (Color) -> [Color]
+ ) {
+ self.keyPath = keyPath
+ self.limit = limit
+ self.title = title
+ self.sliderTintColorForColor = sliderTintColorForColor
+ }
+
+ init(
+ keyPath: WritableKeyPath,
+ limit: CGFloat,
+ title: String,
+ sliderTint: [Color]
+ ) {
+ self.keyPath = keyPath
+ self.limit = limit
+ self.title = title
+ self.sliderTintColorForColor = { _ in sliderTint }
+ }
+
+ func sliderTintColor(for color: Color) -> [Color] {
+ sliderTintColorForColor(color)
+ }
+
+ static let red: Component = .init(keyPath: \.red, limit: 255, title: "Red") { color in
+ [
+ Color(red: 0, green: color.green, blue: color.blue, alpha: 1),
+ Color(red: 1, green: color.green, blue: color.blue, alpha: 1)
+ ]
+ }
+
+ static let green: Component = .init(keyPath: \.green, limit: 255, title: "Green") { color in
+ [
+ Color(red: color.red, green: 0, blue: color.blue, alpha: 1),
+ Color(red: color.red, green: 1, blue: color.blue, alpha: 1)
+ ]
+ }
+
+ static let blue: Component = .init(keyPath: \.blue, limit: 255, title: "Blue") { color in
+ [
+ Color(red: color.red, green: color.green, blue: 0, alpha: 1),
+ Color(red: color.red, green: color.green, blue: 1, alpha: 1)
+ ]
+ }
+
+ static let hue: Component = .init(keyPath: \.hue, limit: 360, title: "Hue") { color in
+ Array(0...8).map { Color(hue: CGFloat($0) * 45 / 360, saturation: color.saturation, brightness: color.brightness, alpha: 1) }
+ }
+
+ static let saturation: Component = .init(keyPath: \.saturation, limit: 100, title: "Satur.") { color in
+ [
+ .white,
+ Color(hue: color.hue, saturation: 1, brightness: color.brightness, alpha: 1)
+ ]
+ }
+
+ static let brightness: Component = .init(keyPath: \.brightness, limit: 100, title: "Bright") { color in
+ [
+ .black,
+ Color(hue: color.hue, saturation: color.saturation, brightness: 1, alpha: 1)
+ ]
+ }
+
+ static let hslSaturation: Component = .init(keyPath: \.hslSaturation, limit: 100, title: "Satur.") { color in
+ [
+ Color(hue: color.hue, saturation: 0, lightness: color.lightness, alpha: 1),
+ Color(hue: color.hue, saturation: 1, lightness: color.lightness, alpha: 1)
+ ]
+ }
+
+ static let lightness: Component = .init(keyPath: \.lightness, limit: 100, title: "Light") { color in
+ [
+ .black,
+ Color(hue: color.hue, saturation: color.hslSaturation, lightness: 0.5, alpha: 1),
+ .white
+ ]
+ }
+
+ static let white: Component = .init(keyPath: \.white, limit: 255, title: "White") { _ in
+ [
+ .black,
+ .white
+ ]
+ }
+
+ static let alpha: Component = .init(keyPath: \.alpha, limit: 100, title: "Alpha") { color in
+ [
+ Color(red: color.red, green: color.green, blue: color.blue, alpha: 0),
+ Color(red: color.red, green: color.green, blue: color.blue, alpha: 1)
+ ]
+ }
+ }
+}
diff --git a/Tweaks/Alderis/Alderis/ColorPickerAccessibilityViewController.swift b/Tweaks/Alderis/Alderis/ColorPickerAccessibilityViewController.swift
new file mode 100644
index 0000000..faf1065
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/ColorPickerAccessibilityViewController.swift
@@ -0,0 +1,218 @@
+//
+// ColorPickerAccessibilityViewController.swift
+// Alderis
+//
+// Created by Adam Demasi on 8/5/2022.
+// Copyright © 2022 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+
+internal class ColorPickerAccessibilityViewController: ColorPickerTabViewController {
+
+ static let title = "Contrast Checker"
+ static let imageName = "circle.righthalf.fill"
+
+ private static let percentFormatter: NumberFormatter = {
+ let formatter = NumberFormatter()
+ formatter.numberStyle = .percent
+ return formatter
+ }()
+
+ private var backgroundMode: AccessibilityContrastSelector.Mode = .white {
+ didSet { colorDidChange() }
+ }
+ private var foregroundMode: AccessibilityContrastSelector.Mode = .color {
+ didSet { colorDidChange() }
+ }
+
+ private var scrollView: UIScrollView!
+
+ private var demoContainerView: UIView!
+ private var demoLabels: [UIView]!
+
+ private var contrastStackView: UIStackView!
+ private var contrastRatioLabel: UILabel!
+ private var aaComplianceLabel: AccessibilityComplianceLabel!
+ private var aaaComplianceLabel: AccessibilityComplianceLabel!
+
+ private var backgroundSelector: AccessibilityContrastSelector!
+ private var foregroundSelector: AccessibilityContrastSelector!
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ // Catalyst in iPad UI mode scales all UI down to 77%, so we cancel out the scaling (as well as
+ // we possibly can, given scaling down is throwing away quality) for the demo labels.
+ let scaleFactor: CGFloat = isCatalystPad ? 1 / 0.77 : 1
+
+ let demoTitleLabel = UILabel()
+ demoTitleLabel.translatesAutoresizingMaskIntoConstraints = false
+ demoTitleLabel.font = .systemFont(ofSize: 18 * scaleFactor, weight: .semibold)
+ demoTitleLabel.text = "Size 18 • Contrast Checker"
+
+ let demoImageView = UIImageView(image: Assets.systemImage(named: "sparkles", font: demoTitleLabel.font, scale: .small))
+ demoImageView.translatesAutoresizingMaskIntoConstraints = false
+
+ let titleStackView = UIStackView(arrangedSubviews: [demoImageView, demoTitleLabel])
+ titleStackView.translatesAutoresizingMaskIntoConstraints = false
+ titleStackView.spacing = UIFloat(6)
+ titleStackView.alignment = .center
+
+ let demoSubtitleLabel = UILabel()
+ demoSubtitleLabel.translatesAutoresizingMaskIntoConstraints = false
+ demoSubtitleLabel.font = .systemFont(ofSize: 14 * scaleFactor, weight: .medium)
+ demoSubtitleLabel.text = "Size 14 • Contrast ratios are a measure of how easily text and images can be read, especially by people with lower vision."
+ demoSubtitleLabel.numberOfLines = 0
+
+ let demoTextLabel = TextViewLabel()
+ demoTextLabel.translatesAutoresizingMaskIntoConstraints = false
+ demoTextLabel.linkTextAttributes = [
+ .underlineStyle: NSUnderlineStyle.single.rawValue
+ ]
+ let explainerText = "Size 12 • Learn more about minimum (AA) and enhanced (AAA) contrast."
+ let attributedString = NSMutableAttributedString(string: explainerText,
+ attributes: [
+ .font: UIFont.systemFont(ofSize: 12 * scaleFactor, weight: .regular)
+ ])
+ attributedString.addAttribute(.link,
+ value: URL(string: "https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum")!,
+ range: (attributedString.string as NSString).range(of: "minimum (AA)"))
+ attributedString.addAttribute(.link,
+ value: URL(string: "https://www.w3.org/WAI/WCAG21/Understanding/contrast-enhanced")!,
+ range: (attributedString.string as NSString).range(of: "enhanced (AAA)"))
+ demoTextLabel.attributedText = attributedString
+
+ demoLabels = [demoTitleLabel, demoSubtitleLabel, demoTextLabel]
+
+ let demoStackView = UIStackView(arrangedSubviews: [titleStackView, demoSubtitleLabel, demoTextLabel])
+ demoStackView.translatesAutoresizingMaskIntoConstraints = false
+ demoStackView.axis = .vertical
+ demoStackView.alignment = .leading
+ demoStackView.spacing = UIFloat(8)
+
+ demoContainerView = UIView()
+ demoContainerView.translatesAutoresizingMaskIntoConstraints = false
+ demoContainerView.layer.cornerRadius = 12
+ if #available(iOS 13, *) {
+ demoContainerView.layer.cornerCurve = .continuous
+ }
+ demoContainerView.addSubview(demoStackView)
+
+ contrastRatioLabel = UILabel()
+ contrastRatioLabel.translatesAutoresizingMaskIntoConstraints = false
+ contrastRatioLabel.font = .systemFont(ofSize: UIFloat(16), weight: .medium)
+
+ aaComplianceLabel = AccessibilityComplianceLabel(text: "AA")
+ aaaComplianceLabel = AccessibilityComplianceLabel(text: "AAA")
+
+ let complianceStackView = UIStackView(arrangedSubviews: [UIView(), aaComplianceLabel, aaaComplianceLabel])
+ complianceStackView.translatesAutoresizingMaskIntoConstraints = false
+ complianceStackView.spacing = UIFloat(12)
+
+ contrastStackView = UIStackView(arrangedSubviews: [contrastRatioLabel, complianceStackView])
+ contrastStackView.translatesAutoresizingMaskIntoConstraints = false
+ contrastStackView.spacing = UIFloat(8)
+
+ backgroundSelector = AccessibilityContrastSelector(text: "Background", value: backgroundMode)
+ backgroundSelector.handleChange = { self.backgroundMode = $0 }
+
+ foregroundSelector = AccessibilityContrastSelector(text: "Foreground", value: foregroundMode)
+ foregroundSelector.handleChange = { self.foregroundMode = $0 }
+
+ scrollView = UIScrollView()
+ scrollView.translatesAutoresizingMaskIntoConstraints = false
+ scrollView.alwaysBounceVertical = false
+ view.addSubview(scrollView)
+
+ let rootStackView = UIStackView(arrangedSubviews: [demoContainerView, UIView(), contrastStackView, backgroundSelector, foregroundSelector, UIView()])
+ rootStackView.translatesAutoresizingMaskIntoConstraints = false
+ rootStackView.axis = .vertical
+ rootStackView.alignment = .fill
+ rootStackView.distribution = .fill
+ rootStackView.spacing = UIFloat(10)
+ scrollView.addSubview(rootStackView)
+
+ NSLayoutConstraint.activate([
+ scrollView.topAnchor.constraint(equalTo: view.topAnchor),
+ scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+ scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+
+ rootStackView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor, constant: UIFloat(15)),
+ rootStackView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor, constant: UIFloat(-15)),
+ rootStackView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor, constant: UIFloat(15)),
+ rootStackView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor, constant: UIFloat(-15)),
+ rootStackView.heightAnchor.constraint(equalTo: scrollView.contentLayoutGuide.heightAnchor, constant: UIFloat(-30)),
+ scrollView.contentLayoutGuide.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
+ scrollView.contentLayoutGuide.heightAnchor.constraint(greaterThanOrEqualTo: scrollView.heightAnchor),
+
+ demoStackView.topAnchor.constraint(equalTo: demoContainerView.topAnchor, constant: UIFloat(16)),
+ demoStackView.bottomAnchor.constraint(equalTo: demoContainerView.bottomAnchor, constant: UIFloat(-17)),
+ demoStackView.leadingAnchor.constraint(equalTo: demoContainerView.leadingAnchor, constant: UIFloat(20)),
+ demoStackView.trailingAnchor.constraint(equalTo: demoContainerView.trailingAnchor, constant: UIFloat(-20)),
+
+ contrastStackView.heightAnchor.constraint(greaterThanOrEqualTo: backgroundSelector.heightAnchor),
+ contrastStackView.heightAnchor.constraint(greaterThanOrEqualTo: foregroundSelector.heightAnchor)
+ ])
+
+ colorDidChange()
+ }
+
+ override func viewDidAppear(_ animated: Bool) {
+ super.viewDidAppear(animated)
+
+ scrollView.flashScrollIndicators()
+ }
+
+ override func viewWillLayoutSubviews() {
+ super.viewWillLayoutSubviews()
+
+ contrastStackView.axis = view.frame.size.width > UIFloat(300) ? .horizontal : .vertical
+ }
+
+ override func colorDidChange() {
+ let backgroundColor = backgroundMode.color(withColor: color)
+ let foregroundColor = foregroundMode.color(withColor: color)
+
+ if backgroundColor == foregroundColor {
+ // Change one or the other to not be identical
+ if backgroundMode == foregroundMode {
+ switch backgroundMode {
+ case .black, .white: foregroundMode = .color
+ default: foregroundMode = .white
+ }
+ } else {
+ if foregroundMode == .color {
+ backgroundMode = backgroundColor == .white ? .black : .white
+ } else if backgroundMode == .color {
+ foregroundMode = foregroundColor == .white ? .black : .white
+ }
+ }
+ return
+ }
+
+ backgroundSelector.value = backgroundMode
+ foregroundSelector.value = foregroundMode
+
+ demoContainerView.backgroundColor = backgroundColor.uiColor
+ demoContainerView.tintColor = foregroundColor.uiColor
+ for label in demoLabels {
+ if let label = label as? UILabel {
+ label.textColor = foregroundColor.uiColor
+ } else if let label = label as? UITextView,
+ let attributedString = label.attributedText.mutableCopy() as? NSMutableAttributedString {
+ attributedString.addAttribute(.foregroundColor,
+ value: foregroundColor.uiColor,
+ range: NSRange(location: 0, length: attributedString.string.count))
+ label.attributedText = attributedString
+ }
+ }
+
+ let contrastRatio = foregroundColor.perceivedBrightness(onBackgroundColor: backgroundColor)
+ contrastRatioLabel.text = "Contrast: \(String(format: "%.2f", contrastRatio)) (\(Self.percentFormatter.string(for: contrastRatio / 21)!))"
+ aaComplianceLabel.isCompliant = contrastRatio > 4.5
+ aaaComplianceLabel.isCompliant = contrastRatio > 7
+ }
+
+}
diff --git a/Tweaks/Alderis/Alderis/ColorPickerConfiguration.swift b/Tweaks/Alderis/Alderis/ColorPickerConfiguration.swift
new file mode 100644
index 0000000..ce23ef6
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/ColorPickerConfiguration.swift
@@ -0,0 +1,91 @@
+//
+// ColorPickerConfiguration.swift
+// Alderis
+//
+// Created by Adam Demasi on 10/5/20.
+// Copyright © 2020 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+
+/// An enumeration of the tabs `ColorPickerViewController` features. Use these enumeration values to
+/// set tab-related settings on `ColorPickerConfiguration`.
+@objc(HBColorPickerTab)
+public enum ColorPickerTab: Int, CaseIterable {
+ /// Tab 1: A grid of 9 variations of 11 colours, and a grayscale ramp. The first and default tab.
+ case swatch = 0
+
+ /// Tab 2: A color wheel displaying every possible hue and saturation combination. The user can
+ /// additionally adjust the brightness of the colour using a slider.
+ case map = 1
+
+ /// Tab 3: A set of sliders for red, green, and blue color values, which can be switched to hue,
+ /// saturation, and brightness. The user can additionally copy or enter a color value expressed
+ /// using a CSS-style hexadecimal string, and adjust alpha transparency.
+ case sliders = 2
+
+ /// Tab 4: A tab that allows the user to test various configurations of the color, and its
+ /// conformance to WCAG color contrast.
+ case accessibility = 3
+}
+
+/// ColorPickerConfiguration is used to configure an instance of `ColorPickerViewController`.
+@objc(HBColorPickerConfiguration)
+open class ColorPickerConfiguration: NSObject {
+
+ /// Initialise a configuration object with the required color property configured.
+ @objc public init(color: UIColor) {
+ self.color = color
+ super.init()
+ }
+
+ /// The initial color to use when launching the color picker. Required. If you don’t have a value
+ /// set yet, provide a sensible default.
+ @objc open var color: UIColor
+
+ /// Whether to allow the user to set an alpha transparency value on the color. This controls the
+ /// visibility of an Alpha slider on the Sliders tab. When set to `false`, alpha values provided
+ /// via the `color` property, or by the user when entering a hexadecimal value on the Sliders tab,
+ /// will be discarded.
+ @objc open var supportsAlpha = true
+
+ /// The title to display at the top of the popup. If set to `nil`, no title will be displayed. The
+ /// default is `nil`.
+ @objc open var title: String?
+
+ /// The initial tab to select when the color picker is presented. The default is
+ /// `ColorPickerTab.swatch`.
+ ///
+ /// This value must be found in `visibleTabs`.
+ ///
+ /// - see: `visibleTabs`
+ @objc open var initialTab = ColorPickerTab.swatch
+
+ /// The tabs the user can select and switch between at the top of the window, if tabs are enabled
+ /// by `showTabs`.
+ ///
+ /// - see: `initialTab`
+ @nonobjc open var visibleTabs: [ColorPickerTab] = [.swatch, .map, .sliders, .accessibility]
+
+ /// Maps `visibleTabs` to Objective-C due to Swift limitations. This is an implementation detail.
+ /// Ignore this and use `visibleTabs` per usual.
+ @objc(visibleTabs)
+ open var _visibleTabsObjC: [ColorPickerTab.RawValue] {
+ get { visibleTabs.map(\.rawValue) }
+ set { visibleTabs = newValue.map { ColorPickerTab(rawValue: $0)! } }
+ }
+
+ /// Whether to display the tab selection at the top of the popup. The default is `true`. When set
+ /// to `false`, the user will only be able to access the tab specified in initialTab.
+ @objc open var showTabs = true
+
+ /// When the Smart Invert accessibility feature is enabled, Alderis instructs the system to not
+ /// invert most of its user interface. This ensures the user can make a more accurate color
+ /// selection. If this behavior is not desired, you can disable it here.
+ @objc open var overrideSmartInvert = true
+
+ /// Whether the user can end a drag interaction by dropping on the color picker window, allowing
+ /// them to drag a color from a supporting app. The default is `true`.
+ @objc open var isDropInteractionEnabled = true
+
+}
diff --git a/Tweaks/Alderis/Alderis/ColorPickerDelegate.swift b/Tweaks/Alderis/Alderis/ColorPickerDelegate.swift
new file mode 100644
index 0000000..e989b7e
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/ColorPickerDelegate.swift
@@ -0,0 +1,51 @@
+//
+// ColorPickerDelegate.swift
+// Alderis
+//
+// Created by Adam Demasi on 16/3/20.
+// Copyright © 2020 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+
+/// Use `ColorPickerDelegate` to handle the user’s response to `ColorPickerViewController`.
+@objc(HBColorPickerDelegate)
+public protocol ColorPickerDelegate: NSObjectProtocol {
+
+ /// Informs the delegate that the user has selected a color in the color picker. Optional.
+ ///
+ /// Use this to update your user interface with the new color value, if suitable to your use case.
+ ///
+ /// You should, at minimum, implement either this method or `colorPicker(_:didAccept:)`. If you
+ /// don’t intend to implement this method, it is expected that you implement
+ /// `colorPicker(_:didAccept:)`. If you implement this method and the user selects Cancel, this
+ /// method will be called with the initial color passed in via `ColorPickerConfiguration.color` to
+ /// undo the selection.
+ ///
+ /// - parameter colorPicker: The `ColorPickerViewController` instance that triggered the action.
+ /// - parameter color: The `UIColor` selection the user made.
+ /// - see: `colorPicker(_:didAccept:)`
+ @objc(colorPicker:didSelectColor:)
+ optional func colorPicker(_ colorPicker: ColorPickerViewController, didSelect color: UIColor)
+
+ /// Informs the delegate that the user has dismissed the color picker with a positive response,
+ /// having selected the selected color. Optional.
+ ///
+ /// You should, at minimum, implement either this method or `colorPicker(_:didSelect:)`.
+ ///
+ /// - parameter colorPicker: The `ColorPickerViewController` instance that triggered the action.
+ /// - parameter color: The `UIColor` selection the user made.
+ /// - see: `colorPicker(_:didSelect:)`
+ @objc(colorPicker:didAcceptColor:)
+ optional func colorPicker(_ colorPicker: ColorPickerViewController, didAccept color: UIColor)
+
+ /// Informs the delegate that the user has dismissed the color picker with a negative response.
+ /// Optional.
+ ///
+ /// You usually do not need to handle this condition.
+ ///
+ /// - parameter colorPicker: The `ColorPickerViewController` instance that triggered the action.
+ @objc(colorPickerDidCancel:)
+ optional func colorPickerDidCancel(_ colorPicker: ColorPickerViewController)
+
+}
diff --git a/Tweaks/Alderis/Alderis/ColorPickerInnerViewController.swift b/Tweaks/Alderis/Alderis/ColorPickerInnerViewController.swift
new file mode 100644
index 0000000..f555b50
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/ColorPickerInnerViewController.swift
@@ -0,0 +1,445 @@
+//
+// ColorPickerInnerViewController.swift
+// Alderis
+//
+// Created by Adam Demasi on 12/3/20.
+// Copyright © 2020 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+
+internal extension ColorPickerTab {
+ var tabClass: ColorPickerTabViewController.Type {
+ switch self {
+ case .swatch: return ColorPickerSwatchViewController.self
+ case .map: return ColorPickerMapViewController.self
+ case .sliders: return ColorPickerSlidersViewController.self
+ case .accessibility: return ColorPickerAccessibilityViewController.self
+ }
+ }
+
+ var index: Int { Self.allCases.firstIndex(of: self)! }
+}
+
+internal class ColorPickerInnerViewController: UIViewController {
+
+ weak var delegate: ColorPickerDelegate?
+ let configuration: ColorPickerConfiguration
+ var color: Color
+
+ var tab: ColorPickerTab {
+ get { configuration.visibleTabs[currentTab] }
+ set { currentTab = configuration.visibleTabs.firstIndex(of: newValue) ?? 0 }
+ }
+
+ var compatibilityMode = false
+
+ private var colorPicker: ColorPickerViewController {
+ // swiftlint:disable:next force_cast
+ parent as! ColorPickerViewController
+ }
+
+ init(delegate: ColorPickerDelegate?, configuration: ColorPickerConfiguration) {
+ self.delegate = delegate
+ self.configuration = configuration
+ color = Color(uiColor: configuration.color)
+ super.init(nibName: nil, bundle: nil)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private var currentTab = 0 {
+ didSet { tabDidChange(oldValue: oldValue) }
+ }
+
+ func setColor(_ color: Color, withSource source: ColorPickerTabViewControllerBase? = nil) {
+ self.color = color
+ colorDidChange(withSource: source)
+ }
+
+ private var pageViewController: UIPageViewController!
+ private var tabs = [ColorPickerTabViewController]()
+ private var tabsView: UISegmentedControl!
+ private var titleLabel: UILabel!
+ private var cancelButton: DialogButton?
+ private var saveButton: DialogButton?
+ private var tabsBackgroundView: UIView!
+ private var buttonsBackgroundView: UIView?
+ private var heightConstraint: NSLayoutConstraint!
+ private var backgroundView: UIView!
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ for tabType in configuration.visibleTabs {
+ let tab = tabType.tabClass.init(tabDelegate: self, configuration: configuration, color: color)
+ // Force the view to be initialised
+ tab.loadViewIfNeeded()
+ tabs.append(tab)
+ }
+
+ if configuration.isDropInteractionEnabled {
+ view.addInteraction(UIDropInteraction(delegate: self))
+ }
+
+ backgroundView = UIView()
+ backgroundView.translatesAutoresizingMaskIntoConstraints = false
+ backgroundView.accessibilityIgnoresInvertColors = configuration.overrideSmartInvert
+ view.addSubview(backgroundView)
+
+ let tabsCheckerboardView = UIView()
+ tabsCheckerboardView.translatesAutoresizingMaskIntoConstraints = false
+ tabsCheckerboardView.accessibilityIgnoresInvertColors = configuration.overrideSmartInvert
+ tabsCheckerboardView.backgroundColor = Assets.checkerboardPatternColor
+ view.addSubview(tabsCheckerboardView)
+
+ tabsBackgroundView = UIView()
+ tabsBackgroundView.translatesAutoresizingMaskIntoConstraints = false
+ tabsBackgroundView.accessibilityIgnoresInvertColors = configuration.overrideSmartInvert
+ view.addSubview(tabsBackgroundView)
+
+ let topSeparatorView = SeparatorView(direction: .horizontal)
+ topSeparatorView.translatesAutoresizingMaskIntoConstraints = false
+ tabsBackgroundView.addSubview(topSeparatorView)
+
+ let titleView = UIView()
+ titleView.translatesAutoresizingMaskIntoConstraints = false
+ titleView.isHidden = configuration.title == nil || configuration.title!.isEmpty
+
+ titleLabel = UILabel()
+ titleLabel.translatesAutoresizingMaskIntoConstraints = false
+ titleLabel.textAlignment = .center
+ titleLabel.font = .systemFont(ofSize: UIFloat(17), weight: .semibold)
+ titleLabel.text = configuration.title
+ titleView.addSubview(titleLabel)
+
+ let tabsContainerView = UIView()
+ tabsContainerView.translatesAutoresizingMaskIntoConstraints = false
+ tabsContainerView.isHidden = !configuration.showTabs
+
+ tabsView = UISegmentedControl()
+ tabsView.translatesAutoresizingMaskIntoConstraints = false
+ tabsView.accessibilityIgnoresInvertColors = configuration.overrideSmartInvert
+ tabsView.addTarget(self, action: #selector(segmentControlChanged(_:)), for: .valueChanged)
+ tabsContainerView.addSubview(tabsView)
+
+ if #available(iOS 13, *) {
+ tabsView.selectedSegmentTintColor = UIColor.white.withAlphaComponent(0.35)
+ if isCatalystMac {
+ tabsView.setTitleTextAttributes([ .foregroundColor: Assets.macTabBarSelectionColor ], for: .highlighted)
+ tabsView.setTitleTextAttributes([ .foregroundColor: Assets.macTabBarSelectionColor ], for: .selected)
+ }
+ }
+
+ for (i, tab) in tabs.enumerated() {
+ let tabClass = type(of: tab)
+ #if swift(>=5.3)
+ if #available(iOS 14, *) {
+ tabsView.insertSegment(action: UIAction(title: tabClass.title,
+ image: tabClass.image,
+ handler: { _ in }),
+ at: i,
+ animated: false)
+ } else {
+ tabsView.insertSegment(with: tabClass.image, at: i, animated: false)
+ }
+ #else
+ tabsView.insertSegment(with: tabClass.image, at: i, animated: false)
+ #endif
+ }
+
+ NSLayoutConstraint.activate([
+ tabsView.centerXAnchor.constraint(equalTo: tabsContainerView.centerXAnchor),
+ tabsView.centerYAnchor.constraint(equalTo: tabsContainerView.centerYAnchor),
+ tabsView.leadingAnchor.constraint(greaterThanOrEqualTo: tabsContainerView.leadingAnchor, constant: 4),
+ tabsView.trailingAnchor.constraint(lessThanOrEqualTo: tabsContainerView.trailingAnchor, constant: -4)
+ ])
+
+ if #available(iOS 13, *) {
+ } else {
+ NSLayoutConstraint.activate([
+ tabsView.heightAnchor.constraint(equalToConstant: 32)
+ ])
+ for i in 0.. Bool {
+ return session.items.count == 1 && session.canLoadObjects(ofClass: UIColor.self)
+ }
+
+ /// :nodoc:
+ public func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal {
+ return UIDropProposal(operation: .copy)
+ }
+
+ /// :nodoc:
+ public func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) {
+ session.loadObjects(ofClass: UIColor.self) { items in
+ if let color = items.first as? UIColor {
+ self.setColor(Color(uiColor: color), withSource: nil)
+ }
+ }
+ }
+
+}
+
+extension ColorPickerInnerViewController: UIPopoverPresentationControllerDelegate {
+
+ /// :nodoc:
+ public func presentationControllerWillDismiss(_ presentationController: UIPresentationController) {
+ saveTapped()
+ }
+
+}
diff --git a/Tweaks/Alderis/Alderis/ColorPickerMapSlider.swift b/Tweaks/Alderis/Alderis/ColorPickerMapSlider.swift
new file mode 100644
index 0000000..3da68ff
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/ColorPickerMapSlider.swift
@@ -0,0 +1,49 @@
+//
+// ColorPickerMapSlider.swift
+// Alderis
+//
+// Created by Kabir Oberai on 23/03/20.
+// Copyright © 2020 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+
+internal class ColorPickerMapSlider: ColorPickerComponentSlider {
+
+ init(minImageName: String, maxImageName: String, component: Color.Component, overrideSmartInvert: Bool) {
+ super.init(component: component, overrideSmartInvert: overrideSmartInvert)
+
+ stackView.alignment = .center
+ stackView.spacing = UIFloat(13)
+
+ let leftImageView = UIImageView(image: Assets.systemImage(named: minImageName))
+ leftImageView.translatesAutoresizingMaskIntoConstraints = false
+ leftImageView.contentMode = .center
+ leftImageView.tintColor = Assets.secondaryLabelColor
+ stackView.insertArrangedSubview(leftImageView, at: 0)
+
+ let rightImageView = UIImageView(image: Assets.systemImage(named: maxImageName))
+ rightImageView.translatesAutoresizingMaskIntoConstraints = false
+ rightImageView.contentMode = .center
+ rightImageView.tintColor = Assets.secondaryLabelColor
+ stackView.addArrangedSubview(rightImageView)
+
+ if #available(iOS 13, *) {
+ let symbolConfig = UIImage.SymbolConfiguration(font: .systemFont(ofSize: UIFloat(18), weight: .medium), scale: .medium)
+ leftImageView.preferredSymbolConfiguration = symbolConfig
+ rightImageView.preferredSymbolConfiguration = symbolConfig
+ }
+
+ NSLayoutConstraint.activate([
+ leftImageView.widthAnchor.constraint(equalToConstant: UIFloat(22)),
+ leftImageView.widthAnchor.constraint(equalTo: rightImageView.widthAnchor),
+ leftImageView.heightAnchor.constraint(equalTo: leftImageView.widthAnchor),
+ rightImageView.heightAnchor.constraint(equalTo: rightImageView.widthAnchor)
+ ])
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+}
diff --git a/Tweaks/Alderis/Alderis/ColorPickerMapViewController.swift b/Tweaks/Alderis/Alderis/ColorPickerMapViewController.swift
new file mode 100644
index 0000000..725e692
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/ColorPickerMapViewController.swift
@@ -0,0 +1,79 @@
+//
+// ColorPickerMapViewController.swift
+// Alderis
+//
+// Created by Adam Demasi on 14/3/20.
+// Copyright © 2020 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+
+internal class ColorPickerMapViewController: ColorPickerTabViewController {
+
+ static let title = "Color Wheel"
+ static let imageName = "slider.horizontal.below.rectangle"
+
+ private var wheelView: ColorPickerWheelView!
+ private var sliders = [ColorPickerMapSlider]()
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ wheelView = ColorPickerWheelView(color: color)
+ wheelView.translatesAutoresizingMaskIntoConstraints = false
+ wheelView.accessibilityIgnoresInvertColors = configuration.overrideSmartInvert
+ wheelView.delegate = self
+ view.addSubview(wheelView)
+
+ sliders = [
+ ColorPickerMapSlider(
+ minImageName: "sun.min", maxImageName: "sun.max", component: .brightness,
+ overrideSmartInvert: configuration.overrideSmartInvert
+ )
+ ]
+
+ sliders.forEach {
+ $0.translatesAutoresizingMaskIntoConstraints = false
+ $0.addTarget(self, action: #selector(sliderChanged(_:)), for: .valueChanged)
+ }
+
+ let mainStackView = UIStackView(arrangedSubviews: [wheelView] + sliders)
+ mainStackView.translatesAutoresizingMaskIntoConstraints = false
+ mainStackView.axis = .vertical
+ mainStackView.alignment = .fill
+ mainStackView.distribution = .fill
+ view.addSubview(mainStackView)
+
+ NSLayoutConstraint.activate([
+ mainStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: UIFloat(15)),
+ mainStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: UIFloat(-15)),
+ mainStackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0),
+ mainStackView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: UIFloat(-10))
+ ])
+ }
+
+ override func viewDidLayoutSubviews() {
+ super.viewDidLayoutSubviews()
+ colorDidChange()
+ }
+
+ @objc private func sliderChanged(_ slider: ColorPickerMapSlider) {
+ var color = self.color
+ slider.apply(to: &color)
+ self.setColor(color)
+ }
+
+ override func colorDidChange() {
+ wheelView.color = color
+ sliders.forEach { $0.setColor(color) }
+ }
+
+}
+
+extension ColorPickerMapViewController: ColorPickerWheelViewDelegate {
+
+ func colorPickerWheelView(didSelectColor color: Color) {
+ self.setColor(color)
+ }
+
+}
diff --git a/Tweaks/Alderis/Alderis/ColorPickerNumericSlider.swift b/Tweaks/Alderis/Alderis/ColorPickerNumericSlider.swift
new file mode 100644
index 0000000..784f0c8
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/ColorPickerNumericSlider.swift
@@ -0,0 +1,83 @@
+//
+// ColorPickerNumericSlider.swift
+// Alderis
+//
+// Created by Kabir Oberai on 28/03/20.
+// Copyright © 2020 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+
+internal class ColorPickerNumericSlider: ColorPickerComponentSlider {
+
+ private var textField: UITextField!
+
+ override init(component: Color.Component, overrideSmartInvert: Bool) {
+ super.init(component: component, overrideSmartInvert: overrideSmartInvert)
+
+ stackView.alignment = .fill
+ stackView.spacing = UIFloat(8)
+
+ let label = UILabel()
+ label.translatesAutoresizingMaskIntoConstraints = false
+ label.font = UIFont.systemFont(ofSize: UIFloat(16), weight: .medium)
+ label.text = component.title
+ stackView.insertArrangedSubview(label, at: 0)
+
+ textField = UITextField()
+ textField.translatesAutoresizingMaskIntoConstraints = false
+ textField.delegate = self
+ textField.returnKeyType = .next
+ textField.keyboardType = .numberPad
+ textField.autocapitalizationType = .none
+ textField.autocorrectionType = .no
+ textField.spellCheckingType = .no
+ textField.textAlignment = .right
+ textField.font = Assets.niceMonospaceDigitFont(ofSize: UIFloat(16))
+ stackView.addArrangedSubview(textField)
+
+ NSLayoutConstraint.activate([
+ label.widthAnchor.constraint(equalToConstant: UIFloat(50)),
+ textField.widthAnchor.constraint(equalToConstant: UIFloat(35))
+ ])
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func setColor(_ color: Color) {
+ super.setColor(color)
+ textField.text = "\(Int((color[keyPath: component.keyPath] * component.limit).rounded()))"
+ }
+
+}
+
+extension ColorPickerNumericSlider: UITextFieldDelegate {
+
+ func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
+ let newString = textField.text!.replacingCharacters(in: Range(range, in: textField.text!)!, with: string)
+ guard !newString.isEmpty else { return true }
+
+ // Numeric only, 0-limit
+ let badCharacterSet = CharacterSet(charactersIn: "0123456789").inverted
+ guard newString.rangeOfCharacter(from: badCharacterSet) == nil else {
+ beep()
+ return false
+ }
+ let limit = component.limit
+ guard let value = Int(newString), 0...limit ~= CGFloat(value) else {
+ beep()
+ return false
+ }
+
+ // Run this after the input is fully processed by enqueuing it onto the run loop
+ OperationQueue.main.addOperation {
+ self.value = CGFloat(value) / limit
+ self.sliderChanged()
+ }
+
+ return true
+ }
+
+}
diff --git a/Tweaks/Alderis/Alderis/ColorPickerSlider.swift b/Tweaks/Alderis/Alderis/ColorPickerSlider.swift
new file mode 100644
index 0000000..45872b6
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/ColorPickerSlider.swift
@@ -0,0 +1,206 @@
+//
+// ColorPickerSlider.swift
+// Alderis
+//
+// Created by Kabir Oberai on 28/03/20.
+// Copyright © 2020 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+
+internal class ColorPickerSliderBase: UIControl {
+
+ var overrideSmartInvert: Bool {
+ didSet {
+ slider.accessibilityIgnoresInvertColors = overrideSmartInvert
+ }
+ }
+
+ let stackView: UIStackView
+ let slider: ColorSlider
+
+ var value: CGFloat {
+ get { CGFloat(slider.value) }
+ set { slider.value = Float(newValue) }
+ }
+
+ init(overrideSmartInvert: Bool) {
+ self.overrideSmartInvert = overrideSmartInvert
+
+ slider = ColorSlider()
+ slider.translatesAutoresizingMaskIntoConstraints = false
+ slider.accessibilityIgnoresInvertColors = overrideSmartInvert
+
+ stackView = UIStackView(arrangedSubviews: [slider])
+ stackView.translatesAutoresizingMaskIntoConstraints = false
+ stackView.axis = .horizontal
+ stackView.distribution = .fill
+
+ super.init(frame: .zero)
+
+ slider.addTarget(self, action: #selector(sliderChanged), for: .valueChanged)
+ addSubview(stackView)
+
+ NSLayoutConstraint.activate([
+ stackView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
+ stackView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
+ stackView.topAnchor.constraint(equalTo: self.topAnchor),
+ stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor)
+ ])
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ @objc internal func sliderChanged() {
+ sendActions(for: .valueChanged)
+ }
+
+}
+
+internal protocol ColorPickerSliderProtocol: ColorPickerSliderBase {
+ func setColor(_ color: Color)
+ func apply(to color: inout Color)
+}
+
+internal typealias ColorPickerSlider = ColorPickerSliderBase & ColorPickerSliderProtocol
+
+internal class ColorPickerComponentSlider: ColorPickerSlider {
+
+ let component: Color.Component
+
+ init(component: Color.Component, overrideSmartInvert: Bool) {
+ self.component = component
+ super.init(overrideSmartInvert: overrideSmartInvert)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ func setColor(_ color: Color) {
+ value = color[keyPath: component.keyPath]
+ slider.color = color.uiColor
+ slider.gradientColors = component.sliderTintColor(for: color).map(\.uiColor)
+ }
+
+ func apply(to color: inout Color) {
+ color[keyPath: component.keyPath] = value
+ }
+
+}
+
+internal class ColorSlider: UISlider {
+ private let thumbImage = UIGraphicsImageRenderer(size: CGSize(width: 26, height: 26)).image { _ in }
+
+ var gradientColors = [UIColor]() {
+ didSet { gradientView.gradientLayer.colors = gradientColors.map(\.cgColor) }
+ }
+
+ var color: UIColor? {
+ get { selectionView?.color }
+ set { selectionView?.color = newValue }
+ }
+
+ private var checkerboardView: UIView!
+ private var gradientView: GradientView!
+
+ private var selectionView: ColorWell?
+ private var selectionViewXConstraint: NSLayoutConstraint?
+ private var valueObserver: NSKeyValueObservation?
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+
+ #if swift(>=5.5)
+ var useSliderTrack = !isCatalystMac
+ if #available(iOS 15, *) {
+ preferredBehavioralStyle = .pad
+ useSliderTrack = true
+ }
+ #else
+ let useSliderTrack = true
+ #endif
+ if useSliderTrack {
+ setMinimumTrackImage(UIImage(), for: .normal)
+ setMaximumTrackImage(UIImage(), for: .normal)
+ setThumbImage(thumbImage, for: .normal)
+ }
+
+ checkerboardView = UIView()
+ checkerboardView.translatesAutoresizingMaskIntoConstraints = false
+ checkerboardView.backgroundColor = Assets.checkerboardPatternColor
+ checkerboardView.clipsToBounds = true
+ if #available(iOS 13, *) {
+ checkerboardView.layer.cornerCurve = .continuous
+ }
+ insertSubview(checkerboardView, at: 0)
+
+ gradientView = GradientView()
+ gradientView.translatesAutoresizingMaskIntoConstraints = false
+ gradientView.gradientLayer.startPoint = CGPoint(x: 0, y: 0)
+ gradientView.gradientLayer.endPoint = CGPoint(x: 1, y: 0)
+ gradientView.gradientLayer.allowsGroupOpacity = false
+ checkerboardView.addSubview(gradientView)
+
+ NSLayoutConstraint.activate([
+ checkerboardView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: UIFloat(-3)),
+ checkerboardView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: UIFloat(3)),
+ checkerboardView.topAnchor.constraint(equalTo: self.topAnchor, constant: -1),
+ checkerboardView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 1),
+
+ gradientView.leadingAnchor.constraint(equalTo: checkerboardView.leadingAnchor),
+ gradientView.trailingAnchor.constraint(equalTo: checkerboardView.trailingAnchor),
+ gradientView.topAnchor.constraint(equalTo: checkerboardView.topAnchor),
+ gradientView.bottomAnchor.constraint(equalTo: checkerboardView.bottomAnchor),
+ ])
+
+ if useSliderTrack {
+ let selectionView = ColorWell()
+ selectionView.translatesAutoresizingMaskIntoConstraints = false
+ selectionView.isDragInteractionEnabled = false
+ selectionView.isDropInteractionEnabled = false
+ #if swift(>=5.3)
+ if #available(iOS 14, *) {
+ selectionView.isContextMenuInteractionEnabled = false
+ }
+ #endif
+ insertSubview(selectionView, aboveSubview: checkerboardView)
+ self.selectionView = selectionView
+
+ selectionViewXConstraint = selectionView.leadingAnchor.constraint(equalTo: checkerboardView.leadingAnchor)
+
+ // Remove minimum width constraint configured by ColorWell internally
+ let selectionWidthConstraint = selectionView.constraints.first { $0.firstAnchor == selectionView.widthAnchor }
+ selectionWidthConstraint?.isActive = false
+
+ NSLayoutConstraint.activate([
+ selectionViewXConstraint!,
+ selectionView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
+ selectionView.widthAnchor.constraint(equalToConstant: UIFloat(24))
+ ])
+
+ valueObserver = observe(\.value) { _, _ in self.valueChanged() }
+ }
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func layoutSubviews() {
+ super.layoutSubviews()
+ checkerboardView.layer.cornerRadius = checkerboardView.frame.size.height / 2
+ valueChanged()
+ }
+
+ private func valueChanged() {
+ guard let selectionView = selectionView,
+ let selectionViewXConstraint = selectionViewXConstraint else {
+ return
+ }
+ let spacing = frame.size.height - selectionView.frame.size.height
+ selectionViewXConstraint.constant = (spacing / 2) + ((frame.size.width - selectionView.frame.size.width - spacing) * CGFloat(value))
+ }
+}
diff --git a/Tweaks/Alderis/Alderis/ColorPickerSlidersViewController.swift b/Tweaks/Alderis/Alderis/ColorPickerSlidersViewController.swift
new file mode 100644
index 0000000..67d7625
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/ColorPickerSlidersViewController.swift
@@ -0,0 +1,266 @@
+//
+// ColorPickerSlidersViewController.swift
+// Alderis
+//
+// Created by Adam Demasi on 14/3/20.
+// Copyright © 2020 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+import AudioToolbox
+
+internal class ColorPickerSlidersViewController: ColorPickerTabViewController {
+
+ static let title = "Sliders"
+ static let imageName = "slider.horizontal.3"
+
+ private enum Mode: CaseIterable {
+ case rgb, hsl, hsb, white
+
+ var title: String {
+ switch self {
+ case .rgb: return "RGB"
+ case .hsl: return "HSL"
+ case .hsb: return "HSB"
+ case .white: return "White"
+ }
+ }
+
+ private var components: [Color.Component] {
+ switch self {
+ case .rgb: return [.red, .green, .blue, .alpha]
+ case .hsl: return [.hue, .hslSaturation, .lightness, .alpha]
+ case .hsb: return [.hue, .saturation, .brightness, .alpha]
+ case .white: return [.white, .alpha]
+ }
+ }
+
+ func makeSliders(overrideSmartInvert: Bool, supportsAlpha: Bool) -> [ColorPickerNumericSlider] {
+ components.compactMap { component in
+ if component.keyPath == \.alpha && !supportsAlpha {
+ return nil
+ }
+ return ColorPickerNumericSlider(component: component, overrideSmartInvert: overrideSmartInvert)
+ }
+ }
+ }
+
+ private var mode: Mode = .rgb {
+ didSet { updateMode() }
+ }
+
+ private var segmentedControl: UISegmentedControl!
+
+ private var allSliders = [Mode: [ColorPickerNumericSlider]]()
+ private var sliderStacks = [Mode: UIStackView]()
+
+ private let colorWell = ColorWell()
+
+ private var hexTextField: UITextField!
+ private var hexOptions = Color.HexOptions()
+
+ private var eggLabel: UILabel!
+ private var eggString = ""
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ let segmentedControlContainer = UIView()
+ segmentedControlContainer.translatesAutoresizingMaskIntoConstraints = false
+
+ segmentedControl = UISegmentedControl(items: Mode.allCases.map(\.title))
+ segmentedControl.translatesAutoresizingMaskIntoConstraints = false
+ segmentedControl.selectedSegmentIndex = 0
+ segmentedControl.addTarget(self, action: #selector(segmentControlChanged(_:)), for: .valueChanged)
+ segmentedControlContainer.addSubview(segmentedControl)
+
+ let topSpacerView = UIView()
+ topSpacerView.translatesAutoresizingMaskIntoConstraints = false
+
+ let mainStackView = UIStackView(arrangedSubviews: [segmentedControlContainer, topSpacerView])
+ mainStackView.translatesAutoresizingMaskIntoConstraints = false
+ mainStackView.axis = .vertical
+ mainStackView.alignment = .fill
+ mainStackView.distribution = .fill
+ mainStackView.spacing = UIFloat(6)
+ view.addSubview(mainStackView)
+
+ for mode in Mode.allCases {
+ let modeSliders = mode.makeSliders(overrideSmartInvert: configuration.overrideSmartInvert,
+ supportsAlpha: configuration.supportsAlpha)
+ for slider in modeSliders {
+ slider.addTarget(self, action: #selector(sliderChanged(_:)), for: .valueChanged)
+ }
+ allSliders[mode] = modeSliders
+
+ let sliderStackView = UIStackView(arrangedSubviews: modeSliders)
+ sliderStackView.axis = .vertical
+ sliderStackView.alignment = .fill
+ sliderStackView.distribution = .fill
+ sliderStackView.spacing = UIFloat(10)
+ sliderStacks[mode] = sliderStackView
+ mainStackView.addArrangedSubview(sliderStackView)
+ }
+
+ colorWell.accessibilityIgnoresInvertColors = configuration.overrideSmartInvert
+ colorWell.isDragInteractionEnabled = true
+ colorWell.isDropInteractionEnabled = false
+
+ hexTextField = UITextField()
+ hexTextField.translatesAutoresizingMaskIntoConstraints = false
+ hexTextField.delegate = self
+ hexTextField.textAlignment = .right
+ hexTextField.returnKeyType = .done
+ hexTextField.autocapitalizationType = .none
+ hexTextField.autocorrectionType = .no
+ hexTextField.spellCheckingType = .no
+ hexTextField.font = Assets.niceMonospaceDigitFont(ofSize: UIFloat(16))
+ hexTextField.setContentHuggingPriority(.required, for: .vertical)
+ hexTextField.setContentHuggingPriority(.defaultHigh, for: .horizontal)
+
+ eggLabel = UILabel()
+ eggLabel.translatesAutoresizingMaskIntoConstraints = false
+ eggLabel.accessibilityIgnoresInvertColors = configuration.overrideSmartInvert
+ eggLabel.font = UIFont.systemFont(ofSize: UIFloat(24), weight: .heavy)
+ eggLabel.isHidden = true
+
+ let bottomSpacerView = UIView()
+ bottomSpacerView.translatesAutoresizingMaskIntoConstraints = false
+ mainStackView.addArrangedSubview(bottomSpacerView)
+
+ let hexStackView = UIStackView(arrangedSubviews: [colorWell, eggLabel, hexTextField])
+ hexStackView.translatesAutoresizingMaskIntoConstraints = false
+ hexStackView.axis = .horizontal
+ hexStackView.alignment = .fill
+ hexStackView.distribution = .fill
+ hexStackView.spacing = UIFloat(10)
+ mainStackView.addArrangedSubview(hexStackView)
+
+ NSLayoutConstraint.activate([
+ mainStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: UIFloat(15)),
+ mainStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: UIFloat(-15)),
+ mainStackView.topAnchor.constraint(equalTo: view.topAnchor, constant: UIFloat(15)),
+ mainStackView.bottomAnchor.constraint(lessThanOrEqualTo: view.bottomAnchor, constant: UIFloat(-15)),
+
+ segmentedControl.topAnchor.constraint(equalTo: segmentedControlContainer.topAnchor),
+ segmentedControl.bottomAnchor.constraint(equalTo: segmentedControlContainer.bottomAnchor),
+ segmentedControl.centerXAnchor.constraint(equalTo: segmentedControlContainer.centerXAnchor),
+
+ topSpacerView.heightAnchor.constraint(equalToConstant: UIFloat(3)),
+ bottomSpacerView.heightAnchor.constraint(equalToConstant: UIFloat(3)),
+
+ colorWell.widthAnchor.constraint(equalToConstant: UIFloat(32)),
+ colorWell.heightAnchor.constraint(equalTo: colorWell.widthAnchor)
+ ])
+
+ updateMode()
+ }
+
+ @objc func segmentControlChanged(_ sender: UISegmentedControl) {
+ view.endEditing(true)
+ mode = Mode.allCases[sender.selectedSegmentIndex]
+ }
+
+ override func touchesBegan(_ touches: Set, with event: UIEvent?) {
+ super.touchesBegan(touches, with: event)
+ view.endEditing(true)
+ }
+
+ func updateMode() {
+ for (stackMode, stack) in sliderStacks {
+ stack.isHidden = stackMode != mode
+ }
+ colorDidChange()
+ }
+
+ func setColor(_ color: Color, hexOptions: Color.HexOptions, shouldBroadcast: Bool = true) {
+ self.hexOptions = hexOptions
+ super.setColor(color, shouldBroadcast: shouldBroadcast)
+ }
+
+ override func setColor(_ color: Color, shouldBroadcast: Bool = true) {
+ self.setColor(color, hexOptions: [], shouldBroadcast: shouldBroadcast)
+ }
+
+ @objc func sliderChanged(_ slider: ColorPickerNumericSlider) {
+ var color = self.color
+ slider.apply(to: &color)
+ setColor(color)
+ }
+
+ override func colorDidChange() {
+ allSliders[mode]?.forEach {
+ $0.setColor(color)
+ }
+
+ colorWell.color = color.uiColor
+ hexTextField.text = color.hexString(with: hexOptions)
+
+ if #available(iOS 13, *) {
+ } else {
+ let foregroundColor: UIColor = color.isDark ? .white : .black
+ segmentedControl.setTitleTextAttributes([
+ .foregroundColor: foregroundColor
+ ], for: .selected)
+ }
+ }
+
+}
+
+extension ColorPickerSlidersViewController: UITextFieldDelegate {
+
+ func textFieldShouldReturn(_ textField: UITextField) -> Bool {
+ view.endEditing(true)
+ return true
+ }
+
+ func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
+ let newString = textField.text!.replacingCharacters(in: Range(range, in: textField.text!)!, with: string)
+ guard !newString.isEmpty else { return true }
+
+ // #AAAAAA
+ eggString = "\(eggString.suffix(3))\(string)"
+ if eggString.lowercased() == "holo" {
+ self.setColor(Color(red: 51 / 255, green: 181 / 255, blue: 229 / 255, alpha: 1))
+ eggLabel.text = "Praise DuARTe"
+ eggLabel.textColor = color.uiColor
+ eggLabel.isHidden = false
+ eggString = ""
+ return false
+ }
+
+ let canonicalizedString = newString.hasPrefix("#") ? newString.dropFirst() : Substring(newString)
+ guard canonicalizedString.count <= 8 else {
+ beep()
+ return false
+ }
+
+ let badCharacterSet = CharacterSet(charactersIn: "0123456789ABCDEFabcdef").inverted
+ guard canonicalizedString.rangeOfCharacter(from: badCharacterSet) == nil else {
+ beep()
+ return false
+ }
+
+ if canonicalizedString.count != 6 && canonicalizedString.count != 8 {
+ // User is probably still typing it out. Don’t do anything yet.
+ return true
+ }
+
+ guard var uiColor = UIColor(propertyListValue: "#\(canonicalizedString)") else {
+ return true
+ }
+
+ if !configuration.supportsAlpha {
+ // Discard the alpha component.
+ uiColor = uiColor.withAlphaComponent(1)
+ }
+
+ let color = Color(uiColor: uiColor)
+ OperationQueue.main.addOperation {
+ self.setColor(color, hexOptions: canonicalizedString.count == 3 ? .allowShorthand : [])
+ }
+
+ return true
+ }
+
+}
diff --git a/Tweaks/Alderis/Alderis/ColorPickerSwatchViewController.swift b/Tweaks/Alderis/Alderis/ColorPickerSwatchViewController.swift
new file mode 100644
index 0000000..b249f74
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/ColorPickerSwatchViewController.swift
@@ -0,0 +1,303 @@
+//
+// ColorPickerSwatchViewController.swift
+// Alderis
+//
+// Created by Adam Demasi on 13/3/20.
+// Copyright © 2020 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+
+internal class ColorPickerSwatchViewController: ColorPickerTabViewController {
+
+ private class ColorLayer: CALayer {
+ let color: Color
+ init(color: Color) {
+ self.color = color
+ super.init()
+ backgroundColor = color.uiColor.cgColor
+ }
+ override init(layer: Any) {
+ color = Color(white: 1, alpha: 1)
+ super.init(layer: layer)
+ }
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+ }
+
+ static let title = "Swatch"
+ static let imageName = "square.grid.4x3.fill"
+
+ static let colorSwatch = [
+ [
+ Color(hue: 0.000000, saturation: 0.000000, brightness: 1.000000, alpha: 1),
+ Color(hue: 0.000000, saturation: 0.000000, brightness: 0.921569, alpha: 1),
+ Color(hue: 0.000000, saturation: 0.000000, brightness: 0.839216, alpha: 1),
+ Color(hue: 0.000000, saturation: 0.000000, brightness: 0.760784, alpha: 1),
+ Color(hue: 0.000000, saturation: 0.000000, brightness: 0.678431, alpha: 1),
+ Color(hue: 0.000000, saturation: 0.000000, brightness: 0.600000, alpha: 1),
+ Color(hue: 0.000000, saturation: 0.000000, brightness: 0.521569, alpha: 1),
+ Color(hue: 0.000000, saturation: 0.000000, brightness: 0.439216, alpha: 1),
+ Color(hue: 0.000000, saturation: 0.000000, brightness: 0.360784, alpha: 1),
+ Color(hue: 0.000000, saturation: 0.000000, brightness: 0.278431, alpha: 1),
+ Color(hue: 0.000000, saturation: 0.000000, brightness: 0.200000, alpha: 1),
+ Color(hue: 0.000000, saturation: 0.000000, brightness: 0.000000, alpha: 1),
+ ],
+ [
+ Color(hue: 0.542793, saturation: 1.000000, brightness: 0.290196, alpha: 1),
+ Color(hue: 0.612403, saturation: 0.988506, brightness: 0.341176, alpha: 1),
+ Color(hue: 0.703704, saturation: 0.915255, brightness: 0.231373, alpha: 1),
+ Color(hue: 0.787878, saturation: 0.901640, brightness: 0.239216, alpha: 1),
+ Color(hue: 0.937107, saturation: 0.883333, brightness: 0.235294, alpha: 1),
+ Color(hue: 0.010989, saturation: 0.989130, brightness: 0.360784, alpha: 1),
+ Color(hue: 0.051852, saturation: 1.000000, brightness: 0.352941, alpha: 1),
+ Color(hue: 0.096591, saturation: 1.000000, brightness: 0.345098, alpha: 1),
+ Color(hue: 0.118217, saturation: 1.000000, brightness: 0.337255, alpha: 1),
+ Color(hue: 0.158497, saturation: 1.000000, brightness: 0.400000, alpha: 1),
+ Color(hue: 0.179012, saturation: 0.952941, brightness: 0.333333, alpha: 1),
+ Color(hue: 0.251773, saturation: 0.758064, brightness: 0.243137, alpha: 1),
+ ],
+ [
+ Color(hue: 0.539604, saturation: 1.000000, brightness: 0.396078, alpha: 1),
+ Color(hue: 0.603825, saturation: 0.991870, brightness: 0.482353, alpha: 1),
+ Color(hue: 0.703704, saturation: 0.878049, brightness: 0.321569, alpha: 1),
+ Color(hue: 0.789473, saturation: 0.853933, brightness: 0.349020, alpha: 1),
+ Color(hue: 0.939614, saturation: 0.811765, brightness: 0.333333, alpha: 1),
+ Color(hue: 0.021629, saturation: 1.000000, brightness: 0.513725, alpha: 1),
+ Color(hue: 0.055555, saturation: 1.000000, brightness: 0.482353, alpha: 1),
+ Color(hue: 0.101093, saturation: 1.000000, brightness: 0.478431, alpha: 1),
+ Color(hue: 0.122222, saturation: 1.000000, brightness: 0.470588, alpha: 1),
+ Color(hue: 0.158273, saturation: 0.985816, brightness: 0.552941, alpha: 1),
+ Color(hue: 0.177469, saturation: 0.915254, brightness: 0.462745, alpha: 1),
+ Color(hue: 0.251366, saturation: 0.701148, brightness: 0.341176, alpha: 1),
+ ],
+ [
+ Color(hue: 0.538732, saturation: 0.993007, brightness: 0.560784, alpha: 1),
+ Color(hue: 0.601578, saturation: 1.000000, brightness: 0.662745, alpha: 1),
+ Color(hue: 0.719697, saturation: 0.924370, brightness: 0.466667, alpha: 1),
+ Color(hue: 0.788333, saturation: 0.806452, brightness: 0.486275, alpha: 1),
+ Color(hue: 0.938596, saturation: 0.785124, brightness: 0.474510, alpha: 1),
+ Color(hue: 0.023941, saturation: 1.000000, brightness: 0.709804, alpha: 1),
+ Color(hue: 0.059730, saturation: 1.000000, brightness: 0.678431, alpha: 1),
+ Color(hue: 0.102564, saturation: 1.000000, brightness: 0.662745, alpha: 1),
+ Color(hue: 0.123232, saturation: 0.993976, brightness: 0.650980, alpha: 1),
+ Color(hue: 0.159864, saturation: 1.000000, brightness: 0.768627, alpha: 1),
+ Color(hue: 0.177704, saturation: 0.915151, brightness: 0.647059, alpha: 1),
+ Color(hue: 0.255020, saturation: 0.680328, brightness: 0.478431, alpha: 1),
+ ],
+ [
+ Color(hue: 0.537037, saturation: 1.000000, brightness: 0.705882, alpha: 1),
+ Color(hue: 0.599688, saturation: 1.000000, brightness: 0.839216, alpha: 1),
+ Color(hue: 0.706284, saturation: 0.824324, brightness: 0.580392, alpha: 1),
+ Color(hue: 0.785333, saturation: 0.791139, brightness: 0.619608, alpha: 1),
+ Color(hue: 0.938746, saturation: 0.764707, brightness: 0.600000, alpha: 1),
+ Color(hue: 0.026549, saturation: 1.000000, brightness: 0.886275, alpha: 1),
+ Color(hue: 0.061927, saturation: 1.000000, brightness: 0.854902, alpha: 1),
+ Color(hue: 0.103175, saturation: 0.995261, brightness: 0.827451, alpha: 1),
+ Color(hue: 0.125000, saturation: 0.995215, brightness: 0.819608, alpha: 1),
+ Color(hue: 0.160544, saturation: 1.000000, brightness: 0.960784, alpha: 1),
+ Color(hue: 0.179211, saturation: 0.889952, brightness: 0.819608, alpha: 1),
+ Color(hue: 0.253968, saturation: 0.668789, brightness: 0.615686, alpha: 1),
+ ],
+ [
+ Color(hue: 0.542438, saturation: 1.000000, brightness: 0.847059, alpha: 1),
+ Color(hue: 0.603018, saturation: 1.000000, brightness: 0.996078, alpha: 1),
+ Color(hue: 0.716435, saturation: 0.808989, brightness: 0.698039, alpha: 1),
+ Color(hue: 0.792237, saturation: 0.776596, brightness: 0.737255, alpha: 1),
+ Color(hue: 0.942857, saturation: 0.756756, brightness: 0.725490, alpha: 1),
+ Color(hue: 0.030627, saturation: 0.917647, brightness: 1.000000, alpha: 1),
+ Color(hue: 0.069281, saturation: 1.000000, brightness: 1.000000, alpha: 1),
+ Color(hue: 0.111549, saturation: 0.996078, brightness: 1.000000, alpha: 1),
+ Color(hue: 0.131093, saturation: 1.000000, brightness: 0.992157, alpha: 1),
+ Color(hue: 0.164021, saturation: 0.744094, brightness: 0.996078, alpha: 1),
+ Color(hue: 0.184162, saturation: 0.766949, brightness: 0.925490, alpha: 1),
+ Color(hue: 0.260163, saturation: 0.657754, brightness: 0.733333, alpha: 1),
+ ],
+ [
+ Color(hue: 0.535193, saturation: 0.996032, brightness: 0.988235, alpha: 1),
+ Color(hue: 0.601190, saturation: 0.771653, brightness: 0.996078, alpha: 1),
+ Color(hue: 0.707665, saturation: 0.795745, brightness: 0.921569, alpha: 1),
+ Color(hue: 0.786096, saturation: 0.769547, brightness: 0.952941, alpha: 1),
+ Color(hue: 0.938597, saturation: 0.743478, brightness: 0.901961, alpha: 1),
+ Color(hue: 0.017143, saturation: 0.686275, brightness: 1.000000, alpha: 1),
+ Color(hue: 0.056466, saturation: 0.717647, brightness: 1.000000, alpha: 1),
+ Color(hue: 0.102094, saturation: 0.751968, brightness: 0.996078, alpha: 1),
+ Color(hue: 0.122396, saturation: 0.755906, brightness: 0.996078, alpha: 1),
+ Color(hue: 0.157658, saturation: 0.580392, brightness: 1.000000, alpha: 1),
+ Color(hue: 0.179952, saturation: 0.577406, brightness: 0.937255, alpha: 1),
+ Color(hue: 0.254310, saturation: 0.549763, brightness: 0.827451, alpha: 1),
+ ],
+ [
+ Color(hue: 0.537255, saturation: 0.674603, brightness: 0.988235, alpha: 1),
+ Color(hue: 0.605516, saturation: 0.545098, brightness: 1.000000, alpha: 1),
+ Color(hue: 0.719048, saturation: 0.688976, brightness: 0.996078, alpha: 1),
+ Color(hue: 0.790419, saturation: 0.657481, brightness: 0.996078, alpha: 1),
+ Color(hue: 0.940000, saturation: 0.525210, brightness: 0.933333, alpha: 1),
+ Color(hue: 0.013333, saturation: 0.490196, brightness: 1.000000, alpha: 1),
+ Color(hue: 0.051282, saturation: 0.509804, brightness: 1.000000, alpha: 1),
+ Color(hue: 0.098039, saturation: 0.533333, brightness: 1.000000, alpha: 1),
+ Color(hue: 0.120098, saturation: 0.533333, brightness: 1.000000, alpha: 1),
+ Color(hue: 0.157321, saturation: 0.419608, brightness: 1.000000, alpha: 1),
+ Color(hue: 0.180135, saturation: 0.409091, brightness: 0.949020, alpha: 1),
+ Color(hue: 0.256097, saturation: 0.371041, brightness: 0.866667, alpha: 1),
+ ],
+ [
+ Color(hue: 0.540881, saturation: 0.418972, brightness: 0.992157, alpha: 1),
+ Color(hue: 0.607954, saturation: 0.345098, brightness: 1.000000, alpha: 1),
+ Color(hue: 0.720760, saturation: 0.448818, brightness: 0.996078, alpha: 1),
+ Color(hue: 0.790124, saturation: 0.425197, brightness: 0.996078, alpha: 1),
+ Color(hue: 0.941667, saturation: 0.327869, brightness: 0.956863, alpha: 1),
+ Color(hue: 0.012500, saturation: 0.313725, brightness: 1.000000, alpha: 1),
+ Color(hue: 0.051587, saturation: 0.329412, brightness: 1.000000, alpha: 1),
+ Color(hue: 0.093869, saturation: 0.341176, brightness: 1.000000, alpha: 1),
+ Color(hue: 0.116279, saturation: 0.338582, brightness: 0.996078, alpha: 1),
+ Color(hue: 0.157143, saturation: 0.274510, brightness: 1.000000, alpha: 1),
+ Color(hue: 0.179687, saturation: 0.259109, brightness: 0.968627, alpha: 1),
+ Color(hue: 0.254902, saturation: 0.219828, brightness: 0.909804, alpha: 1),
+ ],
+ [
+ Color(hue: 0.548077, saturation: 0.203922, brightness: 1.000000, alpha: 1),
+ Color(hue: 0.609848, saturation: 0.172549, brightness: 1.000000, alpha: 1),
+ Color(hue: 0.716981, saturation: 0.208661, brightness: 0.996078, alpha: 1),
+ Color(hue: 0.783019, saturation: 0.207843, brightness: 1.000000, alpha: 1),
+ Color(hue: 0.942983, saturation: 0.152611, brightness: 0.976471, alpha: 1),
+ Color(hue: 0.012821, saturation: 0.152941, brightness: 1.000000, alpha: 1),
+ Color(hue: 0.048781, saturation: 0.160784, brightness: 1.000000, alpha: 1),
+ Color(hue: 0.093023, saturation: 0.168627, brightness: 1.000000, alpha: 1),
+ Color(hue: 0.115080, saturation: 0.164706, brightness: 1.000000, alpha: 1),
+ Color(hue: 0.156566, saturation: 0.129921, brightness: 0.996078, alpha: 1),
+ Color(hue: 0.182796, saturation: 0.123999, brightness: 0.980392, alpha: 1),
+ Color(hue: 0.262820, saturation: 0.109243, brightness: 0.933333, alpha: 1),
+ ]
+ ]
+
+ let colors = ColorPickerSwatchViewController.colorSwatch
+
+ private var colorRows = [[ColorLayer]]()
+ private var colorDict = [String: ColorLayer]()
+
+ var containerView: UIView!
+ var selectionView: UIView!
+ var containerViewHeightConstraint: NSLayoutConstraint!
+ var selectionViewWidthConstraint: NSLayoutConstraint!
+ var selectionViewXConstraint: NSLayoutConstraint!
+ var selectionViewYConstraint: NSLayoutConstraint!
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ containerView = UIView()
+ containerView.translatesAutoresizingMaskIntoConstraints = false
+ containerView.accessibilityIgnoresInvertColors = configuration.overrideSmartInvert
+ view.addSubview(containerView)
+
+ for row in colors {
+ var colorRow = [ColorLayer]()
+ for color in row {
+ let colorLayer = ColorLayer(color: color)
+ containerView.layer.addSublayer(colorLayer)
+ colorDict[color.hexString()] = colorLayer
+ colorRow.append(colorLayer)
+ }
+ colorRows.append(colorRow)
+ }
+
+ view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(gestureRecognizerFired(_:))))
+ let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(gestureRecognizerFired(_:)))
+ panGestureRecognizer.maximumNumberOfTouches = 1
+ view.addGestureRecognizer(panGestureRecognizer)
+
+ selectionView = UIView()
+ selectionView.translatesAutoresizingMaskIntoConstraints = false
+ selectionView.isUserInteractionEnabled = false
+ selectionView.layer.borderColor = UIColor.white.cgColor
+ selectionView.layer.borderWidth = 2
+ selectionView.layer.shadowOffset = CGSize(width: 0, height: 0)
+ selectionView.layer.shadowOpacity = 1
+ selectionView.layer.shadowColor = UIColor(white: 0, alpha: 0.1).cgColor
+ view.addSubview(selectionView)
+
+ containerViewHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: 0)
+ selectionViewWidthConstraint = selectionView.widthAnchor.constraint(equalToConstant: UIFloat(20))
+ let selectionViewBaseXConstraint = selectionView.leftAnchor.constraint(equalTo: view.leftAnchor)
+ selectionViewBaseXConstraint.priority = .defaultLow
+ let selectionViewBaseYConstraint = selectionView.topAnchor.constraint(equalTo: view.topAnchor)
+ selectionViewBaseYConstraint.priority = .defaultLow
+ selectionViewXConstraint = selectionView.leftAnchor.constraint(equalTo: view.leftAnchor)
+ selectionViewYConstraint = selectionView.topAnchor.constraint(equalTo: view.topAnchor)
+
+ NSLayoutConstraint.activate([
+ containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ containerView.topAnchor.constraint(equalTo: view.topAnchor),
+ containerView.bottomAnchor.constraint(greaterThanOrEqualTo: view.bottomAnchor),
+ containerViewHeightConstraint,
+
+ selectionViewWidthConstraint,
+ selectionView.heightAnchor.constraint(equalTo: selectionView.widthAnchor),
+ selectionViewBaseXConstraint,
+ selectionViewBaseYConstraint,
+ selectionViewXConstraint,
+ selectionViewYConstraint
+ ])
+
+ colorDidChange()
+ }
+
+ override func viewDidLayoutSubviews() {
+ super.viewDidLayoutSubviews()
+
+ var x: CGFloat = 0, y: CGFloat = 0
+ let size = view.frame.size.width / CGFloat(colors[0].count)
+ for row in colorRows {
+ x = 0
+ for item in row {
+ item.frame = CGRect(x: x * size, y: y * size, width: size, height: size)
+ x += 1
+ }
+ y += 1
+ }
+
+ containerViewHeightConstraint.constant = y * size
+ selectionViewWidthConstraint.constant = size
+ UIView.performWithoutAnimation {
+ colorDidChange()
+ }
+ }
+
+ @objc private func gestureRecognizerFired(_ sender: UIGestureRecognizer) {
+ switch sender.state {
+ case .began, .changed, .ended:
+ let location = sender.location(in: containerView)
+ guard let colorView = containerView.layer.hitTest(location) as? ColorLayer else {
+ return
+ }
+ self.setColor(colorView.color)
+ case .possible, .cancelled, .failed:
+ break
+ @unknown default:
+ break
+ }
+ }
+
+ func setSelection(to colorLayer: CALayer?) {
+ let wasHidden = selectionView.isHidden
+ selectionView.isHidden = colorLayer == nil
+ selectionViewXConstraint.constant = colorLayer?.frame.origin.x ?? 0
+ selectionViewYConstraint.constant = colorLayer?.frame.origin.y ?? 0
+ if wasHidden {
+ view.layoutIfNeeded()
+ } else {
+ UIView.animate(withDuration: 0.2) {
+ self.view.layoutIfNeeded()
+ }
+ }
+ }
+
+ override func colorDidChange() {
+ guard selectionView != nil else { return }
+ setSelection(to: colorDict[color.hexString()])
+ }
+
+}
diff --git a/Tweaks/Alderis/Alderis/ColorPickerTabViewController.swift b/Tweaks/Alderis/Alderis/ColorPickerTabViewController.swift
new file mode 100644
index 0000000..3b27951
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/ColorPickerTabViewController.swift
@@ -0,0 +1,60 @@
+//
+// ColorPickerTabViewController.swift
+// Alderis
+//
+// Created by Kabir Oberai on 23/03/20.
+// Copyright © 2020 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+
+internal protocol ColorPickerTabDelegate: AnyObject {
+ func colorPickerTab(_ tab: ColorPickerTabViewControllerBase, didSelect color: Color)
+}
+
+internal class ColorPickerTabViewControllerBase: UIViewController {
+
+ unowned var tabDelegate: ColorPickerTabDelegate
+
+ private(set) var configuration: ColorPickerConfiguration
+
+ private(set) var color: Color {
+ didSet { colorDidChange() }
+ }
+
+ func colorDidChange() {}
+
+ func setColor(_ color: Color, shouldBroadcast: Bool = true) {
+ if self.color == color {
+ return
+ }
+ self.color = color
+ if shouldBroadcast {
+ tabDelegate.colorPickerTab(self, didSelect: color)
+ }
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ required init(tabDelegate: ColorPickerTabDelegate, configuration: ColorPickerConfiguration, color: Color) {
+ self.tabDelegate = tabDelegate
+ self.configuration = configuration
+ self.color = color
+ super.init(nibName: nil, bundle: nil)
+ }
+
+}
+
+internal protocol ColorPickerTabViewControllerProtocol: ColorPickerTabViewControllerBase {
+ static var title: String { get }
+ static var imageName: String { get }
+ static var image: UIImage { get }
+}
+
+extension ColorPickerTabViewControllerProtocol {
+ static var image: UIImage { Assets.systemImage(named: imageName, font: .systemFont(ofSize: UIFloat(20), weight: .medium)) ?? UIImage() }
+}
+
+internal typealias ColorPickerTabViewController = ColorPickerTabViewControllerBase & ColorPickerTabViewControllerProtocol
diff --git a/Tweaks/Alderis/Alderis/ColorPickerViewController.swift b/Tweaks/Alderis/Alderis/ColorPickerViewController.swift
new file mode 100644
index 0000000..386d693
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/ColorPickerViewController.swift
@@ -0,0 +1,385 @@
+//
+// ColorPickerViewController.swift
+// Alderis
+//
+// Created by Adam Demasi on 12/3/20.
+// Copyright © 2020 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+
+/// Provides the Color Picker user interface.
+///
+/// Present this view controller to display the color picker. Do not push it onto a navigation
+/// controller stack. In horizontally and vertically regular size class environments, for instance
+/// on iPad and Mac, the picker will be presented as a popover. This means that you must set
+/// `sourceView` or other similar properties on the view controller’s `popoverPresentationController`
+/// before presentation.
+///
+/// To review examples of `ColorPickerViewController` in use, run `pod try Alderis`.
+@objc(HBColorPickerViewController)
+open class ColorPickerViewController: UIViewController {
+
+ /// Do not rely on this fallback value - always specify a color!
+ private static let defaultColor = UIColor(white: 0.6, alpha: 1)
+
+ /// Initialise an instance of `ColorPickerViewController` with a configuration object.
+ ///
+ /// Remember to set the `delegate` before presenting the view controller.
+ @objc public init(configuration: ColorPickerConfiguration) {
+ self.configuration = configuration
+ super.init(nibName: nil, bundle: nil)
+ setUp()
+ }
+
+ /// The delegate that will receive the user’s selection upon tapping the Done button, or a
+ /// cancellation upon tapping the Cancel button.
+ @objc open weak var delegate: ColorPickerDelegate? {
+ didSet { innerViewController?.delegate = delegate }
+ }
+
+ /// The configuration of the color picker. Use this to set the initially selected color, as well
+ /// as other behavioral options.
+ ///
+ /// Making changes to this value or its properties after the color picker interface has been
+ /// presented may result in undefined behavior.
+ ///
+ /// - see: `ColorPickerConfiguration`
+ @objc open var configuration: ColorPickerConfiguration!
+
+ /// Deprecated. Set `ColorPickerConfiguration.overrideSmartInvert` instead.
+ ///
+ /// - see: `ColorPickerConfiguration.overrideSmartInvert`
+ @available(*, deprecated, message: "Use ColorPickerConfiguration instead")
+ @objc open var overrideSmartInvert = true
+
+ /// Deprecated. Set `ColorPickerConfiguration.color` instead.
+ ///
+ /// - see: `ColorPickerConfiguration.color`
+ @available(*, deprecated, message: "Use ColorPickerConfiguration instead")
+ @objc open var color = ColorPickerViewController.defaultColor
+
+ // A width divisible by 12 (the number of items wide in the swatch).
+ private var finalWidth: CGFloat {
+ if modalPresentationStyle == .popover {
+ return UIFloat(336)
+ } else {
+ return floor(min(UIFloat(384), view.frame.size.width - 30) / 12) * 12
+ }
+ }
+
+ private var isFullScreen: Bool { modalPresentationStyle != .popover }
+
+ private var innerViewController: ColorPickerInnerViewController!
+
+ private var backdropView: UIView!
+ private var backgroundView: UIVisualEffectView!
+
+ private var widthLayoutConstraint: NSLayoutConstraint!
+ private var bottomLayoutConstraint: NSLayoutConstraint!
+ private var bottomAnimatingLayoutConstraint: NSLayoutConstraint!
+
+ // swiftlint:disable:next weak_delegate
+ private lazy var _transitioningDelegate = BottomSheetTransitioningDelegate()
+
+ private var initialBottomSafeAreaInset: CGFloat?
+ private var isKeyboardVisible = false
+
+ /// :nodoc:
+ override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
+ self.configuration = nil
+ super.init(nibName: nil, bundle: nil)
+ setUp()
+ }
+
+ /// :nodoc:
+ required public init?(coder: NSCoder) {
+ self.configuration = nil
+ super.init(coder: coder)
+ setUp()
+ }
+
+ private func setUp() {
+ if traitCollection.horizontalSizeClass == .regular && traitCollection.verticalSizeClass == .regular {
+ modalPresentationStyle = .popover
+ } else {
+ modalPresentationStyle = .overCurrentContext
+ transitioningDelegate = _transitioningDelegate
+ }
+ }
+
+ /// :nodoc:
+ override open func viewDidLoad() {
+ super.viewDidLoad()
+
+ var compatibilityMode = false
+ if configuration == nil {
+ let deprecatedAPI: ColorPickerViewControllerDeprecatedMethods = self
+ // Yes, Swift, I know my code for handling deprecated API usage uses deprecated API 🙄
+ if deprecatedAPI.color == ColorPickerViewController.defaultColor {
+ fatalError("Alderis: You need to set a configuration. https://hbang.github.io/Alderis/")
+ }
+ NSLog("Alderis: Deprecated configuration API in use. This will be removed in a future release. Migrate to using ColorPickerConfiguration. https://hbang.github.io/Alderis/")
+ compatibilityMode = true
+ configuration = ColorPickerConfiguration(color: deprecatedAPI.color)
+ configuration.overrideSmartInvert = deprecatedAPI.overrideSmartInvert
+ }
+
+ if !configuration.supportsAlpha {
+ // Force the color to be fully opaque.
+ configuration.color = configuration.color.withAlphaComponent(1)
+ }
+
+ navigationController?.isNavigationBarHidden = true
+ view.backgroundColor = .clear
+ preferredContentSize = .zero
+
+ if isFullScreen {
+ backdropView = UIView()
+ backdropView.translatesAutoresizingMaskIntoConstraints = false
+ backdropView.backgroundColor = Assets.backdropColor
+ backdropView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dismissGestureFired(_:))))
+ view.addSubview(backdropView)
+ }
+
+ let containerView = UIView()
+ containerView.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview(containerView)
+
+ if isFullScreen {
+ let style: UIBlurEffect.Style
+ if #available(iOS 13, *) {
+ style = .systemThinMaterial
+ } else {
+ style = .light
+ }
+ backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: style))
+ backgroundView.translatesAutoresizingMaskIntoConstraints = false
+ backgroundView.clipsToBounds = true
+ backgroundView.layer.cornerRadius = 13
+ if #available(iOS 13, *) {
+ backgroundView.layer.cornerCurve = .continuous
+ }
+ containerView.addSubview(backgroundView)
+ }
+
+ innerViewController = ColorPickerInnerViewController(delegate: delegate, configuration: configuration)
+ innerViewController.compatibilityMode = compatibilityMode
+ innerViewController.willMove(toParent: self)
+ addChild(innerViewController)
+ innerViewController.view.translatesAutoresizingMaskIntoConstraints = false
+
+ if isFullScreen {
+ innerViewController.view.clipsToBounds = true
+ innerViewController.view.layer.cornerRadius = 13
+ innerViewController.view.layer.borderWidth = 1
+ innerViewController.view.layer.borderColor = Assets.borderColor.cgColor
+ if #available(iOS 13, *) {
+ innerViewController.view.layer.cornerCurve = .continuous
+ }
+ } else {
+ popoverPresentationController?.delegate = innerViewController
+ }
+
+ containerView.addSubview(innerViewController.view)
+
+ widthLayoutConstraint = containerView.widthAnchor.constraint(equalToConstant: finalWidth)
+ bottomLayoutConstraint = view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 0)
+ bottomAnimatingLayoutConstraint = view.bottomAnchor.constraint(equalTo: containerView.topAnchor)
+
+ NSLayoutConstraint.activate(
+ [
+ widthLayoutConstraint,
+ innerViewController.view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
+ innerViewController.view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
+ innerViewController.view.topAnchor.constraint(equalTo: containerView.topAnchor),
+ innerViewController.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
+ ] +
+ (isFullScreen ? [
+ backdropView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ backdropView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ backdropView.topAnchor.constraint(equalTo: view.topAnchor),
+ backdropView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+
+ backgroundView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
+ backgroundView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
+ backgroundView.topAnchor.constraint(equalTo: containerView.topAnchor),
+ backgroundView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
+
+ containerView.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
+ bottomLayoutConstraint
+ ] : [
+ containerView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
+ containerView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
+ containerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
+ containerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
+ ])
+ )
+ }
+
+ /// :nodoc:
+ override open func viewWillLayoutSubviews() {
+ super.viewWillLayoutSubviews()
+
+ widthLayoutConstraint.constant = finalWidth
+
+ if isFullScreen {
+ innerViewController.view.layer.borderWidth = 1 / (view.window?.screen.scale ?? 1)
+ }
+ }
+
+ /// :nodoc:
+ override open func viewSafeAreaInsetsDidChange() {
+ super.viewSafeAreaInsetsDidChange()
+
+ if !isKeyboardVisible {
+ initialBottomSafeAreaInset = view.safeAreaInsets.bottom
+ bottomLayoutConstraint.constant = initialBottomSafeAreaInset == 0 ? 15 : 0
+ }
+ }
+
+ /// :nodoc:
+ open override func preferredContentSizeDidChange(forChildContentContainer container: UIContentContainer) {
+ super.preferredContentSizeDidChange(forChildContentContainer: container)
+
+ if !isFullScreen {
+ preferredContentSize = CGSize(width: finalWidth,
+ height: innerViewController.preferredContentSize.height)
+ }
+ }
+
+ /// :nodoc:
+ override open func viewWillAppear(_ animated: Bool) {
+ super.viewWillAppear(animated)
+
+ if navigationController != nil && navigationController!.viewControllers.count > 1 {
+ assertionFailure("Do not push \(String(describing: type(of: self))) onto a navigation controller stack. It must be presented using UIViewController.present(_:animated:completion:).")
+ }
+
+ if animated && isFullScreen {
+ backdropView.alpha = 0
+ bottomLayoutConstraint.isActive = false
+ bottomAnimatingLayoutConstraint.isActive = true
+ view.layoutIfNeeded()
+
+ UIView.animate(withDuration: 0.4,
+ delay: 0,
+ usingSpringWithDamping: 2,
+ initialSpringVelocity: 0.5,
+ options: [],
+ animations: {
+ self.backdropView.alpha = 1
+ self.bottomLayoutConstraint.isActive = true
+ self.bottomAnimatingLayoutConstraint.isActive = false
+ self.view.layoutIfNeeded()
+ },
+ completion: nil)
+ }
+ }
+
+ private let keyboardNotificationNames = [
+ UIResponder.keyboardWillShowNotification,
+ UIResponder.keyboardWillHideNotification,
+ UIResponder.keyboardWillChangeFrameNotification
+ ]
+
+ /// :nodoc:
+ override open func viewDidAppear(_ animated: Bool) {
+ super.viewDidAppear(animated)
+
+ for name in keyboardNotificationNames {
+ NotificationCenter.default.addObserver(self, selector: #selector(keyboardFrameWillChange(_:)), name: name, object: nil)
+ }
+ }
+
+ /// :nodoc:
+ override open func viewWillDisappear(_ animated: Bool) {
+ super.viewWillDisappear(animated)
+
+ for name in keyboardNotificationNames {
+ NotificationCenter.default.removeObserver(self, name: name, object: nil)
+ }
+
+ if animated && isFullScreen {
+ backdropView.alpha = 1
+ bottomLayoutConstraint.isActive = true
+ bottomAnimatingLayoutConstraint.isActive = false
+ view.layoutIfNeeded()
+
+ UIView.animate(withDuration: 0.4,
+ delay: 0,
+ usingSpringWithDamping: 0.8,
+ initialSpringVelocity: 0.5,
+ options: [],
+ animations: {
+ self.backdropView.alpha = 0
+ self.bottomLayoutConstraint.isActive = false
+ self.bottomAnimatingLayoutConstraint.isActive = true
+ self.view.layoutIfNeeded()
+ },
+ completion: nil)
+ }
+ }
+
+ /// :nodoc:
+ override open func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
+ super.traitCollectionDidChange(previousTraitCollection)
+
+ if isFullScreen {
+ // CGColor doesn’t support dynamic colors, so we need to set this again.
+ innerViewController.view.layer.borderColor = Assets.borderColor.cgColor
+ }
+ }
+
+ @objc private func keyboardFrameWillChange(_ notification: Notification) {
+ if !isFullScreen {
+ return
+ }
+
+ guard let userInfo = notification.userInfo,
+ let keyboardEndFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect,
+ let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval,
+ let curve = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? UInt
+ else {
+ return
+ }
+
+ isKeyboardVisible = notification.name != UIResponder.keyboardWillHideNotification
+
+ var options: UIView.AnimationOptions = .beginFromCurrentState
+ options.insert(.init(rawValue: curve << 16))
+
+ UIView.animate(withDuration: duration,
+ delay: 0,
+ options: options,
+ animations: {
+ let keyboardHeight: CGFloat = (self.isKeyboardVisible ? keyboardEndFrame.size.height : 0)
+ let keyboardExtraMargin: CGFloat = (self.isKeyboardVisible && self.initialBottomSafeAreaInset != 0 ? 15 : 0)
+ let bottom = max(keyboardHeight - (self.initialBottomSafeAreaInset ?? 0), 0) + keyboardExtraMargin
+ self.additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: bottom, right: 0)
+ self.view.layoutIfNeeded()
+ },
+ completion: nil)
+ }
+
+ @objc private func dismissGestureFired(_ gestureRecognizer: UITapGestureRecognizer) {
+ if gestureRecognizer.state == .ended {
+ if isKeyboardVisible {
+ view.endEditing(true)
+ } else {
+ innerViewController.saveTapped()
+ dismiss(animated: true, completion: nil)
+ }
+ }
+ }
+
+}
+
+/// :nodoc:
+private protocol ColorPickerViewControllerDeprecatedMethods {
+ var color: UIColor { get }
+ var overrideSmartInvert: Bool { get }
+}
+
+/// :nodoc:
+extension ColorPickerViewController: ColorPickerViewControllerDeprecatedMethods {}
diff --git a/Tweaks/Alderis/Alderis/ColorPickerWheelView.swift b/Tweaks/Alderis/Alderis/ColorPickerWheelView.swift
new file mode 100644
index 0000000..2f03938
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/ColorPickerWheelView.swift
@@ -0,0 +1,242 @@
+//
+// ColorPickerMapView.swift
+// Alderis
+//
+// Created by Adam Demasi on 14/3/20.
+// Copyright © 2020 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+
+internal protocol ColorPickerWheelViewDelegate: AnyObject {
+ func colorPickerWheelView(didSelectColor color: Color)
+}
+
+internal class ColorPickerWheelView: UIView {
+
+ weak var delegate: ColorPickerWheelViewDelegate?
+
+ var color: Color {
+ didSet { updateColor() }
+ }
+
+ private var containerView: UIView!
+ private var wheelView: ColorPickerWheelInnerView!
+ private var selectionView: ColorWell!
+ private var selectionViewXConstraint: NSLayoutConstraint!
+ private var selectionViewYConstraint: NSLayoutConstraint!
+ private var selectionViewFingerDownConstraint: NSLayoutConstraint!
+
+ private var isFingerDown = false
+ private let touchDownFeedbackGenerator = UIImpactFeedbackGenerator(style: .medium)
+ private let touchUpFeedbackGenerator = UIImpactFeedbackGenerator(style: .light)
+
+ init(color: Color) {
+ self.color = color
+ super.init(frame: .zero)
+
+ containerView = UIView()
+ containerView.translatesAutoresizingMaskIntoConstraints = false
+ containerView.clipsToBounds = true
+ addSubview(containerView)
+
+ containerView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(gestureRecognizerFired(_:))))
+ containerView.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(gestureRecognizerFired(_:))))
+ let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(gestureRecognizerFired(_:)))
+ panGestureRecognizer.maximumNumberOfTouches = 1
+ containerView.addGestureRecognizer(panGestureRecognizer)
+
+ wheelView = ColorPickerWheelInnerView()
+ wheelView.translatesAutoresizingMaskIntoConstraints = false
+ wheelView.handleLayout = { [weak self] in self?.setNeedsLayout() }
+ containerView.addSubview(wheelView)
+
+ selectionView = ColorWell()
+ selectionView.translatesAutoresizingMaskIntoConstraints = false
+ selectionView.isDragInteractionEnabled = false
+ selectionView.isDropInteractionEnabled = false
+ #if swift(>=5.3)
+ if #available(iOS 14, *) {
+ selectionView.isContextMenuInteractionEnabled = false
+ }
+ #endif
+ containerView.addSubview(selectionView)
+
+ selectionViewXConstraint = selectionView.leftAnchor.constraint(equalTo: containerView.leftAnchor)
+ selectionViewYConstraint = selectionView.topAnchor.constraint(equalTo: containerView.topAnchor)
+ // https://www.youtube.com/watch?v=Qs8kDiOwPBA
+ selectionViewFingerDownConstraint = selectionView.widthAnchor.constraint(equalToConstant: 56)
+ let selectionViewNormalConstraint = selectionView.widthAnchor.constraint(equalToConstant: UIFloat(24))
+ selectionViewNormalConstraint.priority = .defaultHigh
+
+ // Remove minimum width constraint configured by ColorWell internally
+ let selectionWidthConstraint = selectionView.constraints.first { $0.firstAnchor == selectionView.widthAnchor }
+ selectionWidthConstraint?.isActive = false
+
+ NSLayoutConstraint.activate([
+ containerView.centerXAnchor.constraint(equalTo: self.centerXAnchor),
+ containerView.topAnchor.constraint(equalTo: self.topAnchor),
+ containerView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
+ containerView.widthAnchor.constraint(equalTo: containerView.heightAnchor, constant: UIFloat(30)),
+
+ wheelView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: UIFloat(30)),
+ wheelView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: UIFloat(-30)),
+ wheelView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: UIFloat(15)),
+ wheelView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: UIFloat(-15)),
+
+ selectionViewXConstraint,
+ selectionViewYConstraint,
+ selectionViewNormalConstraint,
+ selectionView.heightAnchor.constraint(equalTo: selectionView.widthAnchor)
+ ])
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func layoutSubviews() {
+ super.layoutSubviews()
+ updateSelectionPoint()
+ }
+
+ private func updateColor() {
+ wheelView.brightness = color.brightness
+ selectionView.backgroundColor = color.uiColor
+ updateSelectionPoint()
+ }
+
+ private func updateSelectionPoint() {
+ let colorPoint = pointForColor(color, in: wheelView.frame.size)
+ let point = CGPoint(x: wheelView.frame.origin.x + colorPoint.x - (selectionView.frame.size.width / 2),
+ y: min(
+ frame.size.height - selectionView.frame.size.height - 1,
+ max(1, wheelView.frame.origin.y + colorPoint.y - (selectionView.frame.size.height / 2))
+ ))
+ selectionViewXConstraint.constant = point.x
+ selectionViewYConstraint.constant = point.y
+ }
+
+ private func colorAt(position: CGPoint, in size: CGSize) -> Color {
+ let point = CGPoint(x: (size.width / 2) - position.x,
+ y: (size.height / 2) - position.y)
+ let h = 180 + round(atan2(point.y, point.x) * (180 / .pi))
+ let handleRange = size.width / 2
+ let handleDistance = min(sqrt(point.x * point.x + point.y * point.y), handleRange)
+ let s = round(100 / handleRange * handleDistance)
+ return Color(hue: h / 360, saturation: s / 100, brightness: color.brightness, alpha: 1)
+ }
+
+ private func pointForColor(_ color: Color, in size: CGSize) -> CGPoint {
+ let handleRange = size.width / 2
+ let handleAngle = (color.hue * 360) * (.pi / 180)
+ let handleDistance = color.saturation * handleRange
+ return CGPoint(x: (size.width / 2) + handleDistance * cos(handleAngle),
+ y: (size.height / 2) + handleDistance * sin(handleAngle))
+ }
+
+ @objc private func gestureRecognizerFired(_ sender: UIGestureRecognizer) {
+ switch sender.state {
+ case .began, .changed, .ended:
+ var location = sender.location(in: containerView)
+ location.x -= wheelView.frame.origin.x
+ location.y -= wheelView.frame.origin.y
+ color = colorAt(position: location, in: wheelView.frame.size)
+ delegate?.colorPickerWheelView(didSelectColor: color)
+ case .possible, .cancelled, .failed:
+ break
+ @unknown default:
+ break
+ }
+
+ if sender is UITapGestureRecognizer {
+ return
+ }
+
+ switch sender.state {
+ case .began, .ended, .cancelled:
+ isFingerDown = sender.state == .began
+ selectionViewFingerDownConstraint.isActive = isFingerDown && !isCatalyst
+ updateSelectionPoint()
+ UIView.animate(withDuration: 0.2, animations: {
+ self.containerView.layoutIfNeeded()
+ self.updateSelectionPoint()
+ }, completion: { _ in
+ self.updateSelectionPoint()
+ })
+ if sender.state == .began {
+ touchDownFeedbackGenerator.impactOccurred()
+ } else {
+ touchUpFeedbackGenerator.impactOccurred()
+ }
+ case .possible, .changed, .failed:
+ break
+ @unknown default:
+ break
+ }
+ }
+
+}
+
+private class ColorPickerWheelInnerView: UIView {
+ private var brightnessView: UIView!
+
+ var brightness: CGFloat {
+ get { 1 - brightnessView.alpha }
+ set { brightnessView.alpha = 1 - newValue }
+ }
+
+ var handleLayout: (() -> Void)!
+
+ private var saturationMask: GradientView!
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+
+ clipsToBounds = true
+
+ let hueView = GradientView()
+ hueView.translatesAutoresizingMaskIntoConstraints = false
+ hueView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+ hueView.gradientLayer.type = .conic
+ hueView.gradientLayer.colors = Color.Component.hue.sliderTintColor(for: Color(red: 1, green: 0, blue: 0, alpha: 1)).map(\.uiColor.cgColor)
+ hueView.gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
+ hueView.gradientLayer.endPoint = CGPoint(x: 0.5, y: 0)
+ hueView.gradientLayer.transform = CATransform3DMakeRotation(0.5 * .pi, 0, 0, 1)
+ addSubview(hueView)
+
+ let saturationView = UIView()
+ saturationView.translatesAutoresizingMaskIntoConstraints = false
+ saturationView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+ saturationView.backgroundColor = .white
+ addSubview(saturationView)
+
+ saturationMask = GradientView()
+ saturationMask.gradientLayer.type = .radial
+ saturationMask.gradientLayer.colors = [UIColor.white.cgColor, UIColor.clear.cgColor]
+ saturationMask.gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
+ saturationMask.gradientLayer.endPoint = CGPoint(x: 1, y: 1)
+ saturationView.mask = saturationMask
+
+ brightnessView = UIView()
+ brightnessView.translatesAutoresizingMaskIntoConstraints = false
+ brightnessView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+ brightnessView.backgroundColor = .black
+ addSubview(brightnessView)
+ }
+
+ convenience init() {
+ self.init(frame: .zero)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func layoutSubviews() {
+ super.layoutSubviews()
+ layer.cornerRadius = frame.size.height / 2
+ saturationMask.frame = bounds
+ handleLayout()
+ }
+}
diff --git a/Tweaks/Alderis/Alderis/ColorWell.swift b/Tweaks/Alderis/Alderis/ColorWell.swift
new file mode 100644
index 0000000..196cdac
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/ColorWell.swift
@@ -0,0 +1,403 @@
+//
+// ColorWell.swift
+// Alderis
+//
+// Created by Adam Demasi on 15/3/20.
+// Copyright © 2020 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+import CoreServices
+
+/// ColorWell can be used to present the user’s color selection in your user interface. It
+/// optionally also supports drag-and-drop operations.
+///
+/// By default, drop interactions are supported, which causes a `UIControl.Event.valueChanged` event
+/// to be emitted. Optionally, drag operations can be enabled, allowing the color to be dropped
+/// elsewhere.
+///
+/// You can also use `UIControl.Event.touchUpInside` to perform an action, such as to initialise
+/// and present an instance of `ColorPickerViewController`.
+@objc(HBColorWell)
+open class ColorWell: UIControl {
+
+ /// Set the color to be displayed by the view.
+ @objc open var color: UIColor? {
+ get { colorView.backgroundColor }
+ set { colorView.backgroundColor = newValue }
+ }
+
+ /// Override the default border color if desired.
+ @objc open var borderColor: UIColor? {
+ didSet { updateBorderColor() }
+ }
+
+ /// Whether the user can begin a drag interaction from this view, allowing them to drop the color
+ /// into a supporting app. The default is `false`.
+ @objc open var isDragInteractionEnabled = false {
+ didSet { updateDragDropInteraction() }
+ }
+
+ /// Whether the user can end a drag interaction by dropping on this view, allowing them to drag a
+ /// color from a supporting app onto this view. The default is true.
+ ///
+ /// To handle a color being dropped on this view, add an action for the `.valueChanged` event. For
+ /// example:
+ ///
+ /// ```swift
+ /// colorWell.addTarget(self, action: #selector(self.handleColorDidChange(_:)), for: .valueChanged)
+ /// ```
+ @objc open var isDropInteractionEnabled = true {
+ didSet { updateDragDropInteraction() }
+ }
+
+ #if swift(>=5.3)
+ /// Whether the user can long press (iPhone) or right-click (Mac/iPad) the view, allowing them to
+ /// copy the color in various formats, or paste a color from another source.
+ ///
+ /// To handle a color being pasted via the context menu, add an action for the `.valueChanged`
+ /// event. For example:
+ ///
+ /// ```swift
+ /// colorWell.addTarget(self, action: #selector(self.handleColorDidChange(_:)), for: .valueChanged)
+ /// ```
+ ///
+ /// Requires iOS 14 or newer.
+ @available(iOS 14, *)
+ open override var isContextMenuInteractionEnabled: Bool {
+ didSet { updateDragDropInteraction() }
+ }
+ #endif
+
+ private var colorView: UIView!
+ private var dragInteraction: UIDragInteraction!
+ private var dropInteraction: UIDropInteraction!
+ private var tapGestureRecognizer: UITapGestureRecognizer!
+
+ private var contextMenuTitle: String {
+ if let color = color {
+ return "Color: \(Color(uiColor: color).hexString())"
+ }
+ return "No color"
+ }
+
+ /// :nodoc:
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ setUp()
+ }
+
+ /// :nodoc:
+ required public init?(coder: NSCoder) {
+ super.init(coder: coder)
+ setUp()
+ }
+
+ private func setUp() {
+ clipsToBounds = true
+ backgroundColor = Assets.checkerboardPatternColor
+ borderColor = .white
+ layer.masksToBounds = false
+ layer.shadowColor = UIColor.black.cgColor
+ layer.shadowOffset = .zero
+ layer.shadowOpacity = 0.75
+ layer.shadowRadius = 1
+
+ colorView = UIView()
+ colorView.translatesAutoresizingMaskIntoConstraints = false
+ colorView.clipsToBounds = true
+ addSubview(colorView)
+
+ dragInteraction = UIDragInteraction(delegate: self)
+ dragInteraction.isEnabled = true
+ dropInteraction = UIDropInteraction(delegate: self)
+ updateDragDropInteraction()
+
+ tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTapGestureRecognizerFired(_:)))
+ tapGestureRecognizer.isEnabled = false
+ addGestureRecognizer(tapGestureRecognizer)
+
+ #if swift(>=5.3)
+ if #available(iOS 14, *) {
+ isContextMenuInteractionEnabled = true
+ }
+ #endif
+
+ #if swift(>=5.5)
+ if #available(iOS 15, *) {
+ toolTip = contextMenuTitle
+ toolTipInteraction?.delegate = self
+ }
+ #endif
+
+ NSLayoutConstraint.activate([
+ self.widthAnchor.constraint(greaterThanOrEqualToConstant: UIFloat(32)),
+ self.heightAnchor.constraint(equalTo: self.widthAnchor),
+
+ colorView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
+ colorView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
+ colorView.topAnchor.constraint(equalTo: self.topAnchor),
+ colorView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
+ ])
+ }
+
+ private func updateTapGestureRecognizer() {
+ let hasTouchUpActions = allControlEvents.contains(.touchUpInside)
+ if hasTouchUpActions {
+ accessibilityTraits.insert(.button)
+ } else {
+ accessibilityTraits.remove(.button)
+ }
+ tapGestureRecognizer.isEnabled = hasTouchUpActions
+ }
+
+ private func updateBorderColor() {
+ layer.borderColor = borderColor?.cgColor
+ }
+
+ private func updateDragDropInteraction() {
+ isUserInteractionEnabled = isDragInteractionEnabled || isDropInteractionEnabled
+ #if swift(>=5.3)
+ if #available(iOS 14, *) {
+ isUserInteractionEnabled = isUserInteractionEnabled || isContextMenuInteractionEnabled
+ }
+ #endif
+
+ if isDragInteractionEnabled {
+ addInteraction(dragInteraction)
+ } else {
+ removeInteraction(dragInteraction)
+ }
+
+ if isDropInteractionEnabled {
+ addInteraction(dropInteraction)
+ } else {
+ removeInteraction(dropInteraction)
+ }
+ }
+
+ /// :nodoc:
+ override open func layoutSubviews() {
+ super.layoutSubviews()
+
+ layer.cornerRadius = frame.size.width / 2
+ layer.shadowPath = CGPath(ellipseIn: bounds, transform: nil)
+ colorView.layer.cornerRadius = layer.cornerRadius
+ }
+
+ /// :nodoc:
+ override open func didMoveToWindow() {
+ super.didMoveToWindow()
+ let scale = window?.screen.scale ?? 1
+ layer.borderWidth = (scale > 2 ? 2 : 1) / scale
+ }
+
+ /// :nodoc:
+ override open func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
+ super.traitCollectionDidChange(previousTraitCollection)
+ updateBorderColor()
+ }
+
+ /// :nodoc:
+ open override func addTarget(_ target: Any?, action: Selector, for controlEvents: UIControl.Event) {
+ super.addTarget(target, action: action, for: controlEvents)
+ updateTapGestureRecognizer()
+ }
+
+ /// :nodoc:
+ @available(iOS 14, macCatalyst 14, *)
+ open override func addAction(_ action: UIAction, for controlEvents: UIControl.Event) {
+ super.addAction(action, for: controlEvents)
+ updateTapGestureRecognizer()
+ }
+
+ /// :nodoc:
+ open override func removeTarget(_ target: Any?, action: Selector?, for controlEvents: UIControl.Event) {
+ super.removeTarget(target, action: action, for: controlEvents)
+ updateTapGestureRecognizer()
+ }
+
+ /// :nodoc:
+ @available(iOS 14, macCatalyst 14, *)
+ open override func removeAction(_ action: UIAction, for controlEvents: UIControl.Event) {
+ super.removeAction(action, for: controlEvents)
+ updateTapGestureRecognizer()
+ }
+
+ /// :nodoc:
+ @available(iOS 14, macCatalyst 14, *)
+ open override func removeAction(identifiedBy actionIdentifier: UIAction.Identifier, for controlEvents: UIControl.Event) {
+ super.removeAction(identifiedBy: actionIdentifier, for: controlEvents)
+ updateTapGestureRecognizer()
+ }
+
+ @objc private func handleTapGestureRecognizerFired(_ sender: UITapGestureRecognizer) {
+ if sender.state == .ended {
+ sendActions(for: .touchUpInside)
+ }
+ }
+
+}
+
+/// :nodoc:
+extension ColorWell { // UIResponder
+
+ open override var canBecomeFirstResponder: Bool { true }
+
+ open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
+ switch action {
+ case #selector(copy(_:)), #selector(copyHex), #selector(copyRGB), #selector(copyHSL),
+ #selector(copyObjC), #selector(copySwift):
+ return color != nil
+
+ case #selector(paste(_:)):
+ return UIPasteboard.general.hasColors || UIPasteboard.general.hasStrings
+
+ default:
+ return super.canPerformAction(action, withSender: sender)
+ }
+ }
+
+ open override func copy(_ sender: Any?) {
+ UIPasteboard.general.color = color
+ }
+
+ open override func paste(_ sender: Any?) {
+ if let color = UIPasteboard.general.color ?? UIColor(propertyListValue: UIPasteboard.general.string ?? "") {
+ self.color = color
+ sendActions(for: .valueChanged)
+ }
+ }
+
+ @objc private func copyHex(_ sender: Any?) {
+ UIPasteboard.general.string = Color(uiColor: color!).hexString
+ }
+
+ @objc private func copyRGB(_ sender: Any?) {
+ UIPasteboard.general.string = Color(uiColor: color!).rgbString
+ }
+
+ @objc private func copyHSL(_ sender: Any?) {
+ UIPasteboard.general.string = Color(uiColor: color!).hslString
+ }
+
+ @objc private func copyObjC(_ sender: Any?) {
+ UIPasteboard.general.string = Color(uiColor: color!).objcString
+ }
+
+ @objc private func copySwift(_ sender: Any?) {
+ UIPasteboard.general.string = Color(uiColor: color!).swiftString
+ }
+
+}
+
+/// :nodoc:
+extension ColorWell: UIDragInteractionDelegate {
+
+ /// :nodoc:
+ public func dragInteraction(_ interaction: UIDragInteraction, itemsForBeginning session: UIDragSession) -> [UIDragItem] {
+ guard let color = color else {
+ return []
+ }
+ let provider = NSItemProvider(object: color)
+ let item = UIDragItem(itemProvider: provider)
+ item.localObject = color
+ return [item]
+ }
+
+}
+
+/// :nodoc:
+extension ColorWell: UIDropInteractionDelegate {
+
+ /// :nodoc:
+ public func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool {
+ return session.items.count == 1 && session.canLoadObjects(ofClass: UIColor.self)
+ }
+
+ /// :nodoc:
+ public func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal {
+ return UIDropProposal(operation: .copy)
+ }
+
+ /// :nodoc:
+ public func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) {
+ session.loadObjects(ofClass: UIColor.self) { items in
+ if let color = items.first as? UIColor {
+ self.color = color
+ self.sendActions(for: .valueChanged)
+ }
+ }
+ }
+
+}
+
+#if swift(>=5.5)
+/// :nodoc:
+@available(iOS 15, *)
+extension ColorWell: UIToolTipInteractionDelegate {
+
+ /// :nodoc:
+ public func toolTipInteraction(_ interaction: UIToolTipInteraction, configurationAt point: CGPoint) -> UIToolTipConfiguration? {
+ UIToolTipConfiguration(toolTip: contextMenuTitle)
+ }
+
+}
+#endif
+
+#if swift(>=5.3)
+/// :nodoc:
+@available(iOS 13, *)
+extension ColorWell { // UIContextMenuInteractionDelegate
+
+ /// :nodoc:
+ open override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
+ return UIContextMenuConfiguration(identifier: color, previewProvider: nil) { items in
+ var children = [UIMenuElement]()
+ if isCatalyst {
+ children += [
+ UIMenu(title: "", options: .displayInline, children: [
+ UICommand(title: self.contextMenuTitle,
+ action: #selector(self.doesNotRecognizeSelector(_:)),
+ attributes: .disabled)
+ ])
+ ]
+ }
+ children += items
+
+ var objcImageName = "chevron.left.slash.chevron.right"
+ var swiftImageName = "chevron.left.slash.chevron.right"
+ if #available(iOS 14, *) {
+ objcImageName = "curlybraces"
+ swiftImageName = "swift"
+ }
+ children += [
+ UIMenu(title: "", options: .displayInline, children: [
+ UICommand(title: "Copy as Hex",
+ image: UIImage(systemName: "number"),
+ action: #selector(self.copyHex(_:))),
+ UICommand(title: "Copy as RGB",
+ image: UIImage(systemName: "r.circle"),
+ action: #selector(self.copyRGB(_:))),
+ UICommand(title: "Copy as HSL",
+ image: UIImage(systemName: "h.circle"),
+ action: #selector(self.copyHSL(_:))),
+ UICommand(title: "Copy as Objective-C",
+ image: UIImage(systemName: objcImageName),
+ action: #selector(self.copyObjC(_:))),
+ UICommand(title: "Copy as Swift",
+ image: UIImage(systemName: swiftImageName),
+ action: #selector(self.copySwift(_:))),
+ ])
+ ]
+ return UIMenu(title: self.contextMenuTitle, children: children)
+ }
+ }
+
+}
+#endif
+
+/// Deprecated. Use `ColorWell` instead.
+@available(*, deprecated, renamed: "ColorWell")
+@objc(HBColorPickerCircleView)
+open class ColorPickerCircleView: ColorWell {}
diff --git a/Tweaks/Alderis/Alderis/DialogButton.swift b/Tweaks/Alderis/Alderis/DialogButton.swift
new file mode 100644
index 0000000..98d5ba3
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/DialogButton.swift
@@ -0,0 +1,34 @@
+//
+// DialogButton.swift
+// Alderis
+//
+// Created by Adam Demasi on 28/9/20.
+// Copyright © 2020 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+
+internal class DialogButton: UIButton {
+
+ var highlightBackgroundColor: UIColor?
+
+ init() {
+ super.init(frame: .zero)
+
+ addTarget(self, action: #selector(self.handleTouchDown), for: [.touchDown, .touchDragEnter])
+ addTarget(self, action: #selector(self.handleTouchUp), for: [.touchUpInside, .touchUpOutside, .touchDragExit, .touchCancel])
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ @objc private func handleTouchDown() {
+ backgroundColor = highlightBackgroundColor
+ }
+
+ @objc private func handleTouchUp() {
+ backgroundColor = nil
+ }
+
+}
diff --git a/Tweaks/Alderis/Alderis/GradientView.swift b/Tweaks/Alderis/Alderis/GradientView.swift
new file mode 100644
index 0000000..532112f
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/GradientView.swift
@@ -0,0 +1,18 @@
+//
+// GradientView.swift
+// Alderis
+//
+// Created by Adam Demasi on 11/5/2022.
+// Copyright © 2022 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+
+internal class GradientView: UIView {
+
+ override class var layerClass: AnyClass { CAGradientLayer.self }
+
+ // swiftlint:disable:next force_cast
+ var gradientLayer: CAGradientLayer { layer as! CAGradientLayer }
+
+}
diff --git a/Tweaks/Alderis/Alderis/Info.plist b/Tweaks/Alderis/Alderis/Info.plist
new file mode 100644
index 0000000..c0701c6
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/Info.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ $(MARKETING_VERSION)
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+
+
diff --git a/Tweaks/Alderis/Alderis/NSBeep.swift b/Tweaks/Alderis/Alderis/NSBeep.swift
new file mode 100644
index 0000000..6cf7a43
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/NSBeep.swift
@@ -0,0 +1,29 @@
+//
+// NSBeep.swift
+// Alderis
+//
+// Created by Adam Demasi on 8/5/2022.
+// Copyright © 2022 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+
+internal typealias NSBeepType = @convention(c) () -> Void
+
+internal let NSBeep: NSBeepType? = {
+ if isCatalyst,
+ let appkit = dlopen("/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit", RTLD_LAZY),
+ let beep = dlsym(appkit, "NSBeep") {
+ return unsafeBitCast(beep, to: NSBeepType.self)
+ }
+ return nil
+}()
+
+internal func beep() {
+ if isCatalyst {
+ NSBeep?()
+ } else {
+ let feedbackGenerator = UINotificationFeedbackGenerator()
+ feedbackGenerator.notificationOccurred(.error)
+ }
+}
diff --git a/Tweaks/Alderis/Alderis/SeparatorView.swift b/Tweaks/Alderis/Alderis/SeparatorView.swift
new file mode 100644
index 0000000..d279a7d
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/SeparatorView.swift
@@ -0,0 +1,60 @@
+//
+// SeparatorView.swift
+// Alderis
+//
+// Created by Adam Demasi on 12/3/20.
+// Copyright © 2020 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+
+internal class SeparatorView: UIView {
+
+ enum Direction {
+ case horizontal, vertical
+ }
+
+ var direction: Direction {
+ didSet { updateConstraints() }
+ }
+
+ private var widthConstraint: NSLayoutConstraint!
+ private var heightConstraint: NSLayoutConstraint!
+
+ init(direction: Direction) {
+ self.direction = direction
+ super.init(frame: .zero)
+
+ backgroundColor = Assets.separatorColor
+
+ widthConstraint = widthAnchor.constraint(equalToConstant: 1)
+ heightConstraint = heightAnchor.constraint(equalToConstant: 1)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func updateConstraints() {
+ super.updateConstraints()
+
+ let constant = 1 / (window?.screen.scale ?? 1)
+
+ switch direction {
+ case .horizontal:
+ widthConstraint.isActive = false
+ heightConstraint.isActive = true
+ heightConstraint.constant = constant
+ case .vertical:
+ widthConstraint.isActive = true
+ heightConstraint.isActive = false
+ widthConstraint.constant = constant
+ }
+ }
+
+ override func didMoveToWindow() {
+ super.didMoveToWindow()
+ updateConstraints()
+ }
+
+}
diff --git a/Tweaks/Alderis/Alderis/TextViewLabel.swift b/Tweaks/Alderis/Alderis/TextViewLabel.swift
new file mode 100644
index 0000000..aa539dd
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/TextViewLabel.swift
@@ -0,0 +1,86 @@
+//
+// TextViewLabel.swift
+// Alderis
+//
+// Created by Adam Demasi on 8/5/2022.
+// Copyright © 2022 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+import SafariServices
+
+internal class TextViewLabel: UITextView {
+
+ override init(frame: CGRect, textContainer: NSTextContainer?) {
+ super.init(frame: frame, textContainer: textContainer)
+
+ delegate = self
+ backgroundColor = nil
+ textContainerInset = .zero
+ self.textContainer.lineFragmentPadding = 0
+ isEditable = false
+ isScrollEnabled = false
+ adjustsFontForContentSizeCategory = true
+ }
+
+ convenience init() {
+ self.init(frame: .zero, textContainer: nil)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
+ // 🧡 to https://stackoverflow.com/a/44878203/709376 for this
+ guard super.point(inside: point, with: event),
+ let position = closestPosition(to: point),
+ let range = tokenizer.rangeEnclosingPosition(position,
+ with: .character,
+ inDirection: .layout(.left)) else {
+ return false
+ }
+ let index = offset(from: beginningOfDocument, to: range.start)
+ return attributedText.attribute(.link, at: index, effectiveRange: nil) != nil
+ }
+
+ override var selectedRange: NSRange {
+ get { NSRange() }
+ set {}
+ }
+
+ private var viewController: UIViewController? {
+ var responder: UIResponder? = self
+ while responder != nil {
+ if let viewController = responder as? UIViewController {
+ return viewController
+ }
+ responder = responder?.next
+ }
+ return nil
+ }
+
+}
+
+extension TextViewLabel: UITextViewDelegate {
+
+ func textView(_ textView: UITextView, shouldInteractWith url: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
+ #if targetEnvironment(macCatalyst)
+ // No need to do anything custom.
+ return true
+ #else
+ switch interaction {
+ case .invokeDefaultAction:
+ let safariViewController = SFSafariViewController(url: url)
+ safariViewController.modalPresentationStyle = .formSheet
+ viewController?.present(safariViewController, animated: true)
+ return false
+ case .presentActions, .preview:
+ return true
+ @unknown default:
+ return true
+ }
+ #endif
+ }
+
+}
diff --git a/Tweaks/Alderis/Alderis/UIColorAdditions.swift b/Tweaks/Alderis/Alderis/UIColorAdditions.swift
new file mode 100644
index 0000000..aa9aacd
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/UIColorAdditions.swift
@@ -0,0 +1,199 @@
+//
+// UIColorAdditions.swift
+// Alderis
+//
+// Created by Ryan Nair on 10/5/20.
+// Copyright © 2020 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+
+/// ColorPropertyListValue is a protocol representing types that can be passed to the\
+/// `UIColor.init(propertyListValue:)` initialiser. `String` and `Array` both conform to this type.
+///
+/// - see: `UIColor.init(propertyListValue:)`
+public protocol ColorPropertyListValue {}
+
+/// A string can represent a `ColorPropertyListValue`.
+///
+/// - see: `UIColor.init(propertyListValue:)`
+extension String: ColorPropertyListValue {}
+
+/// An array of integers can represent a `ColorPropertyListValue`.
+///
+/// - see: `UIColor.init(propertyListValue:)`
+extension Array: ColorPropertyListValue where Element: FixedWidthInteger {}
+
+/// Alderis provides extensions to `UIColor` for the purpose of serializing and deserializing colors
+/// into representations that can be stored in property lists, JSON, and similar formats.
+public extension UIColor {
+
+ /// Initializes and returns a color object using data from the specified object.
+ ///
+ /// The value is expected to be one of:
+ ///
+ /// * An array of 3 or 4 integer RGB or RGBA color components, with values between 0 and 255 (e.g.
+ /// `[218, 192, 222]`)
+ /// * A CSS-style hex string, with an optional alpha component (e.g. `#DAC0DE` or `#DACODE55`)
+ /// * A short CSS-style hex string, with an optional alpha component (e.g. `#DC0` or `#DC05`)
+ ///
+ /// Use `-[UIColor initWithHbcp_propertyListValue:]` to access this method from Objective-C.
+ ///
+ /// - parameter value: The object to retrieve data from. See the discussion for the supported object
+ /// types.
+ /// - returns: An initialized color object, or nil if the value does not conform to the expected
+ /// type. The color information represented by this object is in the device RGB colorspace.
+ /// - see: `propertyListValue`
+ @nonobjc convenience init?(propertyListValue: ColorPropertyListValue?) {
+ if let array = propertyListValue as? [Int], array.count == 3 || array.count == 4 {
+ let floats = array.map(CGFloat.init(_:))
+ self.init(red: floats[0] / 255,
+ green: floats[1] / 255,
+ blue: floats[2] / 255,
+ alpha: array.count == 4 ? floats[3] : 1)
+ return
+ } else if var string = propertyListValue as? String {
+ if string.count == 4 || string.count == 5 {
+ let r = String(repeating: string[string.index(string.startIndex, offsetBy: 1)], count: 2)
+ let g = String(repeating: string[string.index(string.startIndex, offsetBy: 2)], count: 2)
+ let b = String(repeating: string[string.index(string.startIndex, offsetBy: 3)], count: 2)
+ let a = string.count == 5 ? String(repeating: string[string.index(string.startIndex, offsetBy: 4)], count: 2) : "FF"
+ string = r + g + b + a
+ }
+
+ var hex: UInt64 = 0
+ let scanner = Scanner(string: string)
+ guard scanner.scanString("#", into: nil),
+ scanner.scanHexInt64(&hex) else {
+ return nil
+ }
+
+ if string.count == 9 {
+ self.init(red: CGFloat((hex & 0xFF000000) >> 24) / 255,
+ green: CGFloat((hex & 0x00FF0000) >> 16) / 255,
+ blue: CGFloat((hex & 0x0000FF00) >> 8) / 255,
+ alpha: CGFloat((hex & 0x000000FF) >> 0) / 255)
+ return
+ } else {
+ var alpha: Float = 1
+ if scanner.scanString(":", into: nil) {
+ // Continue scanning to get the alpha component.
+ scanner.scanFloat(&alpha)
+ }
+
+ self.init(red: CGFloat((hex & 0xFF0000) >> 16) / 255,
+ green: CGFloat((hex & 0x00FF00) >> 8) / 255,
+ blue: CGFloat((hex & 0x0000FF) >> 0) / 255,
+ alpha: CGFloat(alpha))
+ return
+ }
+ }
+
+ return nil
+ }
+
+ /// Maps `init(propertyListValue:)` to Objective-C due to Swift limitations. This is an
+ /// implementation detail. Ignore this and use `UIColor(propertyListValue:)` or
+ /// `-[UIColor initWithHbcp_propertyListValue:]` as per usual.
+ ///
+ /// - parameter value: The object to retrieve data from. See the discussion for the supported
+ /// object types.
+ /// - returns: An initialized color object, or nil if the value does not conform to the expected
+ /// type. The color information represented by this object is in the device RGB colorspace.
+ /// device RGB colorspace.
+ /// - see: `init(propertyListValue:)`
+ @objc(initWithHbcp_propertyListValue:)
+ convenience init?(_propertyListValueObjC propertyListValue: Any?) {
+ if let value = propertyListValue as? String {
+ self.init(propertyListValue: value)
+ } else if let value = propertyListValue as? [Int] {
+ self.init(propertyListValue: value)
+ } else {
+ return nil
+ }
+ }
+
+ /// Returns a string that represents the color.
+ ///
+ /// The output is a string in the format `#AABBCC:0.5`, where the initial `#AABBCC` portion is a
+ /// 6-character CSS-style hex string, and the final `:0.5` portion represents the alpha value. If
+ /// the color’s alpha value is `1`, the alpha portion is excluded.
+ ///
+ /// If the color is dynamic, for instance if it is a UIKit system color, or initialised via
+ /// `UIColor(dynamicProvider:)`, the color that matches the current trait collection is used.
+ ///
+ /// - returns: A string representing the color, in the format discussed above.
+ /// - see: `init(propertyListValue:)`
+ @objc(hbcp_propertyListValue)
+ var propertyListValue: String {
+ var alpha: CGFloat = 0
+ getRed(nil, green: nil, blue: nil, alpha: &alpha)
+
+ let color = Color(uiColor: self.withAlphaComponent(1))
+ let hexString = color.hexString()
+ let alphaString = alpha == 1 ? "" : String(format: ":%.5G", alpha)
+ return "\(hexString)\(alphaString)"
+ }
+
+ /// Returns a hexadecimal string that represents the color.
+ ///
+ /// The output is a string in the format `#AABBCCDD`, where the initial `#AABBCC` portion is a
+ /// 6-character CSS-style hex string, and the final `DD` portion is the hexadecimal representation
+ /// of the alpha value, supported by [recent web browsers](https://caniuse.com/css-rrggbbaa).
+ ///
+ /// If the color is dynamic, for instance if it is a UIKit system color, or initialised via
+ /// `UIColor(dynamicProvider:)`, the color that matches the current trait collection is used.
+ ///
+ /// - returns: A string representing the color, in the format discussed above.
+ @objc(hbcp_hexString)
+ var hexString: String { Color(uiColor: self).hexString }
+
+ /// Returns an RGB string that represents the color.
+ ///
+ /// The output is a string in the format of `rgba(170, 187, 204, 0.5)`, or `rgb(170, 187, 204)` if
+ /// the color’s alpha value is `1`.
+ ///
+ /// If the color is dynamic, for instance if it is a UIKit system color, or initialised via
+ /// `UIColor(dynamicProvider:)`, the color that matches the current trait collection is used.
+ ///
+ /// - returns: A string representing the color, in the format discussed above.
+ @objc(hbcp_rgbString)
+ var rgbString: String { Color(uiColor: self).rgbString }
+
+ /// Returns an HSL string that represents the color.
+ ///
+ /// The output is a string in the format of `hsla(170, 187, 204, 0.5)`, or `hsl(170, 187, 204)` if
+ /// the color’s alpha value is `1`.
+ ///
+ /// If the color is dynamic, for instance if it is a UIKit system color, or initialised via
+ /// `UIColor(dynamicProvider:)`, the color that matches the current trait collection is used.
+ ///
+ /// - returns: A string representing the color, in the format discussed above.
+ @objc(hbcp_hslString)
+ var hslString: String { Color(uiColor: self).hslString }
+
+ /// Returns an Objective-C UIColor string that represents the color.
+ ///
+ /// The output is a string in the format of
+ /// `[UIColor colorWithRed:0.667 green:0.733 blue:0.800 alpha:1.00]`.
+ ///
+ /// If the color is dynamic, for instance if it is a UIKit system color, or initialised via
+ /// `UIColor(dynamicProvider:)`, the color that matches the current trait collection is used.
+ ///
+ /// - returns: A string representing the color, in the format discussed above.
+ @objc(hbcp_objcString)
+ var objcString: String { Color(uiColor: self).objcString }
+
+ /// Returns a Swift UIColor string that represents the color.
+ ///
+ /// The output is a string in the format of
+ /// `UIColor(red: 0.667, green: 0.733, blue: 0.800, alpha: 1.00)`.
+ ///
+ /// If the color is dynamic, for instance if it is a UIKit system color, or initialised via
+ /// `UIColor(dynamicProvider:)`, the color that matches the current trait collection is used.
+ ///
+ /// - returns: A string representing the color, in the format discussed above.
+ @objc(hbcp_swiftString)
+ var swiftString: String { Color(uiColor: self).swiftString }
+
+}
diff --git a/Tweaks/Alderis/Alderis/UIFloat.swift b/Tweaks/Alderis/Alderis/UIFloat.swift
new file mode 100644
index 0000000..c2c379a
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/UIFloat.swift
@@ -0,0 +1,34 @@
+//
+// UIFloat.swift
+// Alderis
+//
+// Created by Adam Demasi on 8/5/2022.
+// Copyright © 2022 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+
+internal let isCatalyst: Bool = {
+ if #available(iOS 13, *) {
+ return ProcessInfo.processInfo.isMacCatalystApp
+ }
+ return false
+}()
+
+// Catalyst iPad mode, with iOS UI at 0.77 scale
+internal let isCatalystPad = isCatalyst && UIDevice.current.userInterfaceIdiom == .pad
+
+// Catalyst Mac mode, with Mac UI at 1.00 scale
+internal let isCatalystMac: Bool = {
+ #if swift(>=5.3)
+ if #available(iOS 14, *) {
+ return UIDevice.current.userInterfaceIdiom == .mac
+ }
+ #endif
+ return false
+}()
+
+// Inspired by https://www.highcaffeinecontent.com/blog/20220216-Where-Mac-Catalyst-Falls-Short
+internal func UIFloat(_ value: CGFloat) -> CGFloat {
+ return floor(value * (isCatalystMac ? 0.77 : 1))
+}
diff --git a/Tweaks/Alderis/Alderis/UIFontDescriptorAdditions.swift b/Tweaks/Alderis/Alderis/UIFontDescriptorAdditions.swift
new file mode 100644
index 0000000..416b31a
--- /dev/null
+++ b/Tweaks/Alderis/Alderis/UIFontDescriptorAdditions.swift
@@ -0,0 +1,32 @@
+//
+// UIFontDescriptorAdditions.swift
+// Alderis
+//
+// Created by Adam Demasi on 7/5/2022.
+// Copyright © 2022 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+
+internal extension UIFontDescriptor.FeatureKey {
+
+ // Abstracts a messy API change made in iOS 15.
+ static var alderisFeature: Self {
+ #if swift(>=5.5)
+ if #available(iOS 15, *) {
+ return .type
+ }
+ #endif
+ return .featureIdentifier
+ }
+
+ static var alderisSelector: Self {
+ #if swift(>=5.5)
+ if #available(iOS 15, *) {
+ return .selector
+ }
+ #endif
+ return .typeIdentifier
+ }
+
+}
diff --git a/Tweaks/Alderis/Demo/Alderis Demo.xcodeproj/project.pbxproj b/Tweaks/Alderis/Demo/Alderis Demo.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..dc379f3
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Alderis Demo.xcodeproj/project.pbxproj
@@ -0,0 +1,444 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 51;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 129B4F59BA5C9D60F07CE7E0 /* libPods-Alderis Demo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ABFF0FA1430DE1F1C311810B /* libPods-Alderis Demo.a */; };
+ CF39D44B222BC07E001EF57F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CF39D44A222BC07E001EF57F /* Assets.xcassets */; };
+ CF6B3B9E2446A13C000A608B /* FirstViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF6B3B9D2446A13C000A608B /* FirstViewController.swift */; };
+ CF83082423F7819A00157D3F /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CF83082323F7819A00157D3F /* Launch Screen.storyboard */; };
+ CFE70278241A593200083903 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFE70277241A593200083903 /* AppDelegate.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ CF73D33E241F9C23000B1B10 /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ 2B5DB4BF0F331C22D5DFB481 /* Pods-Alderis Demo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Alderis Demo.release.xcconfig"; path = "Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo.release.xcconfig"; sourceTree = ""; };
+ 9FD89BFF752E9D684C11E887 /* Pods-Alderis Demo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Alderis Demo.debug.xcconfig"; path = "Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo.debug.xcconfig"; sourceTree = ""; };
+ ABFF0FA1430DE1F1C311810B /* libPods-Alderis Demo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Alderis Demo.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+ CF39D43E222BC07C001EF57F /* Alderis Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Alderis Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+ CF39D44A222BC07E001EF57F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ CF39D44F222BC07E001EF57F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ CF6B3B9D2446A13C000A608B /* FirstViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstViewController.swift; sourceTree = ""; };
+ CF83082323F7819A00157D3F /* Launch Screen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = ""; };
+ CFCF774D242113EE00379864 /* AlderisDemo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AlderisDemo.entitlements; sourceTree = ""; };
+ CFE70277241A593200083903 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ CF39D43B222BC07C001EF57F /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 129B4F59BA5C9D60F07CE7E0 /* libPods-Alderis Demo.a in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 13755DEE92D2481708433C7E /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ ABFF0FA1430DE1F1C311810B /* libPods-Alderis Demo.a */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+ AB49FDA0EE3B301C0383D6FF /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ 9FD89BFF752E9D684C11E887 /* Pods-Alderis Demo.debug.xcconfig */,
+ 2B5DB4BF0F331C22D5DFB481 /* Pods-Alderis Demo.release.xcconfig */,
+ );
+ path = Pods;
+ sourceTree = "";
+ };
+ CF39D435222BC07C001EF57F = {
+ isa = PBXGroup;
+ children = (
+ CF39D440222BC07C001EF57F /* Alderis Demo */,
+ CF39D43F222BC07C001EF57F /* Products */,
+ AB49FDA0EE3B301C0383D6FF /* Pods */,
+ 13755DEE92D2481708433C7E /* Frameworks */,
+ );
+ indentWidth = 2;
+ sourceTree = "";
+ tabWidth = 2;
+ usesTabs = 1;
+ };
+ CF39D43F222BC07C001EF57F /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ CF39D43E222BC07C001EF57F /* Alderis Demo.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ CF39D440222BC07C001EF57F /* Alderis Demo */ = {
+ isa = PBXGroup;
+ children = (
+ CFCF774D242113EE00379864 /* AlderisDemo.entitlements */,
+ CF83082323F7819A00157D3F /* Launch Screen.storyboard */,
+ CFE70277241A593200083903 /* AppDelegate.swift */,
+ CF6B3B9D2446A13C000A608B /* FirstViewController.swift */,
+ CF39D44A222BC07E001EF57F /* Assets.xcassets */,
+ CF39D44F222BC07E001EF57F /* Info.plist */,
+ );
+ path = "Alderis Demo";
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ CF39D43D222BC07C001EF57F /* Alderis Demo */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = CF39D454222BC07E001EF57F /* Build configuration list for PBXNativeTarget "Alderis Demo" */;
+ buildPhases = (
+ B02FB21B4CF370106F8C5ADD /* [CP] Check Pods Manifest.lock */,
+ CF39D43A222BC07C001EF57F /* Sources */,
+ CF39D43B222BC07C001EF57F /* Frameworks */,
+ CF39D43C222BC07C001EF57F /* Resources */,
+ CF73D33E241F9C23000B1B10 /* Embed Frameworks */,
+ CF79DA2F25171E2D00F17BCB /* SwiftLint */,
+ 638C9DB382A92024C20F1301 /* [CP] Copy Pods Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = "Alderis Demo";
+ productName = "Alderis Demo";
+ productReference = CF39D43E222BC07C001EF57F /* Alderis Demo.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ CF39D436222BC07C001EF57F /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastUpgradeCheck = 1330;
+ ORGANIZATIONNAME = "HASHBANG Productions";
+ TargetAttributes = {
+ CF39D43D222BC07C001EF57F = {
+ CreatedOnToolsVersion = 10.0;
+ LastSwiftMigration = 1130;
+ };
+ };
+ };
+ buildConfigurationList = CF39D439222BC07C001EF57F /* Build configuration list for PBXProject "Alderis Demo" */;
+ compatibilityVersion = "Xcode 9.3";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = CF39D435222BC07C001EF57F;
+ productRefGroup = CF39D43F222BC07C001EF57F /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ CF39D43D222BC07C001EF57F /* Alderis Demo */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ CF39D43C222BC07C001EF57F /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ CF83082423F7819A00157D3F /* Launch Screen.storyboard in Resources */,
+ CF39D44B222BC07E001EF57F /* Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 638C9DB382A92024C20F1301 /* [CP] Copy Pods Resources */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-resources-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Copy Pods Resources";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-resources-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-resources.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ B02FB21B4CF370106F8C5ADD /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-Alderis Demo-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
+ CF79DA2F25171E2D00F17BCB /* SwiftLint */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ );
+ name = SwiftLint;
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "if which swiftlint >/dev/null; then\n\tswiftlint\nelse\n\techo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ CF39D43A222BC07C001EF57F /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ CFE70278241A593200083903 /* AppDelegate.swift in Sources */,
+ CF6B3B9E2446A13C000A608B /* FirstViewController.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ CF39D452222BC07E001EF57F /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ };
+ name = Debug;
+ };
+ CF39D453222BC07E001EF57F /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ CF39D455222BC07E001EF57F /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 9FD89BFF752E9D684C11E887 /* Pods-Alderis Demo.debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
+ ASSETCATALOG_COMPILER_SKIP_APP_STORE_DEPLOYMENT = YES;
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = "Alderis Demo/AlderisDemo.entitlements";
+ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;
+ DEVELOPMENT_TEAM = N2LN9ZT493;
+ INFOPLIST_FILE = "Alderis Demo/Info.plist";
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = ws.hbang.AlderisDemo;
+ PRODUCT_NAME = "Alderis Demo";
+ SUPPORTS_MACCATALYST = YES;
+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2,6";
+ };
+ name = Debug;
+ };
+ CF39D456222BC07E001EF57F /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 2B5DB4BF0F331C22D5DFB481 /* Pods-Alderis Demo.release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
+ ASSETCATALOG_COMPILER_SKIP_APP_STORE_DEPLOYMENT = YES;
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = "Alderis Demo/AlderisDemo.entitlements";
+ CODE_SIGN_STYLE = Automatic;
+ DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;
+ INFOPLIST_FILE = "Alderis Demo/Info.plist";
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = ws.hbang.AlderisDemo;
+ PRODUCT_NAME = "Alderis Demo";
+ SUPPORTS_MACCATALYST = YES;
+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2,6";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ CF39D439222BC07C001EF57F /* Build configuration list for PBXProject "Alderis Demo" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ CF39D452222BC07E001EF57F /* Debug */,
+ CF39D453222BC07E001EF57F /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ CF39D454222BC07E001EF57F /* Build configuration list for PBXNativeTarget "Alderis Demo" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ CF39D455222BC07E001EF57F /* Debug */,
+ CF39D456222BC07E001EF57F /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = CF39D436222BC07C001EF57F /* Project object */;
+}
diff --git a/Tweaks/Alderis/Demo/Alderis Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Tweaks/Alderis/Demo/Alderis Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..e210b76
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Alderis Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/Tweaks/Alderis/Demo/Alderis Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Tweaks/Alderis/Demo/Alderis Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Alderis Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/Tweaks/Alderis/Demo/Alderis Demo.xcodeproj/xcshareddata/xcschemes/Alderis Demo.xcscheme b/Tweaks/Alderis/Demo/Alderis Demo.xcodeproj/xcshareddata/xcschemes/Alderis Demo.xcscheme
new file mode 100644
index 0000000..24f8b16
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Alderis Demo.xcodeproj/xcshareddata/xcschemes/Alderis Demo.xcscheme
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tweaks/Alderis/Demo/Alderis Demo.xcworkspace/contents.xcworkspacedata b/Tweaks/Alderis/Demo/Alderis Demo.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..870adfc
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Alderis Demo.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/Tweaks/Alderis/Demo/Alderis Demo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Tweaks/Alderis/Demo/Alderis Demo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Alderis Demo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/Tweaks/Alderis/Demo/Alderis Demo/AlderisDemo.entitlements b/Tweaks/Alderis/Demo/Alderis Demo/AlderisDemo.entitlements
new file mode 100644
index 0000000..ee95ab7
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Alderis Demo/AlderisDemo.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.network.client
+
+
+
diff --git a/Tweaks/Alderis/Demo/Alderis Demo/AppDelegate.swift b/Tweaks/Alderis/Demo/Alderis Demo/AppDelegate.swift
new file mode 100644
index 0000000..cbd357f
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Alderis Demo/AppDelegate.swift
@@ -0,0 +1,35 @@
+//
+// AppDelegate.swift
+// Alderis Demo
+//
+// Created by Adam Demasi on 12/3/20.
+// Copyright © 2020 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+
+@UIApplicationMain
+class AppDelegate: UIResponder, UIApplicationDelegate {
+
+ var window: UIWindow?
+
+ // swiftlint:disable:next line_length
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
+ window = UIWindow(frame: UIScreen.main.bounds)
+ window!.tintColor = UIColor(hue: 0.939614, saturation: 0.811765, brightness: 0.333333, alpha: 1)
+
+ let tabBarController = UITabBarController()
+ let viewController = UINavigationController(rootViewController: FirstViewController())
+ if #available(iOS 13, *) {
+ let tabIcon = UIImage(systemName: "paintbrush.fill")?.withBaselineOffset(fromBottom: 2)
+ viewController.tabBarItem = UITabBarItem(title: "Alderis Demo", image: tabIcon, tag: 0)
+ }
+ tabBarController.viewControllers = [viewController]
+
+ window!.rootViewController = tabBarController
+ window!.makeKeyAndVisible()
+
+ return true
+ }
+
+}
diff --git a/Tweaks/Alderis/Demo/Alderis Demo/Assets.xcassets/AppIcon.appiconset/AppIcon120x120.png b/Tweaks/Alderis/Demo/Alderis Demo/Assets.xcassets/AppIcon.appiconset/AppIcon120x120.png
new file mode 100644
index 0000000..f27b7ab
Binary files /dev/null and b/Tweaks/Alderis/Demo/Alderis Demo/Assets.xcassets/AppIcon.appiconset/AppIcon120x120.png differ
diff --git a/Tweaks/Alderis/Demo/Alderis Demo/Assets.xcassets/AppIcon.appiconset/AppIcon152x152.png b/Tweaks/Alderis/Demo/Alderis Demo/Assets.xcassets/AppIcon.appiconset/AppIcon152x152.png
new file mode 100644
index 0000000..25d48de
Binary files /dev/null and b/Tweaks/Alderis/Demo/Alderis Demo/Assets.xcassets/AppIcon.appiconset/AppIcon152x152.png differ
diff --git a/Tweaks/Alderis/Demo/Alderis Demo/Assets.xcassets/AppIcon.appiconset/AppIcon167x167.png b/Tweaks/Alderis/Demo/Alderis Demo/Assets.xcassets/AppIcon.appiconset/AppIcon167x167.png
new file mode 100644
index 0000000..3d96ee3
Binary files /dev/null and b/Tweaks/Alderis/Demo/Alderis Demo/Assets.xcassets/AppIcon.appiconset/AppIcon167x167.png differ
diff --git a/Tweaks/Alderis/Demo/Alderis Demo/Assets.xcassets/AppIcon.appiconset/AppIcon180x180.png b/Tweaks/Alderis/Demo/Alderis Demo/Assets.xcassets/AppIcon.appiconset/AppIcon180x180.png
new file mode 100644
index 0000000..031e9f8
Binary files /dev/null and b/Tweaks/Alderis/Demo/Alderis Demo/Assets.xcassets/AppIcon.appiconset/AppIcon180x180.png differ
diff --git a/Tweaks/Alderis/Demo/Alderis Demo/Assets.xcassets/AppIcon.appiconset/Contents.json b/Tweaks/Alderis/Demo/Alderis Demo/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..352a52d
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Alderis Demo/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,102 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "AppIcon120x120.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "filename" : "AppIcon180x180.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "filename" : "AppIcon152x152.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "filename" : "AppIcon167x167.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Tweaks/Alderis/Demo/Alderis Demo/Assets.xcassets/Contents.json b/Tweaks/Alderis/Demo/Alderis Demo/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Alderis Demo/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Tweaks/Alderis/Demo/Alderis Demo/Base.lproj/LaunchScreen.storyboard b/Tweaks/Alderis/Demo/Alderis Demo/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..bfa3612
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Alderis Demo/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tweaks/Alderis/Demo/Alderis Demo/FirstViewController.swift b/Tweaks/Alderis/Demo/Alderis Demo/FirstViewController.swift
new file mode 100644
index 0000000..3018f73
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Alderis Demo/FirstViewController.swift
@@ -0,0 +1,321 @@
+//
+// FirstViewController.swift
+// Alderis Demo
+//
+// Created by Adam Demasi on 15/4/20.
+// Copyright © 2020 HASHBANG Productions. All rights reserved.
+//
+
+import UIKit
+import Alderis
+
+class FirstViewController: UIViewController {
+
+ private var color = UIColor(hue: 0.939614, saturation: 0.811765, brightness: 0.333333, alpha: 1)
+
+ private var colorWell: ColorWell!
+ private var uikitWell: UIView?
+
+ // swiftlint:disable:next function_body_length
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ title = "Alderis Demo"
+ if #available(iOS 13, *) {
+ view.backgroundColor = .systemBackground
+ } else {
+ view.backgroundColor = .white
+ }
+
+ #if targetEnvironment(macCatalyst)
+ navigationController?.isNavigationBarHidden = true
+ #endif
+
+ let stackView = UIStackView()
+ stackView.translatesAutoresizingMaskIntoConstraints = false
+ stackView.axis = .vertical
+ stackView.alignment = .center
+ stackView.spacing = 10
+ view.addSubview(stackView)
+
+ let mainButton = UIButton(type: .system)
+ if #available(iOS 15, *) {
+ var config = UIButton.Configuration.filled()
+ config.buttonSize = .large
+ mainButton.configuration = config
+ } else {
+ mainButton.titleLabel!.font = UIFont.systemFont(ofSize: 34, weight: .semibold)
+ }
+ mainButton.setTitle("Present", for: .normal)
+ mainButton.addTarget(self, action: #selector(self.presentColorPicker), for: .touchUpInside)
+ stackView.addArrangedSubview(mainButton)
+
+ // swiftlint:disable comma
+ let buttons: [(title: String, action: Selector)] = [
+ ("Present with customised title", #selector(presentColorPickerCustomisedTitle)),
+ ("Present with customised initial tab", #selector(presentColorPickerCustomisedInitialTab)),
+ ("Present with customised tabs", #selector(presentColorPickerCustomisedTabs)),
+ ("Present with tabs hidden", #selector(presentColorPickerNoTabs)),
+ ("Present with customised title, tabs hidden", #selector(presentColorPickerCustomisedTitleNoTabs)),
+ ("Present without alpha", #selector(presentColorPickerNoAlpha)),
+ ("Present without overriding Smart Invert", #selector(presentColorPickerNoOverrideSmartInvert)),
+ ("Present using deprecated API", #selector(presentColorPickerDeprecatedAPI)),
+ ("Present UIKit Color Picker", #selector(presentUIKitColorPicker))
+ ]
+ // swiftlint:enable comma
+
+ for item in buttons {
+ let button = UIButton(type: .system)
+ if #available(iOS 15, *) {
+ var config = UIButton.Configuration.plain()
+ config.buttonSize = .mini
+ config.macIdiomStyle = .borderlessTinted
+ button.configuration = config
+ }
+ button.setTitle(item.title, for: .normal)
+ button.addTarget(self, action: item.action, for: .touchUpInside)
+ stackView.addArrangedSubview(button)
+ }
+
+ let spacerView = UIView()
+ stackView.addArrangedSubview(spacerView)
+
+ let wellsLabel = UILabel()
+ wellsLabel.font = UIFont.preferredFont(forTextStyle: .headline)
+ wellsLabel.textAlignment = .center
+ wellsLabel.text = "Color wells (try out drag and drop!)"
+ stackView.addArrangedSubview(wellsLabel)
+
+ colorWell = ColorWell()
+ colorWell.isDragInteractionEnabled = true
+ colorWell.isDropInteractionEnabled = true
+ colorWell.addTarget(self, action: #selector(self.colorWellValueChanged(_:)), for: .valueChanged)
+ colorWell.addTarget(self, action: #selector(self.presentColorPicker), for: .touchUpInside)
+
+ let dragOrDropColorWell = ColorWell()
+ dragOrDropColorWell.isDragInteractionEnabled = true
+ dragOrDropColorWell.isDropInteractionEnabled = true
+ dragOrDropColorWell.color = .systemPurple
+
+ let nonDraggableWell = ColorWell()
+ nonDraggableWell.isDragInteractionEnabled = false
+ nonDraggableWell.isDropInteractionEnabled = true
+ nonDraggableWell.color = .systemOrange
+
+ let nonDroppableWell = ColorWell()
+ nonDroppableWell.isDragInteractionEnabled = true
+ nonDroppableWell.isDropInteractionEnabled = false
+ nonDroppableWell.color = .systemTeal
+
+ let nonDragOrDropWell = ColorWell()
+ nonDragOrDropWell.isDragInteractionEnabled = false
+ nonDragOrDropWell.isDropInteractionEnabled = false
+ nonDragOrDropWell.color = .systemGreen
+
+ let wellsStackView = UIStackView(arrangedSubviews: [colorWell,
+ dragOrDropColorWell,
+ nonDraggableWell,
+ nonDroppableWell,
+ nonDragOrDropWell])
+ wellsStackView.translatesAutoresizingMaskIntoConstraints = false
+ wellsStackView.axis = .horizontal
+ wellsStackView.alignment = .center
+ wellsStackView.spacing = 10
+
+ if #available(iOS 14, *) {
+ let uikitWell = UIColorWell()
+ uikitWell.addTarget(self, action: #selector(self.uikitColorWellValueChanged(_:)), for: .valueChanged)
+ wellsStackView.addArrangedSubview(uikitWell)
+ self.uikitWell = uikitWell
+ }
+
+ stackView.addArrangedSubview(wellsStackView)
+
+ NSLayoutConstraint.activate([
+ stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 15),
+ stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -15),
+ stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
+
+ spacerView.heightAnchor.constraint(equalToConstant: 0)
+ ])
+
+ var isMac = false
+ if #available(iOS 14, *) {
+ isMac = UIDevice.current.userInterfaceIdiom == .mac
+ }
+
+ NSLayoutConstraint.activate(wellsStackView.arrangedSubviews.flatMap { view in
+ [
+ view.widthAnchor.constraint(equalToConstant: isMac ? 24 : 32),
+ view.heightAnchor.constraint(equalTo: view.widthAnchor)
+ ]
+ })
+ }
+
+ override func viewDidAppear(_ animated: Bool) {
+ super.viewDidAppear(animated)
+
+ view.window!.tintColor = color
+ colorWell.color = color
+ if #available(iOS 14, *),
+ let uikitWell = uikitWell as? UIColorWell {
+ uikitWell.selectedColor = color
+ }
+ }
+
+ @objc func colorWellValueChanged(_ sender: ColorWell) {
+ NSLog("Color well value changed with value %@", String(describing: sender.color))
+ view.window!.tintColor = sender.color
+ if #available(iOS 14, *),
+ let uikitWell = uikitWell as? UIColorWell {
+ uikitWell.selectedColor = sender.color
+ }
+ }
+
+ @available(iOS 14, *)
+ @objc func uikitColorWellValueChanged(_ sender: UIColorWell) {
+ NSLog("UIKit color well value changed with value %@", String(describing: sender.selectedColor))
+ view.window!.tintColor = sender.selectedColor
+ colorWell.color = sender.selectedColor
+ }
+
+ @objc func presentColorPicker(_ sender: UIView) {
+ let configuration = ColorPickerConfiguration(color: color)
+ let colorPickerViewController = ColorPickerViewController(configuration: configuration)
+ colorPickerViewController.delegate = self
+ colorPickerViewController.popoverPresentationController?.sourceView = sender
+ tabBarController!.present(colorPickerViewController, animated: true)
+ }
+
+ @objc func presentColorPickerCustomisedTitle(_ sender: UIView) {
+ let configuration = ColorPickerConfiguration(color: color)
+ configuration.title = "Select an Awesome Color"
+
+ let colorPickerViewController = ColorPickerViewController(configuration: configuration)
+ colorPickerViewController.delegate = self
+ colorPickerViewController.popoverPresentationController?.sourceView = sender
+ tabBarController!.present(colorPickerViewController, animated: true)
+ }
+
+ @objc func presentColorPickerCustomisedInitialTab(_ sender: UIView) {
+ let configuration = ColorPickerConfiguration(color: color)
+ configuration.initialTab = .map
+
+ let colorPickerViewController = ColorPickerViewController(configuration: configuration)
+ colorPickerViewController.delegate = self
+ colorPickerViewController.popoverPresentationController?.sourceView = sender
+ tabBarController!.present(colorPickerViewController, animated: true)
+ }
+
+ @objc func presentColorPickerCustomisedTabs(_ sender: UIView) {
+ let configuration = ColorPickerConfiguration(color: color)
+ configuration.visibleTabs = [.map, .sliders]
+ configuration.initialTab = .sliders
+
+ let colorPickerViewController = ColorPickerViewController(configuration: configuration)
+ colorPickerViewController.delegate = self
+ colorPickerViewController.popoverPresentationController?.sourceView = sender
+ tabBarController!.present(colorPickerViewController, animated: true)
+ }
+
+ @objc func presentColorPickerNoAlpha(_ sender: UIView) {
+ let configuration = ColorPickerConfiguration(color: color.withAlphaComponent(0.5))
+ configuration.supportsAlpha = false
+
+ let colorPickerViewController = ColorPickerViewController(configuration: configuration)
+ colorPickerViewController.delegate = self
+ colorPickerViewController.popoverPresentationController?.sourceView = sender
+ tabBarController!.present(colorPickerViewController, animated: true)
+ }
+
+ @objc func presentColorPickerNoTabs(_ sender: UIView) {
+ let configuration = ColorPickerConfiguration(color: color)
+ configuration.showTabs = false
+
+ let colorPickerViewController = ColorPickerViewController(configuration: configuration)
+ colorPickerViewController.delegate = self
+ colorPickerViewController.popoverPresentationController?.sourceView = sender
+ tabBarController!.present(colorPickerViewController, animated: true)
+ }
+
+ @objc func presentColorPickerCustomisedTitleNoTabs(_ sender: UIView) {
+ let configuration = ColorPickerConfiguration(color: color)
+ configuration.title = "Select an Awesome Color"
+ configuration.showTabs = false
+
+ let colorPickerViewController = ColorPickerViewController(configuration: configuration)
+ colorPickerViewController.delegate = self
+ colorPickerViewController.popoverPresentationController?.sourceView = sender
+ tabBarController!.present(colorPickerViewController, animated: true)
+ }
+
+ @objc func presentColorPickerNoOverrideSmartInvert(_ sender: UIView) {
+ let configuration = ColorPickerConfiguration(color: color)
+ configuration.overrideSmartInvert = false
+
+ let colorPickerViewController = ColorPickerViewController(configuration: configuration)
+ colorPickerViewController.delegate = self
+ colorPickerViewController.popoverPresentationController?.sourceView = sender
+ tabBarController!.present(colorPickerViewController, animated: true)
+ }
+
+ @objc func presentColorPickerDeprecatedAPI(_ sender: UIView) {
+ let colorPickerViewController = ColorPickerViewController()
+ colorPickerViewController.delegate = self
+ colorPickerViewController.color = color
+ colorPickerViewController.popoverPresentationController?.sourceView = sender
+ tabBarController!.present(colorPickerViewController, animated: true)
+ }
+
+ @objc func presentUIKitColorPicker(_ sender: UIView) {
+ if #available(iOS 14, *) {
+ let colorPickerViewController = UIColorPickerViewController()
+ colorPickerViewController.delegate = self
+ colorPickerViewController.selectedColor = color
+ colorPickerViewController.popoverPresentationController?.sourceView = sender
+ tabBarController!.present(colorPickerViewController, animated: true)
+ } else {
+ fatalError("UIColorPickerViewController is only available as of iOS 14")
+ }
+ }
+
+}
+
+extension FirstViewController: ColorPickerDelegate {
+
+ func colorPicker(_ colorPicker: ColorPickerViewController, didSelect color: UIColor) {
+ NSLog("User selected color %@ (%@)", color.propertyListValue, String(describing: color))
+ self.color = color
+ view.window!.tintColor = color
+ colorWell.color = color
+ }
+
+ func colorPicker(_ colorPicker: ColorPickerViewController, didAccept color: UIColor) {
+ NSLog("User accepted color %@ (%@)", color.propertyListValue, String(describing: color))
+ }
+
+ func colorPickerDidCancel(_ colorPicker: ColorPickerViewController) {
+ NSLog("Color picker cancelled")
+ }
+
+}
+
+@available(iOS 14, *)
+extension FirstViewController: UIColorPickerViewControllerDelegate {
+
+ func colorPickerViewControllerDidSelectColor(_ viewController: UIColorPickerViewController) {
+ NSLog("UIKit color picker value changed with color %@ (%@)",
+ viewController.selectedColor.propertyListValue,
+ String(describing: viewController.selectedColor))
+ color = viewController.selectedColor
+ view.window!.tintColor = viewController.selectedColor
+ if let uikitWell = uikitWell as? UIColorWell {
+ uikitWell.selectedColor = viewController.selectedColor
+ }
+ }
+
+ func colorPickerViewControllerDidFinish(_ viewController: UIColorPickerViewController) {
+ NSLog("UIKit color picker finished")
+ }
+
+}
diff --git a/Tweaks/Alderis/Demo/Alderis Demo/HBColorPickerSectionHeaderView.h b/Tweaks/Alderis/Demo/Alderis Demo/HBColorPickerSectionHeaderView.h
new file mode 100644
index 0000000..33d3631
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Alderis Demo/HBColorPickerSectionHeaderView.h
@@ -0,0 +1,19 @@
+//
+// HBColorPickerSectionHeaderView.h
+// Alderis Demo
+//
+// Created by Adam Demasi on 31/3/19.
+// Copyright © 2019 HASHBANG Productions. All rights reserved.
+//
+
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface HBColorPickerSectionHeaderView : UICollectionReusableView
+
+@property (nonatomic, strong) UILabel *titleLabel;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Tweaks/Alderis/Demo/Alderis Demo/HBColorPickerSectionHeaderView.m b/Tweaks/Alderis/Demo/Alderis Demo/HBColorPickerSectionHeaderView.m
new file mode 100644
index 0000000..5bc241b
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Alderis Demo/HBColorPickerSectionHeaderView.m
@@ -0,0 +1,43 @@
+//
+// HBColorPickerSectionHeaderView.m
+// Alderis Demo
+//
+// Created by Adam Demasi on 31/3/19.
+// Copyright © 2019 HASHBANG Productions. All rights reserved.
+//
+
+#import "HBColorPickerSectionHeaderView.h"
+#import "CompactConstraint.h"
+
+@implementation HBColorPickerSectionHeaderView
+
+- (instancetype)initWithFrame:(CGRect)frame {
+ self = [super initWithFrame:frame];
+
+ if (self) {
+ _titleLabel = [[UILabel alloc] init];
+ _titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
+ _titleLabel.font = [UIFont boldSystemFontOfSize:18.f];
+ [self addSubview:_titleLabel];
+
+ [self hb_addCompactConstraints:@[
+ @"titleLabel.left = self.left + horizontalMargin",
+ @"titleLabel.right = self.right - horizontalMargin",
+ @"titleLabel.top = self.top + topMargin",
+ @"titleLabel.bottom = self.bottom - bottomMargin"
+ ]
+ metrics:@{
+ @"horizontalMargin": @15.f,
+ @"topMargin": @10.f,
+ @"bottomMargin": @0.f
+ }
+ views:@{
+ @"self": self,
+ @"titleLabel": _titleLabel
+ }];
+ }
+
+ return self;
+}
+
+@end
diff --git a/Tweaks/Alderis/Demo/Alderis Demo/HBColorPickerSwatchCell.h b/Tweaks/Alderis/Demo/Alderis Demo/HBColorPickerSwatchCell.h
new file mode 100644
index 0000000..6cb6071
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Alderis Demo/HBColorPickerSwatchCell.h
@@ -0,0 +1,17 @@
+//
+// HBColorPickerSwatchCell.h
+// Alderis Demo
+//
+// Created by Adam Demasi on 4/3/19.
+// Copyright © 2019 HASHBANG Productions. All rights reserved.
+//
+
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface HBColorPickerSwatchCell : UICollectionViewCell
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Tweaks/Alderis/Demo/Alderis Demo/HBColorPickerSwatchCell.m b/Tweaks/Alderis/Demo/Alderis Demo/HBColorPickerSwatchCell.m
new file mode 100644
index 0000000..40031a8
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Alderis Demo/HBColorPickerSwatchCell.m
@@ -0,0 +1,32 @@
+//
+// HBColorPickerSwatchCell.m
+// Alderis Demo
+//
+// Created by Adam Demasi on 4/3/19.
+// Copyright © 2019 HASHBANG Productions. All rights reserved.
+//
+
+#import "HBColorPickerSwatchCell.h"
+
+@implementation HBColorPickerSwatchCell
+
+- (instancetype)initWithFrame:(CGRect)frame {
+ self = [super initWithFrame:frame];
+
+ if (self) {
+ self.clipsToBounds = YES;
+// self.layer.cornerRadius = 22.f;
+// self.layer.borderColor = [UIColor grayColor].CGColor;
+ }
+
+ return self;
+}
+
+- (void)didMoveToWindow {
+ [super didMoveToWindow];
+
+// CGFloat scale = self.window.screen.scale ?: 1.f;
+// self.layer.borderWidth = scale > 2.f ? 2.f / scale : 1.f / scale;
+}
+
+@end
diff --git a/Tweaks/Alderis/Demo/Alderis Demo/Info.plist b/Tweaks/Alderis/Demo/Alderis Demo/Info.plist
new file mode 100644
index 0000000..247a221
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Alderis Demo/Info.plist
@@ -0,0 +1,45 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ Alderis Demo
+ CFBundleDisplayName
+ Alderis Demo
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ Launch Screen
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+
+
diff --git a/Tweaks/Alderis/Demo/Alderis Demo/Launch Screen.storyboard b/Tweaks/Alderis/Demo/Alderis Demo/Launch Screen.storyboard
new file mode 100644
index 0000000..2cb83b5
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Alderis Demo/Launch Screen.storyboard
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tweaks/Alderis/Demo/Podfile b/Tweaks/Alderis/Demo/Podfile
new file mode 100644
index 0000000..b1d2d98
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Podfile
@@ -0,0 +1,5 @@
+platform :ios, '12.0'
+
+target 'Alderis Demo' do
+ pod 'Alderis', :path => '..'
+end
diff --git a/Tweaks/Alderis/Demo/Podfile.lock b/Tweaks/Alderis/Demo/Podfile.lock
new file mode 100644
index 0000000..d737eea
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Podfile.lock
@@ -0,0 +1,16 @@
+PODS:
+ - Alderis (1.1.2)
+
+DEPENDENCIES:
+ - Alderis (from `..`)
+
+EXTERNAL SOURCES:
+ Alderis:
+ :path: ".."
+
+SPEC CHECKSUMS:
+ Alderis: 847a404817e585ac7dae36904d7591405520534f
+
+PODFILE CHECKSUM: ec7a2ee9a64089a4374e61824363ea96330e7a8a
+
+COCOAPODS: 1.11.3
diff --git a/Tweaks/Alderis/Demo/Pods/Headers/Private/Alderis/Alderis.h b/Tweaks/Alderis/Demo/Pods/Headers/Private/Alderis/Alderis.h
new file mode 120000
index 0000000..8bf4bb3
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Headers/Private/Alderis/Alderis.h
@@ -0,0 +1 @@
+../../../../../Alderis/Alderis.h
\ No newline at end of file
diff --git a/Tweaks/Alderis/Demo/Pods/Headers/Private/Alderis/AlderisSDKCompatibility.h b/Tweaks/Alderis/Demo/Pods/Headers/Private/Alderis/AlderisSDKCompatibility.h
new file mode 120000
index 0000000..3e24d10
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Headers/Private/Alderis/AlderisSDKCompatibility.h
@@ -0,0 +1 @@
+../../../../../Alderis/AlderisSDKCompatibility.h
\ No newline at end of file
diff --git a/Tweaks/Alderis/Demo/Pods/Headers/Public/Alderis/Alderis-umbrella.h b/Tweaks/Alderis/Demo/Pods/Headers/Public/Alderis/Alderis-umbrella.h
new file mode 120000
index 0000000..f7d2fbd
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Headers/Public/Alderis/Alderis-umbrella.h
@@ -0,0 +1 @@
+../../../Target Support Files/Alderis/Alderis-umbrella.h
\ No newline at end of file
diff --git a/Tweaks/Alderis/Demo/Pods/Headers/Public/Alderis/Alderis.h b/Tweaks/Alderis/Demo/Pods/Headers/Public/Alderis/Alderis.h
new file mode 120000
index 0000000..8bf4bb3
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Headers/Public/Alderis/Alderis.h
@@ -0,0 +1 @@
+../../../../../Alderis/Alderis.h
\ No newline at end of file
diff --git a/Tweaks/Alderis/Demo/Pods/Headers/Public/Alderis/Alderis.modulemap b/Tweaks/Alderis/Demo/Pods/Headers/Public/Alderis/Alderis.modulemap
new file mode 120000
index 0000000..03e68ee
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Headers/Public/Alderis/Alderis.modulemap
@@ -0,0 +1 @@
+../../../Target Support Files/Alderis/Alderis.modulemap
\ No newline at end of file
diff --git a/Tweaks/Alderis/Demo/Pods/Headers/Public/Alderis/AlderisSDKCompatibility.h b/Tweaks/Alderis/Demo/Pods/Headers/Public/Alderis/AlderisSDKCompatibility.h
new file mode 120000
index 0000000..3e24d10
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Headers/Public/Alderis/AlderisSDKCompatibility.h
@@ -0,0 +1 @@
+../../../../../Alderis/AlderisSDKCompatibility.h
\ No newline at end of file
diff --git a/Tweaks/Alderis/Demo/Pods/Local Podspecs/Alderis.podspec.json b/Tweaks/Alderis/Demo/Pods/Local Podspecs/Alderis.podspec.json
new file mode 100644
index 0000000..8230522
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Local Podspecs/Alderis.podspec.json
@@ -0,0 +1,33 @@
+{
+ "name": "Alderis",
+ "version": "1.1.2",
+ "summary": "A fresh new color picker, with a gentle, fun, and dead simple user interface.",
+ "description": "Alderis is a fresh new color picker, with a gentle, fun, and dead simple user\ninterface. It aims to incorporate the usual elements of a color picker, in a way\nthat users will find easy and fun to use.\n\nThe user can start by selecting a color they like on the initial color palette\ntab, and either accept it, or refine it using the color wheel and adjustment\nsliders found on the two other tabs.",
+ "homepage": "https://github.com/hbang/Alderis",
+ "screenshots": [
+ "https://github.com/hbang/Alderis/raw/main/screenshots/alderis-1.jpg",
+ "https://github.com/hbang/Alderis/raw/main/screenshots/alderis-2.jpg",
+ "https://github.com/hbang/Alderis/raw/main/screenshots/alderis-3.jpg",
+ "https://github.com/hbang/Alderis/raw/main/screenshots/alderis-4.jpg"
+ ],
+ "license": "Apache License, Version 2.0",
+ "authors": "HASHBANG Productions",
+ "social_media_url": "https://twitter.com/hashbang",
+ "swift_versions": "5.0",
+ "platforms": {
+ "ios": "12.0"
+ },
+ "source": {
+ "git": "https://github.com/hbang/Alderis.git",
+ "tag": "1.1.2"
+ },
+ "requires_arc": true,
+ "source_files": [
+ "Alderis/*.swift",
+ "Alderis/*.h"
+ ],
+ "resource_bundles": {
+ "Alderis": "Alderis/Assets-ios12.xcassets"
+ },
+ "swift_version": "5.0"
+}
diff --git a/Tweaks/Alderis/Demo/Pods/Manifest.lock b/Tweaks/Alderis/Demo/Pods/Manifest.lock
new file mode 100644
index 0000000..d737eea
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Manifest.lock
@@ -0,0 +1,16 @@
+PODS:
+ - Alderis (1.1.2)
+
+DEPENDENCIES:
+ - Alderis (from `..`)
+
+EXTERNAL SOURCES:
+ Alderis:
+ :path: ".."
+
+SPEC CHECKSUMS:
+ Alderis: 847a404817e585ac7dae36904d7591405520534f
+
+PODFILE CHECKSUM: ec7a2ee9a64089a4374e61824363ea96330e7a8a
+
+COCOAPODS: 1.11.3
diff --git a/Tweaks/Alderis/Demo/Pods/Pods.xcodeproj/project.pbxproj b/Tweaks/Alderis/Demo/Pods/Pods.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..07d1a0c
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Pods.xcodeproj/project.pbxproj
@@ -0,0 +1,922 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 51;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 05103D0E4FCCCF239AD0A0B6BC8BF438 /* AlderisSDKCompatibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 33AF563198A93F314C97104EA7E89655 /* AlderisSDKCompatibility.h */; settings = {ATTRIBUTES = (Project, ); }; };
+ 0D8BF7C4464C83EF0B028B01A78AEDD6 /* ColorPickerAccessibilityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 895BC4B0779E64166AAFB40F10B8C78C /* ColorPickerAccessibilityViewController.swift */; };
+ 1249F0A1123D8CD9B892BBC474175825 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98BA600B082CA0F5CE446945D05C3FED /* Color.swift */; };
+ 135D3BAA68E17A0AC6B3667951342ED9 /* ColorPickerConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E33FF69927289707F0B298D6590DE7F /* ColorPickerConfiguration.swift */; };
+ 1EAAE13CA289E1465B194A38124C4D5F /* ColorWell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F57C4296EB375AC2FAD236F6F8D89677 /* ColorWell.swift */; };
+ 2939CFC7F4E151B8BA170E2718FE5C2D /* ColorPickerInnerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE38B60A89EFE92ABBBD9B9DC6B76B9E /* ColorPickerInnerViewController.swift */; };
+ 37F5ABFCFA0B0D72CAEFBDFA3B5ED787 /* Alderis-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 97CE419BD858D6FE5C2D08A274A5FCC4 /* Alderis-umbrella.h */; settings = {ATTRIBUTES = (Project, ); }; };
+ 4CB88CD902C9400106C9CDC4C3BADDD9 /* Alderis.h in Headers */ = {isa = PBXBuildFile; fileRef = CA71B8BBD169668C8D0BC006FB4A5EBB /* Alderis.h */; settings = {ATTRIBUTES = (Project, ); }; };
+ 50DCDD2604D2D2195B7FE86549C8C60A /* ColorPickerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEE27FA6F4BFD5E6412B78F11FE678C3 /* ColorPickerDelegate.swift */; };
+ 53966AFCE54B78B4BEFD48FF19423AC2 /* TextViewLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 412F9DA6B91118873136801E797D0D53 /* TextViewLabel.swift */; };
+ 5B575D82A33FC6ADA46F8463ECD56545 /* ColorPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7EF72B33574BD440D1C8476026F3DEE /* ColorPickerViewController.swift */; };
+ 612EFA95A4DD8D4015CF8FA9A1878399 /* ColorPickerSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA93ECBA0BF9F1EDB0DF41B1F6BB122 /* ColorPickerSlider.swift */; };
+ 61BC805E3601DBC8943B104CE9DAB27D /* BottomSheetTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C58F11F0D4691B733DF3C64E9CAA22C /* BottomSheetTransition.swift */; };
+ 6309E88FA19CA2C7E1397C854EF70D66 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90A7B12A211D20F3215B791362C4A167 /* Assets.swift */; };
+ 65FF5EEC743492FF458C8C511687895F /* Alderis-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 25FF3569DF1635C4D4EFDBE3EC713871 /* Alderis-dummy.m */; };
+ 675572C2B585A87F28A077B811DA8FA5 /* ColorPickerWheelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38BF69FA9AA99E27F929AA33A622BC50 /* ColorPickerWheelView.swift */; };
+ 69E9416DB84F9C1578C79463C00F1034 /* AccessibilityComplianceLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CB3607C30014F8358C3875AF8F0A7C3 /* AccessibilityComplianceLabel.swift */; };
+ 6A38E3A03B08C4DB4E72A35AF6C42293 /* SeparatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6656E3EB483BC4EFB619B8B7DAA8A9D9 /* SeparatorView.swift */; };
+ 7AE2A9360F7B1AFEF18CA72FAB8E47CA /* DialogButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57A8871167DE887D9D09AAEFB7FF3A37 /* DialogButton.swift */; };
+ 7C15B0F74C21E30A6D2FE833A039C6A5 /* ColorPickerSlidersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22A1DF5EB1BEC3637C580A1C8270A196 /* ColorPickerSlidersViewController.swift */; };
+ 815F9C399ED6E92FB5C7CBA666339C43 /* UIFontDescriptorAdditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABB5DB2028B61124D7915C993C57531 /* UIFontDescriptorAdditions.swift */; };
+ 854293819B45401802BEBE4DAE9593D5 /* AccessibilityContrastSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B1B52CBBF19B2BB0FA72B98D54EB376 /* AccessibilityContrastSelector.swift */; };
+ 8E81254A16B7C247CF0DEBA561FFFBBF /* Pods-Alderis Demo-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E0B28B43B7BC45127DE1EA921314EAA /* Pods-Alderis Demo-dummy.m */; };
+ A27A1ADC9991A8C08AE350860C1B4894 /* NSBeep.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09DB8593FFDFFE77DA2BC61903E23688 /* NSBeep.swift */; };
+ A4B55DD8DF7981D5348FED7150137FD4 /* ColorPickerMapSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CA6996BA5910AA62E840ECDAC8A311D /* ColorPickerMapSlider.swift */; };
+ B07A5681AD8C8E562F7768F8687EA98D /* ColorPickerNumericSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04C96783D143FBCCA45297A1B50D52C6 /* ColorPickerNumericSlider.swift */; };
+ B622882FF8E7B4CCFFB32813BDCB2D34 /* UIFloat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 193C648A76B81FC98BAD74C9D92EE779 /* UIFloat.swift */; };
+ BA43D3FA7CD5A3F1F61BB205626EE7F7 /* UIColorAdditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F684AF5C41A475CC3FB2946E5DF2ED /* UIColorAdditions.swift */; };
+ BC1230C95F458765C8CEBDF80109609B /* ColorPickerSwatchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFE5AC5F8B2C3F386620685EAF5D2739 /* ColorPickerSwatchViewController.swift */; };
+ D951C553E2937D74043A298EFD7BC0BA /* Pods-Alderis Demo-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 16A2838DE41B9772EA81132CE1FE138B /* Pods-Alderis Demo-umbrella.h */; settings = {ATTRIBUTES = (Project, ); }; };
+ DCF8E0093062AD806401B93579A00946 /* Assets-ios12.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4BDC12530AEE0050A48E2C0437FFE7BA /* Assets-ios12.xcassets */; };
+ E5F02AF27C52B7EEDFA8F5CE568058FC /* ColorPickerMapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32455B59318B845D222D6DD4C86D0F14 /* ColorPickerMapViewController.swift */; };
+ EB32A80054921FB5EEECD0B2C1BA1FB8 /* GradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EDAAEA299AE2C5EB2BB7172634E896 /* GradientView.swift */; };
+ F8144CFF31B54A06D202B96AA7FA0CF1 /* ColorPickerTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B1135B4A039DE8DAA72EEA1D127815 /* ColorPickerTabViewController.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 406A8A6B603CDE598EAB01818A637160 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = E06BFBDAEB84B8EBA0D1A8B43D16EF27;
+ remoteInfo = "Alderis-Alderis";
+ };
+ EE5F7F514D1321804708AF70F97259CC /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 0BF6D5F4E6EC1CF170610CBAB0F6CEF2;
+ remoteInfo = Alderis;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+ 02B116C65675DAEA942559AE27E67F15 /* highlight.css */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.css; name = highlight.css; path = docs/css/highlight.css; sourceTree = ""; };
+ 02F847ACA8E7B867DD5935F065F04DD4 /* jazzy.js */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.javascript; name = jazzy.js; path = docs/js/jazzy.js; sourceTree = ""; };
+ 04C96783D143FBCCA45297A1B50D52C6 /* ColorPickerNumericSlider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ColorPickerNumericSlider.swift; path = Alderis/ColorPickerNumericSlider.swift; sourceTree = ""; };
+ 08E8A13AC29E80A401DF99D68805957A /* Alderis-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Alderis-prefix.pch"; sourceTree = ""; };
+ 0974F2B8473A65FEDBC4983C7BE0D053 /* UI Components.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html.documentation; name = "UI Components.html"; path = "docs/docsets/Alderis.docset/Contents/Resources/Documents/UI Components.html"; sourceTree = ""; };
+ 09DB8593FFDFFE77DA2BC61903E23688 /* NSBeep.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NSBeep.swift; path = Alderis/NSBeep.swift; sourceTree = ""; };
+ 0BB90EF60C711FCDCD835AC9AB34EF94 /* dash.png */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.png; name = dash.png; path = docs/docsets/Alderis.docset/Contents/Resources/Documents/img/dash.png; sourceTree = ""; };
+ 0E81A451910A2F39C165935A61188DD7 /* Alderis */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; name = Alderis; path = libAlderis.a; sourceTree = BUILT_PRODUCTS_DIR; };
+ 0EBF7EA81D2DEA250A5A1C0522E8D7A3 /* jazzy.search.js */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.javascript; name = jazzy.search.js; path = docs/docsets/Alderis.docset/Contents/Resources/Documents/js/jazzy.search.js; sourceTree = ""; };
+ 11B1135B4A039DE8DAA72EEA1D127815 /* ColorPickerTabViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ColorPickerTabViewController.swift; path = Alderis/ColorPickerTabViewController.swift; sourceTree = ""; };
+ 12CF8765DD7A138CB9A12850F4F4AF9A /* alderis-2.jpg */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.jpeg; name = "alderis-2.jpg"; path = "docs/screenshots/alderis-2.jpg"; sourceTree = ""; };
+ 135BC3D71ADFA71F8713F5F29FB7D3D2 /* ColorPickerTab.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html.documentation; name = ColorPickerTab.html; path = docs/docsets/Alderis.docset/Contents/Resources/Documents/Enums/ColorPickerTab.html; sourceTree = ""; };
+ 16A2838DE41B9772EA81132CE1FE138B /* Pods-Alderis Demo-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-Alderis Demo-umbrella.h"; sourceTree = ""; };
+ 193C648A76B81FC98BAD74C9D92EE779 /* UIFloat.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIFloat.swift; path = Alderis/UIFloat.swift; sourceTree = ""; };
+ 2073961377AA380E66ADD8B2B1414B70 /* logo.jpg */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.jpeg; name = logo.jpg; path = docs/docsets/Alderis.docset/Contents/Resources/Documents/screenshots/logo.jpg; sourceTree = ""; };
+ 2100D41A9E3FA821A631CC2A8231B340 /* carat.png */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.png; name = carat.png; path = docs/img/carat.png; sourceTree = ""; };
+ 229FF675CBC5FBD3E5814D3A434BEE86 /* logo.jpg */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.jpeg; name = logo.jpg; path = docs/screenshots/logo.jpg; sourceTree = ""; };
+ 22A1DF5EB1BEC3637C580A1C8270A196 /* ColorPickerSlidersViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ColorPickerSlidersViewController.swift; path = Alderis/ColorPickerSlidersViewController.swift; sourceTree = ""; };
+ 2393CC4938AC17F8A44EA2F5850A9F7C /* lunr.min.js */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.javascript; name = lunr.min.js; path = docs/docsets/Alderis.docset/Contents/Resources/Documents/js/lunr.min.js; sourceTree = ""; };
+ 2442B5DB4407D4A13C865A36A51ECE46 /* Pods-Alderis Demo-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-Alderis Demo-acknowledgements.plist"; sourceTree = ""; };
+ 25FF3569DF1635C4D4EFDBE3EC713871 /* Alderis-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Alderis-dummy.m"; sourceTree = ""; };
+ 2CB3607C30014F8358C3875AF8F0A7C3 /* AccessibilityComplianceLabel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AccessibilityComplianceLabel.swift; path = Alderis/AccessibilityComplianceLabel.swift; sourceTree = ""; };
+ 3176D00931C79BF799AF1F6717276229 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = Info.plist; path = docs/docsets/Alderis.docset/Contents/Info.plist; sourceTree = ""; };
+ 32455B59318B845D222D6DD4C86D0F14 /* ColorPickerMapViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ColorPickerMapViewController.swift; path = Alderis/ColorPickerMapViewController.swift; sourceTree = ""; };
+ 32B2DF317783C03E472F09C6A18A2137 /* Deprecated.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html.documentation; name = Deprecated.html; path = docs/docsets/Alderis.docset/Contents/Resources/Documents/Deprecated.html; sourceTree = ""; };
+ 33AF563198A93F314C97104EA7E89655 /* AlderisSDKCompatibility.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = AlderisSDKCompatibility.h; path = Alderis/AlderisSDKCompatibility.h; sourceTree = ""; };
+ 346A89CF7100B245C0D8FFBB735BA330 /* alderis-1.jpg */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.jpeg; name = "alderis-1.jpg"; path = "docs/screenshots/alderis-1.jpg"; sourceTree = ""; };
+ 34B81EA50C950B2A5BB60792DF6C2321 /* alderis-4.jpg */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.jpeg; name = "alderis-4.jpg"; path = "docs/docsets/Alderis.docset/Contents/Resources/Documents/screenshots/alderis-4.jpg"; sourceTree = ""; };
+ 36E2384C9860D24702DA564950E42638 /* ColorPickerViewController.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html.documentation; name = ColorPickerViewController.html; path = docs/docsets/Alderis.docset/Contents/Resources/Documents/Classes/ColorPickerViewController.html; sourceTree = ""; };
+ 38BF69FA9AA99E27F929AA33A622BC50 /* ColorPickerWheelView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ColorPickerWheelView.swift; path = Alderis/ColorPickerWheelView.swift; sourceTree = ""; };
+ 390A3C384514B2A881E2611AA3A7AFAF /* Guides.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html.documentation; name = Guides.html; path = docs/docsets/Alderis.docset/Contents/Resources/Documents/Guides.html; sourceTree = ""; };
+ 39151D3F804E2ACE7EB4A41F0519640F /* preference-bundles.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html.documentation; name = "preference-bundles.html"; path = "docs/docsets/Alderis.docset/Contents/Resources/Documents/preference-bundles.html"; sourceTree = ""; };
+ 39CCC3F183B912E6298F07E239499149 /* typeahead.jquery.js */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.javascript; name = typeahead.jquery.js; path = docs/docsets/Alderis.docset/Contents/Resources/Documents/js/typeahead.jquery.js; sourceTree = ""; };
+ 3BE1332117E0FB3058648B05E510FC05 /* jazzy.search.js */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.javascript; name = jazzy.search.js; path = docs/js/jazzy.search.js; sourceTree = ""; };
+ 3F26A160257BC65D007E7AC9351E1F46 /* Pods-Alderis Demo.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-Alderis Demo.modulemap"; sourceTree = ""; };
+ 408B7D127DAD5D88CCD0B96EBF7E1740 /* jazzy.css */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.css; name = jazzy.css; path = docs/docsets/Alderis.docset/Contents/Resources/Documents/css/jazzy.css; sourceTree = ""; };
+ 412F9DA6B91118873136801E797D0D53 /* TextViewLabel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TextViewLabel.swift; path = Alderis/TextViewLabel.swift; sourceTree = ""; };
+ 426F13D795E5EEB02FFB85344C6E177B /* alderis-4.jpg */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.jpeg; name = "alderis-4.jpg"; path = "docs/screenshots/alderis-4.jpg"; sourceTree = ""; };
+ 427B5E705542B1CB268BA88393B7E81C /* typeahead.jquery.js */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.javascript; name = typeahead.jquery.js; path = docs/js/typeahead.jquery.js; sourceTree = ""; };
+ 42D5D9AC25A2A1829039F043ACC13982 /* UI Components.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html; name = "UI Components.html"; path = "docs/UI Components.html"; sourceTree = ""; };
+ 4339F8A837B317D1831C93FD9779103A /* Pods-Alderis Demo */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; name = "Pods-Alderis Demo"; path = "libPods-Alderis Demo.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 456BDD346100CE3A8518F4826FB3407E /* UIColor.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html.documentation; name = UIColor.html; path = docs/docsets/Alderis.docset/Contents/Resources/Documents/Extensions/UIColor.html; sourceTree = ""; };
+ 46B301E1CAEEF9AC54CE7AB98AE94FF8 /* jazzy.js */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.javascript; name = jazzy.js; path = docs/docsets/Alderis.docset/Contents/Resources/Documents/js/jazzy.js; sourceTree = ""; };
+ 490D4F0EB791E3F5DCFE8DA18519BCE9 /* ColorPickerViewController.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html; name = ColorPickerViewController.html; path = docs/Classes/ColorPickerViewController.html; sourceTree = ""; };
+ 4ABB5DB2028B61124D7915C993C57531 /* UIFontDescriptorAdditions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIFontDescriptorAdditions.swift; path = Alderis/UIFontDescriptorAdditions.swift; sourceTree = ""; };
+ 4BDC12530AEE0050A48E2C0437FFE7BA /* Assets-ios12.xcassets */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = folder.assetcatalog; name = "Assets-ios12.xcassets"; path = "Alderis/Assets-ios12.xcassets"; sourceTree = ""; };
+ 4D7238C16049AC7D52380A3F0F037DA1 /* Alderis.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Alderis.release.xcconfig; sourceTree = ""; };
+ 4DAF9BE7C45EEC6325555273285DBCFF /* Pods-Alderis Demo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-Alderis Demo.debug.xcconfig"; sourceTree = ""; };
+ 56A04476FF0C6D9343BC0479260BF23C /* spinner.gif */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.gif; name = spinner.gif; path = docs/docsets/Alderis.docset/Contents/Resources/Documents/img/spinner.gif; sourceTree = ""; };
+ 56C95B256105E52E1EBED49808BA072D /* gh.png */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.png; name = gh.png; path = docs/img/gh.png; sourceTree = ""; };
+ 57A8871167DE887D9D09AAEFB7FF3A37 /* DialogButton.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DialogButton.swift; path = Alderis/DialogButton.swift; sourceTree = ""; };
+ 5BFEDA5074456612DCD226636CB27A97 /* ColorPickerDelegate.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html.documentation; name = ColorPickerDelegate.html; path = docs/docsets/Alderis.docset/Contents/Resources/Documents/Protocols/ColorPickerDelegate.html; sourceTree = ""; };
+ 5C53194D55F86B1096691514B424C4B6 /* ColorPickerDelegate.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html; name = ColorPickerDelegate.html; path = docs/Protocols/ColorPickerDelegate.html; sourceTree = ""; };
+ 5DFA99D99F720D80A8C0F3CBA36A6722 /* alderis-2.jpg */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.jpeg; name = "alderis-2.jpg"; path = "docs/docsets/Alderis.docset/Contents/Resources/Documents/screenshots/alderis-2.jpg"; sourceTree = ""; };
+ 6085501632610BFE778FFF92DF0A5748 /* preference-bundles.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html; name = "preference-bundles.html"; path = "docs/preference-bundles.html"; sourceTree = ""; };
+ 60B41A93CBFB859E0CC522EAEF362A58 /* ColorPickerConfiguration.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html; name = ColorPickerConfiguration.html; path = docs/Classes/ColorPickerConfiguration.html; sourceTree = ""; };
+ 61B0B4CC82C04F8049F6D13378E10D29 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
+ 61BCC763200E79B1A67858928D3BFF6D /* dash.png */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.png; name = dash.png; path = docs/img/dash.png; sourceTree = ""; };
+ 655E41783F73F7420DA545011AE41E59 /* Alderis.tgz */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file; name = Alderis.tgz; path = docs/docsets/Alderis.tgz; sourceTree = ""; };
+ 65F7A4EE3B7BC951DCA59097E498A594 /* gh.png */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.png; name = gh.png; path = docs/docsets/Alderis.docset/Contents/Resources/Documents/img/gh.png; sourceTree = ""; };
+ 6656E3EB483BC4EFB619B8B7DAA8A9D9 /* SeparatorView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SeparatorView.swift; path = Alderis/SeparatorView.swift; sourceTree = ""; };
+ 6B1B52CBBF19B2BB0FA72B98D54EB376 /* AccessibilityContrastSelector.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AccessibilityContrastSelector.swift; path = Alderis/AccessibilityContrastSelector.swift; sourceTree = ""; };
+ 6E0B28B43B7BC45127DE1EA921314EAA /* Pods-Alderis Demo-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-Alderis Demo-dummy.m"; sourceTree = ""; };
+ 6E11449DA1AF7B8354272EA71429E6DF /* search.json */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.json; name = search.json; path = docs/search.json; sourceTree = ""; };
+ 6FE4AFB52F6E21D973EB81FA5D47DE99 /* spinner.gif */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.gif; name = spinner.gif; path = docs/img/spinner.gif; sourceTree = ""; };
+ 7C3BAA2B975E5AB6E45B7501A38D0E50 /* jquery.min.js */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.javascript; name = jquery.min.js; path = docs/js/jquery.min.js; sourceTree = ""; };
+ 7C4AEA4097E03F8F1E1FF76936BFC278 /* alderis-demo.mp4 */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file; name = "alderis-demo.mp4"; path = "docs/screenshots/alderis-demo.mp4"; sourceTree = ""; };
+ 7CA6996BA5910AA62E840ECDAC8A311D /* ColorPickerMapSlider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ColorPickerMapSlider.swift; path = Alderis/ColorPickerMapSlider.swift; sourceTree = ""; };
+ 81C90428B5B99754A43DA6313C54EA89 /* ColorWell.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html; name = ColorWell.html; path = docs/Classes/ColorWell.html; sourceTree = ""; };
+ 86AD6BD58D889622FFD4FFAB222FB92F /* Deprecated.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html; name = Deprecated.html; path = docs/Deprecated.html; sourceTree = ""; };
+ 895BC4B0779E64166AAFB40F10B8C78C /* ColorPickerAccessibilityViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ColorPickerAccessibilityViewController.swift; path = Alderis/ColorPickerAccessibilityViewController.swift; sourceTree = ""; };
+ 8ACDBAED02C2312FD0DDD22FF5C8F90C /* lunr.min.js */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.javascript; name = lunr.min.js; path = docs/js/lunr.min.js; sourceTree = ""; };
+ 8BEDC7D63209B1F7C6891F0A83428EC0 /* Pods-Alderis Demo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-Alderis Demo.release.xcconfig"; sourceTree = ""; };
+ 8E33FF69927289707F0B298D6590DE7F /* ColorPickerConfiguration.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ColorPickerConfiguration.swift; path = Alderis/ColorPickerConfiguration.swift; sourceTree = ""; };
+ 909F399540F2BEB7DC4E5B64D2D23826 /* Alderis.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; path = Alderis.podspec; sourceTree = ""; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
+ 90A7B12A211D20F3215B791362C4A167 /* Assets.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Assets.swift; path = Alderis/Assets.swift; sourceTree = ""; };
+ 922CFAFDF66D62804DBB612073906B88 /* index.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html.documentation; name = index.html; path = docs/docsets/Alderis.docset/Contents/Resources/Documents/index.html; sourceTree = ""; };
+ 92B1C7079B2E8A4A28B8E33816D706EB /* index.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html; name = index.html; path = docs/index.html; sourceTree = ""; };
+ 92C09CC7B803B95560EB8939CC6E04F4 /* ColorWell.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html.documentation; name = ColorWell.html; path = docs/docsets/Alderis.docset/Contents/Resources/Documents/Classes/ColorWell.html; sourceTree = ""; };
+ 96A206F94AE69D710EB7B7C8762C8A62 /* LICENSE.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = ""; };
+ 96D71531FEDE7846F4A8F71D072D1591 /* Alderis.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Alderis.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
+ 97CE419BD858D6FE5C2D08A274A5FCC4 /* Alderis-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Alderis-umbrella.h"; sourceTree = ""; };
+ 98BA600B082CA0F5CE446945D05C3FED /* Color.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Color.swift; path = Alderis/Color.swift; sourceTree = ""; };
+ 9C58F11F0D4691B733DF3C64E9CAA22C /* BottomSheetTransition.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BottomSheetTransition.swift; path = Alderis/BottomSheetTransition.swift; sourceTree = ""; };
+ 9C59604B7017DD6EE566A7E63D207201 /* jazzy.css */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.css; name = jazzy.css; path = docs/css/jazzy.css; sourceTree = ""; };
+ 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
+ 9DE62A37068140A5FCA8272D4E7A546B /* ResourceBundle-Alderis-Alderis-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "ResourceBundle-Alderis-Alderis-Info.plist"; sourceTree = ""; };
+ 9F8B0A840EE850B85532904D3477EA6C /* migrating-to-11.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html.documentation; name = "migrating-to-11.html"; path = "docs/docsets/Alderis.docset/Contents/Resources/Documents/migrating-to-11.html"; sourceTree = ""; };
+ A00D32F1DB750C7A433206BB9423273C /* jquery.min.js */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.javascript; name = jquery.min.js; path = docs/docsets/Alderis.docset/Contents/Resources/Documents/js/jquery.min.js; sourceTree = ""; };
+ A11066E4ED4E95D10035D29173139102 /* ColorPickerTab.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html; name = ColorPickerTab.html; path = docs/Enums/ColorPickerTab.html; sourceTree = ""; };
+ A4CAB22104CFE97E066C888EA84A1FB5 /* Extensions.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html; name = Extensions.html; path = docs/Extensions.html; sourceTree = ""; };
+ ADC5E34B53A672560E13EAF0D89BA5F6 /* alderis-1.jpg */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.jpeg; name = "alderis-1.jpg"; path = "docs/docsets/Alderis.docset/Contents/Resources/Documents/screenshots/alderis-1.jpg"; sourceTree = ""; };
+ AE38B60A89EFE92ABBBD9B9DC6B76B9E /* ColorPickerInnerViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ColorPickerInnerViewController.swift; path = Alderis/ColorPickerInnerViewController.swift; sourceTree = ""; };
+ B2E0D7FED34ACD1971214E2CE7A0916F /* alderis-3.jpg */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.jpeg; name = "alderis-3.jpg"; path = "docs/screenshots/alderis-3.jpg"; sourceTree = ""; };
+ B3128C93A74BE7F8CE1FC5D5717740D7 /* Pods-Alderis Demo-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-Alderis Demo-acknowledgements.markdown"; sourceTree = ""; };
+ B49DA50C46A4E7E3D74A2BA21F4E972D /* alderis-demo.gif */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.gif; name = "alderis-demo.gif"; path = "docs/screenshots/alderis-demo.gif"; sourceTree = ""; };
+ C6E7523EE0A4CF72A207ADDD734DEE45 /* Pods-Alderis Demo-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-Alderis Demo-resources.sh"; sourceTree = ""; };
+ C72FB199C848386888B509700DC92309 /* Color Picker.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html; name = "Color Picker.html"; path = "docs/Color Picker.html"; sourceTree = ""; };
+ C94AE49628F942E40E6FC907AF341F0C /* UIColor.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html; name = UIColor.html; path = docs/Extensions/UIColor.html; sourceTree = ""; };
+ CA71B8BBD169668C8D0BC006FB4A5EBB /* Alderis.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = Alderis.h; path = Alderis/Alderis.h; sourceTree = ""; };
+ CD75327C7B0D6532BB1C51ADF5E5F5C1 /* alderis-3.jpg */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.jpeg; name = "alderis-3.jpg"; path = "docs/docsets/Alderis.docset/Contents/Resources/Documents/screenshots/alderis-3.jpg"; sourceTree = ""; };
+ CF1C0CA74112CFEF515F9B106B84EED3 /* Color Picker.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html.documentation; name = "Color Picker.html"; path = "docs/docsets/Alderis.docset/Contents/Resources/Documents/Color Picker.html"; sourceTree = ""; };
+ D3193B5C2AFD71349D8FE05C8859B534 /* ColorPickerConfiguration.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html.documentation; name = ColorPickerConfiguration.html; path = docs/docsets/Alderis.docset/Contents/Resources/Documents/Classes/ColorPickerConfiguration.html; sourceTree = ""; };
+ DA583C23AB5145F55CE214AAA9F21D69 /* Guides.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html; name = Guides.html; path = docs/Guides.html; sourceTree = ""; };
+ DDB4D91E9BF02F44556BF6B3E8AECE44 /* highlight.css */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.css; name = highlight.css; path = docs/docsets/Alderis.docset/Contents/Resources/Documents/css/highlight.css; sourceTree = ""; };
+ DF3AF36BDEB0B6B51C5330F99693D3D8 /* alderis-demo.gif */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.gif; name = "alderis-demo.gif"; path = "docs/docsets/Alderis.docset/Contents/Resources/Documents/screenshots/alderis-demo.gif"; sourceTree = ""; };
+ E09142CF0722BCBDFF7C6EF2C6E3C5AD /* Alderis.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = Alderis.modulemap; sourceTree = ""; };
+ E7DCD05E0FBC03FB1CB46C061461DCE4 /* Extensions.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html.documentation; name = Extensions.html; path = docs/docsets/Alderis.docset/Contents/Resources/Documents/Extensions.html; sourceTree = ""; };
+ EBC28A5101D4B8328FB9CB18F7370042 /* docSet.dsidx */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file; name = docSet.dsidx; path = docs/docsets/Alderis.docset/Contents/Resources/docSet.dsidx; sourceTree = ""; };
+ EF35F95BEC555044C7C3CBBC6AF054FB /* migrating-to-11.html */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.html; name = "migrating-to-11.html"; path = "docs/migrating-to-11.html"; sourceTree = ""; };
+ EFE5AC5F8B2C3F386620685EAF5D2739 /* ColorPickerSwatchViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ColorPickerSwatchViewController.swift; path = Alderis/ColorPickerSwatchViewController.swift; sourceTree = ""; };
+ F15D960A598C031F6A6BF1896262FA7B /* Alderis.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Alderis.debug.xcconfig; sourceTree = ""; };
+ F4758ABB829DE115D62896232DB57BAC /* search.json */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.json; name = search.json; path = docs/docsets/Alderis.docset/Contents/Resources/Documents/search.json; sourceTree = ""; };
+ F57C4296EB375AC2FAD236F6F8D89677 /* ColorWell.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ColorWell.swift; path = Alderis/ColorWell.swift; sourceTree = ""; };
+ F5F684AF5C41A475CC3FB2946E5DF2ED /* UIColorAdditions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIColorAdditions.swift; path = Alderis/UIColorAdditions.swift; sourceTree = ""; };
+ F7EF72B33574BD440D1C8476026F3DEE /* ColorPickerViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ColorPickerViewController.swift; path = Alderis/ColorPickerViewController.swift; sourceTree = ""; };
+ F8EDAAEA299AE2C5EB2BB7172634E896 /* GradientView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GradientView.swift; path = Alderis/GradientView.swift; sourceTree = ""; };
+ FDA93ECBA0BF9F1EDB0DF41B1F6BB122 /* ColorPickerSlider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ColorPickerSlider.swift; path = Alderis/ColorPickerSlider.swift; sourceTree = ""; };
+ FDB1859B2497FB32CBD6018DA07E3903 /* Alderis.xml */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = Alderis.xml; path = docs/docsets/Alderis.xml; sourceTree = ""; };
+ FDC0B355146987F48BAC2F801702D175 /* alderis-demo.mp4 */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file; name = "alderis-demo.mp4"; path = "docs/docsets/Alderis.docset/Contents/Resources/Documents/screenshots/alderis-demo.mp4"; sourceTree = ""; };
+ FE815F230EB0E506A0E93FC9B05DEF16 /* carat.png */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.png; name = carat.png; path = docs/docsets/Alderis.docset/Contents/Resources/Documents/img/carat.png; sourceTree = ""; };
+ FEE27FA6F4BFD5E6412B78F11FE678C3 /* ColorPickerDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ColorPickerDelegate.swift; path = Alderis/ColorPickerDelegate.swift; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 093A79D3DC07E777E846D998AB14C931 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 7B383053112517B152D012AEDB5956A0 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DA7ECD0D42EFE4684D076E810F8EDFF5 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 197C13DD8906A694068BF18639FCC930 /* Development Pods */ = {
+ isa = PBXGroup;
+ children = (
+ 899E89AB43D963792788E66158E22535 /* Alderis */,
+ );
+ name = "Development Pods";
+ sourceTree = "";
+ };
+ 887692E1D0CD32ADA45C133CE07E5777 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 0E81A451910A2F39C165935A61188DD7 /* Alderis */,
+ 96D71531FEDE7846F4A8F71D072D1591 /* Alderis.bundle */,
+ 4339F8A837B317D1831C93FD9779103A /* Pods-Alderis Demo */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 899E89AB43D963792788E66158E22535 /* Alderis */ = {
+ isa = PBXGroup;
+ children = (
+ 2CB3607C30014F8358C3875AF8F0A7C3 /* AccessibilityComplianceLabel.swift */,
+ 6B1B52CBBF19B2BB0FA72B98D54EB376 /* AccessibilityContrastSelector.swift */,
+ CA71B8BBD169668C8D0BC006FB4A5EBB /* Alderis.h */,
+ 33AF563198A93F314C97104EA7E89655 /* AlderisSDKCompatibility.h */,
+ 90A7B12A211D20F3215B791362C4A167 /* Assets.swift */,
+ 4BDC12530AEE0050A48E2C0437FFE7BA /* Assets-ios12.xcassets */,
+ 9C58F11F0D4691B733DF3C64E9CAA22C /* BottomSheetTransition.swift */,
+ 98BA600B082CA0F5CE446945D05C3FED /* Color.swift */,
+ 895BC4B0779E64166AAFB40F10B8C78C /* ColorPickerAccessibilityViewController.swift */,
+ 8E33FF69927289707F0B298D6590DE7F /* ColorPickerConfiguration.swift */,
+ FEE27FA6F4BFD5E6412B78F11FE678C3 /* ColorPickerDelegate.swift */,
+ AE38B60A89EFE92ABBBD9B9DC6B76B9E /* ColorPickerInnerViewController.swift */,
+ 7CA6996BA5910AA62E840ECDAC8A311D /* ColorPickerMapSlider.swift */,
+ 32455B59318B845D222D6DD4C86D0F14 /* ColorPickerMapViewController.swift */,
+ 04C96783D143FBCCA45297A1B50D52C6 /* ColorPickerNumericSlider.swift */,
+ FDA93ECBA0BF9F1EDB0DF41B1F6BB122 /* ColorPickerSlider.swift */,
+ 22A1DF5EB1BEC3637C580A1C8270A196 /* ColorPickerSlidersViewController.swift */,
+ EFE5AC5F8B2C3F386620685EAF5D2739 /* ColorPickerSwatchViewController.swift */,
+ 11B1135B4A039DE8DAA72EEA1D127815 /* ColorPickerTabViewController.swift */,
+ F7EF72B33574BD440D1C8476026F3DEE /* ColorPickerViewController.swift */,
+ 38BF69FA9AA99E27F929AA33A622BC50 /* ColorPickerWheelView.swift */,
+ F57C4296EB375AC2FAD236F6F8D89677 /* ColorWell.swift */,
+ 57A8871167DE887D9D09AAEFB7FF3A37 /* DialogButton.swift */,
+ F8EDAAEA299AE2C5EB2BB7172634E896 /* GradientView.swift */,
+ 09DB8593FFDFFE77DA2BC61903E23688 /* NSBeep.swift */,
+ 6656E3EB483BC4EFB619B8B7DAA8A9D9 /* SeparatorView.swift */,
+ 412F9DA6B91118873136801E797D0D53 /* TextViewLabel.swift */,
+ F5F684AF5C41A475CC3FB2946E5DF2ED /* UIColorAdditions.swift */,
+ 193C648A76B81FC98BAD74C9D92EE779 /* UIFloat.swift */,
+ 4ABB5DB2028B61124D7915C993C57531 /* UIFontDescriptorAdditions.swift */,
+ BCB9C80F62D03A4D91FB7BDD74E87218 /* Pod */,
+ EC4B0BEA27DDFEE43BB8A01A8A88802E /* Support Files */,
+ );
+ name = Alderis;
+ path = ../..;
+ sourceTree = "";
+ };
+ A1837F8ECDB0D608D38E29E5F08D2348 /* Pods-Alderis Demo */ = {
+ isa = PBXGroup;
+ children = (
+ 3F26A160257BC65D007E7AC9351E1F46 /* Pods-Alderis Demo.modulemap */,
+ B3128C93A74BE7F8CE1FC5D5717740D7 /* Pods-Alderis Demo-acknowledgements.markdown */,
+ 2442B5DB4407D4A13C865A36A51ECE46 /* Pods-Alderis Demo-acknowledgements.plist */,
+ 6E0B28B43B7BC45127DE1EA921314EAA /* Pods-Alderis Demo-dummy.m */,
+ C6E7523EE0A4CF72A207ADDD734DEE45 /* Pods-Alderis Demo-resources.sh */,
+ 16A2838DE41B9772EA81132CE1FE138B /* Pods-Alderis Demo-umbrella.h */,
+ 4DAF9BE7C45EEC6325555273285DBCFF /* Pods-Alderis Demo.debug.xcconfig */,
+ 8BEDC7D63209B1F7C6891F0A83428EC0 /* Pods-Alderis Demo.release.xcconfig */,
+ );
+ name = "Pods-Alderis Demo";
+ path = "Target Support Files/Pods-Alderis Demo";
+ sourceTree = "";
+ };
+ BCB9C80F62D03A4D91FB7BDD74E87218 /* Pod */ = {
+ isa = PBXGroup;
+ children = (
+ 909F399540F2BEB7DC4E5B64D2D23826 /* Alderis.podspec */,
+ 655E41783F73F7420DA545011AE41E59 /* Alderis.tgz */,
+ FDB1859B2497FB32CBD6018DA07E3903 /* Alderis.xml */,
+ ADC5E34B53A672560E13EAF0D89BA5F6 /* alderis-1.jpg */,
+ 346A89CF7100B245C0D8FFBB735BA330 /* alderis-1.jpg */,
+ 5DFA99D99F720D80A8C0F3CBA36A6722 /* alderis-2.jpg */,
+ 12CF8765DD7A138CB9A12850F4F4AF9A /* alderis-2.jpg */,
+ CD75327C7B0D6532BB1C51ADF5E5F5C1 /* alderis-3.jpg */,
+ B2E0D7FED34ACD1971214E2CE7A0916F /* alderis-3.jpg */,
+ 34B81EA50C950B2A5BB60792DF6C2321 /* alderis-4.jpg */,
+ 426F13D795E5EEB02FFB85344C6E177B /* alderis-4.jpg */,
+ DF3AF36BDEB0B6B51C5330F99693D3D8 /* alderis-demo.gif */,
+ B49DA50C46A4E7E3D74A2BA21F4E972D /* alderis-demo.gif */,
+ FDC0B355146987F48BAC2F801702D175 /* alderis-demo.mp4 */,
+ 7C4AEA4097E03F8F1E1FF76936BFC278 /* alderis-demo.mp4 */,
+ FE815F230EB0E506A0E93FC9B05DEF16 /* carat.png */,
+ 2100D41A9E3FA821A631CC2A8231B340 /* carat.png */,
+ C72FB199C848386888B509700DC92309 /* Color Picker.html */,
+ CF1C0CA74112CFEF515F9B106B84EED3 /* Color Picker.html */,
+ 60B41A93CBFB859E0CC522EAEF362A58 /* ColorPickerConfiguration.html */,
+ D3193B5C2AFD71349D8FE05C8859B534 /* ColorPickerConfiguration.html */,
+ 5BFEDA5074456612DCD226636CB27A97 /* ColorPickerDelegate.html */,
+ 5C53194D55F86B1096691514B424C4B6 /* ColorPickerDelegate.html */,
+ 135BC3D71ADFA71F8713F5F29FB7D3D2 /* ColorPickerTab.html */,
+ A11066E4ED4E95D10035D29173139102 /* ColorPickerTab.html */,
+ 490D4F0EB791E3F5DCFE8DA18519BCE9 /* ColorPickerViewController.html */,
+ 36E2384C9860D24702DA564950E42638 /* ColorPickerViewController.html */,
+ 81C90428B5B99754A43DA6313C54EA89 /* ColorWell.html */,
+ 92C09CC7B803B95560EB8939CC6E04F4 /* ColorWell.html */,
+ 0BB90EF60C711FCDCD835AC9AB34EF94 /* dash.png */,
+ 61BCC763200E79B1A67858928D3BFF6D /* dash.png */,
+ 86AD6BD58D889622FFD4FFAB222FB92F /* Deprecated.html */,
+ 32B2DF317783C03E472F09C6A18A2137 /* Deprecated.html */,
+ EBC28A5101D4B8328FB9CB18F7370042 /* docSet.dsidx */,
+ E7DCD05E0FBC03FB1CB46C061461DCE4 /* Extensions.html */,
+ A4CAB22104CFE97E066C888EA84A1FB5 /* Extensions.html */,
+ 65F7A4EE3B7BC951DCA59097E498A594 /* gh.png */,
+ 56C95B256105E52E1EBED49808BA072D /* gh.png */,
+ 390A3C384514B2A881E2611AA3A7AFAF /* Guides.html */,
+ DA583C23AB5145F55CE214AAA9F21D69 /* Guides.html */,
+ 02B116C65675DAEA942559AE27E67F15 /* highlight.css */,
+ DDB4D91E9BF02F44556BF6B3E8AECE44 /* highlight.css */,
+ 922CFAFDF66D62804DBB612073906B88 /* index.html */,
+ 92B1C7079B2E8A4A28B8E33816D706EB /* index.html */,
+ 3176D00931C79BF799AF1F6717276229 /* Info.plist */,
+ 9C59604B7017DD6EE566A7E63D207201 /* jazzy.css */,
+ 408B7D127DAD5D88CCD0B96EBF7E1740 /* jazzy.css */,
+ 46B301E1CAEEF9AC54CE7AB98AE94FF8 /* jazzy.js */,
+ 02F847ACA8E7B867DD5935F065F04DD4 /* jazzy.js */,
+ 0EBF7EA81D2DEA250A5A1C0522E8D7A3 /* jazzy.search.js */,
+ 3BE1332117E0FB3058648B05E510FC05 /* jazzy.search.js */,
+ A00D32F1DB750C7A433206BB9423273C /* jquery.min.js */,
+ 7C3BAA2B975E5AB6E45B7501A38D0E50 /* jquery.min.js */,
+ 96A206F94AE69D710EB7B7C8762C8A62 /* LICENSE.md */,
+ 2073961377AA380E66ADD8B2B1414B70 /* logo.jpg */,
+ 229FF675CBC5FBD3E5814D3A434BEE86 /* logo.jpg */,
+ 2393CC4938AC17F8A44EA2F5850A9F7C /* lunr.min.js */,
+ 8ACDBAED02C2312FD0DDD22FF5C8F90C /* lunr.min.js */,
+ 9F8B0A840EE850B85532904D3477EA6C /* migrating-to-11.html */,
+ EF35F95BEC555044C7C3CBBC6AF054FB /* migrating-to-11.html */,
+ 39151D3F804E2ACE7EB4A41F0519640F /* preference-bundles.html */,
+ 6085501632610BFE778FFF92DF0A5748 /* preference-bundles.html */,
+ 61B0B4CC82C04F8049F6D13378E10D29 /* README.md */,
+ F4758ABB829DE115D62896232DB57BAC /* search.json */,
+ 6E11449DA1AF7B8354272EA71429E6DF /* search.json */,
+ 56A04476FF0C6D9343BC0479260BF23C /* spinner.gif */,
+ 6FE4AFB52F6E21D973EB81FA5D47DE99 /* spinner.gif */,
+ 39CCC3F183B912E6298F07E239499149 /* typeahead.jquery.js */,
+ 427B5E705542B1CB268BA88393B7E81C /* typeahead.jquery.js */,
+ 0974F2B8473A65FEDBC4983C7BE0D053 /* UI Components.html */,
+ 42D5D9AC25A2A1829039F043ACC13982 /* UI Components.html */,
+ 456BDD346100CE3A8518F4826FB3407E /* UIColor.html */,
+ C94AE49628F942E40E6FC907AF341F0C /* UIColor.html */,
+ );
+ name = Pod;
+ sourceTree = "";
+ };
+ BDF19F2547EA527C68B46E462B39F6D6 /* Targets Support Files */ = {
+ isa = PBXGroup;
+ children = (
+ A1837F8ECDB0D608D38E29E5F08D2348 /* Pods-Alderis Demo */,
+ );
+ name = "Targets Support Files";
+ sourceTree = "";
+ };
+ CF1408CF629C7361332E53B88F7BD30C = {
+ isa = PBXGroup;
+ children = (
+ 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */,
+ 197C13DD8906A694068BF18639FCC930 /* Development Pods */,
+ D89477F20FB1DE18A04690586D7808C4 /* Frameworks */,
+ 887692E1D0CD32ADA45C133CE07E5777 /* Products */,
+ BDF19F2547EA527C68B46E462B39F6D6 /* Targets Support Files */,
+ );
+ sourceTree = "";
+ };
+ D89477F20FB1DE18A04690586D7808C4 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+ EC4B0BEA27DDFEE43BB8A01A8A88802E /* Support Files */ = {
+ isa = PBXGroup;
+ children = (
+ E09142CF0722BCBDFF7C6EF2C6E3C5AD /* Alderis.modulemap */,
+ 25FF3569DF1635C4D4EFDBE3EC713871 /* Alderis-dummy.m */,
+ 08E8A13AC29E80A401DF99D68805957A /* Alderis-prefix.pch */,
+ 97CE419BD858D6FE5C2D08A274A5FCC4 /* Alderis-umbrella.h */,
+ F15D960A598C031F6A6BF1896262FA7B /* Alderis.debug.xcconfig */,
+ 4D7238C16049AC7D52380A3F0F037DA1 /* Alderis.release.xcconfig */,
+ 9DE62A37068140A5FCA8272D4E7A546B /* ResourceBundle-Alderis-Alderis-Info.plist */,
+ );
+ name = "Support Files";
+ path = "Demo/Pods/Target Support Files/Alderis";
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+ 51A2924DCFAFC3B05542EA589886B1C6 /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 4CB88CD902C9400106C9CDC4C3BADDD9 /* Alderis.h in Headers */,
+ 37F5ABFCFA0B0D72CAEFBDFA3B5ED787 /* Alderis-umbrella.h in Headers */,
+ 05103D0E4FCCCF239AD0A0B6BC8BF438 /* AlderisSDKCompatibility.h in Headers */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 98EEA9AFD506EEC0E957CF8454860A08 /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ D951C553E2937D74043A298EFD7BC0BA /* Pods-Alderis Demo-umbrella.h in Headers */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+ 0BF6D5F4E6EC1CF170610CBAB0F6CEF2 /* Alderis */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = C437B582C0CD78FA49BA3C9157B57927 /* Build configuration list for PBXNativeTarget "Alderis" */;
+ buildPhases = (
+ 51A2924DCFAFC3B05542EA589886B1C6 /* Headers */,
+ 8B61953302CC432EB3F74A8EC097B279 /* Sources */,
+ 7B383053112517B152D012AEDB5956A0 /* Frameworks */,
+ D84A6533DC71D25B63EED5E38DCCF222 /* Copy generated compatibility header */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ D321C14625B095BD1368B46D005293EF /* PBXTargetDependency */,
+ );
+ name = Alderis;
+ productName = Alderis;
+ productReference = 0E81A451910A2F39C165935A61188DD7 /* Alderis */;
+ productType = "com.apple.product-type.library.static";
+ };
+ 9E56E22F79A7CE7FED2693342F797A57 /* Pods-Alderis Demo */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = B5722471F152263BB3FE3DEF51C789D0 /* Build configuration list for PBXNativeTarget "Pods-Alderis Demo" */;
+ buildPhases = (
+ 98EEA9AFD506EEC0E957CF8454860A08 /* Headers */,
+ 96050FC4E13B8F5C9F1D8D224D1953EC /* Sources */,
+ 093A79D3DC07E777E846D998AB14C931 /* Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 55B65CD199127E2795039C1DB95BFB0C /* PBXTargetDependency */,
+ );
+ name = "Pods-Alderis Demo";
+ productName = "Pods-Alderis Demo";
+ productReference = 4339F8A837B317D1831C93FD9779103A /* Pods-Alderis Demo */;
+ productType = "com.apple.product-type.library.static";
+ };
+ E06BFBDAEB84B8EBA0D1A8B43D16EF27 /* Alderis-Alderis */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 126BE766A5469BB6DD9826BB45369530 /* Build configuration list for PBXNativeTarget "Alderis-Alderis" */;
+ buildPhases = (
+ C568396AF857A063AA905A65555EAE01 /* Sources */,
+ DA7ECD0D42EFE4684D076E810F8EDFF5 /* Frameworks */,
+ F92A57EF1BCA2E27937952913AF9DD9B /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = "Alderis-Alderis";
+ productName = Alderis;
+ productReference = 96D71531FEDE7846F4A8F71D072D1591 /* Alderis.bundle */;
+ productType = "com.apple.product-type.bundle";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ BFDFE7DC352907FC980B868725387E98 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastSwiftUpdateCheck = 1240;
+ LastUpgradeCheck = 1240;
+ };
+ buildConfigurationList = 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */;
+ compatibilityVersion = "Xcode 10.0";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ Base,
+ en,
+ );
+ mainGroup = CF1408CF629C7361332E53B88F7BD30C;
+ productRefGroup = 887692E1D0CD32ADA45C133CE07E5777 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 0BF6D5F4E6EC1CF170610CBAB0F6CEF2 /* Alderis */,
+ E06BFBDAEB84B8EBA0D1A8B43D16EF27 /* Alderis-Alderis */,
+ 9E56E22F79A7CE7FED2693342F797A57 /* Pods-Alderis Demo */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ F92A57EF1BCA2E27937952913AF9DD9B /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DCF8E0093062AD806401B93579A00946 /* Assets-ios12.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ D84A6533DC71D25B63EED5E38DCCF222 /* Copy generated compatibility header */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${DERIVED_SOURCES_DIR}/${PRODUCT_MODULE_NAME}-Swift.h",
+ "${PODS_ROOT}/Headers/Public/Alderis/Alderis.modulemap",
+ "${PODS_ROOT}/Headers/Public/Alderis/Alderis-umbrella.h",
+ );
+ name = "Copy generated compatibility header";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "${BUILT_PRODUCTS_DIR}/${PRODUCT_MODULE_NAME}.modulemap",
+ "${BUILT_PRODUCTS_DIR}/Alderis-umbrella.h",
+ "${BUILT_PRODUCTS_DIR}/Swift Compatibility Header/${PRODUCT_MODULE_NAME}-Swift.h",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "COMPATIBILITY_HEADER_PATH=\"${BUILT_PRODUCTS_DIR}/Swift Compatibility Header/${PRODUCT_MODULE_NAME}-Swift.h\"\nMODULE_MAP_PATH=\"${BUILT_PRODUCTS_DIR}/${PRODUCT_MODULE_NAME}.modulemap\"\n\nditto \"${DERIVED_SOURCES_DIR}/${PRODUCT_MODULE_NAME}-Swift.h\" \"${COMPATIBILITY_HEADER_PATH}\"\nditto \"${PODS_ROOT}/Headers/Public/Alderis/Alderis.modulemap\" \"${MODULE_MAP_PATH}\"\nditto \"${PODS_ROOT}/Headers/Public/Alderis/Alderis-umbrella.h\" \"${BUILT_PRODUCTS_DIR}\"\nprintf \"\\n\\nmodule ${PRODUCT_MODULE_NAME}.Swift {\\n header \\\"${COMPATIBILITY_HEADER_PATH}\\\"\\n requires objc\\n}\\n\" >> \"${MODULE_MAP_PATH}\"\n";
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 8B61953302CC432EB3F74A8EC097B279 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 69E9416DB84F9C1578C79463C00F1034 /* AccessibilityComplianceLabel.swift in Sources */,
+ 854293819B45401802BEBE4DAE9593D5 /* AccessibilityContrastSelector.swift in Sources */,
+ 65FF5EEC743492FF458C8C511687895F /* Alderis-dummy.m in Sources */,
+ 6309E88FA19CA2C7E1397C854EF70D66 /* Assets.swift in Sources */,
+ 61BC805E3601DBC8943B104CE9DAB27D /* BottomSheetTransition.swift in Sources */,
+ 1249F0A1123D8CD9B892BBC474175825 /* Color.swift in Sources */,
+ 0D8BF7C4464C83EF0B028B01A78AEDD6 /* ColorPickerAccessibilityViewController.swift in Sources */,
+ 135D3BAA68E17A0AC6B3667951342ED9 /* ColorPickerConfiguration.swift in Sources */,
+ 50DCDD2604D2D2195B7FE86549C8C60A /* ColorPickerDelegate.swift in Sources */,
+ 2939CFC7F4E151B8BA170E2718FE5C2D /* ColorPickerInnerViewController.swift in Sources */,
+ A4B55DD8DF7981D5348FED7150137FD4 /* ColorPickerMapSlider.swift in Sources */,
+ E5F02AF27C52B7EEDFA8F5CE568058FC /* ColorPickerMapViewController.swift in Sources */,
+ B07A5681AD8C8E562F7768F8687EA98D /* ColorPickerNumericSlider.swift in Sources */,
+ 612EFA95A4DD8D4015CF8FA9A1878399 /* ColorPickerSlider.swift in Sources */,
+ 7C15B0F74C21E30A6D2FE833A039C6A5 /* ColorPickerSlidersViewController.swift in Sources */,
+ BC1230C95F458765C8CEBDF80109609B /* ColorPickerSwatchViewController.swift in Sources */,
+ F8144CFF31B54A06D202B96AA7FA0CF1 /* ColorPickerTabViewController.swift in Sources */,
+ 5B575D82A33FC6ADA46F8463ECD56545 /* ColorPickerViewController.swift in Sources */,
+ 675572C2B585A87F28A077B811DA8FA5 /* ColorPickerWheelView.swift in Sources */,
+ 1EAAE13CA289E1465B194A38124C4D5F /* ColorWell.swift in Sources */,
+ 7AE2A9360F7B1AFEF18CA72FAB8E47CA /* DialogButton.swift in Sources */,
+ EB32A80054921FB5EEECD0B2C1BA1FB8 /* GradientView.swift in Sources */,
+ A27A1ADC9991A8C08AE350860C1B4894 /* NSBeep.swift in Sources */,
+ 6A38E3A03B08C4DB4E72A35AF6C42293 /* SeparatorView.swift in Sources */,
+ 53966AFCE54B78B4BEFD48FF19423AC2 /* TextViewLabel.swift in Sources */,
+ BA43D3FA7CD5A3F1F61BB205626EE7F7 /* UIColorAdditions.swift in Sources */,
+ B622882FF8E7B4CCFFB32813BDCB2D34 /* UIFloat.swift in Sources */,
+ 815F9C399ED6E92FB5C7CBA666339C43 /* UIFontDescriptorAdditions.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 96050FC4E13B8F5C9F1D8D224D1953EC /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 8E81254A16B7C247CF0DEBA561FFFBBF /* Pods-Alderis Demo-dummy.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ C568396AF857A063AA905A65555EAE01 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 55B65CD199127E2795039C1DB95BFB0C /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ name = Alderis;
+ target = 0BF6D5F4E6EC1CF170610CBAB0F6CEF2 /* Alderis */;
+ targetProxy = EE5F7F514D1321804708AF70F97259CC /* PBXContainerItemProxy */;
+ };
+ D321C14625B095BD1368B46D005293EF /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ name = "Alderis-Alderis";
+ target = E06BFBDAEB84B8EBA0D1A8B43D16EF27 /* Alderis-Alderis */;
+ targetProxy = 406A8A6B603CDE598EAB01818A637160 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin XCBuildConfiguration section */
+ 09F3B7AD1520CE6F6C0A503058268F79 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 8BEDC7D63209B1F7C6891F0A83428EC0 /* Pods-Alderis Demo.release.xcconfig */;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
+ CLANG_ENABLE_OBJC_WEAK = NO;
+ "CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
+ "CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ MACH_O_TYPE = staticlib;
+ MODULEMAP_FILE = "Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo.modulemap";
+ OTHER_LDFLAGS = "";
+ OTHER_LIBTOOLFLAGS = "";
+ PODS_ROOT = "$(SRCROOT)";
+ PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}";
+ SDKROOT = iphoneos;
+ SKIP_INSTALL = YES;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 243AD1FF67BA11AD2C1237B6D72C1AEA /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 4DAF9BE7C45EEC6325555273285DBCFF /* Pods-Alderis Demo.debug.xcconfig */;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
+ CLANG_ENABLE_OBJC_WEAK = NO;
+ "CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
+ "CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ MACH_O_TYPE = staticlib;
+ MODULEMAP_FILE = "Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo.modulemap";
+ OTHER_LDFLAGS = "";
+ OTHER_LIBTOOLFLAGS = "";
+ PODS_ROOT = "$(SRCROOT)";
+ PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}";
+ SDKROOT = iphoneos;
+ SKIP_INSTALL = YES;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 2B9E26EAE2CD392AD762421F663075A1 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "POD_CONFIGURATION_DEBUG=1",
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ STRIP_INSTALLED_PRODUCT = NO;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ SYMROOT = "${SRCROOT}/../build";
+ };
+ name = Debug;
+ };
+ 32C472C250AD6233A5A55B5200970444 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = F15D960A598C031F6A6BF1896262FA7B /* Alderis.debug.xcconfig */;
+ buildSettings = {
+ CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/Alderis";
+ DEVELOPMENT_TEAM = N2LN9ZT493;
+ IBSC_MODULE = Alderis;
+ INFOPLIST_FILE = "Target Support Files/Alderis/ResourceBundle-Alderis-Alderis-Info.plist";
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ PRODUCT_NAME = Alderis;
+ SDKROOT = iphoneos;
+ SKIP_INSTALL = YES;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ WRAPPER_EXTENSION = bundle;
+ };
+ name = Debug;
+ };
+ 48E2F0964C45ACF1380D5197F8C8A485 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = F15D960A598C031F6A6BF1896262FA7B /* Alderis.debug.xcconfig */;
+ buildSettings = {
+ CLANG_ENABLE_OBJC_WEAK = NO;
+ "CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
+ "CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
+ GCC_PREFIX_HEADER = "Target Support Files/Alderis/Alderis-prefix.pch";
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ MODULEMAP_FILE = Headers/Public/Alderis/Alderis.modulemap;
+ OTHER_LDFLAGS = "";
+ OTHER_LIBTOOLFLAGS = "";
+ PRIVATE_HEADERS_FOLDER_PATH = "";
+ PRODUCT_MODULE_NAME = Alderis;
+ PRODUCT_NAME = Alderis;
+ PUBLIC_HEADERS_FOLDER_PATH = "";
+ SDKROOT = iphoneos;
+ SKIP_INSTALL = YES;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 63FAF33E1C55B71A5F5A8B3CC8749F99 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "POD_CONFIGURATION_RELEASE=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ STRIP_INSTALLED_PRODUCT = NO;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ SWIFT_VERSION = 5.0;
+ SYMROOT = "${SRCROOT}/../build";
+ };
+ name = Release;
+ };
+ ED8FFBDEE9F6373244786C888DEDA0C6 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 4D7238C16049AC7D52380A3F0F037DA1 /* Alderis.release.xcconfig */;
+ buildSettings = {
+ CLANG_ENABLE_OBJC_WEAK = NO;
+ "CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
+ "CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
+ GCC_PREFIX_HEADER = "Target Support Files/Alderis/Alderis-prefix.pch";
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ MODULEMAP_FILE = Headers/Public/Alderis/Alderis.modulemap;
+ OTHER_LDFLAGS = "";
+ OTHER_LIBTOOLFLAGS = "";
+ PRIVATE_HEADERS_FOLDER_PATH = "";
+ PRODUCT_MODULE_NAME = Alderis;
+ PRODUCT_NAME = Alderis;
+ PUBLIC_HEADERS_FOLDER_PATH = "";
+ SDKROOT = iphoneos;
+ SKIP_INSTALL = YES;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ FCFAE712F7B981CE1EF4FAB963869E91 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 4D7238C16049AC7D52380A3F0F037DA1 /* Alderis.release.xcconfig */;
+ buildSettings = {
+ CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/Alderis";
+ DEVELOPMENT_TEAM = N2LN9ZT493;
+ IBSC_MODULE = Alderis;
+ INFOPLIST_FILE = "Target Support Files/Alderis/ResourceBundle-Alderis-Alderis-Info.plist";
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ PRODUCT_NAME = Alderis;
+ SDKROOT = iphoneos;
+ SKIP_INSTALL = YES;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ WRAPPER_EXTENSION = bundle;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 126BE766A5469BB6DD9826BB45369530 /* Build configuration list for PBXNativeTarget "Alderis-Alderis" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 32C472C250AD6233A5A55B5200970444 /* Debug */,
+ FCFAE712F7B981CE1EF4FAB963869E91 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 2B9E26EAE2CD392AD762421F663075A1 /* Debug */,
+ 63FAF33E1C55B71A5F5A8B3CC8749F99 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ B5722471F152263BB3FE3DEF51C789D0 /* Build configuration list for PBXNativeTarget "Pods-Alderis Demo" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 243AD1FF67BA11AD2C1237B6D72C1AEA /* Debug */,
+ 09F3B7AD1520CE6F6C0A503058268F79 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ C437B582C0CD78FA49BA3C9157B57927 /* Build configuration list for PBXNativeTarget "Alderis" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 48E2F0964C45ACF1380D5197F8C8A485 /* Debug */,
+ ED8FFBDEE9F6373244786C888DEDA0C6 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = BFDFE7DC352907FC980B868725387E98 /* Project object */;
+}
diff --git a/Tweaks/Alderis/Demo/Pods/Target Support Files/Alderis/Alderis-dummy.m b/Tweaks/Alderis/Demo/Pods/Target Support Files/Alderis/Alderis-dummy.m
new file mode 100644
index 0000000..cc0acd6
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Target Support Files/Alderis/Alderis-dummy.m
@@ -0,0 +1,5 @@
+#import
+@interface PodsDummy_Alderis : NSObject
+@end
+@implementation PodsDummy_Alderis
+@end
diff --git a/Tweaks/Alderis/Demo/Pods/Target Support Files/Alderis/Alderis-prefix.pch b/Tweaks/Alderis/Demo/Pods/Target Support Files/Alderis/Alderis-prefix.pch
new file mode 100644
index 0000000..beb2a24
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Target Support Files/Alderis/Alderis-prefix.pch
@@ -0,0 +1,12 @@
+#ifdef __OBJC__
+#import
+#else
+#ifndef FOUNDATION_EXPORT
+#if defined(__cplusplus)
+#define FOUNDATION_EXPORT extern "C"
+#else
+#define FOUNDATION_EXPORT extern
+#endif
+#endif
+#endif
+
diff --git a/Tweaks/Alderis/Demo/Pods/Target Support Files/Alderis/Alderis-umbrella.h b/Tweaks/Alderis/Demo/Pods/Target Support Files/Alderis/Alderis-umbrella.h
new file mode 100644
index 0000000..9e9378d
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Target Support Files/Alderis/Alderis-umbrella.h
@@ -0,0 +1,18 @@
+#ifdef __OBJC__
+#import
+#else
+#ifndef FOUNDATION_EXPORT
+#if defined(__cplusplus)
+#define FOUNDATION_EXPORT extern "C"
+#else
+#define FOUNDATION_EXPORT extern
+#endif
+#endif
+#endif
+
+#import "Alderis.h"
+#import "AlderisSDKCompatibility.h"
+
+FOUNDATION_EXPORT double AlderisVersionNumber;
+FOUNDATION_EXPORT const unsigned char AlderisVersionString[];
+
diff --git a/Tweaks/Alderis/Demo/Pods/Target Support Files/Alderis/Alderis.debug.xcconfig b/Tweaks/Alderis/Demo/Pods/Target Support Files/Alderis/Alderis.debug.xcconfig
new file mode 100644
index 0000000..8db202b
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Target Support Files/Alderis/Alderis.debug.xcconfig
@@ -0,0 +1,13 @@
+CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
+CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Alderis
+GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
+HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/Alderis" "${PODS_ROOT}/Headers/Public"
+OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -import-underlying-module -Xcc -fmodule-map-file="${SRCROOT}/${MODULEMAP_FILE}"
+PODS_BUILD_DIR = ${BUILD_DIR}
+PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
+PODS_ROOT = ${SRCROOT}
+PODS_TARGET_SRCROOT = ${PODS_ROOT}/../..
+PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
+PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
+SKIP_INSTALL = YES
+USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
diff --git a/Tweaks/Alderis/Demo/Pods/Target Support Files/Alderis/Alderis.modulemap b/Tweaks/Alderis/Demo/Pods/Target Support Files/Alderis/Alderis.modulemap
new file mode 100644
index 0000000..042d2a0
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Target Support Files/Alderis/Alderis.modulemap
@@ -0,0 +1,6 @@
+module Alderis {
+ umbrella header "Alderis-umbrella.h"
+
+ export *
+ module * { export * }
+}
diff --git a/Tweaks/Alderis/Demo/Pods/Target Support Files/Alderis/Alderis.release.xcconfig b/Tweaks/Alderis/Demo/Pods/Target Support Files/Alderis/Alderis.release.xcconfig
new file mode 100644
index 0000000..8db202b
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Target Support Files/Alderis/Alderis.release.xcconfig
@@ -0,0 +1,13 @@
+CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
+CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Alderis
+GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
+HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/Alderis" "${PODS_ROOT}/Headers/Public"
+OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -import-underlying-module -Xcc -fmodule-map-file="${SRCROOT}/${MODULEMAP_FILE}"
+PODS_BUILD_DIR = ${BUILD_DIR}
+PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
+PODS_ROOT = ${SRCROOT}
+PODS_TARGET_SRCROOT = ${PODS_ROOT}/../..
+PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
+PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
+SKIP_INSTALL = YES
+USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
diff --git a/Tweaks/Alderis/Demo/Pods/Target Support Files/Alderis/ResourceBundle-Alderis-Alderis-Info.plist b/Tweaks/Alderis/Demo/Pods/Target Support Files/Alderis/ResourceBundle-Alderis-Alderis-Info.plist
new file mode 100644
index 0000000..146e63e
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Target Support Files/Alderis/ResourceBundle-Alderis-Alderis-Info.plist
@@ -0,0 +1,24 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleIdentifier
+ ${PRODUCT_BUNDLE_IDENTIFIER}
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ ${PRODUCT_NAME}
+ CFBundlePackageType
+ BNDL
+ CFBundleShortVersionString
+ 1.1.2
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1
+ NSPrincipalClass
+
+
+
diff --git a/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-acknowledgements.markdown b/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-acknowledgements.markdown
new file mode 100644
index 0000000..9ae34b8
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-acknowledgements.markdown
@@ -0,0 +1,209 @@
+# Acknowledgements
+This application makes use of the following third party libraries:
+
+## Alderis
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+Generated by CocoaPods - https://cocoapods.org
diff --git a/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-acknowledgements.plist b/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-acknowledgements.plist
new file mode 100644
index 0000000..3cbb799
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-acknowledgements.plist
@@ -0,0 +1,241 @@
+
+
+
+
+ PreferenceSpecifiers
+
+
+ FooterText
+ This application makes use of the following third party libraries:
+ Title
+ Acknowledgements
+ Type
+ PSGroupSpecifier
+
+
+ FooterText
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ License
+ Apache License, Version 2.0
+ Title
+ Alderis
+ Type
+ PSGroupSpecifier
+
+
+ FooterText
+ Generated by CocoaPods - https://cocoapods.org
+ Title
+
+ Type
+ PSGroupSpecifier
+
+
+ StringsTable
+ Acknowledgements
+ Title
+ Acknowledgements
+
+
diff --git a/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-dummy.m b/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-dummy.m
new file mode 100644
index 0000000..bdfd81c
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-dummy.m
@@ -0,0 +1,5 @@
+#import
+@interface PodsDummy_Pods_Alderis_Demo : NSObject
+@end
+@implementation PodsDummy_Pods_Alderis_Demo
+@end
diff --git a/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-resources-Debug-input-files.xcfilelist b/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-resources-Debug-input-files.xcfilelist
new file mode 100644
index 0000000..1df96fb
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-resources-Debug-input-files.xcfilelist
@@ -0,0 +1,2 @@
+${PODS_ROOT}/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-resources.sh
+${PODS_CONFIGURATION_BUILD_DIR}/Alderis/Alderis.bundle
\ No newline at end of file
diff --git a/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-resources-Debug-output-files.xcfilelist b/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-resources-Debug-output-files.xcfilelist
new file mode 100644
index 0000000..60900db
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-resources-Debug-output-files.xcfilelist
@@ -0,0 +1 @@
+${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Alderis.bundle
\ No newline at end of file
diff --git a/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-resources-Release-input-files.xcfilelist b/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-resources-Release-input-files.xcfilelist
new file mode 100644
index 0000000..1df96fb
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-resources-Release-input-files.xcfilelist
@@ -0,0 +1,2 @@
+${PODS_ROOT}/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-resources.sh
+${PODS_CONFIGURATION_BUILD_DIR}/Alderis/Alderis.bundle
\ No newline at end of file
diff --git a/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-resources-Release-output-files.xcfilelist b/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-resources-Release-output-files.xcfilelist
new file mode 100644
index 0000000..60900db
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-resources-Release-output-files.xcfilelist
@@ -0,0 +1 @@
+${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Alderis.bundle
\ No newline at end of file
diff --git a/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-resources.sh b/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-resources.sh
new file mode 100755
index 0000000..79e5f3c
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-resources.sh
@@ -0,0 +1,129 @@
+#!/bin/sh
+set -e
+set -u
+set -o pipefail
+
+function on_error {
+ echo "$(realpath -mq "${0}"):$1: error: Unexpected failure"
+}
+trap 'on_error $LINENO' ERR
+
+if [ -z ${UNLOCALIZED_RESOURCES_FOLDER_PATH+x} ]; then
+ # If UNLOCALIZED_RESOURCES_FOLDER_PATH is not set, then there's nowhere for us to copy
+ # resources to, so exit 0 (signalling the script phase was successful).
+ exit 0
+fi
+
+mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
+
+RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt
+> "$RESOURCES_TO_COPY"
+
+XCASSET_FILES=()
+
+# This protects against multiple targets copying the same framework dependency at the same time. The solution
+# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
+RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
+
+case "${TARGETED_DEVICE_FAMILY:-}" in
+ 1,2)
+ TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
+ ;;
+ 1)
+ TARGET_DEVICE_ARGS="--target-device iphone"
+ ;;
+ 2)
+ TARGET_DEVICE_ARGS="--target-device ipad"
+ ;;
+ 3)
+ TARGET_DEVICE_ARGS="--target-device tv"
+ ;;
+ 4)
+ TARGET_DEVICE_ARGS="--target-device watch"
+ ;;
+ *)
+ TARGET_DEVICE_ARGS="--target-device mac"
+ ;;
+esac
+
+install_resource()
+{
+ if [[ "$1" = /* ]] ; then
+ RESOURCE_PATH="$1"
+ else
+ RESOURCE_PATH="${PODS_ROOT}/$1"
+ fi
+ if [[ ! -e "$RESOURCE_PATH" ]] ; then
+ cat << EOM
+error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script.
+EOM
+ exit 1
+ fi
+ case $RESOURCE_PATH in
+ *.storyboard)
+ echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
+ ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
+ ;;
+ *.xib)
+ echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
+ ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
+ ;;
+ *.framework)
+ echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
+ mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
+ echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
+ rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
+ ;;
+ *.xcdatamodel)
+ echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true
+ xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom"
+ ;;
+ *.xcdatamodeld)
+ echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true
+ xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd"
+ ;;
+ *.xcmappingmodel)
+ echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true
+ xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
+ ;;
+ *.xcassets)
+ ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH"
+ XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE")
+ ;;
+ *)
+ echo "$RESOURCE_PATH" || true
+ echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY"
+ ;;
+ esac
+}
+if [[ "$CONFIGURATION" == "Debug" ]]; then
+ install_resource "${PODS_CONFIGURATION_BUILD_DIR}/Alderis/Alderis.bundle"
+fi
+if [[ "$CONFIGURATION" == "Release" ]]; then
+ install_resource "${PODS_CONFIGURATION_BUILD_DIR}/Alderis/Alderis.bundle"
+fi
+
+mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
+rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
+if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then
+ mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
+ rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
+fi
+rm -f "$RESOURCES_TO_COPY"
+
+if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "${XCASSET_FILES:-}" ]
+then
+ # Find all other xcassets (this unfortunately includes those of path pods and other targets).
+ OTHER_XCASSETS=$(find -L "$PWD" -iname "*.xcassets" -type d)
+ while read line; do
+ if [[ $line != "${PODS_ROOT}*" ]]; then
+ XCASSET_FILES+=("$line")
+ fi
+ done <<<"$OTHER_XCASSETS"
+
+ if [ -z ${ASSETCATALOG_COMPILER_APPICON_NAME+x} ]; then
+ printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
+ else
+ printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" --app-icon "${ASSETCATALOG_COMPILER_APPICON_NAME}" --output-partial-info-plist "${TARGET_TEMP_DIR}/assetcatalog_generated_info_cocoapods.plist"
+ fi
+fi
diff --git a/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-umbrella.h b/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-umbrella.h
new file mode 100644
index 0000000..5a0ea94
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo-umbrella.h
@@ -0,0 +1,16 @@
+#ifdef __OBJC__
+#import
+#else
+#ifndef FOUNDATION_EXPORT
+#if defined(__cplusplus)
+#define FOUNDATION_EXPORT extern "C"
+#else
+#define FOUNDATION_EXPORT extern
+#endif
+#endif
+#endif
+
+
+FOUNDATION_EXPORT double Pods_Alderis_DemoVersionNumber;
+FOUNDATION_EXPORT const unsigned char Pods_Alderis_DemoVersionString[];
+
diff --git a/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo.debug.xcconfig b/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo.debug.xcconfig
new file mode 100644
index 0000000..ada2059
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo.debug.xcconfig
@@ -0,0 +1,15 @@
+ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
+CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
+GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
+HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/Alderis"
+LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" "${PODS_CONFIGURATION_BUILD_DIR}/Alderis" /usr/lib/swift
+OTHER_CFLAGS = $(inherited) -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/Alderis/Alderis.modulemap"
+OTHER_LDFLAGS = $(inherited) -ObjC -l"Alderis"
+OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/Alderis/Alderis.modulemap"
+PODS_BUILD_DIR = ${BUILD_DIR}
+PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
+PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
+PODS_ROOT = ${SRCROOT}/Pods
+PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
+SWIFT_INCLUDE_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alderis"
+USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
diff --git a/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo.modulemap b/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo.modulemap
new file mode 100644
index 0000000..c1a6429
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo.modulemap
@@ -0,0 +1,6 @@
+module Pods_Alderis_Demo {
+ umbrella header "Pods-Alderis Demo-umbrella.h"
+
+ export *
+ module * { export * }
+}
diff --git a/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo.release.xcconfig b/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo.release.xcconfig
new file mode 100644
index 0000000..ada2059
--- /dev/null
+++ b/Tweaks/Alderis/Demo/Pods/Target Support Files/Pods-Alderis Demo/Pods-Alderis Demo.release.xcconfig
@@ -0,0 +1,15 @@
+ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
+CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
+GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
+HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/Alderis"
+LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" "${PODS_CONFIGURATION_BUILD_DIR}/Alderis" /usr/lib/swift
+OTHER_CFLAGS = $(inherited) -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/Alderis/Alderis.modulemap"
+OTHER_LDFLAGS = $(inherited) -ObjC -l"Alderis"
+OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/Alderis/Alderis.modulemap"
+PODS_BUILD_DIR = ${BUILD_DIR}
+PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
+PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
+PODS_ROOT = ${SRCROOT}/Pods
+PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
+SWIFT_INCLUDE_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alderis"
+USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
diff --git a/Tweaks/Alderis/LICENSE.md b/Tweaks/Alderis/LICENSE.md
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/Tweaks/Alderis/LICENSE.md
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/Tweaks/Alderis/Makefile b/Tweaks/Alderis/Makefile
new file mode 100644
index 0000000..7d86e1a
--- /dev/null
+++ b/Tweaks/Alderis/Makefile
@@ -0,0 +1,23 @@
+export TARGET = iphone:latest:14.0
+
+FRAMEWORK_OUTPUT_DIR = $(THEOS_OBJ_DIR)/xcode_derived/install/Library/Frameworks
+ALDERIS_SDK_DIR = $(THEOS_OBJ_DIR)/alderis_sdk_$(THEOS_PACKAGE_BASE_VERSION)
+
+export ADDITIONAL_CFLAGS = -fobjc-arc -Wextra -Wno-unused-parameter -F$(FRAMEWORK_OUTPUT_DIR)
+export ADDITIONAL_LDFLAGS = -F$(FRAMEWORK_OUTPUT_DIR)
+
+INSTALL_TARGET_PROCESSES = Preferences
+
+include $(THEOS)/makefiles/common.mk
+
+XCODEPROJ_NAME = Alderis
+
+FINALPACKAGE = 1
+
+Alderis_XCODEFLAGS = DYLIB_INSTALL_NAME_BASE=/Library/Frameworks BUILD_LIBRARY_FOR_DISTRIBUTION=YES ARCHS="$(ARCHS)" -quiet
+
+SUBPROJECTS = lcpshim
+
+include $(THEOS_MAKE_PATH)/xcodeproj.mk
+include $(THEOS_MAKE_PATH)/aggregate.mk
+
diff --git a/Tweaks/Alderis/Package.swift b/Tweaks/Alderis/Package.swift
new file mode 100644
index 0000000..297fb87
--- /dev/null
+++ b/Tweaks/Alderis/Package.swift
@@ -0,0 +1,16 @@
+// swift-tools-version:5.2
+
+import PackageDescription
+
+let package = Package(
+ name: "Alderis",
+ platforms: [
+ .iOS(.v12)
+ ],
+ products: [
+ .library(name: "Alderis", targets: ["Alderis"]),
+ ],
+ targets: [
+ .target(name: "Alderis", path: "Alderis")
+ ]
+)
diff --git a/Tweaks/Alderis/README.md b/Tweaks/Alderis/README.md
new file mode 100644
index 0000000..5d4322c
--- /dev/null
+++ b/Tweaks/Alderis/README.md
@@ -0,0 +1,93 @@
+# 
+
+**Try it yourself: `pod try Alderis` **
+
+Alderis is a fresh new color picker, with a gentle, fun, and dead simple user interface. It aims to incorporate the usual elements of a color picker, in a way that users will find easy and fun to use.
+
+The user can start by selecting a color they like on the initial color palette tab, and either accept it, or refine it using the color wheel and adjustment sliders found on the two other tabs.
+
+Alderis is named for the Alderamin (Alpha) star in the Cepheus (Cephei) constellation. (There is no dependency on the [Cephei](https://hbang.github.io/libcephei/) project.)
+
+## Why do I want this? Isn’t there already a color picker in iOS?
+Alderis was [originally released](https://twitter.com/hbkirb/status/1239332547437326337) before iOS 14 was unveiled with a built-in [color picker](https://developer.apple.com/design/human-interface-guidelines/ios/controls/color-wells/) feature, which seems to be heavily inspired by Alderis. However, rather than throw in the towel here, I’m considering this an opportunity to continue building what I feel is a better, less cluttered user experience, with more configuration options for apps to fine-tune a color picker UI suitable for each use case and target market.
+
+If this doesn’t seem important to you, the answer is simple: skip Alderis and use [UIColorPickerViewController](https://developer.apple.com/documentation/uikit/uicolorpickerviewcontroller). If it does, read on.
+
+### Feature Comparison
+
+
+ Alderis UIKit
+ Minimal UI ✔️ ✖️
+ Drag and drop colors within app ✔️ ✔️ *
+ Drag and drop colors across apps ✔️ ✖️
+ Color grid ✔️ ✔️
+ Color spectrum/wheel ✔️ ✔️
+ RGB sliders ✔️ ✔️
+ HSB sliders ✔️ ✖️
+ Grayscale slider ✔️ ✖️
+ Opacity slider ✔️ ✔️
+ Hex color code support ✔️ ✔️
+ Color space support (Display P3) ✖️ ✔️
+ Save favorite colors ✖️ ✔️
+ Saved colors shared across apps ✖️ ✔️
+ Grab color from screen (eyedropper) ✖️ ✔️
+ Customise title label ✔️ ✖️
+ Customise tab row visibility ✔️ ✖️
+ Customise visible tabs ✔️ ✖️
+ Customise initially selected tab ✔️ ✖️
+ Customise opacity slider visibility ✔️ ✔️
+ Customise color grid ✖️ ✖️
+ Interface Builder support ✖️ ✖️
+ SwiftUI support w/o UIKit bridging ✖️ ✖️
+ Observe value change with KVO ✖️ ✔️
+
+
+\* UIColorWell supports dragging colors *onto* it, but not *out* of it. Alderis supports both directions, which are separately configurable. The default Alderis ColorWell behavior matches UIColorWell. Alderis supports drag and drop on the color picker window; UIKit does not.
+
+## Installation
+
+### CocoaPods
+Add to your Podfile:
+
+```ruby
+pod 'Alderis', '~> 1.2.0'
+```
+
+And then run `pod install`.
+
+### Carthage
+Add to your Cartfile:
+
+```ruby
+github 'hbang/Alderis' ~> 1.2.0
+```
+
+And then run `carthage update`.
+
+### Swift Package Manager
+1. Click File → Swift Packages → Add Package Dependency.
+2. Enter `http://github.com/hbang/Alderis.git`.
+3. Specify `1.2` as the version filter.
+
+Or, manually add it to your Package.swift:
+
+```swift
+dependencies: [
+ .package(url: "http://github.com/hbang/Alderis.git", from: "1.2.0")
+]
+```
+
+And then run `swift package update`.
+
+### Jailbreak packages
+Add `ws.hbang.alderis (>= 1.2)` to your `Depends:` list.
+
+#### Preference Bundles and libcolorpicker Compatibility
+Alderis acts as a drop-in replacement for [libcolorpicker](https://github.com/atomikpanda/libcolorpicker), an abandoned but still very popular color picker library on jailbroken iOS. Packages can simply change their dependencies list to replace `org.thebigboss.libcolorpicker` with `ws.hbang.alderis (>= 1.2)` to switch their color picker to Alderis. No other changes required!
+
+For more information, refer to [the docs](https://hbang.github.io/Alderis/preference-bundles.html).
+
+## License
+Licensed under the Apache License, version 2.0. Refer to [LICENSE.md](https://github.com/hbang/Alderis/blob/main/LICENSE.md).
+
+Header backdrop photo credit: [John-Mark Smith](https://unsplash.com/@mrrrk_smith) on Unsplash
diff --git a/Tweaks/Alderis/build-fat.sh b/Tweaks/Alderis/build-fat.sh
new file mode 100755
index 0000000..e8d22ec
--- /dev/null
+++ b/Tweaks/Alderis/build-fat.sh
@@ -0,0 +1,56 @@
+#!/bin/bash
+# This makes me sad 🙁
+
+set -e
+
+if [[ $(arch) != i386 ]]; then
+ echo "Not working on ARM. Try Rosetta instead?"
+ exit 1
+fi
+
+PROJECT_DIR=$(realpath $(dirname $0))
+
+THEOS_OBJ_DIR=$PROJECT_DIR/.theos/obj
+THEOS_STAGING_DIR=$PROJECT_DIR/.theos/_
+FRAMEWORK_OUTPUT_DIR=$THEOS_OBJ_DIR/install/Library/Frameworks
+
+echo
+echo Building modern
+echo
+make clean
+sudo xcode-select -switch /Applications/Xcode-13.4.0.app/Contents/Developer
+make package \
+ FINALPACKAGE=1
+cp $FRAMEWORK_OUTPUT_DIR/Alderis.framework/Alderis Alderis-ios14
+
+echo
+echo Building legacy
+echo
+make clean
+mkdir -p $THEOS_OBJ_DIR
+mv Alderis-ios14 $THEOS_OBJ_DIR
+sudo xcode-select -switch /Applications/Xcode-11.7.app/Contents/Developer
+make package \
+ BUILD_LEGACY_ARM64E=1 \
+ THEOS_PLATFORM_SDK_ROOT=/Applications/Xcode-11.7.app/Contents/Developer \
+ FINALPACKAGE=1
+cp $FRAMEWORK_OUTPUT_DIR/Alderis.framework/Alderis $THEOS_OBJ_DIR/Alderis-ios12
+
+echo
+cp $THEOS_OBJ_DIR/Alderis-ios{12,14} $THEOS_STAGING_DIR/Library/Frameworks/Alderis.framework
+sudo xcode-select -switch /Applications/Xcode-13.4.0.app/Contents/Developer
+echo Alderis-ios12:
+otool -h $THEOS_STAGING_DIR/Library/Frameworks/Alderis.framework/Alderis-ios12
+echo
+echo Alderis-ios14:
+otool -h $THEOS_STAGING_DIR/Library/Frameworks/Alderis.framework/Alderis-ios14
+echo
+echo libcolorpicker.dylib:
+otool -h $THEOS_STAGING_DIR/usr/lib/libcolorpicker.dylib
+
+echo
+echo Packaging
+echo
+rm $THEOS_STAGING_DIR/Library/Frameworks/Alderis.framework/Alderis
+ln -s Alderis-ios12 $THEOS_STAGING_DIR/Library/Frameworks/Alderis.framework/Alderis
+$THEOS/bin/dm.pl -b -Zlzma -z9 .theos/_ packages/
diff --git a/Tweaks/Alderis/control b/Tweaks/Alderis/control
new file mode 100644
index 0000000..65b8187
--- /dev/null
+++ b/Tweaks/Alderis/control
@@ -0,0 +1,16 @@
+Package: ws.hbang.alderis
+Name: Alderis Color Picker
+Depends: firmware (>= 12.0), firmware (>= 12.2) | ${LIBSWIFT}, uikittools
+Replaces: org.thebigboss.libcolorpicker, me.nepeta.libcolorpicker
+Provides: org.thebigboss.libcolorpicker (= 99.0), me.nepeta.libcolorpicker (= 99.0)
+Version: 1.2
+Architecture: iphoneos-arm
+Description: Color picker support library for tweaks
+Maintainer: HASHBANG Productions
+Author: HASHBANG Productions
+Section: Development
+Depiction: https://chariz.com/get/alderis
+Icon: https://img.chariz.cloud/icon/alderis/icon@3x.png
+Support: https://hashbang.productions/support/
+Tag: role::developer, compatible_min::ios12.0
+dev: hbang
diff --git a/Tweaks/Alderis/info/Migrating to 1.1.md b/Tweaks/Alderis/info/Migrating to 1.1.md
new file mode 100644
index 0000000..158bea5
--- /dev/null
+++ b/Tweaks/Alderis/info/Migrating to 1.1.md
@@ -0,0 +1,42 @@
+## Migrating to 1.1
+
+### ColorPickerConfiguration
+A variety of configuration options have been added, configured on a new `ColorPickerConfiguration` class.
+
+Code that looks like this:
+
+```swift
+let colorPicker = ColorPickerViewController()
+colorPicker.color = UIColor(red: 1, green: 0, blue: 1, alpha: 0)
+present(colorPicker, animated: true, completion: nil)
+```
+
+Should now become:
+
+```swift
+let configuration = ColorPickerConfiguration(color: UIColor(red: 1, green: 0, blue: 1, alpha: 0))
+// Do any other configuration you want here…
+let colorPicker = ColorPickerViewController(configuration: configuration)
+present(colorPicker, animated: true, completion: nil)
+```
+
+### Delegate changes
+`ColorPickerDelegate.colorPicker(_:didSelect:)` is now fired with every change made within the color picker interface. Ensure any work done in this method does not assume the value is the user’s final selection. You might use this to update your user interface based on the current selection. If there is nothing useful you can do to improve the user experience, don’t implement this method.
+
+The new `ColorPickerDelegate.colorPicker(_:didAccept:)` method is now used to signal the user dismissing the color picker with a positive response, by tapping the Done button or dismissing the popover.
+
+For compatibility, if the color is set via the deprecated `ColorPickerViewController.color` API, the delegate behaves as it did in Alderis 1.0.
+
+### Popover style
+Alderis now uses popovers, providing a more integrated interface design on iPad and Mac Catalyst. In order to support this, some popover presentation parameters must be set. If they are not set, UIKit throws an exception on presenting the view controller.
+
+For example:
+
+```swift
+@IBAction func presentColorPicker(_ sender: UIView) {
+ let configuration = ColorPickerConfiguration(color: UIColor(red: 1, green: 0, blue: 1, alpha: 0))
+ let colorPicker = ColorPickerViewController(configuration: configuration)
+ colorPicker.popoverPresentationController?.sourceView = sender
+ present(colorPicker, animated: true, completion: nil)
+}
+```
diff --git a/Tweaks/Alderis/info/Preference Bundles.md b/Tweaks/Alderis/info/Preference Bundles.md
new file mode 100644
index 0000000..bcc67ad
--- /dev/null
+++ b/Tweaks/Alderis/info/Preference Bundles.md
@@ -0,0 +1,33 @@
+## Alderis with Preference Bundles
+Alderis acts as a drop-in replacement for [libcolorpicker](https://github.com/atomikpanda/libcolorpicker), an abandoned but still very popular color picker library on jailbroken iOS. Packages can simply change their dependencies list to replace `org.thebigboss.libcolorpicker` with `ws.hbang.alderis (>= 1.1)` to switch the color picker to Alderis. No other changes required!
+
+Alderis also provides a replacement, cleaner interface for preference bundles. Example usage:
+
+```xml
+
+ cell
+ PSLinkCell
+ cellClass
+ HBColorPickerTableCell
+ defaults
+ com.example.myawesomething
+ default
+ #33b5e5
+ label
+ Tint Color
+ showAlphaSlider
+
+ PostNotification
+ com.example.myawesomething/ReloadPrefs
+
+```
+
+Compared to libcolorpicker’s API design, this leans on the fundamentals of Preferences.framework, including using the framework’s built-in preference value getters/setters system. In fact, the only two distinct parts are the `cellClass` and the `showAlphaSlider` key. The rest should seem natural to typical Preference specifier plist usage.
+
+Remember to link against the `libcolorpicker` library from the preference bundle. With Theos, this might look like:
+
+```make
+MyAwesomeThing_LIBRARIES = colorpicker
+```
+
+The functionality described in this section is only available in the jailbreak package for Alderis, specifically in the `libcolorpicker.dylib` binary ([lcpshim](https://github.com/hbang/Alderis/tree/main/lcpshim)), and is not included in the App Store (CocoaPods/Carthage) version.
diff --git a/Tweaks/Alderis/lcpshim/ColorFunctions.m b/Tweaks/Alderis/lcpshim/ColorFunctions.m
new file mode 100644
index 0000000..25d3015
--- /dev/null
+++ b/Tweaks/Alderis/lcpshim/ColorFunctions.m
@@ -0,0 +1,18 @@
+@import Alderis;
+#import "libcolorpicker.h"
+
+UIColor *LCPParseColorString(NSString *hexString, NSString *fallback) {
+ UIColor *result = [[UIColor alloc] initWithHbcp_propertyListValue:hexString];
+ if (result == nil && fallback != nil) {
+ result = [[UIColor alloc] initWithHbcp_propertyListValue:fallback];
+ }
+ return result;
+}
+
+UIColor *colorFromDefaultsWithKey(NSString *identifier, NSString *key, NSString *fallback) {
+ id result = CFBridgingRelease(CFPreferencesCopyValue((__bridge CFStringRef)key, (__bridge CFStringRef)identifier, CFSTR("mobile"), kCFPreferencesCurrentHost));
+ if ([result isKindOfClass:NSString.class]) {
+ return LCPParseColorString((NSString *)result, fallback);
+ }
+ return LCPParseColorString(fallback, nil);
+}
diff --git a/Tweaks/Alderis/lcpshim/HBColorPickerTableCell+Private.h b/Tweaks/Alderis/lcpshim/HBColorPickerTableCell+Private.h
new file mode 100644
index 0000000..ee220ad
--- /dev/null
+++ b/Tweaks/Alderis/lcpshim/HBColorPickerTableCell+Private.h
@@ -0,0 +1,11 @@
+#import "libcolorpicker.h"
+
+@interface HBColorPickerTableCell ()
+
+- (UIColor *)_colorValue;
+- (void)_setColorValue:(UIColor *)color;
+- (void)_updateValue;
+
+- (void)_present;
+
+@end
diff --git a/Tweaks/Alderis/lcpshim/HBColorPickerTableCell.h b/Tweaks/Alderis/lcpshim/HBColorPickerTableCell.h
new file mode 100644
index 0000000..9394306
--- /dev/null
+++ b/Tweaks/Alderis/lcpshim/HBColorPickerTableCell.h
@@ -0,0 +1,5 @@
+#import
+
+@interface HBColorPickerTableCell : PSTableCell
+
+@end
diff --git a/Tweaks/Alderis/lcpshim/HBColorPickerTableCell.m b/Tweaks/Alderis/lcpshim/HBColorPickerTableCell.m
new file mode 100644
index 0000000..043043b
--- /dev/null
+++ b/Tweaks/Alderis/lcpshim/HBColorPickerTableCell.m
@@ -0,0 +1,127 @@
+@import Alderis;
+#import "libcolorpicker.h"
+#import
+
+@interface UIView ()
+- (UIViewController *)_viewControllerForAncestor;
+@end
+
+@interface HBColorPickerTableCell ()
+@end
+
+@implementation HBColorPickerTableCell {
+ HBColorWell *_colorWell;
+ HBColorPickerViewController *_viewController;
+}
+
+#pragma mark - PSTableCell
+
+- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier specifier:(PSSpecifier *)specifier {
+ specifier.cellType = PSButtonCell;
+ self = [super initWithStyle:style reuseIdentifier:reuseIdentifier specifier:specifier];
+ if (self) {
+ self.textLabel.textColor = self.tintColor;
+ self.textLabel.highlightedTextColor = self.tintColor;
+
+ _colorWell = [[HBColorWell alloc] initWithFrame:CGRectMake(0, 0, 32, 32)];
+ _colorWell.isDragInteractionEnabled = YES;
+ _colorWell.isDropInteractionEnabled = YES;
+ [_colorWell addTarget:self action:@selector(_present) forControlEvents:UIControlEventTouchUpInside];
+ [_colorWell addTarget:self action:@selector(_colorWellValueChanged:) forControlEvents:UIControlEventValueChanged];
+ self.accessoryView = _colorWell;
+
+ // This relies on an implementation detail - do not do this yourself!
+ [self addInteraction:[[UIDropInteraction alloc] initWithDelegate:_colorWell]];
+
+ [self _updateValue];
+ }
+ return self;
+}
+
+- (void)refreshCellContentsWithSpecifier:(PSSpecifier *)specifier {
+ specifier.cellType = PSButtonCell;
+ [super refreshCellContentsWithSpecifier:specifier];
+ [self _updateValue];
+ self.textLabel.textColor = self.tintColor;
+ self.textLabel.highlightedTextColor = self.tintColor;
+}
+
+- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated {
+ UIColor *color = _colorWell.color;
+ [super setHighlighted:highlighted animated:animated];
+ // stop deleting my background color Apple!!!
+ _colorWell.color = color;
+}
+
+- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
+ if (selected) {
+ [self _present];
+ }
+}
+
+- (void)tintColorDidChange {
+ [super tintColorDidChange];
+ self.textLabel.textColor = self.tintColor;
+ self.textLabel.highlightedTextColor = self.tintColor;
+}
+
+#pragma mark - Properties
+
+- (NSString *)_hbcp_defaults {
+ return self.specifier.properties[@"defaults"];
+}
+
+- (NSString *)_hbcp_key {
+ return self.specifier.properties[@"key"];
+}
+
+- (NSString *)_hbcp_default {
+ return self.specifier.properties[@"default"];
+}
+
+- (BOOL)_hbcp_supportsAlpha {
+ return self.specifier.properties[@"showAlphaSlider"] ? ((NSNumber *)self.specifier.properties[@"showAlphaSlider"]).boolValue : NO;
+}
+
+#pragma mark - Getters/setters
+
+- (UIColor *)_colorValue {
+ return LCPParseColorString([self.specifier performGetter], self._hbcp_default) ?: [UIColor colorWithWhite:0.6 alpha:1];
+}
+
+- (void)_setColorValue:(UIColor *)color {
+ [self.specifier performSetterWithValue:color.hbcp_propertyListValue];
+ [self _updateValue];
+}
+
+- (void)_updateValue {
+ _colorWell.color = self._colorValue;
+}
+
+#pragma mark - Actions
+
+- (void)_present {
+ _viewController = [[HBColorPickerViewController alloc] init];
+ _viewController.delegate = self;
+ _viewController.popoverPresentationController.sourceView = self;
+
+ HBColorPickerConfiguration *configuration = [[HBColorPickerConfiguration alloc] initWithColor:self._colorValue];
+ configuration.title = self.textLabel.text;
+ configuration.supportsAlpha = self._hbcp_supportsAlpha;
+ _viewController.configuration = configuration;
+
+ UIViewController *rootViewController = self._viewControllerForAncestor ?: [UIApplication sharedApplication].keyWindow.rootViewController;
+ [rootViewController presentViewController:_viewController animated:YES completion:nil];
+}
+
+- (void)_colorWellValueChanged:(HBColorWell *)sender {
+ [self _setColorValue:sender.color];
+}
+
+#pragma mark - HBColorPickerDelegate
+
+- (void)colorPicker:(HBColorPickerViewController *)colorPicker didSelectColor:(UIColor *)color {
+ [self _setColorValue:color];
+}
+
+@end
diff --git a/Tweaks/Alderis/lcpshim/Makefile b/Tweaks/Alderis/lcpshim/Makefile
new file mode 100644
index 0000000..38a9b34
--- /dev/null
+++ b/Tweaks/Alderis/lcpshim/Makefile
@@ -0,0 +1,13 @@
+TARGET = iphone:clang:15.5:13.0
+ARCHS = arm64
+FINALPACKAGE = 1
+include $(THEOS)/makefiles/common.mk
+
+LIBRARY_NAME = libcolorpicker
+
+libcolorpicker_FILES = $(wildcard *.m)
+libcolorpicker_PRIVATE_FRAMEWORKS = Preferences
+libcolorpicker_EXTRA_FRAMEWORKS = Alderis
+libcolorpicker_CFLAGS = -Wno-unguarded-availability -Wno-deprecated-declarations -fmodules
+
+include $(THEOS_MAKE_PATH)/library.mk
diff --git a/Tweaks/Alderis/lcpshim/PFColorAlert.m b/Tweaks/Alderis/lcpshim/PFColorAlert.m
new file mode 100644
index 0000000..cb8db07
--- /dev/null
+++ b/Tweaks/Alderis/lcpshim/PFColorAlert.m
@@ -0,0 +1,66 @@
+@import Alderis;
+#import "libcolorpicker.h"
+
+@interface PFColorAlert ()
+
+@end
+
+@implementation PFColorAlert {
+ PFColorAlert *_strongSelf;
+ HBColorPickerViewController *_viewController;
+ UIColor *_color;
+ BOOL _showAlpha;
+ PFColorAlertCompletion _completion;
+}
+
++ (PFColorAlert *)colorAlertWithStartColor:(UIColor *)startColor showAlpha:(BOOL)showAlpha {
+ return [[self.class alloc] initWithStartColor:startColor showAlpha:showAlpha];
+}
+
+- (PFColorAlert *)initWithStartColor:(UIColor *)startColor showAlpha:(BOOL)showAlpha {
+ self = [super init];
+ if (self) {
+ _color = startColor;
+ _showAlpha = showAlpha;
+ }
+ return self;
+}
+
+- (void)displayWithCompletion:(PFColorAlertCompletion)completion {
+ _completion = [completion copy];
+ _viewController = [[HBColorPickerViewController alloc] init];
+ _viewController.delegate = self;
+
+ UIColor *color = _color ?: [UIColor colorWithWhite:0.6 alpha:1];
+ HBColorPickerConfiguration *configuration = [[HBColorPickerConfiguration alloc] initWithColor:color];
+ configuration.supportsAlpha = _showAlpha;
+ _viewController.configuration = configuration;
+
+ UIWindow *window = [UIApplication sharedApplication].keyWindow;
+ _viewController.popoverPresentationController.sourceView = window;
+ _viewController.popoverPresentationController.sourceRect = window.bounds;
+ _viewController.popoverPresentationController.permittedArrowDirections = 0;
+ [window.rootViewController presentViewController:_viewController animated:YES completion:nil];
+
+ // Keep a strong reference to ourself. The color picker delegate is weakly stored by
+ // HBColorPickerViewController, but some users of PFColorAlert do not keep a strong reference to
+ // the PFColorAlert instance after calling displayWithCompletion:, causing this class to get
+ // deallocated and the delegate never called.
+ _strongSelf = self;
+}
+
+- (void)close {
+ _completion(_color);
+ _strongSelf = nil;
+}
+
+- (void)colorPicker:(HBColorPickerViewController *)colorPicker didSelectColor:(UIColor *)color {
+ _color = [color copy];
+ [self close];
+}
+
+- (void)colorPickerDidCancel:(HBColorPickerViewController *)colorPicker {
+ [self close];
+}
+
+@end
diff --git a/Tweaks/Alderis/lcpshim/PFColorCell.m b/Tweaks/Alderis/lcpshim/PFColorCell.m
new file mode 100644
index 0000000..a5bc6dc
--- /dev/null
+++ b/Tweaks/Alderis/lcpshim/PFColorCell.m
@@ -0,0 +1,30 @@
+#import "libcolorpicker.h"
+#import
+
+@interface PFColorCell : PFLiteColorCell
+
+@end
+
+@implementation PFColorCell
+
+- (NSString *)_hbcp_defaults {
+ return self.specifier.properties[@"color_defaults"];
+}
+
+- (NSString *)_hbcp_key {
+ return self.specifier.properties[@"color_key"];
+}
+
+- (NSString *)_hbcp_default {
+ return self.specifier.properties[@"color_fallback"];
+}
+
+- (NSString *)_hbcp_postNotification {
+ return self.specifier.properties[@"color_postNotification"];
+}
+
+- (BOOL)_hbcp_supportsAlpha {
+ return self.specifier.properties[@"usesAlpha"] ? ((NSNumber *)self.specifier.properties[@"usesAlpha"]).boolValue : NO;
+}
+
+@end
diff --git a/Tweaks/Alderis/lcpshim/PFLiteColorCell.m b/Tweaks/Alderis/lcpshim/PFLiteColorCell.m
new file mode 100644
index 0000000..d3a7605
--- /dev/null
+++ b/Tweaks/Alderis/lcpshim/PFLiteColorCell.m
@@ -0,0 +1,71 @@
+@import Alderis;
+#import "libcolorpicker.h"
+#import "HBColorPickerTableCell+Private.h"
+#import
+
+@implementation PFLiteColorCell
+
+- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier specifier:(PSSpecifier *)specifier {
+ self = [super initWithStyle:style reuseIdentifier:reuseIdentifier specifier:specifier];
+ if (self) {
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ NSLog(@"Alderis: %@: Using libcolorpicker compatibility class. Please consider switching to HBColorPickerTableCell. This warning will only be logged once.", self.class);
+ });
+ }
+ return self;
+}
+
+#pragma mark - Properties
+
+- (NSString *)_hbcp_defaults {
+ return self.specifier.properties[@"libcolorpicker"][@"defaults"];
+}
+
+- (NSString *)_hbcp_key {
+ return self.specifier.properties[@"libcolorpicker"][@"key"];
+}
+
+- (NSString *)_hbcp_default {
+ return self.specifier.properties[@"libcolorpicker"][@"fallback"];
+}
+
+- (NSString *)_hbcp_postNotification {
+ return self.specifier.properties[@"libcolorpicker"][@"PostNotification"];
+}
+
+- (BOOL)_hbcp_supportsAlpha {
+ return self.specifier.properties[@"libcolorpicker"][@"alpha"] ? ((NSNumber *)self.specifier.properties[@"libcolorpicker"][@"alpha"]).boolValue : NO;
+}
+
+#pragma mark - Getters/setters
+
+- (UIColor *)_colorValue {
+ if (self._hbcp_defaults != nil && self._hbcp_key != nil) {
+ // libcolorpicker compatibility
+ NSString *path = [NSString stringWithFormat:@"/var/mobile/Library/Preferences/%@.plist", self._hbcp_defaults];
+ NSDictionary *dictionary = [NSDictionary dictionaryWithContentsOfFile:path];
+ return LCPParseColorString(dictionary[self._hbcp_key], self._hbcp_default);
+ }
+ return [super _colorValue];
+}
+
+- (void)_setColorValue:(UIColor *)color {
+ // libcolorpicker compatibility
+ if (self._hbcp_defaults != nil && self._hbcp_key != nil) {
+ NSLog(@"Alderis: %@: Writing directly to plist file (libcolorpicker compatibility). I’m going to do it since it seems to be somewhat common, but you should be ashamed of yourself. https://hbang.github.io/Alderis/preference-bundles.html", self.class);
+
+ NSString *path = [NSString stringWithFormat:@"/var/mobile/Library/Preferences/%@.plist", self._hbcp_defaults];
+ NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithContentsOfFile:path] ?: [NSMutableDictionary dictionary];
+ dictionary[self._hbcp_key] = color.hbcp_propertyListValue;
+ [dictionary writeToFile:path atomically:YES];
+ if (self._hbcp_postNotification != nil) {
+ CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), (CFStringRef)self._hbcp_postNotification, nil, nil, YES);
+ }
+ [self _updateValue];
+ } else {
+ [super _setColorValue:color];
+ }
+}
+
+@end
diff --git a/Tweaks/Alderis/lcpshim/PFSimpleLiteColorCell.m b/Tweaks/Alderis/lcpshim/PFSimpleLiteColorCell.m
new file mode 100644
index 0000000..a87c7f1
--- /dev/null
+++ b/Tweaks/Alderis/lcpshim/PFSimpleLiteColorCell.m
@@ -0,0 +1,5 @@
+#import "libcolorpicker.h"
+
+@implementation PFSimpleLiteColorCell
+
+@end
diff --git a/Tweaks/Alderis/lcpshim/UIColor+PFColor.m b/Tweaks/Alderis/lcpshim/UIColor+PFColor.m
new file mode 100644
index 0000000..af85b99
--- /dev/null
+++ b/Tweaks/Alderis/lcpshim/UIColor+PFColor.m
@@ -0,0 +1,19 @@
+@import Alderis;
+#import "libcolorpicker.h"
+
+@implementation UIColor (PFColor)
+
++ (UIColor *)PF_colorWithHex:(NSString *)hexString {
+ return [[self.class alloc] initWithHbcp_propertyListValue:hexString];
+}
+
++ (NSString *)PF_hexFromColor:(UIColor *)color {
+ return color.hbcp_propertyListValue;
+}
+
++ (NSString *)hexFromColor:(UIColor *)color {
+ NSLog(@"Alderis: +[UIColor(PFColor) hexFromColor:]: Please migrate to +[UIColor(PFColor) PF_hexFromColor:]. This unprefixed method will be removed in future.");
+ return [self PF_hexFromColor:color];
+}
+
+@end
diff --git a/Tweaks/Alderis/lcpshim/libcolorpicker.h b/Tweaks/Alderis/lcpshim/libcolorpicker.h
new file mode 100644
index 0000000..8c26f3c
--- /dev/null
+++ b/Tweaks/Alderis/lcpshim/libcolorpicker.h
@@ -0,0 +1,53 @@
+//
+// Alderis libcolorpicker Compatibility
+// https://github.com/hbang/Alderis
+//
+// All interfaces declared in this file are deprecated and only provided for out-of-the-box
+// compatibility with libcolorpicker. Do not write new code that uses these interfaces.
+//
+
+@import UIKit;
+#import
+#import
+
+__BEGIN_DECLS
+extern UIColor *LCPParseColorString(NSString *hexString, NSString *fallback);
+
+__attribute__((__deprecated__))
+extern UIColor *colorFromDefaultsWithKey(NSString *identifier, NSString *key, NSString *fallback);
+__END_DECLS
+
+@interface UIColor (PFColor)
+
++ (UIColor *)PF_colorWithHex:(NSString *)hexString;
++ (NSString *)PF_hexFromColor:(UIColor *)color;
+
+/// Do not use this unprefixed method. Migrate to +[UIColor PF_hexFromColor:] immediately. It will
+/// be removed in a future release.
++ (NSString *)hexFromColor:(UIColor *)color __deprecated_msg("Use +[UIColor PF_hexFromColor:]");
+
+@end
+
+typedef void (^PFColorAlertCompletion)(UIColor *color);
+
+@interface PFColorAlert : NSObject
+
++ (instancetype)colorAlertWithStartColor:(UIColor *)startColor showAlpha:(BOOL)showAlpha;
+- (instancetype)initWithStartColor:(UIColor *)startColor showAlpha:(BOOL)showAlpha;
+
+- (void)displayWithCompletion:(PFColorAlertCompletion)completion;
+- (void)close;
+
+@end
+
+@interface HBColorPickerTableCell : PSTableCell
+
+@end
+
+@interface PFLiteColorCell : HBColorPickerTableCell
+
+@end
+
+@interface PFSimpleLiteColorCell : PFLiteColorCell
+
+@end
diff --git a/Tweaks/Alderis/postinst b/Tweaks/Alderis/postinst
new file mode 100755
index 0000000..30c9d43
--- /dev/null
+++ b/Tweaks/Alderis/postinst
@@ -0,0 +1,27 @@
+#!/bin/sh
+set -e
+
+# Install the right version 🙁
+cf_version=$(printf "%.0f" $(cfversion))
+if [ $cf_version -ge 1750 ]; then
+ tag=ios14
+else
+ tag=ios12
+fi
+
+if [ -e /Library/Frameworks/Alderis.framework/Alderis ]; then
+ rm /Library/Frameworks/Alderis.framework/Alderis
+fi
+ln -s Alderis-$tag /Library/Frameworks/Alderis.framework/Alderis
+
+# Tell the package manager to restart system app (SpringBoard) after installation.
+if ! [ -z "$CYDIA" ]; then
+ read -r fd ver <&$fd
+ fi
+fi
+
+exit 0
diff --git a/Tweaks/Alderis/screenshots/alderis-1.jpg b/Tweaks/Alderis/screenshots/alderis-1.jpg
new file mode 100644
index 0000000..f6ff201
Binary files /dev/null and b/Tweaks/Alderis/screenshots/alderis-1.jpg differ
diff --git a/Tweaks/Alderis/screenshots/alderis-2.jpg b/Tweaks/Alderis/screenshots/alderis-2.jpg
new file mode 100644
index 0000000..903d3e6
Binary files /dev/null and b/Tweaks/Alderis/screenshots/alderis-2.jpg differ
diff --git a/Tweaks/Alderis/screenshots/alderis-3.jpg b/Tweaks/Alderis/screenshots/alderis-3.jpg
new file mode 100644
index 0000000..5def6d6
Binary files /dev/null and b/Tweaks/Alderis/screenshots/alderis-3.jpg differ
diff --git a/Tweaks/Alderis/screenshots/alderis-4.jpg b/Tweaks/Alderis/screenshots/alderis-4.jpg
new file mode 100644
index 0000000..92e32bc
Binary files /dev/null and b/Tweaks/Alderis/screenshots/alderis-4.jpg differ
diff --git a/Tweaks/Alderis/screenshots/logo.jpg b/Tweaks/Alderis/screenshots/logo.jpg
new file mode 100644
index 0000000..7bf3b8d
Binary files /dev/null and b/Tweaks/Alderis/screenshots/logo.jpg differ
diff --git a/Tweaks/Cercube/.gitkeep b/Tweaks/Cercube/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/Tweaks/DontEatMyContent/.gitignore b/Tweaks/DontEatMyContent/.gitignore
new file mode 100644
index 0000000..075ed95
--- /dev/null
+++ b/Tweaks/DontEatMyContent/.gitignore
@@ -0,0 +1,17 @@
+.theos/
+packages/
+.test/
+
+# Temporary files
+*~
+~$*.doc*
+~$*.xls*
+~$*.ppt*
+*.xlk
+*.pdf
+
+# .DS_Store files
+*.DS_Store
+
+# Preference files
+.vscode/
\ No newline at end of file
diff --git a/Tweaks/DontEatMyContent/DontEatMyContent.plist b/Tweaks/DontEatMyContent/DontEatMyContent.plist
new file mode 100644
index 0000000..fb2904e
--- /dev/null
+++ b/Tweaks/DontEatMyContent/DontEatMyContent.plist
@@ -0,0 +1 @@
+{ Filter = { Bundles = ( "com.google.ios.youtube" ); }; }
diff --git a/Tweaks/DontEatMyContent/LICENSE.md b/Tweaks/DontEatMyContent/LICENSE.md
new file mode 100644
index 0000000..831fc80
--- /dev/null
+++ b/Tweaks/DontEatMyContent/LICENSE.md
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Foxster
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Tweaks/DontEatMyContent/Makefile b/Tweaks/DontEatMyContent/Makefile
new file mode 100644
index 0000000..10cb52a
--- /dev/null
+++ b/Tweaks/DontEatMyContent/Makefile
@@ -0,0 +1,12 @@
+TARGET := iphone:clang:latest:14.0
+ARCHS = arm64
+
+include $(THEOS)/makefiles/common.mk
+
+TWEAK_NAME = DontEatMyContent
+
+DontEatMyContent_FILES = Tweak.x Settings.x
+DontEatMyContent_CFLAGS = -fobjc-arc
+DontEatMyContent_FRAMEWORKS = UIKit
+
+include $(THEOS_MAKE_PATH)/tweak.mk
diff --git a/Tweaks/DontEatMyContent/README.md b/Tweaks/DontEatMyContent/README.md
new file mode 100644
index 0000000..28ed3b1
--- /dev/null
+++ b/Tweaks/DontEatMyContent/README.md
@@ -0,0 +1,37 @@
+# DontEatMyContent
+Prevent the notch/Dynamic Island from munching on 2:1 video content in YouTube
+
+
+
+
+
+## How It Works
+The rendering view is constrained to the safe area layout guide of its container so it will always be below the notch and Dynamic Island. These constraints are only activated for videos with 2:1 aspect ratio or wider to prevent unintended effects on videos with smaller aspect ratios.
+
+## Compatibility
+Supports iPhone 12 mini, iPhone 13 series and newer **except** iPhone SE 3rd generation, iPhone 13 Pro Max and iPhone 14 Plus.
+
+> **Note**: From [v1.0.4](https://github.com/therealFoxster/DontEatMyContent/releases/tag/v1.0.4) onwards, the tweak only supports YouTube versions that got the [October 2022 redesign](https://blog.youtube/news-and-events/an-updated-look-and-feel-for-youtube/). v1.0.4 was tested and confirmed to be working with YouTube v17.43.1.
+
+## Grab It
+- IPA file: https://therealfoxster.github.io/altsource-viewer/app?source=https://therealfoxster.github.io/altsource/apps.json&id=com.google.ios.youtube
+- DEB file: https://github.com/therealFoxster/DontEatMyContent/releases/latest
+
+## Preview (iPhone 13 mini)
+### Original Implementation
+
+
+
+
+### Tweaked Implementation
+
+
+
+
+### Zoomed to Fill
+
+
+
+
+## License
+[The MIT License](LICENSE.md)
diff --git a/Tweaks/DontEatMyContent/Settings.x b/Tweaks/DontEatMyContent/Settings.x
new file mode 100644
index 0000000..79fc50c
--- /dev/null
+++ b/Tweaks/DontEatMyContent/Settings.x
@@ -0,0 +1,167 @@
+#import "Tweak.h"
+
+// Adapted from
+// https://github.com/PoomSmart/YouPiP/blob/bd04bf37be3d01540db418061164ae17a8f0298e/Settings.x
+// https://github.com/qnblackcat/uYouPlus/blob/265927b3900d886e2085d05bfad7cd4157be87d2/Settings.xm
+
+extern void DEMC_showSnackBar(NSString *text);
+extern NSBundle *DEMC_getTweakBundle();
+extern CGFloat constant;
+
+static const NSInteger sectionId = 517; // DontEatMyContent's section ID (just a random number)
+
+// Category for additional functions
+@interface YTSettingsSectionItemManager (_DEMC)
+- (void)updateDEMCSectionWithEntry:(id)entry;
+@end
+
+%group DEMC_Settings
+
+%hook YTAppSettingsPresentationData
++ (NSArray *)settingsCategoryOrder {
+ NSArray *order = %orig;
+ NSMutableArray *mutableOrder = [order mutableCopy];
+ NSUInteger insertIndex = [order indexOfObject:@(1)]; // Index of Settings > General
+ if (insertIndex != NSNotFound)
+ [mutableOrder insertObject:@(sectionId) atIndex:insertIndex + 1]; // Insert DontEatMyContent settings under General
+ return mutableOrder;
+}
+%end
+
+%hook YTSettingsSectionItemManager
+%new
+- (void)updateDEMCSectionWithEntry:(id)entry {
+ YTSettingsViewController *delegate = [self valueForKey:@"_dataDelegate"];
+ NSMutableArray *sectionItems = [NSMutableArray array]; // Create autoreleased array
+ NSBundle *bundle = DEMC_getTweakBundle();
+
+ // Enabled
+ YTSettingsSectionItem *enabled = [%c(YTSettingsSectionItem) switchItemWithTitle:LOCALIZED_STRING(@"ENABLED")
+ titleDescription:LOCALIZED_STRING(@"TWEAK_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IS_TWEAK_ENABLED
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:ENABLED_KEY];
+
+ YTAlertView *alert = [%c(YTAlertView) confirmationDialogWithAction:^
+ {
+ // https://stackoverflow.com/a/17802404/19227228
+ [[UIApplication sharedApplication] performSelector:@selector(suspend)];
+ [NSThread sleepForTimeInterval:0.5];
+ exit(0);
+ }
+ actionTitle:LOCALIZED_STRING(@"EXIT")
+ cancelTitle:LOCALIZED_STRING(@"LATER")
+ ];
+ alert.title = DEMC;
+ alert.subtitle = LOCALIZED_STRING(@"EXIT_YT_DESC");
+ [alert show];
+
+ return YES;
+ }
+ settingItemId:0
+ ];
+ [sectionItems addObject:enabled];
+
+ // Safe area constant
+ YTSettingsSectionItem *constraintConstant = [%c(YTSettingsSectionItem) itemWithTitle:LOCALIZED_STRING(@"SAFE_AREA_CONST")
+ titleDescription:LOCALIZED_STRING(@"SAFE_AREA_CONST_DESC")
+ accessibilityIdentifier:nil
+ detailTextBlock:^NSString *() {
+ return [NSString stringWithFormat:@"%.1f", constant];
+ }
+ selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger sectionItemIndex) {
+ __block YTSettingsViewController *settingsViewController = [self valueForKey:@"_settingsViewControllerDelegate"];
+ NSMutableArray *rows = [NSMutableArray array];
+
+ float currentConstant = 20.0;
+ float storedConstant = [[NSUserDefaults standardUserDefaults] floatForKey:SAFE_AREA_CONSTANT_KEY];;
+ UInt8 index = 0, selectedIndex = 0;
+ while (currentConstant <= 25.0) {
+ NSString *title = [NSString stringWithFormat:@"%.1f", currentConstant];
+ if (currentConstant == DEFAULT_CONSTANT)
+ title = [NSString stringWithFormat:@"%.1f (%@)", currentConstant, LOCALIZED_STRING(@"DEFAULT")];
+ if (currentConstant == storedConstant)
+ selectedIndex = index;
+ [rows addObject:[%c(YTSettingsSectionItem) checkmarkItemWithTitle:title
+ selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger sectionItemIndex) {
+ [[NSUserDefaults standardUserDefaults] setFloat:currentConstant forKey:SAFE_AREA_CONSTANT_KEY];
+ constant = currentConstant;
+ [settingsViewController reloadData]; // Refresh section's detail text (constant)
+ DEMC_showSnackBar(LOCALIZED_STRING(@"SAFE_AREA_CONST_MESSAGE"));
+ return YES;
+ }
+ ]];
+ currentConstant += 0.5; index++;
+ }
+
+ YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOCALIZED_STRING(@"SAFE_AREA_CONST")
+ pickerSectionTitle:nil
+ rows:rows
+ selectedItemIndex:selectedIndex
+ parentResponder:[self parentResponder]
+ ];
+
+ [settingsViewController pushViewController:picker];
+ return YES;
+ }
+ ];
+ if (IS_TWEAK_ENABLED) [sectionItems addObject:constraintConstant];
+
+ // Color views
+ YTSettingsSectionItem *colorViews = [%c(YTSettingsSectionItem) switchItemWithTitle:LOCALIZED_STRING(@"COLOR_VIEWS")
+ titleDescription:LOCALIZED_STRING(@"COLOR_VIEWS_DESC")
+ accessibilityIdentifier:nil
+ switchOn:IS_COLOR_VIEWS_ENABLED
+ switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:COLOR_VIEWS_ENABLED_KEY];
+ DEMC_showSnackBar(LOCALIZED_STRING(@"CHANGES_SAVED"));
+ return YES;
+ }
+ settingItemId:0
+ ];
+ if (IS_TWEAK_ENABLED) [sectionItems addObject:colorViews];
+
+ // Report an issue
+ YTSettingsSectionItem *reportIssue = [%c(YTSettingsSectionItem) itemWithTitle:LOCALIZED_STRING(@"REPORT_ISSUE")
+ titleDescription:nil
+ accessibilityIdentifier:nil
+ detailTextBlock:nil
+ selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger sectionItemIndex) {
+ return [%c(YTUIUtils) openURL:[NSURL URLWithString:@"https://github.com/therealFoxster/DontEatMyContent/issues/new"]];
+ }
+ ];
+ [sectionItems addObject:reportIssue];
+
+ // View source code
+ YTSettingsSectionItem *sourceCode = [%c(YTSettingsSectionItem) itemWithTitle:LOCALIZED_STRING(@"SOURCE_CODE")
+ titleDescription:nil
+ accessibilityIdentifier:nil
+ detailTextBlock:nil
+ selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger sectionItemIndex) {
+ return [%c(YTUIUtils) openURL:[NSURL URLWithString:@"https://github.com/therealFoxster/DontEatMyContent"]];
+ }
+ ];
+ [sectionItems addObject:sourceCode];
+
+ [delegate setSectionItems:sectionItems
+ forCategory:sectionId
+ title:DEMC
+ titleDescription:nil
+ headerHidden:NO
+ ];
+}
+- (void)updateSectionForCategory:(NSUInteger)category withEntry:(id)entry {
+ if (category == sectionId) {
+ [self updateDEMCSectionWithEntry:entry];
+ return;
+ }
+ %orig;
+}
+%end
+
+%end // group DEMC_Settings
+
+%ctor {
+ %init(DEMC_Settings);
+}
\ No newline at end of file
diff --git a/Tweaks/DontEatMyContent/Tweak.h b/Tweaks/DontEatMyContent/Tweak.h
new file mode 100644
index 0000000..c8d3ef0
--- /dev/null
+++ b/Tweaks/DontEatMyContent/Tweak.h
@@ -0,0 +1,108 @@
+#import
+
+#define DEMC @"DontEatMyContent"
+
+// Keys
+#define ENABLED_KEY @"DEMC_enabled"
+#define COLOR_VIEWS_ENABLED_KEY @"DEMC_colorViewsEnabled"
+#define SAFE_AREA_CONSTANT_KEY @"DEMC_safeAreaConstant"
+
+#define DEFAULT_CONSTANT 22.0
+#define IS_TWEAK_ENABLED [[NSUserDefaults standardUserDefaults] boolForKey:ENABLED_KEY]
+#define IS_COLOR_VIEWS_ENABLED [[NSUserDefaults standardUserDefaults] boolForKey:COLOR_VIEWS_ENABLED_KEY]
+#define LOCALIZED_STRING(s) [bundle localizedStringForKey:s value:nil table:nil]
+
+@interface YTPlayerViewController : UIViewController
+- (id)activeVideoPlayerOverlay;
+- (id)playerView;
+- (BOOL)isCurrentVideoVertical;
+@end
+
+@interface YTPlayerView : UIView
+- (id)renderingView;
+@end
+
+@interface YTMainAppVideoPlayerOverlayViewController : UIViewController
+- (BOOL)isFullscreen;
+@end
+
+@interface MLHAMSBDLSampleBufferRenderingView : UIView
+@end
+
+@interface YTMainAppEngagementPanelViewController : UIViewController
+- (BOOL)isLandscapeEngagementPanel;
+- (BOOL)isPeekingSupported;
+@end
+
+@interface YTEngagementPanelContainerViewController : UIViewController
+- (BOOL)isLandscapeEngagementPanel;
+- (BOOL)isPeekingSupported;
+@end
+
+@interface YTLabel : UILabel
+@property (nonatomic, copy, readwrite) NSString *text;
+@end
+
+@interface YTSettingsCell : UICollectionViewCell
+@end
+
+@interface YTSettingsSectionItemManager : NSObject
+- (id)parentResponder;
+@end
+
+@interface YTSettingsPickerViewController : UIViewController
+- (instancetype)initWithNavTitle:(NSString *)navTitle
+ pickerSectionTitle:(NSString *)pickerSectionTitle
+ rows:(NSArray *)rows
+ selectedItemIndex:(NSUInteger)selectedItemIndex
+ parentResponder:(id)parentResponder;
+@end
+
+@interface YTSettingsSectionItem : NSObject
++ (instancetype)switchItemWithTitle:(NSString *)title
+ titleDescription:(NSString *)titleDescription
+ accessibilityIdentifier:(NSString *)accessibilityIdentifier
+ switchOn:(BOOL)switchOn
+ switchBlock:(BOOL (^)(YTSettingsCell *, BOOL))switchBlock
+ settingItemId:(int)settingItemId;
++ (instancetype)itemWithTitle:(NSString *)title
+ titleDescription:(NSString *)titleDescription
+ accessibilityIdentifier:(NSString *)accessibilityIdentifier
+ detailTextBlock:(id)detailTextBlock
+ selectBlock:(BOOL (^)(YTSettingsCell *, NSUInteger))selectBlock;
++ (instancetype)checkmarkItemWithTitle:(NSString *)title
+ selectBlock:(BOOL (^)(YTSettingsCell *, NSUInteger))selectBlock;
+@end
+
+@interface YTSettingsViewController : UIViewController
+- (void)setSectionItems:(NSMutableArray *)sectionItems
+ forCategory:(NSInteger)category
+ title:(NSString *)title
+ titleDescription:(NSString *)titleDescription
+ headerHidden:(BOOL)headerHidden;
+- (void)pushViewController:(UIViewController *)viewController;
+- (void)reloadData;
+@end
+
+// Alert
+@interface YTAlertView : UIView
+@property (nonatomic, copy, readwrite) NSString *title;
+@property (nonatomic, copy, readwrite) NSString *subtitle;
++ (instancetype)confirmationDialogWithAction:(void (^)(void))action
+ actionTitle:(NSString *)actionTitle
+ cancelTitle:(NSString *)cancelTitle;
+- (void)show;
+@end
+
+// Snack bar
+@interface YTHUDMessage : NSObject
++ (id)messageWithText:(id)text;
+@end
+@interface GOOHUDManagerInternal : NSObject
+- (void)showMessageMainThread:(id)message;
++ (id)sharedInstance;
+@end
+
+@interface YTUIUtils : NSObject
++ (BOOL)openURL:(NSURL *)url;
+@end
\ No newline at end of file
diff --git a/Tweaks/DontEatMyContent/Tweak.x b/Tweaks/DontEatMyContent/Tweak.x
new file mode 100644
index 0000000..2d648a6
--- /dev/null
+++ b/Tweaks/DontEatMyContent/Tweak.x
@@ -0,0 +1,279 @@
+#import
+#import
+#import "Tweak.h"
+
+#define UNSUPPORTED_DEVICES @[@"iPhone14,3", @"iPhone14,6", @"iPhone14,8"]
+#define THRESHOLD 1.99
+
+CGFloat constant; // Makes rendering view a bit larger since constraining to safe area leaves a gap between the notch/Dynamic Island and video
+static CGFloat videoAspectRatio = 16/9;
+static BOOL isZoomedToFill = NO;
+static BOOL isEngagementPanelVisible = NO;
+static BOOL isRemoveEngagementPanelViewControllerWithIdentifierCalled = NO;
+
+static MLHAMSBDLSampleBufferRenderingView *renderingView;
+static NSLayoutConstraint *widthConstraint, *heightConstraint, *centerXConstraint, *centerYConstraint;
+
+static BOOL DEMC_isDeviceSupported();
+static void DEMC_activateConstraints();
+static void DEMC_deactivateConstraints();
+static void DEMC_centerRenderingView();
+void DEMC_showSnackBar(NSString *text);
+NSBundle *DEMC_getTweakBundle();
+
+%group DEMC_Tweak
+
+// Retrieve video aspect ratio
+%hook YTPlayerView
+- (void)setAspectRatio:(CGFloat)aspectRatio {
+ %orig(aspectRatio);
+ videoAspectRatio = aspectRatio;
+}
+%end
+
+%hook YTPlayerViewController
+- (void)viewDidAppear:(BOOL)animated {
+ YTPlayerView *playerView = [self playerView];
+ UIView *renderingViewContainer = [playerView valueForKey:@"_renderingViewContainer"];
+ renderingView = [playerView renderingView];
+
+ widthConstraint = [renderingView.widthAnchor constraintEqualToAnchor:renderingViewContainer.safeAreaLayoutGuide.widthAnchor constant:constant];
+ heightConstraint = [renderingView.heightAnchor constraintEqualToAnchor:renderingViewContainer.safeAreaLayoutGuide.heightAnchor constant:constant];
+ centerXConstraint = [renderingView.centerXAnchor constraintEqualToAnchor:renderingViewContainer.centerXAnchor];
+ centerYConstraint = [renderingView.centerYAnchor constraintEqualToAnchor:renderingViewContainer.centerYAnchor];
+
+ if (IS_COLOR_VIEWS_ENABLED) {
+ playerView.backgroundColor = [UIColor blueColor];
+ renderingViewContainer.backgroundColor = [UIColor greenColor];
+ renderingView.backgroundColor = [UIColor redColor];
+ } else {
+ playerView.backgroundColor = nil;
+ renderingViewContainer.backgroundColor = nil;
+ renderingView.backgroundColor = [UIColor blackColor];
+ }
+
+ YTMainAppVideoPlayerOverlayViewController *activeVideoPlayerOverlay = [self activeVideoPlayerOverlay];
+
+ // Must check class since YTInlineMutedPlaybackPlayerOverlayViewController doesn't have -(BOOL)isFullscreen
+ if ([activeVideoPlayerOverlay isKindOfClass:%c(YTMainAppVideoPlayerOverlayViewController)] &&
+ [activeVideoPlayerOverlay isFullscreen] && !isZoomedToFill && !isEngagementPanelVisible)
+ DEMC_activateConstraints();
+
+ %orig(animated);
+}
+// New video played
+- (void)playbackController:(id)playbackController didActivateVideo:(id)video withPlaybackData:(id)playbackData {
+ %orig(playbackController, video, playbackData);
+
+ isEngagementPanelVisible = NO;
+ isRemoveEngagementPanelViewControllerWithIdentifierCalled = NO;
+
+ if ([[self activeVideoPlayerOverlay] isFullscreen])
+ // New video played while in full screen (landscape)
+ // Activate since new videos played in full screen aren't zoomed-to-fill by default
+ // (i.e. the notch/Dynamic Island will cut into content when playing a new video in full screen)
+ DEMC_activateConstraints();
+ else if (![self isCurrentVideoVertical] && ((YTPlayerView *)[self playerView]).userInteractionEnabled)
+ DEMC_deactivateConstraints();
+}
+- (void)setPlayerViewLayout:(int)layout {
+ %orig(layout);
+
+ if (![[self activeVideoPlayerOverlay] isKindOfClass:%c(YTMainAppVideoPlayerOverlayViewController)]) return;
+
+ switch (layout) {
+ case 1: // Mini bar
+ break;
+ case 2:
+ DEMC_deactivateConstraints();
+ break;
+ case 3: // Fullscreen
+ if (!isZoomedToFill && !isEngagementPanelVisible) DEMC_activateConstraints();
+ break;
+ default:
+ break;
+ }
+}
+%end
+
+// Pinch to zoom
+%hook YTVideoFreeZoomOverlayView
+- (void)didRecognizePinch:(UIPinchGestureRecognizer *)pinchGestureRecognizer {
+ DEMC_deactivateConstraints();
+ %orig(pinchGestureRecognizer);
+}
+// Detect zoom to fill
+- (void)showLabelForSnapState:(NSInteger)snapState {
+ if (snapState == 0) { // Original
+ isZoomedToFill = NO;
+ DEMC_activateConstraints();
+ } else if (snapState == 1) { // Zoomed to fill
+ isZoomedToFill = YES;
+ // No need to deactivate constraints as it's already done in -(void)didRecognizePinch:(UIPinchGestureRecognizer *)
+ }
+ %orig(snapState);
+}
+%end
+
+// Mini bar dismiss
+%hook YTWatchMiniBarViewController
+- (void)dismissMiniBarWithVelocity:(CGFloat)velocity gestureType:(int)gestureType {
+ %orig(velocity, gestureType);
+ isZoomedToFill = NO; // YouTube undoes zoom-to-fill when mini bar is dismissed
+}
+- (void)dismissMiniBarWithVelocity:(CGFloat)velocity gestureType:(int)gestureType skipShouldDismissCheck:(BOOL)skipShouldDismissCheck {
+ %orig(velocity, gestureType, skipShouldDismissCheck);
+ isZoomedToFill = NO;
+}
+%end
+
+%hook YTMainAppEngagementPanelViewController
+// Engagement panel (comment, description, etc.) about to show up
+- (void)viewWillAppear:(BOOL)animated {
+ if ([self isPeekingSupported]) {
+ // Shorts (only Shorts support peeking, I think)
+ } else {
+ // Everything else
+ isEngagementPanelVisible = YES;
+ if ([self isLandscapeEngagementPanel]) {
+ DEMC_deactivateConstraints();
+ }
+ }
+ %orig(animated);
+}
+%end
+
+%hook YTEngagementPanelContainerViewController
+// Engagement panel about to dismiss
+- (void)notifyEngagementPanelContainerControllerWillHideFinalPanel {
+ // Crashes if plays new video while in full screen causing engagement panel dismissal
+ // Must check if engagement panel was dismissed because new video played
+ // (i.e. if -(void)removeEngagementPanelViewControllerWithIdentifier:(id) was called prior)
+ if (![self isPeekingSupported] && !isRemoveEngagementPanelViewControllerWithIdentifierCalled) {
+ isEngagementPanelVisible = NO;
+ if ([self isLandscapeEngagementPanel] && !isZoomedToFill) {
+ DEMC_activateConstraints();
+ }
+ }
+ %orig;
+}
+- (void)removeEngagementPanelViewControllerWithIdentifier:(id)identifier {
+ // Usually called when engagement panel is open & new video is played or mini bar is dismissed
+ isRemoveEngagementPanelViewControllerWithIdentifierCalled = YES;
+ %orig(identifier);
+}
+%end
+
+%end // group DEMC_Tweak
+
+%group DEMC_UnsupportedDevice
+
+// Get tweak settings' index path & prevent it from being opened on unsupported devices
+id settingsCollectionView;
+NSIndexPath *tweakIndexPath;
+%hook YTCollectionViewController
+- (id)collectionView:(id)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
+ YTSettingsCell *cell = %orig;
+ if ([cell isKindOfClass:%c(YTSettingsCell)]) {
+ YTLabel *title = [cell valueForKey:@"_titleLabel"];
+ if ([title.text isEqualToString:DEMC]) {
+ settingsCollectionView = collectionView;
+ tweakIndexPath = indexPath;
+ }
+ }
+ return cell;
+}
+- (BOOL)collectionView:(id)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath {
+ if (collectionView == settingsCollectionView && indexPath == tweakIndexPath) {
+ NSBundle *bundle = DEMC_getTweakBundle();
+ DEMC_showSnackBar(LOCALIZED_STRING(@"UNSUPPORTED_DEVICE"));
+ return NO;
+ }
+ return %orig;
+}
+%end
+
+%end // group DEMC_UnsupportedDevice
+
+%ctor {
+ if (!DEMC_isDeviceSupported()) {
+ [[NSUserDefaults standardUserDefaults] setBool:NO forKey:ENABLED_KEY];
+ %init(DEMC_UnsupportedDevice);
+ return;
+ }
+
+ constant = [[NSUserDefaults standardUserDefaults] floatForKey:SAFE_AREA_CONSTANT_KEY];
+ if (constant == 0) { // First launch probably
+ constant = DEFAULT_CONSTANT;
+ [[NSUserDefaults standardUserDefaults] setFloat:constant forKey:SAFE_AREA_CONSTANT_KEY];
+ }
+ if (IS_TWEAK_ENABLED) %init(DEMC_Tweak);
+}
+
+static BOOL DEMC_isDeviceSupported() {
+ // Get device model identifier (e.g. iPhone14,4)
+ // https://stackoverflow.com/a/11197770/19227228
+ struct utsname systemInfo;
+ uname(&systemInfo);
+ NSString *deviceModelId = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
+
+ NSArray *unsupportedModelIds = UNSUPPORTED_DEVICES;
+ for (NSString *identifier in unsupportedModelIds) {
+ if ([deviceModelId isEqualToString:identifier]) {
+ return NO;
+ }
+ }
+
+ if ([deviceModelId containsString:@"iPhone"]) {
+ if ([deviceModelId isEqualToString:@"iPhone13,1"]) {
+ // iPhone 12 mini
+ return YES;
+ }
+ NSString *modelNumber = [[deviceModelId stringByReplacingOccurrencesOfString:@"iPhone" withString:@""] stringByReplacingOccurrencesOfString:@"," withString:@"."];
+ if ([modelNumber floatValue] >= 14.0) {
+ // iPhone 13 series and newer
+ return YES;
+ } else return NO;
+ } else return NO;
+}
+
+static void DEMC_activateConstraints() {
+ if (videoAspectRatio < THRESHOLD) {
+ DEMC_deactivateConstraints();
+ return;
+ }
+ // NSLog(@"activate");
+ DEMC_centerRenderingView();
+ renderingView.translatesAutoresizingMaskIntoConstraints = NO;
+ widthConstraint.active = YES;
+ heightConstraint.active = YES;
+}
+
+static void DEMC_deactivateConstraints() {
+ // NSLog(@"deactivate");
+ renderingView.translatesAutoresizingMaskIntoConstraints = YES;
+}
+
+static void DEMC_centerRenderingView() {
+ centerXConstraint.active = YES;
+ centerYConstraint.active = YES;
+}
+
+void DEMC_showSnackBar(NSString *text) {
+ YTHUDMessage *message = [%c(YTHUDMessage) messageWithText:text];
+ GOOHUDManagerInternal *manager = [%c(GOOHUDManagerInternal) sharedInstance];
+ [manager showMessageMainThread:message];
+}
+
+NSBundle *DEMC_getTweakBundle() {
+ static NSBundle *bundle = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"DontEatMyContent" ofType:@"bundle"];
+ if (bundlePath)
+ bundle = [NSBundle bundleWithPath:bundlePath];
+ else // Rootless
+ bundle = [NSBundle bundleWithPath:ROOT_PATH_NS(@"/Library/Application Support/DontEatMyContent.bundle")];
+ });
+ return bundle;
+}
\ No newline at end of file
diff --git a/Tweaks/DontEatMyContent/control b/Tweaks/DontEatMyContent/control
new file mode 100644
index 0000000..64b908a
--- /dev/null
+++ b/Tweaks/DontEatMyContent/control
@@ -0,0 +1,9 @@
+Package: me.foxster.donteatmycontent
+Name: DontEatMyContent
+Version: 1.0.10
+Architecture: iphoneos-arm
+Description: Prevent the notch/Dynamic Island from munching on 2:1 video content in YouTube
+Maintainer: Foxster
+Author: Foxster
+Section: Tweaks
+Depends: mobilesubstrate (>= 0.9.5000), firmware (>= 14.0)
diff --git a/Tweaks/DontEatMyContent/layout/Library/Application Support/DontEatMyContent.bundle/Info.plist b/Tweaks/DontEatMyContent/layout/Library/Application Support/DontEatMyContent.bundle/Info.plist
new file mode 100644
index 0000000..00c6803
--- /dev/null
+++ b/Tweaks/DontEatMyContent/layout/Library/Application Support/DontEatMyContent.bundle/Info.plist
@@ -0,0 +1,24 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ English
+ CFBundleExecutable
+
+ CFBundleIdentifier
+ me.foxster.donteatmycontent
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundlePackageType
+ BNDL
+ CFBundleShortVersionString
+ 1.0.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1.0
+ NSPrincipalClass
+ DontEatMyContent
+
+
\ No newline at end of file
diff --git a/Tweaks/DontEatMyContent/layout/Library/Application Support/DontEatMyContent.bundle/en.lproj/Localizable.strings b/Tweaks/DontEatMyContent/layout/Library/Application Support/DontEatMyContent.bundle/en.lproj/Localizable.strings
new file mode 100644
index 0000000..3ddf947
--- /dev/null
+++ b/Tweaks/DontEatMyContent/layout/Library/Application Support/DontEatMyContent.bundle/en.lproj/Localizable.strings
@@ -0,0 +1,21 @@
+"ENABLED" = "Enabled";
+"TWEAK_DESC" = "Prevents the notch/Dynamic Island from cutting into 2:1 videos (e.g., MKBHD's videos)";
+
+"SAFE_AREA_CONST" = "Safe area constant";
+"SAFE_AREA_CONST_DESC" = "Customize this constant to push the video closer to or further away from the notch/Dynamic Island. The larger the constant, the closer the video will be to the notch/Dynamic Island, and vice versa";
+"DEFAULT" = "default";
+"SAFE_AREA_CONST_MESSAGE" = "Your changes were saved. If a video is currently playing, dismiss it for changes to take effect";
+
+"COLOR_VIEWS" = "Colored views";
+"COLOR_VIEWS_DESC" = "Sets a red background for the rendering view, green for the rendering view container, and blue for the player view; useful for debugging";
+
+"REPORT_ISSUE" = "Report an issue";
+"SOURCE_CODE" = "View source code";
+
+"CHANGES_SAVED" = "Your changes were saved";
+
+"EXIT" = "Exit";
+"LATER" = "Later";
+"EXIT_YT_DESC" = "Relaunch YouTube to apply changes";
+
+"UNSUPPORTED_DEVICE" = "This device is not supported";
\ No newline at end of file
diff --git a/Tweaks/FLEX/.gitignore b/Tweaks/FLEX/.gitignore
new file mode 100644
index 0000000..496ee2c
--- /dev/null
+++ b/Tweaks/FLEX/.gitignore
@@ -0,0 +1 @@
+.DS_Store
\ No newline at end of file
diff --git a/Tweaks/FLEX/Core/Controllers/FLEXFilteringTableViewController.h b/Tweaks/FLEX/Core/Controllers/FLEXFilteringTableViewController.h
new file mode 100644
index 0000000..f0488a1
--- /dev/null
+++ b/Tweaks/FLEX/Core/Controllers/FLEXFilteringTableViewController.h
@@ -0,0 +1,89 @@
+//
+// FLEXFilteringTableViewController.h
+// FLEX
+//
+// Created by Tanner on 3/9/20.
+// Copyright © 2020 FLEX Team. All rights reserved.
+//
+
+#import "FLEXTableViewController.h"
+
+#pragma mark - FLEXTableViewFiltering
+@protocol FLEXTableViewFiltering
+
+/// An array of visible, "filtered" sections. For example,
+/// if you have 3 sections in \c allSections and the user searches
+/// for something that matches rows in only one section, then
+/// this property would only contain that on matching section.
+@property (nonatomic, copy) NSArray *sections;
+/// An array of all possible sections. Empty sections are to be removed
+/// and the resulting array stored in the \c section property. Setting
+/// this property should immediately set \c sections to \c nonemptySections
+///
+/// Do not manually initialize this property, it will be
+/// initialized for you using the result of \c makeSections.
+@property (nonatomic, copy) NSArray *allSections;
+
+/// This computed property should filter \c allSections for assignment to \c sections
+@property (nonatomic, readonly, copy) NSArray *nonemptySections;
+
+/// This should be able to re-initialize \c allSections
+- (NSArray *)makeSections;
+
+@end
+
+
+#pragma mark - FLEXFilteringTableViewController
+/// A table view which implements \c UITableView* methods using arrays of
+/// \c FLEXTableViewSection objects provied by a special delegate.
+@interface FLEXFilteringTableViewController : FLEXTableViewController
+
+/// Stores the current search query.
+@property (nonatomic, copy) NSString *filterText;
+
+/// This property is set to \c self by default.
+///
+/// This property is used to power almost all of the table view's data source
+/// and delegate methods automatically, including row and section filtering
+/// when the user searches, 3D Touch context menus, row selection, etc.
+///
+/// Setting this property will also set \c searchDelegate to that object.
+@property (nonatomic, weak) id filterDelegate;
+
+/// Defaults to \c NO. If enabled, all filtering will be done by calling
+/// \c onBackgroundQueue:thenOnMainQueue: with the UI updated on the main queue.
+@property (nonatomic) BOOL filterInBackground;
+
+/// Defaults to \c NO. If enabled, one • will be supplied as an index title for each section.
+@property (nonatomic) BOOL wantsSectionIndexTitles;
+
+/// Recalculates the non-empty sections and reloads the table view.
+///
+/// Subclasses may override to perform additional reloading logic,
+/// such as calling \c -reloadSections if needed. Be sure to call
+/// \c super after any logic that would affect the appearance of
+/// the table view, since the table view is reloaded last.
+///
+/// Called at the end of this class's implementation of \c updateSearchResults:
+- (void)reloadData;
+
+/// Invoke this method to call \c -reloadData on each section
+/// in \c self.filterDelegate.allSections.
+- (void)reloadSections;
+
+#pragma mark FLEXTableViewFiltering
+
+@property (nonatomic, copy) NSArray *sections;
+@property (nonatomic, copy) NSArray *allSections;
+
+/// Subclasses can override to hide specific sections under certain conditions
+/// if using \c self as the \c filterDelegate, as is the default.
+///
+/// For example, the object explorer hides the description section when searching.
+@property (nonatomic, readonly, copy) NSArray *nonemptySections;
+
+/// If using \c self as the \c filterDelegate, as is the default,
+/// subclasses should override to provide the sections for the table view.
+- (NSArray *)makeSections;
+
+@end
diff --git a/Tweaks/FLEX/Core/Controllers/FLEXFilteringTableViewController.m b/Tweaks/FLEX/Core/Controllers/FLEXFilteringTableViewController.m
new file mode 100644
index 0000000..b9cd17e
--- /dev/null
+++ b/Tweaks/FLEX/Core/Controllers/FLEXFilteringTableViewController.m
@@ -0,0 +1,209 @@
+//
+// FLEXFilteringTableViewController.m
+// FLEX
+//
+// Created by Tanner on 3/9/20.
+// Copyright © 2020 FLEX Team. All rights reserved.
+//
+
+#import "FLEXFilteringTableViewController.h"
+#import "FLEXTableViewSection.h"
+#import "NSArray+FLEX.h"
+#import "FLEXMacros.h"
+
+@interface FLEXFilteringTableViewController ()
+
+@end
+
+@implementation FLEXFilteringTableViewController
+@synthesize allSections = _allSections;
+
+#pragma mark - View controller lifecycle
+
+- (void)loadView {
+ [super loadView];
+
+ if (!self.filterDelegate) {
+ self.filterDelegate = self;
+ } else {
+ [self _registerCellsForReuse];
+ }
+}
+
+- (void)_registerCellsForReuse {
+ for (FLEXTableViewSection *section in self.filterDelegate.allSections) {
+ if (section.cellRegistrationMapping) {
+ [self.tableView registerCells:section.cellRegistrationMapping];
+ }
+ }
+}
+
+
+#pragma mark - Public
+
+- (void)setFilterDelegate:(id)filterDelegate {
+ _filterDelegate = filterDelegate;
+ filterDelegate.allSections = [filterDelegate makeSections];
+
+ if (self.isViewLoaded) {
+ [self _registerCellsForReuse];
+ }
+}
+
+- (void)reloadData {
+ [self reloadData:self.nonemptySections];
+}
+
+- (void)reloadData:(NSArray *)nonemptySections {
+ // Recalculate displayed sections
+ self.filterDelegate.sections = nonemptySections;
+
+ // Refresh table view
+ if (self.isViewLoaded) {
+ [self.tableView reloadData];
+ }
+}
+
+- (void)reloadSections {
+ for (FLEXTableViewSection *section in self.filterDelegate.allSections) {
+ [section reloadData];
+ }
+}
+
+
+#pragma mark - Search
+
+- (void)updateSearchResults:(NSString *)newText {
+ NSArray *(^filter)(void) = ^NSArray *{
+ self.filterText = newText;
+
+ // Sections will adjust data based on this property
+ for (FLEXTableViewSection *section in self.filterDelegate.allSections) {
+ section.filterText = newText;
+ }
+
+ return nil;
+ };
+
+ if (self.filterInBackground) {
+ [self onBackgroundQueue:filter thenOnMainQueue:^(NSArray *unused) {
+ if ([self.searchText isEqualToString:newText]) {
+ [self reloadData];
+ }
+ }];
+ } else {
+ filter();
+ [self reloadData];
+ }
+}
+
+
+#pragma mark Filtering
+
+- (NSArray *)nonemptySections {
+ return [self.filterDelegate.allSections flex_filtered:^BOOL(FLEXTableViewSection *section, NSUInteger idx) {
+ return section.numberOfRows > 0;
+ }];
+}
+
+- (NSArray *)makeSections {
+ return @[];
+}
+
+- (void)setAllSections:(NSArray *)allSections {
+ _allSections = allSections.copy;
+ // Only display nonempty sections
+ self.sections = self.nonemptySections;
+}
+
+- (void)setSections:(NSArray *)sections {
+ // Allow sections to reload a portion of the table view at will
+ [sections enumerateObjectsUsingBlock:^(FLEXTableViewSection *s, NSUInteger idx, BOOL *stop) {
+ [s setTable:self.tableView section:idx];
+ }];
+ _sections = sections.copy;
+}
+
+
+#pragma mark - UITableViewDataSource
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
+ return self.filterDelegate.sections.count;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+ return self.filterDelegate.sections[section].numberOfRows;
+}
+
+- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
+ return self.filterDelegate.sections[section].title;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+ NSString *reuse = [self.filterDelegate.sections[indexPath.section] reuseIdentifierForRow:indexPath.row];
+ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuse forIndexPath:indexPath];
+ [self.filterDelegate.sections[indexPath.section] configureCell:cell forRow:indexPath.row];
+ return cell;
+}
+
+- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
+ return UITableViewAutomaticDimension;
+}
+
+- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
+ if (self.wantsSectionIndexTitles) {
+ return [NSArray flex_forEachUpTo:self.filterDelegate.sections.count map:^id(NSUInteger i) {
+ return @"⦁";
+ }];
+ }
+
+ return nil;
+}
+
+
+#pragma mark - UITableViewDelegate
+
+- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath {
+ return [self.filterDelegate.sections[indexPath.section] canSelectRow:indexPath.row];
+}
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+ FLEXTableViewSection *section = self.filterDelegate.sections[indexPath.section];
+
+ void (^action)(UIViewController *) = [section didSelectRowAction:indexPath.row];
+ UIViewController *details = [section viewControllerToPushForRow:indexPath.row];
+
+ if (action) {
+ action(self);
+ [tableView deselectRowAtIndexPath:indexPath animated:YES];
+ } else if (details) {
+ [self.navigationController pushViewController:details animated:YES];
+ } else {
+ [NSException raise:NSInternalInconsistencyException
+ format:@"Row is selectable but has no action or view controller"];
+ }
+}
+
+- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
+ [self.filterDelegate.sections[indexPath.section] didPressInfoButtonAction:indexPath.row](self);
+}
+
+- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point __IOS_AVAILABLE(13.0) {
+ FLEXTableViewSection *section = self.filterDelegate.sections[indexPath.section];
+ NSString *title = [section menuTitleForRow:indexPath.row];
+ NSArray *menuItems = [section menuItemsForRow:indexPath.row sender:self];
+
+ if (menuItems.count) {
+ return [UIContextMenuConfiguration
+ configurationWithIdentifier:nil
+ previewProvider:nil
+ actionProvider:^UIMenu *(NSArray *suggestedActions) {
+ return [UIMenu menuWithTitle:title children:menuItems];
+ }
+ ];
+ }
+
+ return nil;
+}
+
+@end
diff --git a/Tweaks/FLEX/Core/Controllers/FLEXNavigationController.h b/Tweaks/FLEX/Core/Controllers/FLEXNavigationController.h
new file mode 100644
index 0000000..f0533a8
--- /dev/null
+++ b/Tweaks/FLEX/Core/Controllers/FLEXNavigationController.h
@@ -0,0 +1,19 @@
+//
+// FLEXNavigationController.h
+// FLEX
+//
+// Created by Tanner on 1/30/20.
+// Copyright © 2020 FLEX Team. All rights reserved.
+//
+
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FLEXNavigationController : UINavigationController
+
++ (instancetype)withRootViewController:(UIViewController *)rootVC;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Tweaks/FLEX/Core/Controllers/FLEXNavigationController.m b/Tweaks/FLEX/Core/Controllers/FLEXNavigationController.m
new file mode 100644
index 0000000..d460fdd
--- /dev/null
+++ b/Tweaks/FLEX/Core/Controllers/FLEXNavigationController.m
@@ -0,0 +1,196 @@
+//
+// FLEXNavigationController.m
+// FLEX
+//
+// Created by Tanner on 1/30/20.
+// Copyright © 2020 FLEX Team. All rights reserved.
+//
+
+#import "FLEXNavigationController.h"
+#import "FLEXExplorerViewController.h"
+#import "FLEXTabList.h"
+
+@interface UINavigationController (Private)
+- (void)_gestureRecognizedInteractiveHide:(UIGestureRecognizer *)sender;
+@end
+@interface UIPanGestureRecognizer (Private)
+- (void)_setDelegate:(id)delegate;
+@end
+
+@interface FLEXNavigationController ()
+@property (nonatomic, readonly) BOOL toolbarWasHidden;
+@property (nonatomic) BOOL waitingToAddTab;
+@property (nonatomic, readonly) BOOL canShowToolbar;
+@property (nonatomic) BOOL didSetupPendingDismissButtons;
+@property (nonatomic) UISwipeGestureRecognizer *navigationBarSwipeGesture;
+@end
+
+@implementation FLEXNavigationController
+
++ (instancetype)withRootViewController:(UIViewController *)rootVC {
+ return [[self alloc] initWithRootViewController:rootVC];
+}
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+
+ self.waitingToAddTab = YES;
+
+ // Add gesture to reveal toolbar if hidden
+ UITapGestureRecognizer *navbarTapGesture = [[UITapGestureRecognizer alloc]
+ initWithTarget:self action:@selector(handleNavigationBarTap:)
+ ];
+
+ // Don't cancel touches to work around bug on versions of iOS prior to 13
+ navbarTapGesture.cancelsTouchesInView = NO;
+ [self.navigationBar addGestureRecognizer:navbarTapGesture];
+
+ // Add gesture to dismiss if not presented with a sheet style
+ if (@available(iOS 13, *)) {
+ switch (self.modalPresentationStyle) {
+ case UIModalPresentationAutomatic:
+ case UIModalPresentationPageSheet:
+ case UIModalPresentationFormSheet:
+ break;
+
+ default:
+ [self addNavigationBarSwipeGesture];
+ break;
+ }
+ } else {
+ [self addNavigationBarSwipeGesture];
+ }
+}
+
+- (void)viewWillAppear:(BOOL)animated {
+ [super viewWillAppear:animated];
+
+ if (self.beingPresented && !self.didSetupPendingDismissButtons) {
+ for (UIViewController *vc in self.viewControllers) {
+ [self addNavigationBarItemsToViewController:vc.navigationItem];
+ }
+
+ self.didSetupPendingDismissButtons = YES;
+ }
+}
+
+- (void)viewDidAppear:(BOOL)animated {
+ [super viewDidAppear:animated];
+
+ if (self.waitingToAddTab) {
+ // Only add new tab if we're presented properly
+ if ([self.presentingViewController isKindOfClass:[FLEXExplorerViewController class]]) {
+ // New navigation controllers always add themselves as new tabs,
+ // tabs are closed by FLEXExplorerViewController
+ [FLEXTabList.sharedList addTab:self];
+ self.waitingToAddTab = NO;
+ }
+ }
+}
+
+- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
+ [super pushViewController:viewController animated:animated];
+ [self addNavigationBarItemsToViewController:viewController.navigationItem];
+}
+
+- (void)dismissAnimated {
+ // Tabs are only closed if the done button is pressed; this
+ // allows you to leave a tab open by dragging down to dismiss
+ if ([self.presentingViewController isKindOfClass:[FLEXExplorerViewController class]]) {
+ [FLEXTabList.sharedList closeTab:self];
+ }
+
+ [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
+}
+
+- (BOOL)canShowToolbar {
+ return self.topViewController.toolbarItems.count > 0;
+}
+
+- (void)addNavigationBarItemsToViewController:(UINavigationItem *)navigationItem {
+ if (!self.presentingViewController) {
+ return;
+ }
+
+ // Check if a done item already exists
+ for (UIBarButtonItem *item in navigationItem.rightBarButtonItems) {
+ if (item.style == UIBarButtonItemStyleDone) {
+ return;
+ }
+ }
+
+ // Give root view controllers a Done button if it does not already have one
+ UIBarButtonItem *done = [[UIBarButtonItem alloc]
+ initWithBarButtonSystemItem:UIBarButtonSystemItemDone
+ target:self
+ action:@selector(dismissAnimated)
+ ];
+
+ // Prepend the button if other buttons exist already
+ NSArray *existingItems = navigationItem.rightBarButtonItems;
+ if (existingItems.count) {
+ navigationItem.rightBarButtonItems = [@[done] arrayByAddingObjectsFromArray:existingItems];
+ } else {
+ navigationItem.rightBarButtonItem = done;
+ }
+
+ // Keeps us from calling this method again on
+ // the same view controllers in -viewWillAppear:
+ self.didSetupPendingDismissButtons = YES;
+}
+
+- (void)addNavigationBarSwipeGesture {
+ UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc]
+ initWithTarget:self action:@selector(handleNavigationBarSwipe:)
+ ];
+ swipe.direction = UISwipeGestureRecognizerDirectionDown;
+ swipe.delegate = self;
+ self.navigationBarSwipeGesture = swipe;
+ [self.navigationBar addGestureRecognizer:swipe];
+}
+
+- (void)handleNavigationBarSwipe:(UISwipeGestureRecognizer *)sender {
+ if (sender.state == UIGestureRecognizerStateRecognized) {
+ [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
+ }
+}
+
+- (void)handleNavigationBarTap:(UIGestureRecognizer *)sender {
+ // Don't reveal the toolbar if we were just tapping a button
+ CGPoint location = [sender locationInView:self.navigationBar];
+ UIView *hitView = [self.navigationBar hitTest:location withEvent:nil];
+ if ([hitView isKindOfClass:[UIControl class]]) {
+ return;
+ }
+
+ if (sender.state == UIGestureRecognizerStateRecognized) {
+ if (self.toolbarHidden && self.canShowToolbar) {
+ [self setToolbarHidden:NO animated:YES];
+ }
+ }
+}
+
+- (BOOL)gestureRecognizer:(UIGestureRecognizer *)g1 shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)g2 {
+ if (g1 == self.navigationBarSwipeGesture && g2 == self.barHideOnSwipeGestureRecognizer) {
+ return YES;
+ }
+
+ return NO;
+}
+
+- (void)_gestureRecognizedInteractiveHide:(UIPanGestureRecognizer *)sender {
+ if (sender.state == UIGestureRecognizerStateRecognized) {
+ BOOL show = self.canShowToolbar;
+ CGFloat yTranslation = [sender translationInView:self.view].y;
+ CGFloat yVelocity = [sender velocityInView:self.view].y;
+ if (yVelocity > 2000) {
+ [self setToolbarHidden:YES animated:YES];
+ } else if (show && yTranslation > 20 && yVelocity > 250) {
+ [self setToolbarHidden:NO animated:YES];
+ } else if (yTranslation < -20) {
+ [self setToolbarHidden:YES animated:YES];
+ }
+ }
+}
+
+@end
diff --git a/Tweaks/FLEX/Core/Controllers/FLEXTableViewController.h b/Tweaks/FLEX/Core/Controllers/FLEXTableViewController.h
new file mode 100644
index 0000000..762c815
--- /dev/null
+++ b/Tweaks/FLEX/Core/Controllers/FLEXTableViewController.h
@@ -0,0 +1,153 @@
+//
+// FLEXTableViewController.h
+// FLEX
+//
+// Created by Tanner on 7/5/19.
+// Copyright © 2020 FLEX Team. All rights reserved.
+//
+
+#import
+#import "FLEXTableView.h"
+@class FLEXScopeCarousel, FLEXWindow, FLEXTableViewSection;
+
+typedef CGFloat FLEXDebounceInterval;
+/// No delay, all events delivered
+extern CGFloat const kFLEXDebounceInstant;
+/// Small delay which makes UI seem smoother by avoiding rapid events
+extern CGFloat const kFLEXDebounceFast;
+/// Slower than Fast, faster than ExpensiveIO
+extern CGFloat const kFLEXDebounceForAsyncSearch;
+/// The least frequent, at just over once per second; for I/O or other expensive operations
+extern CGFloat const kFLEXDebounceForExpensiveIO;
+
+@protocol FLEXSearchResultsUpdating
+/// A method to handle search query update events.
+///
+/// \c searchBarDebounceInterval is used to reduce the frequency at which this
+/// method is called. This method is also called when the search bar becomes
+/// the first responder, and when the selected search bar scope index changes.
+- (void)updateSearchResults:(NSString *)newText;
+@end
+
+@interface FLEXTableViewController : UITableViewController <
+ UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate
+>
+
+/// A grouped table view. Inset on iOS 13.
+///
+/// Simply calls into \c initWithStyle:
+- (id)init;
+
+/// Subclasses may override to configure the controller before \c viewDidLoad:
+- (id)initWithStyle:(UITableViewStyle)style;
+
+@property (nonatomic) FLEXTableView *tableView;
+
+/// If your subclass conforms to \c FLEXSearchResultsUpdating
+/// then this property is assigned to \c self automatically.
+///
+/// Setting \c filterDelegate will also set this property to that object.
+@property (nonatomic, weak) id searchDelegate;
+
+/// Defaults to NO.
+///
+/// Setting this to YES will initialize the carousel and the view.
+@property (nonatomic) BOOL showsCarousel;
+/// A horizontally scrolling list with functionality similar to
+/// that of a search bar's scope bar. You'd want to use this when
+/// you have potentially more than 4 scope options.
+@property (nonatomic) FLEXScopeCarousel *carousel;
+
+/// Defaults to NO.
+///
+/// Setting this to YES will initialize searchController and the view.
+@property (nonatomic) BOOL showsSearchBar;
+/// Defaults to NO.
+///
+/// Setting this to YES will make the search bar appear whenever the view appears.
+/// Otherwise, iOS will only show the search bar when you scroll up.
+@property (nonatomic) BOOL showSearchBarInitially;
+/// Defaults to NO.
+///
+/// Setting this to YES will make the search bar activate whenever the view appears.
+@property (nonatomic) BOOL activatesSearchBarAutomatically;
+
+/// nil unless showsSearchBar is set to YES.
+///
+/// self is used as the default search results updater and delegate.
+/// The search bar will not dim the background or hide the navigation bar by default.
+/// On iOS 11 and up, the search bar will appear in the navigation bar below the title.
+@property (nonatomic) UISearchController *searchController;
+/// Used to initialize the search controller. Defaults to nil.
+@property (nonatomic) UIViewController *searchResultsController;
+/// Defaults to "Fast"
+///
+/// Determines how often search bar results will be "debounced."
+/// Empty query events are always sent instantly. Query events will
+/// be sent when the user has not changed the query for this interval.
+@property (nonatomic) FLEXDebounceInterval searchBarDebounceInterval;
+/// Whether the search bar stays at the top of the view while scrolling.
+///
+/// Calls into self.navigationItem.hidesSearchBarWhenScrolling.
+/// Do not change self.navigationItem.hidesSearchBarWhenScrolling directly,
+/// or it will not be respsected. Use this instead.
+/// Defaults to NO.
+@property (nonatomic) BOOL pinSearchBar;
+/// By default, we will show the search bar's cancel button when
+/// search becomes active and hide it when search is dismissed.
+///
+/// Do not set the showsCancelButton property on the searchController's
+/// searchBar manually. Set this property after turning on showsSearchBar.
+///
+/// Does nothing pre-iOS 13, safe to call on any version.
+@property (nonatomic) BOOL automaticallyShowsSearchBarCancelButton;
+
+/// If using the scope bar, self.searchController.searchBar.selectedScopeButtonIndex.
+/// Otherwise, this is the selected index of the carousel, or NSNotFound if using neither.
+@property (nonatomic) NSInteger selectedScope;
+/// self.searchController.searchBar.text
+@property (nonatomic, readonly, copy) NSString *searchText;
+
+/// A totally optional delegate to forward search results updater calls to.
+/// If a delegate is set, updateSearchResults: is not called on this view controller.
+@property (nonatomic, weak) id searchResultsUpdater;
+
+/// self.view.window as a \c FLEXWindow
+@property (nonatomic, readonly) FLEXWindow *window;
+
+/// Convenient for doing some async processor-intensive searching
+/// in the background before updating the UI back on the main queue.
+- (void)onBackgroundQueue:(NSArray *(^)(void))backgroundBlock thenOnMainQueue:(void(^)(NSArray *))mainBlock;
+
+/// Adds up to 3 additional items to the toolbar in right-to-left order.
+///
+/// That is, the first item in the given array will be the rightmost item behind
+/// any existing toolbar items. By default, buttons for bookmarks and tabs are shown.
+///
+/// If you wish to have more control over how the buttons are arranged or which
+/// buttons are displayed, you can access the properties for the pre-existing
+/// toolbar items directly and manually set \c self.toolbarItems by overriding
+/// the \c setupToolbarItems method below.
+- (void)addToolbarItems:(NSArray *)items;
+
+/// Subclasses may override. You should not need to call this method directly.
+- (void)setupToolbarItems;
+
+@property (nonatomic, readonly) UIBarButtonItem *shareToolbarItem;
+@property (nonatomic, readonly) UIBarButtonItem *bookmarksToolbarItem;
+@property (nonatomic, readonly) UIBarButtonItem *openTabsToolbarItem;
+
+/// Whether or not to display the "share" icon in the middle of the toolbar. NO by default.
+///
+/// Turning this on after you have added custom toolbar items will
+/// push off the leftmost toolbar item and shift the others leftward.
+@property (nonatomic) BOOL showsShareToolbarItem;
+/// Called when the share button is pressed.
+/// Default implementation does nothign. Subclasses may override.
+- (void)shareButtonPressed:(UIBarButtonItem *)sender;
+
+/// Subclasses may call this to opt-out of all toolbar related behavior.
+/// This is necessary if you want to disable the gesture which reveals the toolbar.
+- (void)disableToolbar;
+
+@end
diff --git a/Tweaks/FLEX/Core/Controllers/FLEXTableViewController.m b/Tweaks/FLEX/Core/Controllers/FLEXTableViewController.m
new file mode 100644
index 0000000..70baf3b
--- /dev/null
+++ b/Tweaks/FLEX/Core/Controllers/FLEXTableViewController.m
@@ -0,0 +1,618 @@
+//
+// FLEXTableViewController.m
+// FLEX
+//
+// Created by Tanner on 7/5/19.
+// Copyright © 2020 FLEX Team. All rights reserved.
+//
+
+#import "FLEXTableViewController.h"
+#import "FLEXExplorerViewController.h"
+#import "FLEXBookmarksViewController.h"
+#import "FLEXTabsViewController.h"
+#import "FLEXScopeCarousel.h"
+#import "FLEXTableView.h"
+#import "FLEXUtility.h"
+#import "FLEXResources.h"
+#import "UIBarButtonItem+FLEX.h"
+#import
+
+@interface Block : NSObject
+- (void)invoke;
+@end
+
+CGFloat const kFLEXDebounceInstant = 0.f;
+CGFloat const kFLEXDebounceFast = 0.05;
+CGFloat const kFLEXDebounceForAsyncSearch = 0.15;
+CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
+
+@interface FLEXTableViewController ()
+@property (nonatomic) NSTimer *debounceTimer;
+@property (nonatomic) BOOL didInitiallyRevealSearchBar;
+@property (nonatomic) UITableViewStyle style;
+
+@property (nonatomic) BOOL hasAppeared;
+@property (nonatomic, readonly) UIView *tableHeaderViewContainer;
+
+@property (nonatomic, readonly) BOOL manuallyDeactivateSearchOnDisappear;
+
+@property (nonatomic) UIBarButtonItem *middleToolbarItem;
+@property (nonatomic) UIBarButtonItem *middleLeftToolbarItem;
+@property (nonatomic) UIBarButtonItem *leftmostToolbarItem;
+@end
+
+@implementation FLEXTableViewController
+@dynamic tableView;
+@synthesize showsShareToolbarItem = _showsShareToolbarItem;
+@synthesize tableHeaderViewContainer = _tableHeaderViewContainer;
+@synthesize automaticallyShowsSearchBarCancelButton = _automaticallyShowsSearchBarCancelButton;
+
+#pragma mark - Initialization
+
+- (id)init {
+ if (@available(iOS 13.0, *)) {
+ self = [self initWithStyle:UITableViewStyleInsetGrouped];
+ } else {
+ self = [self initWithStyle:UITableViewStyleGrouped];
+ }
+
+ return self;
+}
+
+- (id)initWithStyle:(UITableViewStyle)style {
+ self = [super initWithStyle:style];
+
+ if (self) {
+ _searchBarDebounceInterval = kFLEXDebounceFast;
+ _showSearchBarInitially = YES;
+ _style = style;
+ _manuallyDeactivateSearchOnDisappear = (
+ NSProcessInfo.processInfo.operatingSystemVersion.majorVersion < 11
+ );
+
+ // We will be our own search delegate if we implement this method
+ if ([self respondsToSelector:@selector(updateSearchResults:)]) {
+ self.searchDelegate = (id)self;
+ }
+ }
+
+ return self;
+}
+
+
+#pragma mark - Public
+
+- (FLEXWindow *)window {
+ return (id)self.view.window;
+}
+
+- (void)setShowsSearchBar:(BOOL)showsSearchBar {
+ if (_showsSearchBar == showsSearchBar) return;
+ _showsSearchBar = showsSearchBar;
+
+ if (showsSearchBar) {
+ UIViewController *results = self.searchResultsController;
+ self.searchController = [[UISearchController alloc] initWithSearchResultsController:results];
+ self.searchController.searchBar.placeholder = @"Filter";
+ self.searchController.searchResultsUpdater = (id)self;
+ self.searchController.delegate = (id)self;
+ self.searchController.dimsBackgroundDuringPresentation = NO;
+ self.searchController.hidesNavigationBarDuringPresentation = NO;
+ /// Not necessary in iOS 13; remove this when iOS 13 is the minimum deployment target
+ self.searchController.searchBar.delegate = self;
+
+ self.automaticallyShowsSearchBarCancelButton = YES;
+
+ if (@available(iOS 13, *)) {
+ self.searchController.automaticallyShowsScopeBar = NO;
+ }
+
+ [self addSearchController:self.searchController];
+ } else {
+ // Search already shown and just set to NO, so remove it
+ [self removeSearchController:self.searchController];
+ }
+}
+
+- (void)setShowsCarousel:(BOOL)showsCarousel {
+ if (_showsCarousel == showsCarousel) return;
+ _showsCarousel = showsCarousel;
+
+ if (showsCarousel) {
+ _carousel = ({ weakify(self)
+
+ FLEXScopeCarousel *carousel = [FLEXScopeCarousel new];
+ carousel.selectedIndexChangedAction = ^(NSInteger idx) { strongify(self);
+ [self.searchDelegate updateSearchResults:self.searchText];
+ };
+
+ // UITableView won't update the header size unless you reset the header view
+ [carousel registerBlockForDynamicTypeChanges:^(FLEXScopeCarousel *_) { strongify(self);
+ [self layoutTableHeaderIfNeeded];
+ }];
+
+ carousel;
+ });
+ [self addCarousel:_carousel];
+ } else {
+ // Carousel already shown and just set to NO, so remove it
+ [self removeCarousel:_carousel];
+ }
+}
+
+- (NSInteger)selectedScope {
+ if (self.searchController.searchBar.showsScopeBar) {
+ return self.searchController.searchBar.selectedScopeButtonIndex;
+ } else if (self.showsCarousel) {
+ return self.carousel.selectedIndex;
+ } else {
+ return 0;
+ }
+}
+
+- (void)setSelectedScope:(NSInteger)selectedScope {
+ if (self.searchController.searchBar.showsScopeBar) {
+ self.searchController.searchBar.selectedScopeButtonIndex = selectedScope;
+ } else if (self.showsCarousel) {
+ self.carousel.selectedIndex = selectedScope;
+ }
+
+ [self.searchDelegate updateSearchResults:self.searchText];
+}
+
+- (NSString *)searchText {
+ return self.searchController.searchBar.text;
+}
+
+- (BOOL)automaticallyShowsSearchBarCancelButton {
+ if (@available(iOS 13, *)) {
+ return self.searchController.automaticallyShowsCancelButton;
+ }
+
+ return _automaticallyShowsSearchBarCancelButton;
+}
+
+- (void)setAutomaticallyShowsSearchBarCancelButton:(BOOL)value {
+ if (@available(iOS 13, *)) {
+ self.searchController.automaticallyShowsCancelButton = value;
+ }
+
+ _automaticallyShowsSearchBarCancelButton = value;
+}
+
+- (void)onBackgroundQueue:(NSArray *(^)(void))backgroundBlock thenOnMainQueue:(void(^)(NSArray *))mainBlock {
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+ NSArray *items = backgroundBlock();
+ dispatch_async(dispatch_get_main_queue(), ^{
+ mainBlock(items);
+ });
+ });
+}
+
+- (void)setsShowsShareToolbarItem:(BOOL)showsShareToolbarItem {
+ _showsShareToolbarItem = showsShareToolbarItem;
+ if (self.isViewLoaded) {
+ [self setupToolbarItems];
+ }
+}
+
+- (void)disableToolbar {
+ self.navigationController.toolbarHidden = YES;
+ self.navigationController.hidesBarsOnSwipe = NO;
+ self.toolbarItems = nil;
+}
+
+
+#pragma mark - View Controller Lifecycle
+
+- (void)loadView {
+ self.view = [FLEXTableView style:self.style];
+ self.tableView.dataSource = self;
+ self.tableView.delegate = self;
+
+ _shareToolbarItem = FLEXBarButtonItemSystem(Action, self, @selector(shareButtonPressed:));
+ _bookmarksToolbarItem = [UIBarButtonItem
+ flex_itemWithImage:FLEXResources.bookmarksIcon target:self action:@selector(showBookmarks)
+ ];
+ _openTabsToolbarItem = [UIBarButtonItem
+ flex_itemWithImage:FLEXResources.openTabsIcon target:self action:@selector(showTabSwitcher)
+ ];
+
+ self.leftmostToolbarItem = UIBarButtonItem.flex_fixedSpace;
+ self.middleLeftToolbarItem = UIBarButtonItem.flex_fixedSpace;
+ self.middleToolbarItem = UIBarButtonItem.flex_fixedSpace;
+}
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+
+ self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag;
+
+ // Toolbar
+ self.navigationController.toolbarHidden = self.toolbarItems.count > 0;
+ self.navigationController.hidesBarsOnSwipe = YES;
+
+ // On iOS 13, the root view controller shows it's search bar no matter what.
+ // Turning this off avoids some weird flash the navigation bar does when we
+ // toggle navigationItem.hidesSearchBarWhenScrolling on and off. The flash
+ // will still happen on subsequent view controllers, but we can at least
+ // avoid it for the root view controller
+ if (@available(iOS 13, *)) {
+ if (self.navigationController.viewControllers.firstObject == self) {
+ _showSearchBarInitially = NO;
+ }
+ }
+}
+
+- (void)viewWillAppear:(BOOL)animated {
+ [super viewWillAppear:animated];
+
+ if (@available(iOS 11.0, *)) {
+ // When going back, make the search bar reappear instead of hiding
+ if ((self.pinSearchBar || self.showSearchBarInitially) && !self.didInitiallyRevealSearchBar) {
+ self.navigationItem.hidesSearchBarWhenScrolling = NO;
+ }
+ }
+
+ // Make the keyboard seem to appear faster
+ if (self.activatesSearchBarAutomatically) {
+ [self makeKeyboardAppearNow];
+ }
+
+ [self setupToolbarItems];
+}
+
+- (void)viewDidAppear:(BOOL)animated {
+ [super viewDidAppear:animated];
+
+ // Allow scrolling to collapse the search bar, only if we don't want it pinned
+ if (@available(iOS 11.0, *)) {
+ if (self.showSearchBarInitially && !self.pinSearchBar && !self.didInitiallyRevealSearchBar) {
+ // All this mumbo jumbo is necessary to work around a bug in iOS 13 up to 13.2
+ // wherein quickly toggling navigationItem.hidesSearchBarWhenScrolling to make
+ // the search bar appear initially results in a bugged search bar that
+ // becomes transparent and floats over the screen as you scroll
+ [UIView animateWithDuration:0.2 animations:^{
+ self.navigationItem.hidesSearchBarWhenScrolling = YES;
+ [self.navigationController.view setNeedsLayout];
+ [self.navigationController.view layoutIfNeeded];
+ }];
+ }
+ }
+
+ if (self.activatesSearchBarAutomatically) {
+ // Keyboard has appeared, now we call this as we soon present our search bar
+ [self removeDummyTextField];
+
+ // Activate the search bar
+ dispatch_async(dispatch_get_main_queue(), ^{
+ // This doesn't work unless it's wrapped in this dispatch_async call
+ [self.searchController.searchBar becomeFirstResponder];
+ });
+ }
+
+ // We only want to reveal the search bar when the view controller first appears.
+ self.didInitiallyRevealSearchBar = YES;
+}
+
+- (void)viewWillDisappear:(BOOL)animated {
+ [super viewWillDisappear:animated];
+
+ if (self.manuallyDeactivateSearchOnDisappear && self.searchController.isActive) {
+ self.searchController.active = NO;
+ }
+}
+
+- (void)didMoveToParentViewController:(UIViewController *)parent {
+ [super didMoveToParentViewController:parent];
+ // Reset this since we are re-appearing under a new
+ // parent view controller and need to show it again
+ self.didInitiallyRevealSearchBar = NO;
+}
+
+
+#pragma mark - Toolbar, Public
+
+- (void)setupToolbarItems {
+ if (!self.isViewLoaded) {
+ return;
+ }
+
+ self.toolbarItems = @[
+ self.leftmostToolbarItem,
+ UIBarButtonItem.flex_flexibleSpace,
+ self.middleLeftToolbarItem,
+ UIBarButtonItem.flex_flexibleSpace,
+ self.middleToolbarItem,
+ UIBarButtonItem.flex_flexibleSpace,
+ self.bookmarksToolbarItem,
+ UIBarButtonItem.flex_flexibleSpace,
+ self.openTabsToolbarItem,
+ ];
+
+ for (UIBarButtonItem *item in self.toolbarItems) {
+ [item _setWidth:60];
+ // This does not work for anything but fixed spaces for some reason
+ // item.width = 60;
+ }
+
+ // Disable tabs entirely when not presented by FLEXExplorerViewController
+ UIViewController *presenter = self.navigationController.presentingViewController;
+ if (![presenter isKindOfClass:[FLEXExplorerViewController class]]) {
+ self.openTabsToolbarItem.enabled = NO;
+ }
+}
+
+- (void)addToolbarItems:(NSArray *)items {
+ if (self.showsShareToolbarItem) {
+ // Share button is in the middle, skip middle button
+ if (items.count > 0) {
+ self.middleLeftToolbarItem = items[0];
+ }
+ if (items.count > 1) {
+ self.leftmostToolbarItem = items[1];
+ }
+ } else {
+ // Add buttons right-to-left
+ if (items.count > 0) {
+ self.middleToolbarItem = items[0];
+ }
+ if (items.count > 1) {
+ self.middleLeftToolbarItem = items[1];
+ }
+ if (items.count > 2) {
+ self.leftmostToolbarItem = items[2];
+ }
+ }
+
+ [self setupToolbarItems];
+}
+
+- (void)setShowsShareToolbarItem:(BOOL)showShare {
+ if (_showsShareToolbarItem != showShare) {
+ _showsShareToolbarItem = showShare;
+
+ if (showShare) {
+ // Push out leftmost item
+ self.leftmostToolbarItem = self.middleLeftToolbarItem;
+ self.middleLeftToolbarItem = self.middleToolbarItem;
+
+ // Use share for middle
+ self.middleToolbarItem = self.shareToolbarItem;
+ } else {
+ // Remove share, shift custom items rightward
+ self.middleToolbarItem = self.middleLeftToolbarItem;
+ self.middleLeftToolbarItem = self.leftmostToolbarItem;
+ self.leftmostToolbarItem = UIBarButtonItem.flex_fixedSpace;
+ }
+ }
+
+ [self setupToolbarItems];
+}
+
+- (void)shareButtonPressed:(UIBarButtonItem *)sender {
+
+}
+
+
+#pragma mark - Private
+
+- (void)debounce:(void(^)(void))block {
+ [self.debounceTimer invalidate];
+
+ self.debounceTimer = [NSTimer
+ scheduledTimerWithTimeInterval:self.searchBarDebounceInterval
+ target:block
+ selector:@selector(invoke)
+ userInfo:nil
+ repeats:NO
+ ];
+}
+
+- (void)layoutTableHeaderIfNeeded {
+ if (self.showsCarousel) {
+ self.carousel.frame = FLEXRectSetHeight(
+ self.carousel.frame, self.carousel.intrinsicContentSize.height
+ );
+ }
+
+ self.tableView.tableHeaderView = self.tableView.tableHeaderView;
+}
+
+- (void)addCarousel:(FLEXScopeCarousel *)carousel {
+ if (@available(iOS 11.0, *)) {
+ self.tableView.tableHeaderView = carousel;
+ } else {
+ carousel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
+
+ CGRect frame = self.tableHeaderViewContainer.frame;
+ CGRect subviewFrame = carousel.frame;
+ subviewFrame.origin.y = 0;
+
+ // Put the carousel below the search bar if it's already there
+ if (self.showsSearchBar) {
+ carousel.frame = subviewFrame = FLEXRectSetY(
+ subviewFrame, self.searchController.searchBar.frame.size.height
+ );
+ frame.size.height += carousel.intrinsicContentSize.height;
+ } else {
+ frame.size.height = carousel.intrinsicContentSize.height;
+ }
+
+ self.tableHeaderViewContainer.frame = frame;
+ [self.tableHeaderViewContainer addSubview:carousel];
+ }
+
+ [self layoutTableHeaderIfNeeded];
+}
+
+- (void)removeCarousel:(FLEXScopeCarousel *)carousel {
+ [carousel removeFromSuperview];
+
+ if (@available(iOS 11.0, *)) {
+ self.tableView.tableHeaderView = nil;
+ } else {
+ if (self.showsSearchBar) {
+ [self removeSearchController:self.searchController];
+ [self addSearchController:self.searchController];
+ } else {
+ self.tableView.tableHeaderView = nil;
+ _tableHeaderViewContainer = nil;
+ }
+ }
+}
+
+- (void)addSearchController:(UISearchController *)controller {
+ if (@available(iOS 11.0, *)) {
+ self.navigationItem.searchController = controller;
+ } else {
+ controller.searchBar.autoresizingMask |= UIViewAutoresizingFlexibleBottomMargin;
+ [self.tableHeaderViewContainer addSubview:controller.searchBar];
+ CGRect subviewFrame = controller.searchBar.frame;
+ CGRect frame = self.tableHeaderViewContainer.frame;
+ frame.size.width = MAX(frame.size.width, subviewFrame.size.width);
+ frame.size.height = subviewFrame.size.height;
+
+ // Move the carousel down if it's already there
+ if (self.showsCarousel) {
+ self.carousel.frame = FLEXRectSetY(
+ self.carousel.frame, subviewFrame.size.height
+ );
+ frame.size.height += self.carousel.frame.size.height;
+ }
+
+ self.tableHeaderViewContainer.frame = frame;
+ [self layoutTableHeaderIfNeeded];
+ }
+}
+
+- (void)removeSearchController:(UISearchController *)controller {
+ if (@available(iOS 11.0, *)) {
+ self.navigationItem.searchController = nil;
+ } else {
+ [controller.searchBar removeFromSuperview];
+
+ if (self.showsCarousel) {
+// self.carousel.frame = FLEXRectRemake(CGPointZero, self.carousel.frame.size);
+ [self removeCarousel:self.carousel];
+ [self addCarousel:self.carousel];
+ } else {
+ self.tableView.tableHeaderView = nil;
+ _tableHeaderViewContainer = nil;
+ }
+ }
+}
+
+- (UIView *)tableHeaderViewContainer {
+ if (!_tableHeaderViewContainer) {
+ _tableHeaderViewContainer = [UIView new];
+ self.tableView.tableHeaderView = self.tableHeaderViewContainer;
+ }
+
+ return _tableHeaderViewContainer;
+}
+
+- (void)showBookmarks {
+ UINavigationController *nav = [[UINavigationController alloc]
+ initWithRootViewController:[FLEXBookmarksViewController new]
+ ];
+ [self presentViewController:nav animated:YES completion:nil];
+}
+
+- (void)showTabSwitcher {
+ UINavigationController *nav = [[UINavigationController alloc]
+ initWithRootViewController:[FLEXTabsViewController new]
+ ];
+ [self presentViewController:nav animated:YES completion:nil];
+}
+
+
+#pragma mark - Search Bar
+
+#pragma mark Faster keyboard
+
+static UITextField *kDummyTextField = nil;
+
+/// Make the keyboard appear instantly. We use this to make the
+/// keyboard appear faster when the search bar is set to appear initially.
+/// You must call \c -removeDummyTextField before your search bar is to appear.
+- (void)makeKeyboardAppearNow {
+ if (!kDummyTextField) {
+ kDummyTextField = [UITextField new];
+ kDummyTextField.autocorrectionType = UITextAutocorrectionTypeNo;
+ }
+
+ kDummyTextField.inputAccessoryView = self.searchController.searchBar.inputAccessoryView;
+ [UIApplication.sharedApplication.keyWindow addSubview:kDummyTextField];
+ [kDummyTextField becomeFirstResponder];
+}
+
+- (void)removeDummyTextField {
+ if (kDummyTextField.superview) {
+ [kDummyTextField removeFromSuperview];
+ }
+}
+
+#pragma mark UISearchResultsUpdating
+
+- (void)updateSearchResultsForSearchController:(UISearchController *)searchController {
+ [self.debounceTimer invalidate];
+ NSString *text = searchController.searchBar.text;
+
+ void (^updateSearchResults)(void) = ^{
+ if (self.searchResultsUpdater) {
+ [self.searchResultsUpdater updateSearchResults:text];
+ } else {
+ [self.searchDelegate updateSearchResults:text];
+ }
+ };
+
+ // Only debounce if we want to, and if we have a non-empty string
+ // Empty string events are sent instantly
+ if (text.length && self.searchBarDebounceInterval > kFLEXDebounceInstant) {
+ [self debounce:updateSearchResults];
+ } else {
+ updateSearchResults();
+ }
+}
+
+
+#pragma mark UISearchControllerDelegate
+
+- (void)willPresentSearchController:(UISearchController *)searchController {
+ // Manually show cancel button for < iOS 13
+ if (!@available(iOS 13, *) && self.automaticallyShowsSearchBarCancelButton) {
+ [searchController.searchBar setShowsCancelButton:YES animated:YES];
+ }
+}
+
+- (void)willDismissSearchController:(UISearchController *)searchController {
+ // Manually hide cancel button for < iOS 13
+ if (!@available(iOS 13, *) && self.automaticallyShowsSearchBarCancelButton) {
+ [searchController.searchBar setShowsCancelButton:NO animated:YES];
+ }
+}
+
+
+#pragma mark UISearchBarDelegate
+
+/// Not necessary in iOS 13; remove this when iOS 13 is the deployment target
+- (void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)selectedScope {
+ [self updateSearchResultsForSearchController:self.searchController];
+}
+
+
+#pragma mark Table View
+
+/// Not having a title in the first section looks weird with a rounded-corner table view style
+- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
+ if (@available(iOS 13, *)) {
+ if (self.style == UITableViewStyleInsetGrouped) {
+ return @" ";
+ }
+ }
+
+ return nil; // For plain/gropued style
+}
+
+@end
diff --git a/Tweaks/FLEX/Core/FLEXSingleRowSection.h b/Tweaks/FLEX/Core/FLEXSingleRowSection.h
new file mode 100644
index 0000000..6b04a0e
--- /dev/null
+++ b/Tweaks/FLEX/Core/FLEXSingleRowSection.h
@@ -0,0 +1,28 @@
+//
+// FLEXSingleRowSection.h
+// FLEX
+//
+// Created by Tanner Bennett on 9/25/19.
+// Copyright © 2020 FLEX Team. All rights reserved.
+//
+
+#import "FLEXTableViewSection.h"
+
+/// A section providing a specific single row.
+///
+/// You may optionally provide a view controller to push when the row
+/// is selected, or an action to perform when it is selected.
+/// Which one is used first is up to the table view data source.
+@interface FLEXSingleRowSection : FLEXTableViewSection
+
+/// @param reuseIdentifier if nil, kFLEXDefaultCell is used.
++ (instancetype)title:(NSString *)sectionTitle
+ reuse:(NSString *)reuseIdentifier
+ cell:(void(^)(__kindof UITableViewCell *cell))cellConfiguration;
+
+@property (nonatomic) UIViewController *pushOnSelection;
+@property (nonatomic) void (^selectionAction)(UIViewController *host);
+/// Called to determine whether the single row should display itself or not.
+@property (nonatomic) BOOL (^filterMatcher)(NSString *filterText);
+
+@end
diff --git a/Tweaks/FLEX/Core/FLEXSingleRowSection.m b/Tweaks/FLEX/Core/FLEXSingleRowSection.m
new file mode 100644
index 0000000..7940941
--- /dev/null
+++ b/Tweaks/FLEX/Core/FLEXSingleRowSection.m
@@ -0,0 +1,87 @@
+//
+// FLEXSingleRowSection.m
+// FLEX
+//
+// Created by Tanner Bennett on 9/25/19.
+// Copyright © 2020 FLEX Team. All rights reserved.
+//
+
+#import "FLEXSingleRowSection.h"
+#import "FLEXTableView.h"
+
+@interface FLEXSingleRowSection ()
+@property (nonatomic, readonly) NSString *reuseIdentifier;
+@property (nonatomic, readonly) void (^cellConfiguration)(__kindof UITableViewCell *cell);
+
+@property (nonatomic) NSString *lastTitle;
+@property (nonatomic) NSString *lastSubitle;
+@end
+
+@implementation FLEXSingleRowSection
+
+#pragma mark - Public
+
++ (instancetype)title:(NSString *)title
+ reuse:(NSString *)reuse
+ cell:(void (^)(__kindof UITableViewCell *))config {
+ return [[self alloc] initWithTitle:title reuse:reuse cell:config];
+}
+
+- (id)initWithTitle:(NSString *)sectionTitle
+ reuse:(NSString *)reuseIdentifier
+ cell:(void (^)(__kindof UITableViewCell *))cellConfiguration {
+ self = [super init];
+ if (self) {
+ _title = sectionTitle;
+ _reuseIdentifier = reuseIdentifier ?: kFLEXDefaultCell;
+ _cellConfiguration = cellConfiguration;
+ }
+
+ return self;
+}
+
+#pragma mark - Overrides
+
+- (NSInteger)numberOfRows {
+ if (self.filterMatcher && self.filterText.length) {
+ return self.filterMatcher(self.filterText) ? 1 : 0;
+ }
+
+ return 1;
+}
+
+- (BOOL)canSelectRow:(NSInteger)row {
+ return self.pushOnSelection || self.selectionAction;
+}
+
+- (void (^)(__kindof UIViewController *))didSelectRowAction:(NSInteger)row {
+ return self.selectionAction;
+}
+
+- (UIViewController *)viewControllerToPushForRow:(NSInteger)row {
+ return self.pushOnSelection;
+}
+
+- (NSString *)reuseIdentifierForRow:(NSInteger)row {
+ return self.reuseIdentifier;
+}
+
+- (void)configureCell:(__kindof UITableViewCell *)cell forRow:(NSInteger)row {
+ cell.textLabel.text = nil;
+ cell.detailTextLabel.text = nil;
+ cell.accessoryType = UITableViewCellAccessoryNone;
+
+ self.cellConfiguration(cell);
+ self.lastTitle = cell.textLabel.text;
+ self.lastSubitle = cell.detailTextLabel.text;
+}
+
+- (NSString *)titleForRow:(NSInteger)row {
+ return self.lastTitle;
+}
+
+- (NSString *)subtitleForRow:(NSInteger)row {
+ return self.lastSubitle;
+}
+
+@end
diff --git a/Tweaks/FLEX/Core/FLEXTableViewSection.h b/Tweaks/FLEX/Core/FLEXTableViewSection.h
new file mode 100644
index 0000000..f43e7d7
--- /dev/null
+++ b/Tweaks/FLEX/Core/FLEXTableViewSection.h
@@ -0,0 +1,146 @@
+//
+// FLEXTableViewSection.h
+// FLEX
+//
+// Created by Tanner on 1/29/20.
+// Copyright © 2020 FLEX Team. All rights reserved.
+//
+
+#import
+#import "NSArray+FLEX.h"
+@class FLEXTableView;
+
+NS_ASSUME_NONNULL_BEGIN
+
+#pragma mark FLEXTableViewSection
+
+/// An abstract base class for table view sections.
+///
+/// Many properties or methods here return nil or some logical equivalent by default.
+/// Even so, most of the methods with defaults are intended to be overriden by subclasses.
+/// Some methods are not implemented at all and MUST be implemented by a subclass.
+@interface FLEXTableViewSection : NSObject {
+ @protected
+ /// Unused by default, use if you want
+ NSString *_title;
+
+ @private
+ __weak UITableView *_tableView;
+ NSInteger _sectionIndex;
+}
+
+#pragma mark - Data
+
+/// A title to be displayed for the custom section.
+/// Subclasses may override or use the \c _title ivar.
+@property (nonatomic, readonly, nullable, copy) NSString *title;
+/// The number of rows in this section. Subclasses must override.
+/// This should not change until \c filterText is changed or \c reloadData is called.
+@property (nonatomic, readonly) NSInteger numberOfRows;
+/// A map of reuse identifiers to \c UITableViewCell (sub)class objects.
+/// Subclasses \e may override this as necessary, but are not required to.
+/// See \c FLEXTableView.h for more information.
+/// @return nil by default.
+@property (nonatomic, readonly, nullable) NSDictionary *cellRegistrationMapping;
+
+/// The section should filter itself based on the contents of this property
+/// as it is set. If it is set to nil or an empty string, it should not filter.
+/// Subclasses should override or observe this property and react to changes.
+///
+/// It is common practice to use two arrays for the underlying model:
+/// One to hold all rows, and one to hold unfiltered rows. When \c setFilterText:
+/// is called, call \c super to store the new value, and re-filter your model accordingly.
+@property (nonatomic, nullable) NSString *filterText;
+
+/// Provides an avenue for the section to refresh data or change the number of rows.
+///
+/// This is called before reloading the table view itself. If your section pulls data
+/// from an external data source, this is a good place to refresh that data entirely.
+/// If your section does not, then it might be simpler for you to just override
+/// \c setFilterText: to call \c super and call \c reloadData.
+- (void)reloadData;
+
+/// Like \c reloadData, but optionally reloads the table view section
+/// associated with this section object, if any. Do not override.
+/// Do not call outside of the main thread.
+- (void)reloadData:(BOOL)updateTable;
+
+/// Provide a table view and section index to allow the section to efficiently reload
+/// its own section of the table when something changes it. The table reference is
+/// held weakly, and subclasses cannot access it or the index. Call this method again
+/// if the section numbers have changed since you last called it.
+- (void)setTable:(UITableView *)tableView section:(NSInteger)index;
+
+#pragma mark - Row Selection
+
+/// Whether the given row should be selectable, such as if tapping the cell
+/// should take the user to a new screen or trigger an action.
+/// Subclasses \e may override this as necessary, but are not required to.
+/// @return \c NO by default
+- (BOOL)canSelectRow:(NSInteger)row;
+
+/// An action "future" to be triggered when the row is selected, if the row
+/// supports being selected as indicated by \c canSelectRow:. Subclasses
+/// must implement this in accordance with how they implement \c canSelectRow:
+/// if they do not implement \c viewControllerToPushForRow:
+/// @return This returns \c nil if no view controller is provided by
+/// \c viewControllerToPushForRow: — otherwise it pushes that view controller
+/// onto \c host.navigationController
+- (nullable void(^)(__kindof UIViewController *host))didSelectRowAction:(NSInteger)row;
+
+/// A view controller to display when the row is selected, if the row
+/// supports being selected as indicated by \c canSelectRow:. Subclasses
+/// must implement this in accordance with how they implement \c canSelectRow:
+/// if they do not implement \c didSelectRowAction:
+/// @return \c nil by default
+- (nullable UIViewController *)viewControllerToPushForRow:(NSInteger)row;
+
+/// Called when the accessory view's detail button is pressed.
+/// @return \c nil by default.
+- (nullable void(^)(__kindof UIViewController *host))didPressInfoButtonAction:(NSInteger)row;
+
+#pragma mark - Context Menus
+
+/// By default, this is the title of the row.
+/// @return The title of the context menu, if any.
+- (nullable NSString *)menuTitleForRow:(NSInteger)row API_AVAILABLE(ios(13.0));
+/// Protected, not intended for public use. \c menuTitleForRow:
+/// already includes the value returned from this method.
+///
+/// By default, this returns \c @"". Subclasses may override to
+/// provide a detailed description of the target of the context menu.
+- (NSString *)menuSubtitleForRow:(NSInteger)row API_AVAILABLE(ios(13.0));
+/// The context menu items, if any. Subclasses may override.
+/// By default, only inludes items for \c copyMenuItemsForRow:.
+- (nullable NSArray *)menuItemsForRow:(NSInteger)row sender:(UIViewController *)sender API_AVAILABLE(ios(13.0));
+/// Subclasses may override to return a list of copiable items.
+///
+/// Every two elements in the list compose a key-value pair, where the key
+/// should be a description of what will be copied, and the values should be
+/// the strings to copy. Return an empty string as a value to show a disabled action.
+- (nullable NSArray *)copyMenuItemsForRow:(NSInteger)row API_AVAILABLE(ios(13.0));
+
+#pragma mark - Cell Configuration
+
+/// Provide a reuse identifier for the given row. Subclasses should override.
+///
+/// Custom reuse identifiers should be specified in \c cellRegistrationMapping.
+/// You may return any of the identifiers in \c FLEXTableView.h
+/// without including them in the \c cellRegistrationMapping.
+/// @return \c kFLEXDefaultCell by default.
+- (NSString *)reuseIdentifierForRow:(NSInteger)row;
+/// Configure a cell for the given row. Subclasses must override.
+- (void)configureCell:(__kindof UITableViewCell *)cell forRow:(NSInteger)row;
+
+#pragma mark - External Convenience
+
+/// For use by whatever view controller uses your section. Not required.
+/// @return An optional title.
+- (nullable NSString *)titleForRow:(NSInteger)row;
+/// For use by whatever view controller uses your section. Not required.
+/// @return An optional subtitle.
+- (nullable NSString *)subtitleForRow:(NSInteger)row;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Tweaks/FLEX/Core/FLEXTableViewSection.m b/Tweaks/FLEX/Core/FLEXTableViewSection.m
new file mode 100644
index 0000000..3fcd96b
--- /dev/null
+++ b/Tweaks/FLEX/Core/FLEXTableViewSection.m
@@ -0,0 +1,137 @@
+//
+// FLEXTableViewSection.m
+// FLEX
+//
+// Created by Tanner on 1/29/20.
+// Copyright © 2020 FLEX Team. All rights reserved.
+//
+
+#import "FLEXTableViewSection.h"
+#import "FLEXTableView.h"
+#import "FLEXUtility.h"
+#import "UIMenu+FLEX.h"
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wincomplete-implementation"
+
+@implementation FLEXTableViewSection
+
+- (NSInteger)numberOfRows {
+ return 0;
+}
+
+- (void)reloadData { }
+
+- (void)reloadData:(BOOL)updateTable {
+ [self reloadData];
+ if (updateTable) {
+ NSIndexSet *index = [NSIndexSet indexSetWithIndex:_sectionIndex];
+ [_tableView reloadSections:index withRowAnimation:UITableViewRowAnimationNone];
+ }
+}
+
+- (void)setTable:(UITableView *)tableView section:(NSInteger)index {
+ _tableView = tableView;
+ _sectionIndex = index;
+}
+
+- (NSDictionary *)cellRegistrationMapping {
+ return nil;
+}
+
+- (BOOL)canSelectRow:(NSInteger)row { return NO; }
+
+- (void (^)(__kindof UIViewController *))didSelectRowAction:(NSInteger)row {
+ UIViewController *toPush = [self viewControllerToPushForRow:row];
+ if (toPush) {
+ return ^(UIViewController *host) {
+ [host.navigationController pushViewController:toPush animated:YES];
+ };
+ }
+
+ return nil;
+}
+
+- (UIViewController *)viewControllerToPushForRow:(NSInteger)row {
+ return nil;
+}
+
+- (void (^)(__kindof UIViewController *))didPressInfoButtonAction:(NSInteger)row {
+ return nil;
+}
+
+- (NSString *)reuseIdentifierForRow:(NSInteger)row {
+ return kFLEXDefaultCell;
+}
+
+- (NSString *)menuTitleForRow:(NSInteger)row {
+ NSString *title = [self titleForRow:row];
+ NSString *subtitle = [self menuSubtitleForRow:row];
+
+ if (subtitle.length) {
+ return [NSString stringWithFormat:@"%@\n\n%@", title, subtitle];
+ }
+
+ return title;
+}
+
+- (NSString *)menuSubtitleForRow:(NSInteger)row {
+ return @"";
+}
+
+- (NSArray *)menuItemsForRow:(NSInteger)row sender:(UIViewController *)sender API_AVAILABLE(ios(13)) {
+ NSArray *copyItems = [self copyMenuItemsForRow:row];
+ NSAssert(copyItems.count % 2 == 0, @"copyMenuItemsForRow: should return an even list");
+
+ if (copyItems.count) {
+ NSInteger numberOfActions = copyItems.count / 2;
+ BOOL collapseMenu = numberOfActions > 4;
+ UIImage *copyIcon = [UIImage systemImageNamed:@"doc.on.doc"];
+
+ NSMutableArray *actions = [NSMutableArray new];
+
+ for (NSInteger i = 0; i < copyItems.count; i += 2) {
+ NSString *key = copyItems[i], *value = copyItems[i+1];
+ NSString *title = collapseMenu ? key : [@"Copy " stringByAppendingString:key];
+
+ UIAction *copy = [UIAction
+ actionWithTitle:title
+ image:copyIcon
+ identifier:nil
+ handler:^(__kindof UIAction *action) {
+ UIPasteboard.generalPasteboard.string = value;
+ }
+ ];
+ if (!value.length) {
+ copy.attributes = UIMenuElementAttributesDisabled;
+ }
+
+ [actions addObject:copy];
+ }
+
+ UIMenu *copyMenu = [UIMenu
+ flex_inlineMenuWithTitle:@"Copy…"
+ image:copyIcon
+ children:actions
+ ];
+
+ if (collapseMenu) {
+ return @[[copyMenu flex_collapsed]];
+ } else {
+ return @[copyMenu];
+ }
+ }
+
+ return @[];
+}
+
+- (NSArray *)copyMenuItemsForRow:(NSInteger)row {
+ return nil;
+}
+
+- (NSString *)titleForRow:(NSInteger)row { return nil; }
+- (NSString *)subtitleForRow:(NSInteger)row { return nil; }
+
+@end
+
+#pragma clang diagnostic pop
diff --git a/Tweaks/FLEX/Core/Views/Carousel/FLEXCarouselCell.h b/Tweaks/FLEX/Core/Views/Carousel/FLEXCarouselCell.h
new file mode 100644
index 0000000..d8afbd0
--- /dev/null
+++ b/Tweaks/FLEX/Core/Views/Carousel/FLEXCarouselCell.h
@@ -0,0 +1,15 @@
+//
+// FLEXCarouselCell.h
+// FLEX
+//
+// Created by Tanner Bennett on 7/17/19.
+// Copyright © 2020 FLEX Team. All rights reserved.
+//
+
+#import