From 755c4ca50bdc65d3cf86239f5f0bc1992f7d04a8 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Fri, 15 Aug 2025 03:03:23 -0600 Subject: [PATCH 01/66] Project updates - Removed various files from Copy Bundle Resources step - Added Embed Build Information to the watchOS target --- Malachite.xcodeproj/project.pbxproj | 48 ++++++++++++++++------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/Malachite.xcodeproj/project.pbxproj b/Malachite.xcodeproj/project.pbxproj index ff0b8f5..84ff327 100755 --- a/Malachite.xcodeproj/project.pbxproj +++ b/Malachite.xcodeproj/project.pbxproj @@ -9,8 +9,6 @@ /* Begin PBXBuildFile section */ 780067E12CBDCEA0004BB595 /* ControlCenterWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 780067CF2CBDCB94004BB595 /* ControlCenterWidget.swift */; }; 7806973A2B27F66E00B4942B /* MalachiteTooltipUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 780697392B27F66E00B4942B /* MalachiteTooltipUtils.swift */; }; - 78163D8E2CCB7BB300146126 /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = 78163D8D2CCB7BAE00146126 /* CHANGELOG.md */; }; - 782FC7762B2DAFAB007709C1 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 782FC7752B2DAFAB007709C1 /* README.md */; }; 7837C1062E34C45B009396B0 /* MalachiteIntentUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78A9DE2C2CD57F4D002C131D /* MalachiteIntentUtils.swift */; }; 7837C1072E34C45B009396B0 /* ControlCenterWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 780067CF2CBDCB94004BB595 /* ControlCenterWidget.swift */; }; 7837C1082E34C45B009396B0 /* LockScreenWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78917F622B99AE72005E10FA /* LockScreenWidget.swift */; }; @@ -18,9 +16,7 @@ 7837C10B2E34C45B009396B0 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78917F5D2B99AE72005E10FA /* SwiftUI.framework */; }; 7837C10C2E34C45B009396B0 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78917F5B2B99AE72005E10FA /* WidgetKit.framework */; }; 7837C10E2E34C45B009396B0 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */; }; - 7837C1102E34C45B009396B0 /* Codesigning.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; }; 7837C1172E34C47D009396B0 /* WidgetBundleWatch.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 7837C1152E34C45B009396B0 /* WidgetBundleWatch.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 78562BC32B450A7600920160 /* PRIVACY_POLICY.md in Resources */ = {isa = PBXBuildFile; fileRef = 78562BC22B450A7600920160 /* PRIVACY_POLICY.md */; }; 7859DE1A2B8308C000D1A998 /* MalachiteAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7859DE192B8308C000D1A998 /* MalachiteAboutView.swift */; }; 785F084D2B12D41100244EB4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 785F084C2B12D41100244EB4 /* AppDelegate.swift */; }; 785F084F2B12D41100244EB4 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 785F084E2B12D41100244EB4 /* SceneDelegate.swift */; }; @@ -30,7 +26,6 @@ 7860DC002B897A3E00450BF8 /* MalachiteGameUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7860DBFF2B897A3E00450BF8 /* MalachiteGameUtils.swift */; }; 78789C742E3494D500862C3D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 785F08552B12D41300244EB4 /* Assets.xcassets */; }; 787B1CAA2B8B095E000AFECC /* Malachite.docc in Sources */ = {isa = PBXBuildFile; fileRef = 787B1CA92B8B095E000AFECC /* Malachite.docc */; }; - 7881A98C2C1F77AB00B1F83B /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */; }; 7886C6C22E45E2E10095460C /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 78B72BA12E332744002E2D4E /* AppIcon.icon */; }; 788848B52CC74B4200B02E37 /* MalachiteCompatibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788848B42CC74B3B00B02E37 /* MalachiteCompatibilityView.swift */; }; 788E74B32B13D8200024B586 /* MalachiteSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788E74B22B13D8200024B586 /* MalachiteSettingsView.swift */; }; @@ -60,11 +55,8 @@ 78A9DE752CD5AE5F002C131D /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */; }; 78B72BA22E332744002E2D4E /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 78B72BA12E332744002E2D4E /* AppIcon.icon */; }; 78B72BB12E332830002E2D4E /* MalachiteWatch.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 78B72BA72E33282E002E2D4E /* MalachiteWatch.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 78B72BD42E3452A2002E2D4E /* build.yml in Resources */ = {isa = PBXBuildFile; fileRef = 78B72BD12E3452A2002E2D4E /* build.yml */; }; 78B72BDA2E345301002E2D4E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B72BD62E345301002E2D4E /* ContentView.swift */; }; 78B72BDB2E345301002E2D4E /* MalachiteWatchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B72BD72E345301002E2D4E /* MalachiteWatchApp.swift */; }; - 78B72BE32E3461DA002E2D4E /* MalachiteIconMasked_Darwin25.png in Resources */ = {isa = PBXBuildFile; fileRef = 78B72BE22E3461DA002E2D4E /* MalachiteIconMasked_Darwin25.png */; }; - 78B72BE42E3461DA002E2D4E /* MalachiteIconMasked_Darwin24.png in Resources */ = {isa = PBXBuildFile; fileRef = 78B72BE12E3461DA002E2D4E /* MalachiteIconMasked_Darwin24.png */; }; 78C7EF9D2D1ECBD1001E332B /* MalachitePreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78C7EF9C2D1ECBCB001E332B /* MalachitePreferences.swift */; }; 78C7EF9E2D1ECBD1001E332B /* MalachitePreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78C7EF9C2D1ECBCB001E332B /* MalachitePreferences.swift */; }; 78C80BF52CDABE7800F6E8A7 /* MalachitePreferencesUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78C80BF32CDABE7200F6E8A7 /* MalachitePreferencesUtils.swift */; }; @@ -87,15 +79,13 @@ 78D570822E3C7D0B0025B9B3 /* MalachiteSettingsDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7897ECA52BA2B87100662EA0 /* MalachiteSettingsDetailView.swift */; }; 78D570832E3C7D0B0025B9B3 /* MalachiteAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7859DE192B8308C000D1A998 /* MalachiteAboutView.swift */; }; 78D570842E3C7E530025B9B3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 785F08552B12D41300244EB4 /* Assets.xcassets */; }; - 78E142CA2DD4262F0016B3DB /* Codesigning.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; }; - 78E142CB2DD4262F0016B3DB /* Codesigning.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; }; - 78E142CC2DD4262F0016B3DB /* Codesigning.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; }; 78E5EFC02B15748400F15250 /* MalachitePhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E5EFBF2B15748400F15250 /* MalachitePhotoPreview.swift */; }; 78E67EE02B27E07300812A17 /* MalachiteHapticUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E67EDC2B27E07300812A17 /* MalachiteHapticUtils.swift */; }; 78E67EE12B27E07300812A17 /* MalachiteViewUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E67EDD2B27E07300812A17 /* MalachiteViewUtils.swift */; }; 78E67EE22B27E07300812A17 /* MalachiteUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E67EDE2B27E07300812A17 /* MalachiteUtils.swift */; }; 78E67EE52B27E0A200812A17 /* MalachiteFunctionUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E67EE42B27E0A200812A17 /* MalachiteFunctionUtils.swift */; }; 78E8265B2E349FEC00A8DB50 /* MalachiteIntentUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78A9DE2C2CD57F4D002C131D /* MalachiteIntentUtils.swift */; }; + 78EC5D5F2E4EC110005044E2 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -523,6 +513,7 @@ 78B72BA42E33282E002E2D4E /* Frameworks */, 78B72BA52E33282E002E2D4E /* Resources */, 7837C11A2E34C47D009396B0 /* Embed Foundation Extensions */, + 78EC5D602E4EC1AD005044E2 /* Embed build information */, ); buildRules = ( ); @@ -590,7 +581,6 @@ files = ( 7837C10E2E34C45B009396B0 /* Localizable.xcstrings in Resources */, 78CD7DB72E359B87003814B5 /* Assets.xcassets in Resources */, - 7837C1102E34C45B009396B0 /* Codesigning.xcconfig in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -598,16 +588,9 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 782FC7762B2DAFAB007709C1 /* README.md in Resources */, - 7881A98C2C1F77AB00B1F83B /* Localizable.xcstrings in Resources */, - 78562BC32B450A7600920160 /* PRIVACY_POLICY.md in Resources */, - 78E142CB2DD4262F0016B3DB /* Codesigning.xcconfig in Resources */, - 78B72BE32E3461DA002E2D4E /* MalachiteIconMasked_Darwin25.png in Resources */, - 78B72BE42E3461DA002E2D4E /* MalachiteIconMasked_Darwin24.png in Resources */, - 78B72BD42E3452A2002E2D4E /* build.yml in Resources */, 78B72BA22E332744002E2D4E /* AppIcon.icon in Resources */, + 78EC5D5F2E4EC110005044E2 /* Localizable.xcstrings in Resources */, 785F08592B12D41300244EB4 /* LaunchScreen.storyboard in Resources */, - 78163D8E2CCB7BB300146126 /* CHANGELOG.md in Resources */, 785F08562B12D41300244EB4 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -618,7 +601,6 @@ files = ( 78CAC36A2CCD99B600A35AE8 /* Localizable.xcstrings in Resources */, 78CD7DB62E359B87003814B5 /* Assets.xcassets in Resources */, - 78E142CA2DD4262F0016B3DB /* Codesigning.xcconfig in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -626,7 +608,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 78E142CC2DD4262F0016B3DB /* Codesigning.xcconfig in Resources */, 78D570842E3C7E530025B9B3 /* Assets.xcassets in Resources */, 78A9DE752CD5AE5F002C131D /* Localizable.xcstrings in Resources */, ); @@ -664,6 +645,26 @@ shellPath = /bin/bash; shellScript = "eval \"${GCC_PREPROCESSOR_DEFINITIONS}\"\n\nPATH=/opt/homebrew/bin:/usr/local/bin:$PATH\nPLISTBUDDY=\"/usr/libexec/PlistBuddy\"\nINFOPLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n\n# If there is no git repo to pull this info from, this is set to undefined later.\nCFBUILDHASH=$(git --git-dir=\"${PROJECT_DIR}/.git\" --work-tree=\"${PROJECT_DIR}\" rev-parse --short HEAD)\nCFBUILDDATE=$(date +'%Y-%m-%d-%H.%M.%S')\nif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && -z $(system_profiler SPHardwareDataType | grep 'MacVM1,1') ]]; then\n # If the build is not INTERNAL, these are set to redacted later.\n CFBUILDUSER=$(whoami)\n CFBUILDHOST=$(hostname)\nelif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && !(-z $(system_profiler SPHardwareDataType | grep 'MacVM1,1')) ]]; then\n # Xcode Cloud built this, but the hostname string is atrocious. Replace with these.\n CFBUILDUSER=\"evaluna (App Store Connect)\"\n CFBUILDHOST=\"Xcode Cloud\"\nfi\n\nif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && -z $(gpg --list-secret-keys | grep 4A5B8C2A065654E24DE50FF03716ACDC524F1879) ]]; then\n echo \"Error: INTERNAL build type detected but Eva's GPG key was not found.\"\n echo \"Error: See 'Embed build information' in Build Phases for a solution\"\n # Be aware that I use the INTERNAL target to gate off any unstable/unready features. By removing this line, \n # you are acknowledging that you will not receive support for any issues encountered with Malachite until you \n # are running DEBUG or RELEASE. If you just want a DEBUG build, change the Build Configuration to Debug in \n # Product > Scheme > Edit Scheme...\n if [[ -z $(system_profiler SPHardwareDataType | grep 'MacVM1,1') ]]; then\n exit\n else\n # This line of code should keep internal TestFlight builds as INTERNAL, and external ones as DEBUG\n if [[ $(git --git-dir=\"${PROJECT_DIR}/.git\" rev-parse --abbrev-ref HEAD) != *\"dev\"* ]]; then\n CFBUILDTYPE=\"DEBUG\"\n fi\n fi\nfi\n\n\n\n# Print all of the variables in the build log. Useful for debugging\necho \"\"\necho \"CFBUILDHASH = ${CFBUILDHASH}\"\necho \"CFBUILDDATE = ${CFBUILDDATE}\"\necho \"CFBUILDTYPE = ${CFBUILDTYPE}\"\nif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" ]]; then\n echo \"CFBUILDUSER = ${CFBUILDUSER}\"\n echo \"CFBUILDHOST = ${CFBUILDHOST}\"\nfi\necho \"INFOPLIST = ${INFOPLIST}\"\n\n# Make sure the key is present in the plist before trying to set it.\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildHash' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildHash string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildDate' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildDate string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildType' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildType string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildUser' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildUser string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildHost' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildHost string\" \"${INFOPLIST}\"; fi\n\n# Fallback values, either to fill in the gaps or prevent app crashes\nif [ -z \"$CFBUILDHASH\" ]; then CFBUILDHASH=undefined; fi\nif [ -z \"$CFBUILDTYPE\" ]; then CFBUILDTYPE=debug; fi\nif [ -z \"$CFBUILDUSER\" ]; then CFBUILDUSER=redacted; fi\nif [ -z \"$CFBUILDHOST\" ]; then CFBUILDHOST=redacted; fi\n\n# Set the values in the Info.plist\n$PLISTBUDDY -c \"Set :CFBuildHash $CFBUILDHASH\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildDate $CFBUILDDATE\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildType $CFBUILDTYPE\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildUser $CFBUILDUSER\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildHost $CFBUILDHOST\" \"${INFOPLIST}\"\n"; }; + 78EC5D602E4EC1AD005044E2 /* Embed build information */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}", + ); + name = "Embed build information"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/bash; + shellScript = "eval \"${GCC_PREPROCESSOR_DEFINITIONS}\"\n\nPATH=/opt/homebrew/bin:/usr/local/bin:$PATH\nPLISTBUDDY=\"/usr/libexec/PlistBuddy\"\nINFOPLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n\n# If there is no git repo to pull this info from, this is set to undefined later.\nCFBUILDHASH=$(git --git-dir=\"${PROJECT_DIR}/.git\" --work-tree=\"${PROJECT_DIR}\" rev-parse --short HEAD)\nCFBUILDDATE=$(date +'%Y-%m-%d-%H.%M.%S')\nif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && -z $(system_profiler SPHardwareDataType | grep 'MacVM1,1') ]]; then\n # If the build is not INTERNAL, these are set to redacted later.\n CFBUILDUSER=$(whoami)\n CFBUILDHOST=$(hostname)\nelif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && !(-z $(system_profiler SPHardwareDataType | grep 'MacVM1,1')) ]]; then\n # Xcode Cloud built this, but the hostname string is atrocious. Replace with these.\n CFBUILDUSER=\"evaluna (App Store Connect)\"\n CFBUILDHOST=\"Xcode Cloud\"\nfi\n\nif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && -z $(gpg --list-secret-keys | grep 4A5B8C2A065654E24DE50FF03716ACDC524F1879) ]]; then\n echo \"Error: INTERNAL build type detected but Eva's GPG key was not found.\"\n echo \"Error: See 'Embed build information' in Build Phases for a solution\"\n # Be aware that I use the INTERNAL target to gate off any unstable/unready features. By removing this line, \n # you are acknowledging that you will not receive support for any issues encountered with Malachite until you \n # are running DEBUG or RELEASE. If you just want a DEBUG build, change the Build Configuration to Debug in \n # Product > Scheme > Edit Scheme...\n if [[ -z $(system_profiler SPHardwareDataType | grep 'MacVM1,1') ]]; then\n exit\n else\n # This line of code should keep internal TestFlight builds as INTERNAL, and external ones as DEBUG\n if [[ $(git --git-dir=\"${PROJECT_DIR}/.git\" rev-parse --abbrev-ref HEAD) != *\"dev\"* ]]; then\n CFBUILDTYPE=\"DEBUG\"\n fi\n fi\nfi\n\n\n\n# Print all of the variables in the build log. Useful for debugging\necho \"\"\necho \"CFBUILDHASH = ${CFBUILDHASH}\"\necho \"CFBUILDDATE = ${CFBUILDDATE}\"\necho \"CFBUILDTYPE = ${CFBUILDTYPE}\"\nif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" ]]; then\n echo \"CFBUILDUSER = ${CFBUILDUSER}\"\n echo \"CFBUILDHOST = ${CFBUILDHOST}\"\nfi\necho \"INFOPLIST = ${INFOPLIST}\"\n\n# Make sure the key is present in the plist before trying to set it.\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildHash' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildHash string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildDate' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildDate string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildType' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildType string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildUser' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildUser string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildHost' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildHost string\" \"${INFOPLIST}\"; fi\n\n# Fallback values, either to fill in the gaps or prevent app crashes\nif [ -z \"$CFBUILDHASH\" ]; then CFBUILDHASH=undefined; fi\nif [ -z \"$CFBUILDTYPE\" ]; then CFBUILDTYPE=debug; fi\nif [ -z \"$CFBUILDUSER\" ]; then CFBUILDUSER=redacted; fi\nif [ -z \"$CFBUILDHOST\" ]; then CFBUILDHOST=redacted; fi\n\n# Set the values in the Info.plist\n$PLISTBUDDY -c \"Set :CFBuildHash $CFBUILDHASH\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildDate $CFBUILDDATE\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildType $CFBUILDTYPE\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildUser $CFBUILDUSER\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildHost $CFBUILDHOST\" \"${INFOPLIST}\"\n"; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -1370,6 +1371,7 @@ ENABLE_ENHANCED_SECURITY = NO; ENABLE_POINTER_AUTHENTICATION = NO; ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; EXCLUDED_ARCHS = arm64e; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = MalachiteWatch/Info.plist; @@ -1408,6 +1410,7 @@ ENABLE_ENHANCED_SECURITY = NO; ENABLE_POINTER_AUTHENTICATION = NO; ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; EXCLUDED_ARCHS = arm64e; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = MalachiteWatch/Info.plist; @@ -1446,6 +1449,7 @@ ENABLE_ENHANCED_SECURITY = NO; ENABLE_POINTER_AUTHENTICATION = NO; ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; EXCLUDED_ARCHS = arm64e; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = MalachiteWatch/Info.plist; From d646fd52681d2540f763ee323aacdc02dffbc612 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Fri, 15 Aug 2025 03:07:31 -0600 Subject: [PATCH 02/66] Massive changes - Add CFBuildBran to show what branch Malachite is built from - Add experimental DeveloperView from the DeveloperView/ folder - Refactor MalachiteSettingsView into SettingsView/ folder - Remove unnecessary code and comments --- Malachite.xcodeproj/project.pbxproj | 525 ++++++++++-------- Malachite/Localizable.xcstrings | 240 +++++--- Malachite/Utilities/MalachiteUtils.swift | 2 + Malachite/Utilities/MalachiteViewUtils.swift | 33 -- .../DeveloperView+BuildInfo.swift | 71 +++ .../DeveloperView+DeviceInfo.swift | 24 + .../DeveloperView+Settings.swift | 96 ++++ .../Views/DeveloperView/DeveloperView.swift | 43 ++ Malachite/Views/MalachiteAboutView.swift | 10 +- .../Views/MalachiteSettingsDetailView.swift | 172 ------ Malachite/Views/MalachiteView.swift | 2 +- .../SettingsView/SettingsView+Help.swift | 143 +++++ .../SettingsView.swift} | 83 +-- 13 files changed, 841 insertions(+), 603 deletions(-) create mode 100644 Malachite/Views/DeveloperView/DeveloperView+BuildInfo.swift create mode 100644 Malachite/Views/DeveloperView/DeveloperView+DeviceInfo.swift create mode 100644 Malachite/Views/DeveloperView/DeveloperView+Settings.swift create mode 100644 Malachite/Views/DeveloperView/DeveloperView.swift delete mode 100755 Malachite/Views/MalachiteSettingsDetailView.swift create mode 100755 Malachite/Views/SettingsView/SettingsView+Help.swift rename Malachite/Views/{MalachiteSettingsView.swift => SettingsView/SettingsView.swift} (91%) diff --git a/Malachite.xcodeproj/project.pbxproj b/Malachite.xcodeproj/project.pbxproj index 84ff327..6436d10 100755 --- a/Malachite.xcodeproj/project.pbxproj +++ b/Malachite.xcodeproj/project.pbxproj @@ -7,84 +7,89 @@ objects = { /* Begin PBXBuildFile section */ - 780067E12CBDCEA0004BB595 /* ControlCenterWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 780067CF2CBDCB94004BB595 /* ControlCenterWidget.swift */; }; - 7806973A2B27F66E00B4942B /* MalachiteTooltipUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 780697392B27F66E00B4942B /* MalachiteTooltipUtils.swift */; }; - 7837C1062E34C45B009396B0 /* MalachiteIntentUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78A9DE2C2CD57F4D002C131D /* MalachiteIntentUtils.swift */; }; - 7837C1072E34C45B009396B0 /* ControlCenterWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 780067CF2CBDCB94004BB595 /* ControlCenterWidget.swift */; }; - 7837C1082E34C45B009396B0 /* LockScreenWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78917F622B99AE72005E10FA /* LockScreenWidget.swift */; }; - 7837C1092E34C45B009396B0 /* MalachiteWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78917F602B99AE72005E10FA /* MalachiteWidgetBundle.swift */; }; 7837C10B2E34C45B009396B0 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78917F5D2B99AE72005E10FA /* SwiftUI.framework */; }; 7837C10C2E34C45B009396B0 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78917F5B2B99AE72005E10FA /* WidgetKit.framework */; }; 7837C10E2E34C45B009396B0 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */; }; 7837C1172E34C47D009396B0 /* WidgetBundleWatch.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 7837C1152E34C45B009396B0 /* WidgetBundleWatch.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 7859DE1A2B8308C000D1A998 /* MalachiteAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7859DE192B8308C000D1A998 /* MalachiteAboutView.swift */; }; 785F084D2B12D41100244EB4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 785F084C2B12D41100244EB4 /* AppDelegate.swift */; }; 785F084F2B12D41100244EB4 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 785F084E2B12D41100244EB4 /* SceneDelegate.swift */; }; - 785F08512B12D41100244EB4 /* MalachiteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 785F08502B12D41100244EB4 /* MalachiteView.swift */; }; - 785F08562B12D41300244EB4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 785F08552B12D41300244EB4 /* Assets.xcassets */; }; 785F08592B12D41300244EB4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 785F08572B12D41300244EB4 /* LaunchScreen.storyboard */; }; - 7860DC002B897A3E00450BF8 /* MalachiteGameUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7860DBFF2B897A3E00450BF8 /* MalachiteGameUtils.swift */; }; - 78789C742E3494D500862C3D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 785F08552B12D41300244EB4 /* Assets.xcassets */; }; + 786DAE232E4F28A600BE3137 /* DeveloperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE172E4F28A600BE3137 /* DeveloperView.swift */; }; + 786DAE242E4F28A600BE3137 /* DeveloperView+BuildInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE182E4F28A600BE3137 /* DeveloperView+BuildInfo.swift */; }; + 786DAE252E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE192E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift */; }; + 786DAE262E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1A2E4F28A600BE3137 /* DeveloperView+Settings.swift */; }; + 786DAE272E4F28A600BE3137 /* MalachiteAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1C2E4F28A600BE3137 /* MalachiteAboutView.swift */; }; + 786DAE282E4F28A600BE3137 /* MalachiteCompatibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1D2E4F28A600BE3137 /* MalachiteCompatibilityView.swift */; }; + 786DAE292E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1E2E4F28A600BE3137 /* MalachitePhotoPreview.swift */; }; + 786DAE2A2E4F28A600BE3137 /* SettingsView+Help.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1F2E4F28A600BE3137 /* SettingsView+Help.swift */; }; + 786DAE2B2E4F28A600BE3137 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE202E4F28A600BE3137 /* SettingsView.swift */; }; + 786DAE2C2E4F28A600BE3137 /* MalachiteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE212E4F28A600BE3137 /* MalachiteView.swift */; }; + 786DAE2D2E4F28A600BE3137 /* DeveloperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE172E4F28A600BE3137 /* DeveloperView.swift */; }; + 786DAE2E2E4F28A600BE3137 /* DeveloperView+BuildInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE182E4F28A600BE3137 /* DeveloperView+BuildInfo.swift */; }; + 786DAE2F2E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE192E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift */; }; + 786DAE302E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1A2E4F28A600BE3137 /* DeveloperView+Settings.swift */; }; + 786DAE312E4F28A600BE3137 /* MalachiteAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1C2E4F28A600BE3137 /* MalachiteAboutView.swift */; }; + 786DAE322E4F28A600BE3137 /* MalachiteCompatibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1D2E4F28A600BE3137 /* MalachiteCompatibilityView.swift */; }; + 786DAE332E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1E2E4F28A600BE3137 /* MalachitePhotoPreview.swift */; }; + 786DAE342E4F28A600BE3137 /* SettingsView+Help.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1F2E4F28A600BE3137 /* SettingsView+Help.swift */; }; + 786DAE352E4F28A600BE3137 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE202E4F28A600BE3137 /* SettingsView.swift */; }; + 786DAE362E4F28A600BE3137 /* MalachiteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE212E4F28A600BE3137 /* MalachiteView.swift */; }; + 786DAE372E4F28A600BE3137 /* DeveloperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE172E4F28A600BE3137 /* DeveloperView.swift */; }; + 786DAE382E4F28A600BE3137 /* DeveloperView+BuildInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE182E4F28A600BE3137 /* DeveloperView+BuildInfo.swift */; }; + 786DAE392E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE192E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift */; }; + 786DAE3A2E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1A2E4F28A600BE3137 /* DeveloperView+Settings.swift */; }; + 786DAE3B2E4F28A600BE3137 /* MalachiteAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1C2E4F28A600BE3137 /* MalachiteAboutView.swift */; }; + 786DAE3C2E4F28A600BE3137 /* MalachiteCompatibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1D2E4F28A600BE3137 /* MalachiteCompatibilityView.swift */; }; + 786DAE3D2E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1E2E4F28A600BE3137 /* MalachitePhotoPreview.swift */; }; + 786DAE3E2E4F28A600BE3137 /* SettingsView+Help.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1F2E4F28A600BE3137 /* SettingsView+Help.swift */; }; + 786DAE3F2E4F28A600BE3137 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE202E4F28A600BE3137 /* SettingsView.swift */; }; + 786DAE402E4F28A600BE3137 /* MalachiteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE212E4F28A600BE3137 /* MalachiteView.swift */; }; + 786DAE4C2E4F28B100BE3137 /* MalachitePreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE412E4F28B100BE3137 /* MalachitePreferences.swift */; }; + 786DAE4D2E4F28B100BE3137 /* MalachitePreferencesUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE422E4F28B100BE3137 /* MalachitePreferencesUtils.swift */; }; + 786DAE4E2E4F28B100BE3137 /* MalachiteFunctionUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE442E4F28B100BE3137 /* MalachiteFunctionUtils.swift */; }; + 786DAE4F2E4F28B100BE3137 /* MalachiteGameUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE452E4F28B100BE3137 /* MalachiteGameUtils.swift */; }; + 786DAE502E4F28B100BE3137 /* MalachiteHapticUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE462E4F28B100BE3137 /* MalachiteHapticUtils.swift */; }; + 786DAE512E4F28B100BE3137 /* MalachiteIntentUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE472E4F28B100BE3137 /* MalachiteIntentUtils.swift */; }; + 786DAE522E4F28B100BE3137 /* MalachiteTooltipUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE482E4F28B100BE3137 /* MalachiteTooltipUtils.swift */; }; + 786DAE532E4F28B100BE3137 /* MalachiteUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE492E4F28B100BE3137 /* MalachiteUtils.swift */; }; + 786DAE542E4F28B100BE3137 /* MalachiteViewUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE4A2E4F28B100BE3137 /* MalachiteViewUtils.swift */; }; + 786DAE552E4F28B100BE3137 /* MalachitePreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE412E4F28B100BE3137 /* MalachitePreferences.swift */; }; + 786DAE562E4F28B100BE3137 /* MalachitePreferencesUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE422E4F28B100BE3137 /* MalachitePreferencesUtils.swift */; }; + 786DAE572E4F28B100BE3137 /* MalachiteFunctionUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE442E4F28B100BE3137 /* MalachiteFunctionUtils.swift */; }; + 786DAE582E4F28B100BE3137 /* MalachiteGameUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE452E4F28B100BE3137 /* MalachiteGameUtils.swift */; }; + 786DAE592E4F28B100BE3137 /* MalachiteHapticUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE462E4F28B100BE3137 /* MalachiteHapticUtils.swift */; }; + 786DAE5A2E4F28B100BE3137 /* MalachiteIntentUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE472E4F28B100BE3137 /* MalachiteIntentUtils.swift */; }; + 786DAE5B2E4F28B100BE3137 /* MalachiteTooltipUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE482E4F28B100BE3137 /* MalachiteTooltipUtils.swift */; }; + 786DAE5C2E4F28B100BE3137 /* MalachiteUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE492E4F28B100BE3137 /* MalachiteUtils.swift */; }; + 786DAE5D2E4F28B100BE3137 /* MalachiteViewUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE4A2E4F28B100BE3137 /* MalachiteViewUtils.swift */; }; + 786DAE5E2E4F28B100BE3137 /* MalachitePreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE412E4F28B100BE3137 /* MalachitePreferences.swift */; }; + 786DAE5F2E4F28B100BE3137 /* MalachitePreferencesUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE422E4F28B100BE3137 /* MalachitePreferencesUtils.swift */; }; + 786DAE602E4F28B100BE3137 /* MalachiteFunctionUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE442E4F28B100BE3137 /* MalachiteFunctionUtils.swift */; }; + 786DAE612E4F28B100BE3137 /* MalachiteGameUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE452E4F28B100BE3137 /* MalachiteGameUtils.swift */; }; + 786DAE622E4F28B100BE3137 /* MalachiteHapticUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE462E4F28B100BE3137 /* MalachiteHapticUtils.swift */; }; + 786DAE632E4F28B100BE3137 /* MalachiteIntentUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE472E4F28B100BE3137 /* MalachiteIntentUtils.swift */; }; + 786DAE642E4F28B100BE3137 /* MalachiteTooltipUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE482E4F28B100BE3137 /* MalachiteTooltipUtils.swift */; }; + 786DAE652E4F28B100BE3137 /* MalachiteUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE492E4F28B100BE3137 /* MalachiteUtils.swift */; }; + 786DAE662E4F28B100BE3137 /* MalachiteViewUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE4A2E4F28B100BE3137 /* MalachiteViewUtils.swift */; }; + 786DAE672E4F28B100BE3137 /* MalachiteIntentUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE472E4F28B100BE3137 /* MalachiteIntentUtils.swift */; }; + 786DAE6D2E4F28B700BE3137 /* ControlCenterWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE682E4F28B700BE3137 /* ControlCenterWidget.swift */; }; + 786DAE6E2E4F28B700BE3137 /* LockScreenWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE6A2E4F28B700BE3137 /* LockScreenWidget.swift */; }; + 786DAE6F2E4F28B700BE3137 /* MalachiteWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE6B2E4F28B700BE3137 /* MalachiteWidgetBundle.swift */; }; + 786DAE712E4F28B700BE3137 /* ControlCenterWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE682E4F28B700BE3137 /* ControlCenterWidget.swift */; }; + 786DAE722E4F28B700BE3137 /* LockScreenWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE6A2E4F28B700BE3137 /* LockScreenWidget.swift */; }; + 786DAE732E4F28B700BE3137 /* MalachiteWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE6B2E4F28B700BE3137 /* MalachiteWidgetBundle.swift */; }; + 786DAE782E4F28B900BE3137 /* MalachiteCaptureBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE762E4F28B900BE3137 /* MalachiteCaptureBundle.swift */; }; + 786DAE7D2E4F28BF00BE3137 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE792E4F28BF00BE3137 /* ContentView.swift */; }; + 786DAE7E2E4F28BF00BE3137 /* MalachiteWatchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE7B2E4F28BF00BE3137 /* MalachiteWatchApp.swift */; }; 787B1CAA2B8B095E000AFECC /* Malachite.docc in Sources */ = {isa = PBXBuildFile; fileRef = 787B1CA92B8B095E000AFECC /* Malachite.docc */; }; - 7886C6C22E45E2E10095460C /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 78B72BA12E332744002E2D4E /* AppIcon.icon */; }; - 788848B52CC74B4200B02E37 /* MalachiteCompatibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788848B42CC74B3B00B02E37 /* MalachiteCompatibilityView.swift */; }; - 788E74B32B13D8200024B586 /* MalachiteSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788E74B22B13D8200024B586 /* MalachiteSettingsView.swift */; }; 78917F5C2B99AE72005E10FA /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78917F5B2B99AE72005E10FA /* WidgetKit.framework */; }; 78917F5E2B99AE72005E10FA /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78917F5D2B99AE72005E10FA /* SwiftUI.framework */; }; - 78917F612B99AE72005E10FA /* MalachiteWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78917F602B99AE72005E10FA /* MalachiteWidgetBundle.swift */; }; - 78917F632B99AE72005E10FA /* LockScreenWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78917F622B99AE72005E10FA /* LockScreenWidget.swift */; }; 78917F692B99AE73005E10FA /* WidgetBundle.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 78917F592B99AE72005E10FA /* WidgetBundle.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 7897ECA62BA2B87100662EA0 /* MalachiteSettingsDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7897ECA52BA2B87100662EA0 /* MalachiteSettingsDetailView.swift */; }; 78A9DD6A2CD55551002C131D /* CaptureBundle.appex in Embed ExtensionKit Extensions */ = {isa = PBXBuildFile; fileRef = 78A9DD612CD55551002C131D /* CaptureBundle.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 78A9DD762CD55558002C131D /* MalachiteCaptureBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78A9DD722CD55558002C131D /* MalachiteCaptureBundle.swift */; }; - 78A9DD7C2CD5562B002C131D /* MalachiteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 785F08502B12D41100244EB4 /* MalachiteView.swift */; }; - 78A9DD7D2CD55631002C131D /* MalachiteAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7859DE192B8308C000D1A998 /* MalachiteAboutView.swift */; }; - 78A9DD7E2CD55631002C131D /* MalachiteUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E67EDE2B27E07300812A17 /* MalachiteUtils.swift */; }; - 78A9DD7F2CD55631002C131D /* MalachiteSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788E74B22B13D8200024B586 /* MalachiteSettingsView.swift */; }; - 78A9DD812CD55631002C131D /* MalachiteFunctionUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E67EE42B27E0A200812A17 /* MalachiteFunctionUtils.swift */; }; - 78A9DD822CD55631002C131D /* MalachitePhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E5EFBF2B15748400F15250 /* MalachitePhotoPreview.swift */; }; - 78A9DD832CD55631002C131D /* MalachiteCompatibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788848B42CC74B3B00B02E37 /* MalachiteCompatibilityView.swift */; }; - 78A9DD842CD55631002C131D /* MalachiteViewUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E67EDD2B27E07300812A17 /* MalachiteViewUtils.swift */; }; - 78A9DD852CD55631002C131D /* MalachiteSettingsDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7897ECA52BA2B87100662EA0 /* MalachiteSettingsDetailView.swift */; }; - 78A9DD862CD55631002C131D /* MalachiteTooltipUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 780697392B27F66E00B4942B /* MalachiteTooltipUtils.swift */; }; - 78A9DD872CD55631002C131D /* MalachiteGameUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7860DBFF2B897A3E00450BF8 /* MalachiteGameUtils.swift */; }; - 78A9DD882CD55631002C131D /* MalachiteHapticUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E67EDC2B27E07300812A17 /* MalachiteHapticUtils.swift */; }; - 78A9DE2D2CD57F51002C131D /* MalachiteIntentUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78A9DE2C2CD57F4D002C131D /* MalachiteIntentUtils.swift */; }; - 78A9DE2F2CD57F51002C131D /* MalachiteIntentUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78A9DE2C2CD57F4D002C131D /* MalachiteIntentUtils.swift */; }; 78A9DE312CD581F1002C131D /* LockedCameraCapture.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78A9DE302CD581F1002C131D /* LockedCameraCapture.framework */; }; 78A9DE752CD5AE5F002C131D /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */; }; - 78B72BA22E332744002E2D4E /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 78B72BA12E332744002E2D4E /* AppIcon.icon */; }; 78B72BB12E332830002E2D4E /* MalachiteWatch.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 78B72BA72E33282E002E2D4E /* MalachiteWatch.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 78B72BDA2E345301002E2D4E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B72BD62E345301002E2D4E /* ContentView.swift */; }; - 78B72BDB2E345301002E2D4E /* MalachiteWatchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B72BD72E345301002E2D4E /* MalachiteWatchApp.swift */; }; - 78C7EF9D2D1ECBD1001E332B /* MalachitePreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78C7EF9C2D1ECBCB001E332B /* MalachitePreferences.swift */; }; - 78C7EF9E2D1ECBD1001E332B /* MalachitePreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78C7EF9C2D1ECBCB001E332B /* MalachitePreferences.swift */; }; - 78C80BF52CDABE7800F6E8A7 /* MalachitePreferencesUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78C80BF32CDABE7200F6E8A7 /* MalachitePreferencesUtils.swift */; }; - 78C80BF62CDABE7800F6E8A7 /* MalachitePreferencesUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78C80BF32CDABE7200F6E8A7 /* MalachitePreferencesUtils.swift */; }; 78CAC36A2CCD99B600A35AE8 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */; }; - 78CD7DB62E359B87003814B5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 785F08552B12D41300244EB4 /* Assets.xcassets */; }; - 78CD7DB72E359B87003814B5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 785F08552B12D41300244EB4 /* Assets.xcassets */; }; - 78D570762E3C7C5E0025B9B3 /* MalachiteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 785F08502B12D41100244EB4 /* MalachiteView.swift */; }; - 78D570772E3C7CB20025B9B3 /* MalachiteUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E67EDE2B27E07300812A17 /* MalachiteUtils.swift */; }; - 78D570782E3C7CB80025B9B3 /* MalachiteTooltipUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 780697392B27F66E00B4942B /* MalachiteTooltipUtils.swift */; }; - 78D570792E3C7CB80025B9B3 /* MalachiteViewUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E67EDD2B27E07300812A17 /* MalachiteViewUtils.swift */; }; - 78D5707A2E3C7CB80025B9B3 /* MalachiteHapticUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E67EDC2B27E07300812A17 /* MalachiteHapticUtils.swift */; }; - 78D5707B2E3C7CB80025B9B3 /* MalachiteFunctionUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E67EE42B27E0A200812A17 /* MalachiteFunctionUtils.swift */; }; - 78D5707C2E3C7CB80025B9B3 /* MalachiteGameUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7860DBFF2B897A3E00450BF8 /* MalachiteGameUtils.swift */; }; - 78D5707D2E3C7CC60025B9B3 /* MalachitePreferencesUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78C80BF32CDABE7200F6E8A7 /* MalachitePreferencesUtils.swift */; }; - 78D5707E2E3C7CC60025B9B3 /* MalachitePreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78C7EF9C2D1ECBCB001E332B /* MalachitePreferences.swift */; }; - 78D5707F2E3C7CF50025B9B3 /* MalachitePhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E5EFBF2B15748400F15250 /* MalachitePhotoPreview.swift */; }; - 78D570802E3C7D050025B9B3 /* MalachiteSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788E74B22B13D8200024B586 /* MalachiteSettingsView.swift */; }; - 78D570812E3C7D050025B9B3 /* MalachiteCompatibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788848B42CC74B3B00B02E37 /* MalachiteCompatibilityView.swift */; }; - 78D570822E3C7D0B0025B9B3 /* MalachiteSettingsDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7897ECA52BA2B87100662EA0 /* MalachiteSettingsDetailView.swift */; }; - 78D570832E3C7D0B0025B9B3 /* MalachiteAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7859DE192B8308C000D1A998 /* MalachiteAboutView.swift */; }; - 78D570842E3C7E530025B9B3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 785F08552B12D41300244EB4 /* Assets.xcassets */; }; - 78E5EFC02B15748400F15250 /* MalachitePhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E5EFBF2B15748400F15250 /* MalachitePhotoPreview.swift */; }; - 78E67EE02B27E07300812A17 /* MalachiteHapticUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E67EDC2B27E07300812A17 /* MalachiteHapticUtils.swift */; }; - 78E67EE12B27E07300812A17 /* MalachiteViewUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E67EDD2B27E07300812A17 /* MalachiteViewUtils.swift */; }; - 78E67EE22B27E07300812A17 /* MalachiteUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E67EDE2B27E07300812A17 /* MalachiteUtils.swift */; }; - 78E67EE52B27E0A200812A17 /* MalachiteFunctionUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E67EE42B27E0A200812A17 /* MalachiteFunctionUtils.swift */; }; - 78E8265B2E349FEC00A8DB50 /* MalachiteIntentUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78A9DE2C2CD57F4D002C131D /* MalachiteIntentUtils.swift */; }; 78EC5D5F2E4EC110005044E2 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */; }; /* End PBXBuildFile section */ @@ -174,59 +179,121 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 780067CF2CBDCB94004BB595 /* ControlCenterWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlCenterWidget.swift; sourceTree = ""; }; - 780697392B27F66E00B4942B /* MalachiteTooltipUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteTooltipUtils.swift; sourceTree = ""; }; 78163D8D2CCB7BAE00146126 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; 782FC7752B2DAFAB007709C1 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; - 7837C0FF2E34A5B7009396B0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 7837C1152E34C45B009396B0 /* WidgetBundleWatch.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetBundleWatch.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 78474D2B2D7AC879006FBB96 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = ""; }; 78562BC22B450A7600920160 /* PRIVACY_POLICY.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = PRIVACY_POLICY.md; sourceTree = SOURCE_ROOT; }; - 7859DE192B8308C000D1A998 /* MalachiteAboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteAboutView.swift; sourceTree = ""; }; 785F08492B12D41100244EB4 /* Malachite.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Malachite.app; sourceTree = BUILT_PRODUCTS_DIR; }; 785F084C2B12D41100244EB4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 785F084E2B12D41100244EB4 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - 785F08502B12D41100244EB4 /* MalachiteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteView.swift; sourceTree = ""; }; - 785F08552B12D41300244EB4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 785F08582B12D41300244EB4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 785F085A2B12D41300244EB4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 7860DBFF2B897A3E00450BF8 /* MalachiteGameUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteGameUtils.swift; sourceTree = ""; }; + 786DAE172E4F28A600BE3137 /* DeveloperView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperView.swift; sourceTree = ""; }; + 786DAE182E4F28A600BE3137 /* DeveloperView+BuildInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DeveloperView+BuildInfo.swift"; sourceTree = ""; }; + 786DAE192E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DeveloperView+DeviceInfo.swift"; sourceTree = ""; }; + 786DAE1A2E4F28A600BE3137 /* DeveloperView+Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DeveloperView+Settings.swift"; sourceTree = ""; }; + 786DAE1C2E4F28A600BE3137 /* MalachiteAboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteAboutView.swift; sourceTree = ""; }; + 786DAE1D2E4F28A600BE3137 /* MalachiteCompatibilityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteCompatibilityView.swift; sourceTree = ""; }; + 786DAE1E2E4F28A600BE3137 /* MalachitePhotoPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachitePhotoPreview.swift; sourceTree = ""; }; + 786DAE1F2E4F28A600BE3137 /* SettingsView+Help.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsView+Help.swift"; sourceTree = ""; }; + 786DAE202E4F28A600BE3137 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + 786DAE212E4F28A600BE3137 /* MalachiteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteView.swift; sourceTree = ""; }; + 786DAE412E4F28B100BE3137 /* MalachitePreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachitePreferences.swift; sourceTree = ""; }; + 786DAE422E4F28B100BE3137 /* MalachitePreferencesUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachitePreferencesUtils.swift; sourceTree = ""; }; + 786DAE442E4F28B100BE3137 /* MalachiteFunctionUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteFunctionUtils.swift; sourceTree = ""; }; + 786DAE452E4F28B100BE3137 /* MalachiteGameUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteGameUtils.swift; sourceTree = ""; }; + 786DAE462E4F28B100BE3137 /* MalachiteHapticUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteHapticUtils.swift; sourceTree = ""; }; + 786DAE472E4F28B100BE3137 /* MalachiteIntentUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteIntentUtils.swift; sourceTree = ""; }; + 786DAE482E4F28B100BE3137 /* MalachiteTooltipUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteTooltipUtils.swift; sourceTree = ""; }; + 786DAE492E4F28B100BE3137 /* MalachiteUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteUtils.swift; sourceTree = ""; }; + 786DAE4A2E4F28B100BE3137 /* MalachiteViewUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteViewUtils.swift; sourceTree = ""; }; + 786DAE682E4F28B700BE3137 /* ControlCenterWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlCenterWidget.swift; sourceTree = ""; }; + 786DAE692E4F28B700BE3137 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 786DAE6A2E4F28B700BE3137 /* LockScreenWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockScreenWidget.swift; sourceTree = ""; }; + 786DAE6B2E4F28B700BE3137 /* MalachiteWidgetBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteWidgetBundle.swift; sourceTree = ""; }; + 786DAE752E4F28B900BE3137 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 786DAE762E4F28B900BE3137 /* MalachiteCaptureBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteCaptureBundle.swift; sourceTree = ""; }; + 786DAE792E4F28BF00BE3137 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 786DAE7A2E4F28BF00BE3137 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 786DAE7B2E4F28BF00BE3137 /* MalachiteWatchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteWatchApp.swift; sourceTree = ""; }; 787B1CA92B8B095E000AFECC /* Malachite.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = Malachite.docc; sourceTree = ""; }; 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = Malachite/Localizable.xcstrings; sourceTree = SOURCE_ROOT; }; 788589912B8974680018E2DA /* Malachite.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Malachite.entitlements; sourceTree = ""; }; - 788848B42CC74B3B00B02E37 /* MalachiteCompatibilityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteCompatibilityView.swift; sourceTree = ""; }; - 788E74B22B13D8200024B586 /* MalachiteSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteSettingsView.swift; sourceTree = ""; }; 78917F592B99AE72005E10FA /* WidgetBundle.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetBundle.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 78917F5B2B99AE72005E10FA /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; 78917F5D2B99AE72005E10FA /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; - 78917F602B99AE72005E10FA /* MalachiteWidgetBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteWidgetBundle.swift; sourceTree = ""; }; - 78917F622B99AE72005E10FA /* LockScreenWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockScreenWidget.swift; sourceTree = ""; }; - 78917F662B99AE73005E10FA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 7897ECA52BA2B87100662EA0 /* MalachiteSettingsDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteSettingsDetailView.swift; sourceTree = ""; }; 78A9DD612CD55551002C131D /* CaptureBundle.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.extensionkit-extension"; includeInIndex = 0; path = CaptureBundle.appex; sourceTree = BUILT_PRODUCTS_DIR; }; - 78A9DD712CD55558002C131D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 78A9DD722CD55558002C131D /* MalachiteCaptureBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteCaptureBundle.swift; sourceTree = ""; }; - 78A9DE2C2CD57F4D002C131D /* MalachiteIntentUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteIntentUtils.swift; sourceTree = ""; }; 78A9DE302CD581F1002C131D /* LockedCameraCapture.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LockedCameraCapture.framework; path = System/Library/Frameworks/LockedCameraCapture.framework; sourceTree = SDKROOT; }; - 78B72BA12E332744002E2D4E /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIcon.icon; sourceTree = ""; }; 78B72BA72E33282E002E2D4E /* MalachiteWatch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MalachiteWatch.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 78B72BCE2E3452A0002E2D4E /* ci_post_clone.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = ci_post_clone.sh; sourceTree = ""; }; - 78B72BCF2E3452A0002E2D4E /* ci_post_xcodebuild.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = ci_post_xcodebuild.sh; sourceTree = ""; }; - 78B72BD12E3452A2002E2D4E /* build.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = build.yml; sourceTree = ""; }; - 78B72BD62E345301002E2D4E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; - 78B72BD72E345301002E2D4E /* MalachiteWatchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteWatchApp.swift; sourceTree = ""; }; - 78B72BE12E3461DA002E2D4E /* MalachiteIconMasked_Darwin24.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = MalachiteIconMasked_Darwin24.png; sourceTree = ""; }; - 78B72BE22E3461DA002E2D4E /* MalachiteIconMasked_Darwin25.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = MalachiteIconMasked_Darwin25.png; sourceTree = ""; }; - 78C7EF9C2D1ECBCB001E332B /* MalachitePreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachitePreferences.swift; sourceTree = ""; }; - 78C80BF32CDABE7200F6E8A7 /* MalachitePreferencesUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachitePreferencesUtils.swift; sourceTree = ""; }; 78E142C92DD426260016B3DB /* Codesigning.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Codesigning.xcconfig; sourceTree = ""; }; - 78E5EFBF2B15748400F15250 /* MalachitePhotoPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachitePhotoPreview.swift; sourceTree = ""; }; - 78E67EDC2B27E07300812A17 /* MalachiteHapticUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MalachiteHapticUtils.swift; sourceTree = ""; }; - 78E67EDD2B27E07300812A17 /* MalachiteViewUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MalachiteViewUtils.swift; sourceTree = ""; }; - 78E67EDE2B27E07300812A17 /* MalachiteUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MalachiteUtils.swift; sourceTree = ""; }; - 78E67EE42B27E0A200812A17 /* MalachiteFunctionUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteFunctionUtils.swift; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 786DADA02E4F283500BE3137 /* Exceptions for "Assets" folder in "Malachite" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + AppIcon.icon, + Assets.xcassets, + ); + target = 785F08482B12D41100244EB4 /* Malachite */; + }; + 786DADA12E4F283500BE3137 /* Exceptions for "Assets" folder in "MalachiteWatch" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + AppIcon.icon, + Assets.xcassets, + ); + target = 78B72BA62E33282E002E2D4E /* MalachiteWatch */; + }; + 786DADA22E4F283500BE3137 /* Exceptions for "Assets" folder in "WidgetBundle" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Assets.xcassets, + ); + target = 78917F582B99AE72005E10FA /* WidgetBundle */; + }; + 786DADA32E4F283500BE3137 /* Exceptions for "Assets" folder in "WidgetBundleWatch" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Assets.xcassets, + ); + target = 7837C1042E34C45B009396B0 /* WidgetBundleWatch */; + }; + 786DADA42E4F283500BE3137 /* Exceptions for "Assets" folder in "CaptureBundle" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Assets.xcassets, + ); + target = 78A9DD602CD55551002C131D /* CaptureBundle */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 786DAD902E4F282E00BE3137 /* .github */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = .github; + sourceTree = ""; + }; + 786DAD932E4F283000BE3137 /* ci_scripts */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = ci_scripts; + sourceTree = ""; + }; + 786DAD982E4F283500BE3137 /* Assets */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 786DADA02E4F283500BE3137 /* Exceptions for "Assets" folder in "Malachite" target */, + 786DADA12E4F283500BE3137 /* Exceptions for "Assets" folder in "MalachiteWatch" target */, + 786DADA22E4F283500BE3137 /* Exceptions for "Assets" folder in "WidgetBundle" target */, + 786DADA32E4F283500BE3137 /* Exceptions for "Assets" folder in "WidgetBundleWatch" target */, + 786DADA42E4F283500BE3137 /* Exceptions for "Assets" folder in "CaptureBundle" target */, + ); + path = Assets; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 7837C10A2E34C45B009396B0 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -264,20 +331,6 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 7859DE182B83089500D1A998 /* Views */ = { - isa = PBXGroup; - children = ( - 788848B42CC74B3B00B02E37 /* MalachiteCompatibilityView.swift */, - 785F08502B12D41100244EB4 /* MalachiteView.swift */, - 78E5EFBF2B15748400F15250 /* MalachitePhotoPreview.swift */, - 788E74B22B13D8200024B586 /* MalachiteSettingsView.swift */, - 7859DE192B8308C000D1A998 /* MalachiteAboutView.swift */, - 7897ECA52BA2B87100662EA0 /* MalachiteSettingsDetailView.swift */, - ); - name = Views; - path = Malachite/Views; - sourceTree = SOURCE_ROOT; - }; 785F08402B12D41100244EB4 = { isa = PBXGroup; children = ( @@ -285,10 +338,10 @@ 78163D8D2CCB7BAE00146126 /* CHANGELOG.md */, 782FC7752B2DAFAB007709C1 /* README.md */, 78562BC22B450A7600920160 /* PRIVACY_POLICY.md */, - 78B72BD32E3452A2002E2D4E /* .github */, - 78B72BD02E3452A0002E2D4E /* ci_scripts */, + 786DAD902E4F282E00BE3137 /* .github */, + 786DAD932E4F283000BE3137 /* ci_scripts */, 785F084B2B12D41100244EB4 /* Malachite */, - 78B72BD82E345301002E2D4E /* MalachiteWatch */, + 786DAE7C2E4F28BF00BE3137 /* MalachiteWatch */, 78917F5A2B99AE72005E10FA /* Frameworks */, 785F084A2B12D41100244EB4 /* Products */, ); @@ -309,121 +362,118 @@ 785F084B2B12D41100244EB4 /* Malachite */ = { isa = PBXGroup; children = ( - 78B72BCD2E34529A002E2D4E /* Assets */, + 786DAD982E4F283500BE3137 /* Assets */, 78E142C92DD426260016B3DB /* Codesigning.xcconfig */, 787B1CA92B8B095E000AFECC /* Malachite.docc */, 788589912B8974680018E2DA /* Malachite.entitlements */, 785F085A2B12D41300244EB4 /* Info.plist */, - 7859DE182B83089500D1A998 /* Views */, - 78E67EDB2B27E05C00812A17 /* Utilities */, - 78A9DD742CD55558002C131D /* CaptureBundle */, - 78917F5F2B99AE72005E10FA /* WidgetBundle */, - 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */, + 786DAE222E4F28A600BE3137 /* Views */, + 786DAE4B2E4F28B100BE3137 /* Utilities */, + 786DAE772E4F28B900BE3137 /* CaptureBundle */, + 786DAE6C2E4F28B700BE3137 /* WidgetBundle */, 785F08572B12D41300244EB4 /* LaunchScreen.storyboard */, + 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */, 785F084C2B12D41100244EB4 /* AppDelegate.swift */, 785F084E2B12D41100244EB4 /* SceneDelegate.swift */, ); path = Malachite; sourceTree = ""; }; - 78917F5A2B99AE72005E10FA /* Frameworks */ = { + 786DAE1B2E4F28A600BE3137 /* DeveloperView */ = { isa = PBXGroup; children = ( - 78A9DE302CD581F1002C131D /* LockedCameraCapture.framework */, - 78917F5B2B99AE72005E10FA /* WidgetKit.framework */, - 78917F5D2B99AE72005E10FA /* SwiftUI.framework */, + 786DAE172E4F28A600BE3137 /* DeveloperView.swift */, + 786DAE182E4F28A600BE3137 /* DeveloperView+BuildInfo.swift */, + 786DAE192E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift */, + 786DAE1A2E4F28A600BE3137 /* DeveloperView+Settings.swift */, ); - name = Frameworks; + path = DeveloperView; sourceTree = ""; }; - 78917F5F2B99AE72005E10FA /* WidgetBundle */ = { + 786DAE222E4F28A600BE3137 /* Views */ = { isa = PBXGroup; children = ( - 78917F602B99AE72005E10FA /* MalachiteWidgetBundle.swift */, - 78917F622B99AE72005E10FA /* LockScreenWidget.swift */, - 780067CF2CBDCB94004BB595 /* ControlCenterWidget.swift */, - 78917F662B99AE73005E10FA /* Info.plist */, - ); - path = WidgetBundle; + 786DAE802E4F28D600BE3137 /* SettingsView */, + 786DAE1B2E4F28A600BE3137 /* DeveloperView */, + 786DAE1C2E4F28A600BE3137 /* MalachiteAboutView.swift */, + 786DAE1D2E4F28A600BE3137 /* MalachiteCompatibilityView.swift */, + 786DAE1E2E4F28A600BE3137 /* MalachitePhotoPreview.swift */, + 786DAE212E4F28A600BE3137 /* MalachiteView.swift */, + ); + path = Views; sourceTree = ""; }; - 78A8FEEE2D2B218400BCA9C5 /* Preferences */ = { + 786DAE432E4F28B100BE3137 /* Preferences */ = { isa = PBXGroup; children = ( - 78C7EF9C2D1ECBCB001E332B /* MalachitePreferences.swift */, - 78C80BF32CDABE7200F6E8A7 /* MalachitePreferencesUtils.swift */, + 786DAE412E4F28B100BE3137 /* MalachitePreferences.swift */, + 786DAE422E4F28B100BE3137 /* MalachitePreferencesUtils.swift */, ); path = Preferences; sourceTree = ""; }; - 78A9DD742CD55558002C131D /* CaptureBundle */ = { - isa = PBXGroup; - children = ( - 78A9DD712CD55558002C131D /* Info.plist */, - 78A9DD722CD55558002C131D /* MalachiteCaptureBundle.swift */, - ); - path = CaptureBundle; - sourceTree = ""; - }; - 78B72BCD2E34529A002E2D4E /* Assets */ = { + 786DAE4B2E4F28B100BE3137 /* Utilities */ = { isa = PBXGroup; children = ( - 785F08552B12D41300244EB4 /* Assets.xcassets */, - 78B72BA12E332744002E2D4E /* AppIcon.icon */, - 78B72BE12E3461DA002E2D4E /* MalachiteIconMasked_Darwin24.png */, - 78B72BE22E3461DA002E2D4E /* MalachiteIconMasked_Darwin25.png */, + 786DAE432E4F28B100BE3137 /* Preferences */, + 786DAE442E4F28B100BE3137 /* MalachiteFunctionUtils.swift */, + 786DAE452E4F28B100BE3137 /* MalachiteGameUtils.swift */, + 786DAE462E4F28B100BE3137 /* MalachiteHapticUtils.swift */, + 786DAE472E4F28B100BE3137 /* MalachiteIntentUtils.swift */, + 786DAE482E4F28B100BE3137 /* MalachiteTooltipUtils.swift */, + 786DAE492E4F28B100BE3137 /* MalachiteUtils.swift */, + 786DAE4A2E4F28B100BE3137 /* MalachiteViewUtils.swift */, ); - path = Assets; + path = Utilities; sourceTree = ""; }; - 78B72BD02E3452A0002E2D4E /* ci_scripts */ = { + 786DAE6C2E4F28B700BE3137 /* WidgetBundle */ = { isa = PBXGroup; children = ( - 78B72BCE2E3452A0002E2D4E /* ci_post_clone.sh */, - 78B72BCF2E3452A0002E2D4E /* ci_post_xcodebuild.sh */, + 786DAE682E4F28B700BE3137 /* ControlCenterWidget.swift */, + 786DAE692E4F28B700BE3137 /* Info.plist */, + 786DAE6A2E4F28B700BE3137 /* LockScreenWidget.swift */, + 786DAE6B2E4F28B700BE3137 /* MalachiteWidgetBundle.swift */, ); - path = ci_scripts; + path = WidgetBundle; sourceTree = ""; }; - 78B72BD22E3452A2002E2D4E /* workflows */ = { + 786DAE772E4F28B900BE3137 /* CaptureBundle */ = { isa = PBXGroup; children = ( - 78B72BD12E3452A2002E2D4E /* build.yml */, + 786DAE752E4F28B900BE3137 /* Info.plist */, + 786DAE762E4F28B900BE3137 /* MalachiteCaptureBundle.swift */, ); - path = workflows; + path = CaptureBundle; sourceTree = ""; }; - 78B72BD32E3452A2002E2D4E /* .github */ = { + 786DAE7C2E4F28BF00BE3137 /* MalachiteWatch */ = { isa = PBXGroup; children = ( - 78B72BD22E3452A2002E2D4E /* workflows */, + 786DAE792E4F28BF00BE3137 /* ContentView.swift */, + 786DAE7A2E4F28BF00BE3137 /* Info.plist */, + 786DAE7B2E4F28BF00BE3137 /* MalachiteWatchApp.swift */, ); - path = .github; + path = MalachiteWatch; sourceTree = ""; }; - 78B72BD82E345301002E2D4E /* MalachiteWatch */ = { + 786DAE802E4F28D600BE3137 /* SettingsView */ = { isa = PBXGroup; children = ( - 7837C0FF2E34A5B7009396B0 /* Info.plist */, - 78B72BD62E345301002E2D4E /* ContentView.swift */, - 78B72BD72E345301002E2D4E /* MalachiteWatchApp.swift */, + 786DAE202E4F28A600BE3137 /* SettingsView.swift */, + 786DAE1F2E4F28A600BE3137 /* SettingsView+Help.swift */, ); - path = MalachiteWatch; + path = SettingsView; sourceTree = ""; }; - 78E67EDB2B27E05C00812A17 /* Utilities */ = { + 78917F5A2B99AE72005E10FA /* Frameworks */ = { isa = PBXGroup; children = ( - 78A8FEEE2D2B218400BCA9C5 /* Preferences */, - 78A9DE2C2CD57F4D002C131D /* MalachiteIntentUtils.swift */, - 78E67EDE2B27E07300812A17 /* MalachiteUtils.swift */, - 78E67EDC2B27E07300812A17 /* MalachiteHapticUtils.swift */, - 78E67EDD2B27E07300812A17 /* MalachiteViewUtils.swift */, - 78E67EE42B27E0A200812A17 /* MalachiteFunctionUtils.swift */, - 780697392B27F66E00B4942B /* MalachiteTooltipUtils.swift */, - 7860DBFF2B897A3E00450BF8 /* MalachiteGameUtils.swift */, + 78A9DE302CD581F1002C131D /* LockedCameraCapture.framework */, + 78917F5B2B99AE72005E10FA /* WidgetKit.framework */, + 78917F5D2B99AE72005E10FA /* SwiftUI.framework */, ); - path = Utilities; + name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ @@ -580,7 +630,6 @@ buildActionMask = 2147483647; files = ( 7837C10E2E34C45B009396B0 /* Localizable.xcstrings in Resources */, - 78CD7DB72E359B87003814B5 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -588,10 +637,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 78B72BA22E332744002E2D4E /* AppIcon.icon in Resources */, 78EC5D5F2E4EC110005044E2 /* Localizable.xcstrings in Resources */, 785F08592B12D41300244EB4 /* LaunchScreen.storyboard in Resources */, - 785F08562B12D41300244EB4 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -600,7 +647,6 @@ buildActionMask = 2147483647; files = ( 78CAC36A2CCD99B600A35AE8 /* Localizable.xcstrings in Resources */, - 78CD7DB62E359B87003814B5 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -608,7 +654,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 78D570842E3C7E530025B9B3 /* Assets.xcassets in Resources */, 78A9DE752CD5AE5F002C131D /* Localizable.xcstrings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -617,8 +662,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 78789C742E3494D500862C3D /* Assets.xcassets in Resources */, - 7886C6C22E45E2E10095460C /* AppIcon.icon in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -643,7 +686,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/bash; - shellScript = "eval \"${GCC_PREPROCESSOR_DEFINITIONS}\"\n\nPATH=/opt/homebrew/bin:/usr/local/bin:$PATH\nPLISTBUDDY=\"/usr/libexec/PlistBuddy\"\nINFOPLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n\n# If there is no git repo to pull this info from, this is set to undefined later.\nCFBUILDHASH=$(git --git-dir=\"${PROJECT_DIR}/.git\" --work-tree=\"${PROJECT_DIR}\" rev-parse --short HEAD)\nCFBUILDDATE=$(date +'%Y-%m-%d-%H.%M.%S')\nif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && -z $(system_profiler SPHardwareDataType | grep 'MacVM1,1') ]]; then\n # If the build is not INTERNAL, these are set to redacted later.\n CFBUILDUSER=$(whoami)\n CFBUILDHOST=$(hostname)\nelif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && !(-z $(system_profiler SPHardwareDataType | grep 'MacVM1,1')) ]]; then\n # Xcode Cloud built this, but the hostname string is atrocious. Replace with these.\n CFBUILDUSER=\"evaluna (App Store Connect)\"\n CFBUILDHOST=\"Xcode Cloud\"\nfi\n\nif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && -z $(gpg --list-secret-keys | grep 4A5B8C2A065654E24DE50FF03716ACDC524F1879) ]]; then\n echo \"Error: INTERNAL build type detected but Eva's GPG key was not found.\"\n echo \"Error: See 'Embed build information' in Build Phases for a solution\"\n # Be aware that I use the INTERNAL target to gate off any unstable/unready features. By removing this line, \n # you are acknowledging that you will not receive support for any issues encountered with Malachite until you \n # are running DEBUG or RELEASE. If you just want a DEBUG build, change the Build Configuration to Debug in \n # Product > Scheme > Edit Scheme...\n if [[ -z $(system_profiler SPHardwareDataType | grep 'MacVM1,1') ]]; then\n exit\n else\n # This line of code should keep internal TestFlight builds as INTERNAL, and external ones as DEBUG\n if [[ $(git --git-dir=\"${PROJECT_DIR}/.git\" rev-parse --abbrev-ref HEAD) != *\"dev\"* ]]; then\n CFBUILDTYPE=\"DEBUG\"\n fi\n fi\nfi\n\n\n\n# Print all of the variables in the build log. Useful for debugging\necho \"\"\necho \"CFBUILDHASH = ${CFBUILDHASH}\"\necho \"CFBUILDDATE = ${CFBUILDDATE}\"\necho \"CFBUILDTYPE = ${CFBUILDTYPE}\"\nif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" ]]; then\n echo \"CFBUILDUSER = ${CFBUILDUSER}\"\n echo \"CFBUILDHOST = ${CFBUILDHOST}\"\nfi\necho \"INFOPLIST = ${INFOPLIST}\"\n\n# Make sure the key is present in the plist before trying to set it.\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildHash' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildHash string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildDate' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildDate string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildType' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildType string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildUser' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildUser string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildHost' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildHost string\" \"${INFOPLIST}\"; fi\n\n# Fallback values, either to fill in the gaps or prevent app crashes\nif [ -z \"$CFBUILDHASH\" ]; then CFBUILDHASH=undefined; fi\nif [ -z \"$CFBUILDTYPE\" ]; then CFBUILDTYPE=debug; fi\nif [ -z \"$CFBUILDUSER\" ]; then CFBUILDUSER=redacted; fi\nif [ -z \"$CFBUILDHOST\" ]; then CFBUILDHOST=redacted; fi\n\n# Set the values in the Info.plist\n$PLISTBUDDY -c \"Set :CFBuildHash $CFBUILDHASH\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildDate $CFBUILDDATE\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildType $CFBUILDTYPE\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildUser $CFBUILDUSER\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildHost $CFBUILDHOST\" \"${INFOPLIST}\"\n"; + shellScript = "eval \"${GCC_PREPROCESSOR_DEFINITIONS}\"\n\nPATH=/opt/homebrew/bin:/usr/local/bin:$PATH\nPLISTBUDDY=\"/usr/libexec/PlistBuddy\"\nINFOPLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n\n# If there is no git repo to pull this info from, this is set to undefined later.\nCFBUILDHASH=$(git --git-dir=\"${PROJECT_DIR}/.git\" --work-tree=\"${PROJECT_DIR}\" rev-parse --short HEAD)\nCFBUILDBRAN=$(git branch --show-current)\nCFBUILDDATE=$(date +'%Y-%m-%d-%H.%M.%S')\nif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && -z $(system_profiler SPHardwareDataType | grep 'MacVM1,1') ]]; then\n # If the build is not INTERNAL, these are set to redacted later.\n CFBUILDUSER=$(whoami)\n CFBUILDHOST=$(hostname)\nelif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && !(-z $(system_profiler SPHardwareDataType | grep 'MacVM1,1')) ]]; then\n # Xcode Cloud built this, but the hostname string is atrocious. Replace with these.\n CFBUILDUSER=\"evaluna (App Store Connect)\"\n CFBUILDHOST=\"Xcode Cloud\"\nfi\n\nif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && -z $(gpg --list-secret-keys | grep 4A5B8C2A065654E24DE50FF03716ACDC524F1879) ]]; then\n echo \"Error: INTERNAL build type detected but Eva's GPG key was not found.\"\n echo \"Error: See 'Embed build information' in Build Phases for a solution\"\n # Be aware that I use the INTERNAL target to gate off any unstable/unready features. By removing this line, \n # you are acknowledging that you will not receive support for any issues encountered with Malachite until you \n # are running DEBUG or RELEASE. If you just want a DEBUG build, change the Build Configuration to Debug in \n # Product > Scheme > Edit Scheme...\n if [[ -z $(system_profiler SPHardwareDataType | grep 'MacVM1,1') ]]; then\n exit\n else\n # This line of code should keep internal TestFlight builds as INTERNAL, and external ones as DEBUG\n if [[ $(git --git-dir=\"${PROJECT_DIR}/.git\" rev-parse --abbrev-ref HEAD) != *\"dev\"* ]]; then\n CFBUILDTYPE=\"DEBUG\"\n fi\n fi\nfi\n\n\n\n# Print all of the variables in the build log. Useful for debugging\necho \"\"\necho \"CFBUILDHASH = ${CFBUILDHASH}\"\necho \"CFBUILDBRAN = ${CFBUILDBRAN}\"\necho \"CFBUILDDATE = ${CFBUILDDATE}\"\necho \"CFBUILDTYPE = ${CFBUILDTYPE}\"\nif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" ]]; then\n echo \"CFBUILDUSER = ${CFBUILDUSER}\"\n echo \"CFBUILDHOST = ${CFBUILDHOST}\"\nfi\necho \"INFOPLIST = ${INFOPLIST}\"\n\n# Make sure the key is present in the plist before trying to set it.\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildHash' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildHash string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildBran' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildBran string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildDate' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildDate string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildType' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildType string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildUser' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildUser string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildHost' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildHost string\" \"${INFOPLIST}\"; fi\n\n# Fallback values, either to fill in the gaps or prevent app crashes\nif [ -z \"$CFBUILDHASH\" ]; then CFBUILDHASH=undefined; fi\nif [ -z \"$CFBUILDBRAN\" ]; then CFBUILDBRAN=undefined; fi\nif [ -z \"$CFBUILDTYPE\" ]; then CFBUILDTYPE=debug; fi\nif [ -z \"$CFBUILDUSER\" ]; then CFBUILDUSER=redacted; fi\nif [ -z \"$CFBUILDHOST\" ]; then CFBUILDHOST=redacted; fi\n\n# Set the values in the Info.plist\n$PLISTBUDDY -c \"Set :CFBuildHash $CFBUILDHASH\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildBran $CFBUILDBRAN\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildDate $CFBUILDDATE\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildType $CFBUILDTYPE\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildUser $CFBUILDUSER\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildHost $CFBUILDHOST\" \"${INFOPLIST}\"\n"; }; 78EC5D602E4EC1AD005044E2 /* Embed build information */ = { isa = PBXShellScriptBuildPhase; @@ -663,7 +706,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/bash; - shellScript = "eval \"${GCC_PREPROCESSOR_DEFINITIONS}\"\n\nPATH=/opt/homebrew/bin:/usr/local/bin:$PATH\nPLISTBUDDY=\"/usr/libexec/PlistBuddy\"\nINFOPLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n\n# If there is no git repo to pull this info from, this is set to undefined later.\nCFBUILDHASH=$(git --git-dir=\"${PROJECT_DIR}/.git\" --work-tree=\"${PROJECT_DIR}\" rev-parse --short HEAD)\nCFBUILDDATE=$(date +'%Y-%m-%d-%H.%M.%S')\nif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && -z $(system_profiler SPHardwareDataType | grep 'MacVM1,1') ]]; then\n # If the build is not INTERNAL, these are set to redacted later.\n CFBUILDUSER=$(whoami)\n CFBUILDHOST=$(hostname)\nelif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && !(-z $(system_profiler SPHardwareDataType | grep 'MacVM1,1')) ]]; then\n # Xcode Cloud built this, but the hostname string is atrocious. Replace with these.\n CFBUILDUSER=\"evaluna (App Store Connect)\"\n CFBUILDHOST=\"Xcode Cloud\"\nfi\n\nif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && -z $(gpg --list-secret-keys | grep 4A5B8C2A065654E24DE50FF03716ACDC524F1879) ]]; then\n echo \"Error: INTERNAL build type detected but Eva's GPG key was not found.\"\n echo \"Error: See 'Embed build information' in Build Phases for a solution\"\n # Be aware that I use the INTERNAL target to gate off any unstable/unready features. By removing this line, \n # you are acknowledging that you will not receive support for any issues encountered with Malachite until you \n # are running DEBUG or RELEASE. If you just want a DEBUG build, change the Build Configuration to Debug in \n # Product > Scheme > Edit Scheme...\n if [[ -z $(system_profiler SPHardwareDataType | grep 'MacVM1,1') ]]; then\n exit\n else\n # This line of code should keep internal TestFlight builds as INTERNAL, and external ones as DEBUG\n if [[ $(git --git-dir=\"${PROJECT_DIR}/.git\" rev-parse --abbrev-ref HEAD) != *\"dev\"* ]]; then\n CFBUILDTYPE=\"DEBUG\"\n fi\n fi\nfi\n\n\n\n# Print all of the variables in the build log. Useful for debugging\necho \"\"\necho \"CFBUILDHASH = ${CFBUILDHASH}\"\necho \"CFBUILDDATE = ${CFBUILDDATE}\"\necho \"CFBUILDTYPE = ${CFBUILDTYPE}\"\nif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" ]]; then\n echo \"CFBUILDUSER = ${CFBUILDUSER}\"\n echo \"CFBUILDHOST = ${CFBUILDHOST}\"\nfi\necho \"INFOPLIST = ${INFOPLIST}\"\n\n# Make sure the key is present in the plist before trying to set it.\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildHash' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildHash string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildDate' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildDate string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildType' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildType string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildUser' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildUser string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildHost' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildHost string\" \"${INFOPLIST}\"; fi\n\n# Fallback values, either to fill in the gaps or prevent app crashes\nif [ -z \"$CFBUILDHASH\" ]; then CFBUILDHASH=undefined; fi\nif [ -z \"$CFBUILDTYPE\" ]; then CFBUILDTYPE=debug; fi\nif [ -z \"$CFBUILDUSER\" ]; then CFBUILDUSER=redacted; fi\nif [ -z \"$CFBUILDHOST\" ]; then CFBUILDHOST=redacted; fi\n\n# Set the values in the Info.plist\n$PLISTBUDDY -c \"Set :CFBuildHash $CFBUILDHASH\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildDate $CFBUILDDATE\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildType $CFBUILDTYPE\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildUser $CFBUILDUSER\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildHost $CFBUILDHOST\" \"${INFOPLIST}\"\n"; + shellScript = "eval \"${GCC_PREPROCESSOR_DEFINITIONS}\"\n\nPATH=/opt/homebrew/bin:/usr/local/bin:$PATH\nPLISTBUDDY=\"/usr/libexec/PlistBuddy\"\nINFOPLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n\n# If there is no git repo to pull this info from, this is set to undefined later.\nCFBUILDHASH=$(git --git-dir=\"${PROJECT_DIR}/.git\" --work-tree=\"${PROJECT_DIR}\" rev-parse --short HEAD)\nCFBUILDBRAN=$(git branch --show-current)\nCFBUILDDATE=$(date +'%Y-%m-%d-%H.%M.%S')\nif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && -z $(system_profiler SPHardwareDataType | grep 'MacVM1,1') ]]; then\n # If the build is not INTERNAL, these are set to redacted later.\n CFBUILDUSER=$(whoami)\n CFBUILDHOST=$(hostname)\nelif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && !(-z $(system_profiler SPHardwareDataType | grep 'MacVM1,1')) ]]; then\n # Xcode Cloud built this, but the hostname string is atrocious. Replace with these.\n CFBUILDUSER=\"evaluna (App Store Connect)\"\n CFBUILDHOST=\"Xcode Cloud\"\nfi\n\nif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && -z $(gpg --list-secret-keys | grep 4A5B8C2A065654E24DE50FF03716ACDC524F1879) ]]; then\n echo \"Error: INTERNAL build type detected but Eva's GPG key was not found.\"\n echo \"Error: See 'Embed build information' in Build Phases for a solution\"\n # Be aware that I use the INTERNAL target to gate off any unstable/unready features. By removing this line, \n # you are acknowledging that you will not receive support for any issues encountered with Malachite until you \n # are running DEBUG or RELEASE. If you just want a DEBUG build, change the Build Configuration to Debug in \n # Product > Scheme > Edit Scheme...\n if [[ -z $(system_profiler SPHardwareDataType | grep 'MacVM1,1') ]]; then\n exit\n else\n # This line of code should keep internal TestFlight builds as INTERNAL, and external ones as DEBUG\n if [[ $(git --git-dir=\"${PROJECT_DIR}/.git\" rev-parse --abbrev-ref HEAD) != *\"dev\"* ]]; then\n CFBUILDTYPE=\"DEBUG\"\n fi\n fi\nfi\n\n\n\n# Print all of the variables in the build log. Useful for debugging\necho \"\"\necho \"CFBUILDHASH = ${CFBUILDHASH}\"\necho \"CFBUILDBRAN = ${CFBUILDBRAN}\"\necho \"CFBUILDDATE = ${CFBUILDDATE}\"\necho \"CFBUILDTYPE = ${CFBUILDTYPE}\"\nif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" ]]; then\n echo \"CFBUILDUSER = ${CFBUILDUSER}\"\n echo \"CFBUILDHOST = ${CFBUILDHOST}\"\nfi\necho \"INFOPLIST = ${INFOPLIST}\"\n\n# Make sure the key is present in the plist before trying to set it.\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildHash' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildHash string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildBran' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildBran string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildDate' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildDate string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildType' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildType string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildUser' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildUser string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildHost' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildHost string\" \"${INFOPLIST}\"; fi\n\n# Fallback values, either to fill in the gaps or prevent app crashes\nif [ -z \"$CFBUILDHASH\" ]; then CFBUILDHASH=undefined; fi\nif [ -z \"$CFBUILDBRAN\" ]; then CFBUILDBRAN=undefined; fi\nif [ -z \"$CFBUILDTYPE\" ]; then CFBUILDTYPE=debug; fi\nif [ -z \"$CFBUILDUSER\" ]; then CFBUILDUSER=redacted; fi\nif [ -z \"$CFBUILDHOST\" ]; then CFBUILDHOST=redacted; fi\n\n# Set the values in the Info.plist\n$PLISTBUDDY -c \"Set :CFBuildHash $CFBUILDHASH\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildBran $CFBUILDBRAN\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildDate $CFBUILDDATE\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildType $CFBUILDTYPE\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildUser $CFBUILDUSER\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildHost $CFBUILDHOST\" \"${INFOPLIST}\"\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -672,10 +715,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7837C1062E34C45B009396B0 /* MalachiteIntentUtils.swift in Sources */, - 7837C1072E34C45B009396B0 /* ControlCenterWidget.swift in Sources */, - 7837C1082E34C45B009396B0 /* LockScreenWidget.swift in Sources */, - 7837C1092E34C45B009396B0 /* MalachiteWidgetBundle.swift in Sources */, + 786DAE712E4F28B700BE3137 /* ControlCenterWidget.swift in Sources */, + 786DAE722E4F28B700BE3137 /* LockScreenWidget.swift in Sources */, + 786DAE732E4F28B700BE3137 /* MalachiteWidgetBundle.swift in Sources */, + 786DAE672E4F28B100BE3137 /* MalachiteIntentUtils.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -683,24 +726,28 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 78E67EE22B27E07300812A17 /* MalachiteUtils.swift in Sources */, - 785F08512B12D41100244EB4 /* MalachiteView.swift in Sources */, - 78C80BF62CDABE7800F6E8A7 /* MalachitePreferencesUtils.swift in Sources */, - 78E67EE02B27E07300812A17 /* MalachiteHapticUtils.swift in Sources */, - 788E74B32B13D8200024B586 /* MalachiteSettingsView.swift in Sources */, - 7806973A2B27F66E00B4942B /* MalachiteTooltipUtils.swift in Sources */, - 7859DE1A2B8308C000D1A998 /* MalachiteAboutView.swift in Sources */, - 7860DC002B897A3E00450BF8 /* MalachiteGameUtils.swift in Sources */, - 7897ECA62BA2B87100662EA0 /* MalachiteSettingsDetailView.swift in Sources */, + 786DAE552E4F28B100BE3137 /* MalachitePreferences.swift in Sources */, + 786DAE562E4F28B100BE3137 /* MalachitePreferencesUtils.swift in Sources */, + 786DAE572E4F28B100BE3137 /* MalachiteFunctionUtils.swift in Sources */, + 786DAE582E4F28B100BE3137 /* MalachiteGameUtils.swift in Sources */, + 786DAE592E4F28B100BE3137 /* MalachiteHapticUtils.swift in Sources */, + 786DAE5A2E4F28B100BE3137 /* MalachiteIntentUtils.swift in Sources */, + 786DAE5B2E4F28B100BE3137 /* MalachiteTooltipUtils.swift in Sources */, + 786DAE5C2E4F28B100BE3137 /* MalachiteUtils.swift in Sources */, + 786DAE5D2E4F28B100BE3137 /* MalachiteViewUtils.swift in Sources */, + 786DAE2D2E4F28A600BE3137 /* DeveloperView.swift in Sources */, + 786DAE2E2E4F28A600BE3137 /* DeveloperView+BuildInfo.swift in Sources */, + 786DAE2F2E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift in Sources */, + 786DAE302E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */, + 786DAE312E4F28A600BE3137 /* MalachiteAboutView.swift in Sources */, + 786DAE322E4F28A600BE3137 /* MalachiteCompatibilityView.swift in Sources */, + 786DAE332E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */, + 786DAE342E4F28A600BE3137 /* SettingsView+Help.swift in Sources */, + 786DAE352E4F28A600BE3137 /* SettingsView.swift in Sources */, + 786DAE362E4F28A600BE3137 /* MalachiteView.swift in Sources */, 785F084D2B12D41100244EB4 /* AppDelegate.swift in Sources */, - 788848B52CC74B4200B02E37 /* MalachiteCompatibilityView.swift in Sources */, - 78A9DE2F2CD57F51002C131D /* MalachiteIntentUtils.swift in Sources */, - 78C7EF9D2D1ECBD1001E332B /* MalachitePreferences.swift in Sources */, 787B1CAA2B8B095E000AFECC /* Malachite.docc in Sources */, - 78E67EE12B27E07300812A17 /* MalachiteViewUtils.swift in Sources */, - 78E5EFC02B15748400F15250 /* MalachitePhotoPreview.swift in Sources */, 785F084F2B12D41100244EB4 /* SceneDelegate.swift in Sources */, - 78E67EE52B27E0A200812A17 /* MalachiteFunctionUtils.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -708,24 +755,28 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 78D5707D2E3C7CC60025B9B3 /* MalachitePreferencesUtils.swift in Sources */, - 78D5707E2E3C7CC60025B9B3 /* MalachitePreferences.swift in Sources */, - 78D570762E3C7C5E0025B9B3 /* MalachiteView.swift in Sources */, - 78E8265B2E349FEC00A8DB50 /* MalachiteIntentUtils.swift in Sources */, - 780067E12CBDCEA0004BB595 /* ControlCenterWidget.swift in Sources */, - 78D570782E3C7CB80025B9B3 /* MalachiteTooltipUtils.swift in Sources */, - 78D570792E3C7CB80025B9B3 /* MalachiteViewUtils.swift in Sources */, - 78D5707A2E3C7CB80025B9B3 /* MalachiteHapticUtils.swift in Sources */, - 78D5707B2E3C7CB80025B9B3 /* MalachiteFunctionUtils.swift in Sources */, - 78D570802E3C7D050025B9B3 /* MalachiteSettingsView.swift in Sources */, - 78D570812E3C7D050025B9B3 /* MalachiteCompatibilityView.swift in Sources */, - 78D5707C2E3C7CB80025B9B3 /* MalachiteGameUtils.swift in Sources */, - 78917F632B99AE72005E10FA /* LockScreenWidget.swift in Sources */, - 78D570822E3C7D0B0025B9B3 /* MalachiteSettingsDetailView.swift in Sources */, - 78D570832E3C7D0B0025B9B3 /* MalachiteAboutView.swift in Sources */, - 78D570772E3C7CB20025B9B3 /* MalachiteUtils.swift in Sources */, - 78D5707F2E3C7CF50025B9B3 /* MalachitePhotoPreview.swift in Sources */, - 78917F612B99AE72005E10FA /* MalachiteWidgetBundle.swift in Sources */, + 786DAE5E2E4F28B100BE3137 /* MalachitePreferences.swift in Sources */, + 786DAE5F2E4F28B100BE3137 /* MalachitePreferencesUtils.swift in Sources */, + 786DAE602E4F28B100BE3137 /* MalachiteFunctionUtils.swift in Sources */, + 786DAE612E4F28B100BE3137 /* MalachiteGameUtils.swift in Sources */, + 786DAE622E4F28B100BE3137 /* MalachiteHapticUtils.swift in Sources */, + 786DAE632E4F28B100BE3137 /* MalachiteIntentUtils.swift in Sources */, + 786DAE642E4F28B100BE3137 /* MalachiteTooltipUtils.swift in Sources */, + 786DAE652E4F28B100BE3137 /* MalachiteUtils.swift in Sources */, + 786DAE662E4F28B100BE3137 /* MalachiteViewUtils.swift in Sources */, + 786DAE372E4F28A600BE3137 /* DeveloperView.swift in Sources */, + 786DAE382E4F28A600BE3137 /* DeveloperView+BuildInfo.swift in Sources */, + 786DAE392E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift in Sources */, + 786DAE6D2E4F28B700BE3137 /* ControlCenterWidget.swift in Sources */, + 786DAE6E2E4F28B700BE3137 /* LockScreenWidget.swift in Sources */, + 786DAE6F2E4F28B700BE3137 /* MalachiteWidgetBundle.swift in Sources */, + 786DAE3A2E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */, + 786DAE3B2E4F28A600BE3137 /* MalachiteAboutView.swift in Sources */, + 786DAE3C2E4F28A600BE3137 /* MalachiteCompatibilityView.swift in Sources */, + 786DAE3D2E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */, + 786DAE3E2E4F28A600BE3137 /* SettingsView+Help.swift in Sources */, + 786DAE3F2E4F28A600BE3137 /* SettingsView.swift in Sources */, + 786DAE402E4F28A600BE3137 /* MalachiteView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -733,22 +784,26 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 78A9DE2D2CD57F51002C131D /* MalachiteIntentUtils.swift in Sources */, - 78A9DD762CD55558002C131D /* MalachiteCaptureBundle.swift in Sources */, - 78A9DD7C2CD5562B002C131D /* MalachiteView.swift in Sources */, - 78A9DD7D2CD55631002C131D /* MalachiteAboutView.swift in Sources */, - 78A9DD7E2CD55631002C131D /* MalachiteUtils.swift in Sources */, - 78A9DD7F2CD55631002C131D /* MalachiteSettingsView.swift in Sources */, - 78C80BF52CDABE7800F6E8A7 /* MalachitePreferencesUtils.swift in Sources */, - 78A9DD812CD55631002C131D /* MalachiteFunctionUtils.swift in Sources */, - 78A9DD822CD55631002C131D /* MalachitePhotoPreview.swift in Sources */, - 78A9DD832CD55631002C131D /* MalachiteCompatibilityView.swift in Sources */, - 78A9DD842CD55631002C131D /* MalachiteViewUtils.swift in Sources */, - 78A9DD852CD55631002C131D /* MalachiteSettingsDetailView.swift in Sources */, - 78A9DD862CD55631002C131D /* MalachiteTooltipUtils.swift in Sources */, - 78A9DD872CD55631002C131D /* MalachiteGameUtils.swift in Sources */, - 78C7EF9E2D1ECBD1001E332B /* MalachitePreferences.swift in Sources */, - 78A9DD882CD55631002C131D /* MalachiteHapticUtils.swift in Sources */, + 786DAE232E4F28A600BE3137 /* DeveloperView.swift in Sources */, + 786DAE242E4F28A600BE3137 /* DeveloperView+BuildInfo.swift in Sources */, + 786DAE252E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift in Sources */, + 786DAE4C2E4F28B100BE3137 /* MalachitePreferences.swift in Sources */, + 786DAE4D2E4F28B100BE3137 /* MalachitePreferencesUtils.swift in Sources */, + 786DAE782E4F28B900BE3137 /* MalachiteCaptureBundle.swift in Sources */, + 786DAE4E2E4F28B100BE3137 /* MalachiteFunctionUtils.swift in Sources */, + 786DAE4F2E4F28B100BE3137 /* MalachiteGameUtils.swift in Sources */, + 786DAE502E4F28B100BE3137 /* MalachiteHapticUtils.swift in Sources */, + 786DAE512E4F28B100BE3137 /* MalachiteIntentUtils.swift in Sources */, + 786DAE522E4F28B100BE3137 /* MalachiteTooltipUtils.swift in Sources */, + 786DAE532E4F28B100BE3137 /* MalachiteUtils.swift in Sources */, + 786DAE542E4F28B100BE3137 /* MalachiteViewUtils.swift in Sources */, + 786DAE262E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */, + 786DAE272E4F28A600BE3137 /* MalachiteAboutView.swift in Sources */, + 786DAE282E4F28A600BE3137 /* MalachiteCompatibilityView.swift in Sources */, + 786DAE292E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */, + 786DAE2A2E4F28A600BE3137 /* SettingsView+Help.swift in Sources */, + 786DAE2B2E4F28A600BE3137 /* SettingsView.swift in Sources */, + 786DAE2C2E4F28A600BE3137 /* MalachiteView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -756,8 +811,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 78B72BDA2E345301002E2D4E /* ContentView.swift in Sources */, - 78B72BDB2E345301002E2D4E /* MalachiteWatchApp.swift in Sources */, + 786DAE7D2E4F28BF00BE3137 /* ContentView.swift in Sources */, + 786DAE7E2E4F28BF00BE3137 /* MalachiteWatchApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Malachite/Localizable.xcstrings b/Malachite/Localizable.xcstrings index 2910c47..f852c3d 100755 --- a/Malachite/Localizable.xcstrings +++ b/Malachite/Localizable.xcstrings @@ -5,6 +5,8 @@ }, "%@ - %@ - %@" : { + "comment" : "A label displaying the version type, version hash, and version date.", + "isCommentAutoGenerated" : true, "localizations" : { "en" : { "stringUnit" : { @@ -12,8 +14,7 @@ "value" : "%1$@ - %2$@ - %3$@" } } - }, - "shouldTranslate" : false + } }, "%@.%@.%@" : { "localizations" : { @@ -400,17 +401,6 @@ } } }, - "Built by %@ on %@" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Built by %1$@ on %2$@" - } - } - }, - "shouldTranslate" : false - }, "compatibility.note" : { "localizations" : { "en" : { @@ -652,54 +642,184 @@ } } }, - "flash.off" : { - "comment" : "Off", + "developer.detail.debug.erase.gamekit" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Off" + "value" : "DEBUG: This option destroys all GameKit related data for the current user. " } } } }, - "flash.on" : { - "comment" : "On", + "developer.detail.debug.erase.userdefaults" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "On" + "value" : "DEBUG: This option destroys all UserDefaults keys." } } } }, - "settings.detail.debug.erase.gamekit" : { + "developer.detail.debug.logging.userdefaults" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "DEBUG: This option destroys all GameKit related data for the current user. " + "value" : "DEBUG: This option dumps UserDefaults on launch." } } } }, - "settings.detail.debug.erase.userdefaults" : { + "developer.footer.debug" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "DEBUG: This option destroys all UserDefaults keys." + "value" : "DEBUG: These options are meant for debugging purposes. " } } } }, - "settings.detail.debug.logging.userdefaults" : { + "developer.header.debug" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "DEBUG: This option dumps UserDefaults on launch." + "value" : "Debug settings" + } + } + } + }, + "developer.header.info" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Build information" + } + } + } + }, + "developer.option.debug.breakapp" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Break the app" + } + } + } + }, + "developer.option.debug.erase.gamekit" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erase GameKit data" + } + } + } + }, + "developer.option.debug.erase.userdefaults" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erase preferences" + } + } + } + }, + "developer.option.debug.logging.userdefaults" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Log preferences to console" + } + } + } + }, + "developer.option.version_branch" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Branch" + } + } + } + }, + "developer.option.version_date" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Date" + } + } + } + }, + "developer.option.version_hash" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Commit hash" + } + } + } + }, + "developer.option.version_host" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hostname" + } + } + } + }, + "developer.option.version_type" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configuration" + } + } + } + }, + "developer.option.version_user" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Username" + } + } + } + }, + "flash.off" : { + "comment" : "Off", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Off" + } + } + } + }, + "flash.on" : { + "comment" : "On", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "On" } } } @@ -864,16 +984,6 @@ } } }, - "settings.footer.debug" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "DEBUG: These options are meant for debugging purposes. " - } - } - } - }, "settings.footer.photo" : { "extractionState" : "manual", "localizations" : { @@ -947,16 +1057,6 @@ } } }, - "settings.header.debug" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Debug settings" - } - } - } - }, "settings.header.photo" : { "localizations" : { "en" : { @@ -1007,46 +1107,6 @@ } } }, - "settings.option.debug.breakapp" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Break the app" - } - } - } - }, - "settings.option.debug.erase.gamekit" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Erase all GameKit data" - } - } - } - }, - "settings.option.debug.erase.userdefaults" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Erase preferences" - } - } - } - }, - "settings.option.debug.logging.userdefaults" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Log preferences to console" - } - } - } - }, "settings.option.photo.continuous" : { "localizations" : { "en" : { @@ -1497,6 +1557,16 @@ } } }, + "view.title.developer" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Developer" + } + } + } + }, "view.title.help" : { "localizations" : { "en" : { diff --git a/Malachite/Utilities/MalachiteUtils.swift b/Malachite/Utilities/MalachiteUtils.swift index 31a704a..1bbdbf4 100755 --- a/Malachite/Utilities/MalachiteUtils.swift +++ b/Malachite/Utilities/MalachiteUtils.swift @@ -45,6 +45,8 @@ public class MalachiteClassesObject : NSObject { public let versionFixer = "0" /// A variable that can be used to pull the git commit hash from the Info.plist public let versionHash = Bundle.main.object(forInfoDictionaryKey: "CFBuildHash") as? String ?? "undefined" + /// A variable that can be used to pull the git branch from the Info.plist + public let versionBranch = Bundle.main.object(forInfoDictionaryKey: "CFBuildBran") as? String ?? "undefined" /// A variable that can be used to pull the build time from the Info.plist public let versionDate = Bundle.main.object(forInfoDictionaryKey: "CFBuildDate") as? String ?? "undefined" /// A variable that can be used to identify the variant of the build from the Info.plist diff --git a/Malachite/Utilities/MalachiteViewUtils.swift b/Malachite/Utilities/MalachiteViewUtils.swift index 2aef750..b616363 100755 --- a/Malachite/Utilities/MalachiteViewUtils.swift +++ b/Malachite/Utilities/MalachiteViewUtils.swift @@ -282,39 +282,6 @@ struct MalachiteCompatibilityViewUtils: View { } } -struct MalachiteSettingsDetailViewUtils: View { - var title: Text - var subtitle: Text - let content: Content? - - init( - title: Text, - subtitle: Text, - @ViewBuilder content: () -> Content? - ) { - self.title = title - self.subtitle = subtitle - self.content = content() ?? nil - } - - var body: some View { - VStack { - HStack { - title - .bold() - Spacer() - - } - HStack { - subtitle - .font(.footnote) - Spacer() - } - } - } -} - - struct MalachiteNagivationViewUtils: View { let content: Content diff --git a/Malachite/Views/DeveloperView/DeveloperView+BuildInfo.swift b/Malachite/Views/DeveloperView/DeveloperView+BuildInfo.swift new file mode 100644 index 0000000..3c9c436 --- /dev/null +++ b/Malachite/Views/DeveloperView/DeveloperView+BuildInfo.swift @@ -0,0 +1,71 @@ +// +// DeveloperView+BuildInfo.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/15/25. +// + +import SwiftUI + +extension DeveloperView { + struct BuildInfo: View { + var utilities: MalachiteClassesObject + + init( + utilities: MalachiteClassesObject + ) { + self.utilities = utilities + } + + var body: some View { + Section(header: Text("developer.header.info")) { + if utilities.versionType == "DEBUG" || utilities.versionType == "INTERNAL" { + HStack { + Text("developer.option.version_type") + .frame(alignment: .leading) + Spacer() + Text(utilities.versionType) + .frame(alignment: .trailing) + } + HStack { + Text("developer.option.version_branch") + .frame(alignment: .leading) + Spacer() + Text(utilities.versionBranch) + .frame(alignment: .trailing) + } + HStack { + Text("developer.option.version_hash") + .frame(alignment: .leading) + Spacer() + Text(utilities.versionHash) + .frame(alignment: .trailing) + } + HStack { + Text("developer.option.version_date") + .frame(alignment: .leading) + Spacer() + Text(utilities.versionDate) + .frame(alignment: .trailing) + } + } + if utilities.versionType == "INTERNAL" { + HStack { + Text("developer.option.version_user") + .frame(alignment: .leading) + Spacer() + Text(utilities.versionUser) + .frame(alignment: .trailing) + } + HStack { + Text("developer.option.version_host") + .frame(alignment: .leading) + Spacer() + Text(utilities.versionHost) + .frame(alignment: .trailing) + } + } + } + } + } +} diff --git a/Malachite/Views/DeveloperView/DeveloperView+DeviceInfo.swift b/Malachite/Views/DeveloperView/DeveloperView+DeviceInfo.swift new file mode 100644 index 0000000..90d2e8b --- /dev/null +++ b/Malachite/Views/DeveloperView/DeveloperView+DeviceInfo.swift @@ -0,0 +1,24 @@ +// +// DeveloperView+DeviceInfo.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/15/25. +// + +import SwiftUI + +extension DeveloperView { + struct DeviceInfo: View { + var utilities: MalachiteClassesObject + + init( + utilities: MalachiteClassesObject + ) { + self.utilities = utilities + } + + var body: some View { + Text("") + } + } +} diff --git a/Malachite/Views/DeveloperView/DeveloperView+Settings.swift b/Malachite/Views/DeveloperView/DeveloperView+Settings.swift new file mode 100644 index 0000000..6cabab0 --- /dev/null +++ b/Malachite/Views/DeveloperView/DeveloperView+Settings.swift @@ -0,0 +1,96 @@ +// +// DeveloperView+Settings.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/15/25. +// + +import SwiftUI + +extension DeveloperView { + struct Settings: View { + var utilities: MalachiteClassesObject + + init( + utilities: MalachiteClassesObject + ) { + self.utilities = utilities + } + + @State private var debugLoggingUserDefaults = false + /// A State variable used for determining whether or not to literally break the app. + @State private var breakApp = false + + var body: some View { + Section { + MalachiteCellViewUtils( + icon: "text.redaction", + disabled: nil, + dangerous: false) + { + Toggle("developer.option.debug.logging.userdefaults", isOn: $debugLoggingUserDefaults) + } + MalachiteCellViewUtils( + icon: "iphone.slash", + disabled: nil, + dangerous: false) + { + Toggle("developer.option.debug.breakapp", isOn: $breakApp) + } + MalachiteCellViewUtils( + icon: "trash", + disabled: nil, + dangerous: true) + { + Button { + utilities.debugNSLog("[Preferences] Resetting all preferences, relaunch the app to complete!") + utilities.preferences.ext.resetPreferences() + } label: { + if #available(iOS 17.0, *) { + Text("developer.option.debug.erase.userdefaults") + .foregroundStyle(.red) + } else { + Text("developer.option.debug.erase.userdefaults") + .foregroundColor(.red) + } + } + } + + if utilities.versionType == "INTERNAL" { + MalachiteCellViewUtils( + icon: "trash", + disabled: nil, + dangerous: true) + { + Button { + utilities.internalNSLog("[Preferences] Resetting all GameKit data!") + utilities.games.achievements.resetAchievements() + } label: { + if #available(iOS 17.0, *) { + Text("developer.option.debug.erase.gamekit") + .foregroundStyle(.red) + } else { + Text("developer.option.debug.erase.gamekit") + .foregroundColor(.red) + } + } + } + } + } + .onAppear { + debugLoggingUserDefaults = utilities.preferences.debug.logging.preferences + breakApp = utilities.preferences.debug.breakApp + } + .onChange(of: debugLoggingUserDefaults) {_ in + utilities.preferences.debug.logging.preferences = debugLoggingUserDefaults + } + .onChange(of: breakApp) {_ in + utilities.preferences.debug.breakApp = breakApp + } + .onDisappear { + utilities.preferences.debug.logging.preferences = debugLoggingUserDefaults + utilities.preferences.debug.breakApp = breakApp + } + } + } +} diff --git a/Malachite/Views/DeveloperView/DeveloperView.swift b/Malachite/Views/DeveloperView/DeveloperView.swift new file mode 100644 index 0000000..92974b6 --- /dev/null +++ b/Malachite/Views/DeveloperView/DeveloperView.swift @@ -0,0 +1,43 @@ +// +// DeveloperView.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/15/25. +// + +import SwiftUI + +struct DeveloperView: View { + /// A State variable used for determining whether or not this view is being presented as a modal. + var dismissAction: (() -> Void) + /// A variable to hold the existing instance of ``MalachiteClassesObject``. + var utilities = MalachiteClassesObject() + + var body: some View { + Form { + Settings(utilities: utilities) + BuildInfo(utilities: utilities) + DeviceInfo(utilities: utilities) + } + .navigationTitle("view.title.developer") + .toolbar(content: { + ToolbarItemGroup(placement: .topBarTrailing) { + if #available(iOS 26.0, *) { + Button { + self.dismissAction() + } label: { + Image(systemName: "checkmark") + .tint(.primary) + } + .buttonStyle(.glassProminent) + } else { + Button { + self.dismissAction() + } label: { + Image(systemName: "checkmark.circle") + } + } + } + }) + } +} diff --git a/Malachite/Views/MalachiteAboutView.swift b/Malachite/Views/MalachiteAboutView.swift index dfd64e8..aa11b37 100755 --- a/Malachite/Views/MalachiteAboutView.swift +++ b/Malachite/Views/MalachiteAboutView.swift @@ -141,7 +141,7 @@ struct MalachiteAboutView: View { var aboutSectionFooter: some View { VStack { - if utilities.versionType == "DEBUG" || utilities.versionType == "INTERNAL" { + if utilities.versionType == "DEBUG" { HStack { Text("\(utilities.versionType) - \(utilities.versionHash) - \(utilities.versionDate)") .font(.footnote) @@ -149,14 +149,6 @@ struct MalachiteAboutView: View { Spacer() } } - if utilities.versionType == "INTERNAL" { - HStack { - Text("Built by \(utilities.versionUser) on \(utilities.versionHost)") - .font(.footnote) - .frame(alignment: .leading) - Spacer() - } - } } } diff --git a/Malachite/Views/MalachiteSettingsDetailView.swift b/Malachite/Views/MalachiteSettingsDetailView.swift deleted file mode 100755 index 9a70da2..0000000 --- a/Malachite/Views/MalachiteSettingsDetailView.swift +++ /dev/null @@ -1,172 +0,0 @@ -// -// MalachiteSettingsDetailView.swift -// Malachite -// -// Created by Eva Isabella Luna on 3/14/24. -// - -import SwiftUI - -struct MalachiteSettingsDetailView: View { - - /// A variable used to hold the function for dismissing with the toolbar item. - var dismissAction: (() -> Void) - - /// A variable used to hold the entire view. - var body: some View { - - Form { - aboutSection - previewSettingsSection - resolutionSettingsSection - photoSettingsSection - watermarkSettingsSection - uiSettingsSection - debugSettingsSection - } - .navigationTitle("view.title.help") - .toolbar(content: { - ToolbarItemGroup(placement: .topBarTrailing) { - if #available(iOS 26.0, *) { - Button { - self.dismissAction() - } label: { - Image(systemName: "checkmark") - .tint(.primary) - } - .buttonStyle(.glassProminent) - } else { - Button { - self.dismissAction() - } label: { - Image(systemName: "checkmark.circle") - } - } - } - }) - } - - /// A variable to hold the about section. - var aboutSection: some View { - Section { - MalachiteSettingsDetailViewUtils(title: Text("view.title.about"), subtitle: Text("view.detail.about")) {} - } - } - - /// A variable to hold the preview settings section. - var previewSettingsSection: some View { - Section(header: Text("settings.header.preview"), footer: Text("settings.footer.preview")) { - MalachiteSettingsDetailViewUtils(title: Text("settings.option.preview.aspect_ratio"), subtitle: Text("settings.detail.preview.aspect_ratio")) {} - MalachiteSettingsDetailViewUtils(title: Text("settings.option.preview.sbtlz"), subtitle: Text("settings.detail.preview.sbtlz")) {} - MalachiteSettingsDetailViewUtils(title: Text("settings.option.preview.zoom_maximum"), subtitle: Text("settings.detail.preview.zoom_maximum")) {} - } - } - - /// A variable to hold the image resolution section. - var resolutionSettingsSection: some View { - Section(header: Text("settings.header.resolution"), footer: Text("settings.footer.resolution")) { - MalachiteSettingsDetailViewUtils(title: Text("settings.option.resolution.ultrawide"), subtitle: Text("settings.detail.resolution.ultrawide")) {} - MalachiteSettingsDetailViewUtils(title: Text("settings.option.resolution.wide"), subtitle: Text("settings.detail.resolution.wide")) {} - MalachiteSettingsDetailViewUtils(title: Text("settings.option.resolution.telephoto"), subtitle: Text("settings.detail.resolution.telephoto")) {} - } - } - - /// A variable to hold the photo settings section. - var photoSettingsSection: some View { - Section(header: Text("settings.header.photo"), footer: Text("settings.footer.photo")) { - MalachiteSettingsDetailViewUtils(title: Text("settings.option.photo.fileformat"), subtitle: Text("settings.detail.photo.fileformat")) {} - MalachiteSettingsDetailViewUtils(title: Text("settings.option.photo.hdr"), subtitle: Text("settings.detail.photo.hdr")) {} - MalachiteSettingsDetailViewUtils(title: Text("settings.option.photo.continuous"), subtitle: Text("settings.detail.photo.continuous")) {} - } - } - - var uiSettingsSection: some View { - Section(header: Text("settings.header.ui"), footer: Text("settings.footer.ui")) { - MalachiteSettingsDetailViewUtils(title: Text("settings.option.ui.tapgesture"), subtitle: Text("settings.detail.ui.tapgesture")) {} - MalachiteSettingsDetailViewUtils(title: Text("settings.option.ui.hiddengestures"), subtitle: Text("settings.detail.ui.hiddengestures")) {} - MalachiteSettingsDetailViewUtils(title: Text("settings.option.ui.idletimer"), subtitle: Text("settings.detail.ui.idletimer")) {} - MalachiteSettingsDetailViewUtils(title: Text("settings.option.ui.haptics"), subtitle: Text("settings.detail.ui.haptics")) {} - MalachiteSettingsDetailViewUtils(title: Text("settings.option.ui.hiddenonlaunch"), subtitle: Text("settings.detail.ui.hiddenonlaunch")) {} - } - } - - /// A variable to hold the watermark settings section. - var watermarkSettingsSection: some View { - Section(header: Text("settings.header.watermark"), footer: Text("settings.footer.watermark")) { - MalachiteSettingsDetailViewUtils(title: Text("settings.option.watermark.enable"), subtitle: Text("settings.detail.watermark.enable")) {} - MalachiteSettingsDetailViewUtils(title: Text("settings.option.watermark.text"), subtitle: Text("settings.detail.watermark.text")) {} - } - } - - /// A variable to hold the debug settings section. Only available with debug builds. - var debugSettingsSection: some View { - Section(header: Text("settings.header.debug"), footer: Text("settings.footer.debug")) { - MalachiteSettingsDetailViewUtils(title: Text("settings.option.debug.logging.userdefaults"), subtitle: Text("settings.detail.debug.logging.userdefaults")) {} - MalachiteSettingsDetailViewUtils(title: Text("settings.option.debug.erase.userdefaults"), subtitle: Text("settings.detail.debug.erase.userdefaults")) {} - MalachiteSettingsDetailViewUtils(title: Text("settings.option.debug.erase.gamekit"), subtitle: Text("settings.detail.debug.erase.gamekit")) {} - } - } -} - -struct MalachiteSettingsDetailUtils: View { - var icon: String - var title: String - var subtitle: String - var dangerous: Bool - - init( - icon: String, - title: String, - subtitle: String?, - dangerous: Bool - ) { - self.icon = icon - self.title = title - self.subtitle = subtitle ?? "" - self.dangerous = dangerous - } - - var body: some View { - HStack() { - Image(systemName: icon) - .frame(maxWidth: 30) - .foregroundStyle(dangerous ? .red : Color.accentColor) - .symbolRenderingMode(.hierarchical) - VStack { - HStack { - if dangerous { - if #available(iOS 17.0, *) { - Text(LocalizedStringKey(title)) - .foregroundStyle(.red) - } else { - Text(LocalizedStringKey(title)) - .foregroundColor(.red) - } - } else { - Text(LocalizedStringKey(title)) - } - Spacer() - } - if subtitle != "" { - HStack { - if dangerous { - if #available(iOS 17.0, *) { - Text(LocalizedStringKey(subtitle)) - .foregroundStyle(.red) - .font(.footnote) - } else { - Text(LocalizedStringKey(subtitle)) - .foregroundColor(.red) - .font(.footnote) - } - } else { - Text(LocalizedStringKey(subtitle)) - .font(.footnote) - } - Spacer() - } - } - } - } - } -} diff --git a/Malachite/Views/MalachiteView.swift b/Malachite/Views/MalachiteView.swift index f2eb692..fa2adf6 100755 --- a/Malachite/Views/MalachiteView.swift +++ b/Malachite/Views/MalachiteView.swift @@ -693,7 +693,7 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A self.present(alert, animated: true, completion: nil) return #elseif MAIN_APP - var aboutView = MalachiteSettingsView(dismissAction: {self.dismiss( animated: true, completion: nil )}) + var aboutView = SettingsView(dismissAction: {self.dismiss( animated: true, completion: nil )}) aboutView.utilities = self.utilities let hostingController = UIHostingController(rootView: aboutView) hostingController.modalPresentationStyle = UIModalPresentationStyle.popover diff --git a/Malachite/Views/SettingsView/SettingsView+Help.swift b/Malachite/Views/SettingsView/SettingsView+Help.swift new file mode 100755 index 0000000..e9701a7 --- /dev/null +++ b/Malachite/Views/SettingsView/SettingsView+Help.swift @@ -0,0 +1,143 @@ +// +// SettingsView+Help.swift +// Malachite +// +// Created by Eva Isabella Luna on 3/14/24. +// + +import SwiftUI + +extension SettingsView { + struct Help: View { + + /// A variable used to hold the function for dismissing with the toolbar item. + var dismissAction: (() -> Void) + + /// A variable used to hold the entire view. + var body: some View { + + Form { + aboutSection + previewSettingsSection + resolutionSettingsSection + photoSettingsSection + watermarkSettingsSection + uiSettingsSection + debugSettingsSection + } + .navigationTitle("view.title.help") + .toolbar(content: { + ToolbarItemGroup(placement: .topBarTrailing) { + if #available(iOS 26.0, *) { + Button { + self.dismissAction() + } label: { + Image(systemName: "checkmark") + .tint(.primary) + } + .buttonStyle(.glassProminent) + } else { + Button { + self.dismissAction() + } label: { + Image(systemName: "checkmark.circle") + } + } + } + }) + } + + /// A variable to hold the about section. + var aboutSection: some View { + Section { + Builder(title: Text("view.title.about"), subtitle: Text("view.detail.about")) {} + } + } + + /// A variable to hold the preview settings section. + var previewSettingsSection: some View { + Section(header: Text("settings.header.preview"), footer: Text("settings.footer.preview")) { + Builder(title: Text("settings.option.preview.aspect_ratio"), subtitle: Text("settings.detail.preview.aspect_ratio")) {} + Builder(title: Text("settings.option.preview.sbtlz"), subtitle: Text("settings.detail.preview.sbtlz")) {} + Builder(title: Text("settings.option.preview.zoom_maximum"), subtitle: Text("settings.detail.preview.zoom_maximum")) {} + } + } + + /// A variable to hold the image resolution section. + var resolutionSettingsSection: some View { + Section(header: Text("settings.header.resolution"), footer: Text("settings.footer.resolution")) { + Builder(title: Text("settings.option.resolution.ultrawide"), subtitle: Text("settings.detail.resolution.ultrawide")) {} + Builder(title: Text("settings.option.resolution.wide"), subtitle: Text("settings.detail.resolution.wide")) {} + Builder(title: Text("settings.option.resolution.telephoto"), subtitle: Text("settings.detail.resolution.telephoto")) {} + } + } + + /// A variable to hold the photo settings section. + var photoSettingsSection: some View { + Section(header: Text("settings.header.photo"), footer: Text("settings.footer.photo")) { + Builder(title: Text("settings.option.photo.fileformat"), subtitle: Text("settings.detail.photo.fileformat")) {} + Builder(title: Text("settings.option.photo.hdr"), subtitle: Text("settings.detail.photo.hdr")) {} + Builder(title: Text("settings.option.photo.continuous"), subtitle: Text("settings.detail.photo.continuous")) {} + } + } + + var uiSettingsSection: some View { + Section(header: Text("settings.header.ui"), footer: Text("settings.footer.ui")) { + Builder(title: Text("settings.option.ui.tapgesture"), subtitle: Text("settings.detail.ui.tapgesture")) {} + Builder(title: Text("settings.option.ui.hiddengestures"), subtitle: Text("settings.detail.ui.hiddengestures")) {} + Builder(title: Text("settings.option.ui.idletimer"), subtitle: Text("settings.detail.ui.idletimer")) {} + Builder(title: Text("settings.option.ui.haptics"), subtitle: Text("settings.detail.ui.haptics")) {} + Builder(title: Text("settings.option.ui.hiddenonlaunch"), subtitle: Text("settings.detail.ui.hiddenonlaunch")) {} + } + } + + /// A variable to hold the watermark settings section. + var watermarkSettingsSection: some View { + Section(header: Text("settings.header.watermark"), footer: Text("settings.footer.watermark")) { + Builder(title: Text("settings.option.watermark.enable"), subtitle: Text("settings.detail.watermark.enable")) {} + Builder(title: Text("settings.option.watermark.text"), subtitle: Text("settings.detail.watermark.text")) {} + } + } + + /// A variable to hold the debug settings section. Only available with debug builds. + var debugSettingsSection: some View { + Section(header: Text("developer.header.debug"), footer: Text("developer.footer.debug")) { + Builder(title: Text("developer.option.debug.logging.userdefaults"), subtitle: Text("developer.detail.debug.logging.userdefaults")) {} + Builder(title: Text("developer.option.debug.erase.userdefaults"), subtitle: Text("developer.detail.debug.erase.userdefaults")) {} + Builder(title: Text("developer.option.debug.erase.gamekit"), subtitle: Text("developer.detail.debug.erase.gamekit")) {} + } + } + + struct Builder: View { + var title: Text + var subtitle: Text + let content: Content? + + init( + title: Text, + subtitle: Text, + @ViewBuilder content: () -> Content? + ) { + self.title = title + self.subtitle = subtitle + self.content = content() ?? nil + } + + var body: some View { + VStack { + HStack { + title + .bold() + Spacer() + + } + HStack { + subtitle + .font(.footnote) + Spacer() + } + } + } + } + } +} diff --git a/Malachite/Views/MalachiteSettingsView.swift b/Malachite/Views/SettingsView/SettingsView.swift similarity index 91% rename from Malachite/Views/MalachiteSettingsView.swift rename to Malachite/Views/SettingsView/SettingsView.swift index b2d2095..6f9cf7a 100755 --- a/Malachite/Views/MalachiteSettingsView.swift +++ b/Malachite/Views/SettingsView/SettingsView.swift @@ -1,5 +1,5 @@ // -// MalachiteSettingsView.swift +// SettingsView.swift // Malachite // // Created by Eva Isabella Luna on 11/26/23. @@ -7,7 +7,7 @@ import SwiftUI -struct MalachiteSettingsView: View { +struct SettingsView: View { /// A State variable used for determining whether or not watermarking is enabled. @State private var watermarkSwitch = false /// A State variable used for determining the current watermark string. @@ -85,8 +85,8 @@ struct MalachiteSettingsView: View { photoSettingsSection watermarkSettingsSection uiSettingsSection - if utilities.versionType == "DEBUG" || utilities.versionType == "INTERNAL" { - debugSettingsSection + if utilities.versionType == "DEBUG" { + DeveloperView.Settings(utilities: utilities) } } .onAppear { onAppear() } @@ -94,7 +94,7 @@ struct MalachiteSettingsView: View { .navigationTitle("view.title.settings") .toolbar(content: { ToolbarItemGroup(placement: .topBarLeading) { - NavigationLink(destination: MalachiteSettingsDetailView(dismissAction: dismissAction)) { + NavigationLink(destination: Help(dismissAction: dismissAction)) { if #available(iOS 26.0, *) { Image(systemName: "questionmark.circle") } else { @@ -144,6 +144,15 @@ struct MalachiteSettingsView: View { Text("view.title.compatibility") } } + MalachiteCellViewUtils( + icon: "wrench.and.screwdriver", + disabled: nil, + dangerous: false) + { + NavigationLink(destination: DeveloperView(dismissAction: dismissAction)) { + Text("view.title.developer") + } + } } } } @@ -514,69 +523,6 @@ struct MalachiteSettingsView: View { } } - /// A variable to hold the debug settings section. Only available with debug builds. - // TODO: Update to the new preferences system - var debugSettingsSection: some View { - Section(header: Text("settings.header.debug")) { - MalachiteCellViewUtils( - icon: "text.redaction", - disabled: nil, - dangerous: false) - { - Toggle("settings.option.debug.logging.userdefaults", isOn: $debugLoggingUserDefaults) - } - MalachiteCellViewUtils( - icon: "iphone.slash", - disabled: nil, - dangerous: false) - { - Toggle("settings.option.debug.breakapp", isOn: $breakApp) - } - MalachiteCellViewUtils( - icon: "trash", - disabled: nil, - dangerous: true) - { - Button { - utilities.debugNSLog("[Preferences] Resetting all preferences, relaunch the app to complete!") - utilities.preferences.ext.resetPreferences() - } label: { - if #available(iOS 17.0, *) { - Text("settings.option.debug.erase.userdefaults") - .foregroundStyle(.red) - } else { - Text("settings.option.debug.erase.userdefaults") - .foregroundColor(.red) - } - } - } - - if utilities.versionType == "INTERNAL" { - MalachiteCellViewUtils( - icon: "trash", - disabled: nil, - dangerous: true) - { - Button { - utilities.internalNSLog("[Preferences] Resetting all GameKit data!") - utilities.games.achievements.resetAchievements() - } label: { - if #available(iOS 17.0, *) { - Text("settings.option.debug.erase.gamekit") - .foregroundStyle(.red) - } else { - Text("settings.option.debug.erase.gamekit") - .foregroundColor(.red) - } - } - } - } - } - .onChange(of: debugLoggingUserDefaults) {_ in - utilities.preferences.debug.logging.preferences = debugLoggingUserDefaults - } - } - func onAppear() { supportsHDR = utilities.function.supportsHDR supportsHEIC = utilities.function.supportsHEIC() @@ -757,3 +703,4 @@ struct MalachiteSettingsView: View { utilities.preferences.debug.breakApp = breakApp } } + From 29f3b7144b020e9944a6030c8287bad2798f80e1 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Fri, 15 Aug 2025 03:07:54 -0600 Subject: [PATCH 03/66] Fixup some more localization stuff --- Malachite/Localizable.xcstrings | 5 ++--- Malachite/Views/MalachiteView.swift | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Malachite/Localizable.xcstrings b/Malachite/Localizable.xcstrings index f852c3d..801efc4 100755 --- a/Malachite/Localizable.xcstrings +++ b/Malachite/Localizable.xcstrings @@ -5,8 +5,6 @@ }, "%@ - %@ - %@" : { - "comment" : "A label displaying the version type, version hash, and version date.", - "isCommentAutoGenerated" : true, "localizations" : { "en" : { "stringUnit" : { @@ -14,7 +12,8 @@ "value" : "%1$@ - %2$@ - %3$@" } } - } + }, + "shouldTranslate" : false }, "%@.%@.%@" : { "localizations" : { diff --git a/Malachite/Views/MalachiteView.swift b/Malachite/Views/MalachiteView.swift index fa2adf6..cfc3379 100755 --- a/Malachite/Views/MalachiteView.swift +++ b/Malachite/Views/MalachiteView.swift @@ -788,8 +788,8 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A let flashSwitcher = AVCaptureIndexPicker("Flash", symbolName: "bolt.fill", numberOfIndexes: 2, localizedTitleTransform: { index in switch index { - case 0: return NSLocalizedString("flash.off", comment: "Off") - case 1: return NSLocalizedString("flash.on", comment: "On") + case 0: return NSLocalizedString("flash.off", comment: "") + case 1: return NSLocalizedString("flash.on", comment: "") default: return "" } }) From 56ebe2914f6ae419b382db3501f614fce82b105c Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Sat, 16 Aug 2025 17:51:27 -0600 Subject: [PATCH 04/66] Massive changes - Refactor SettingsView into multiple files - Refactor SettingsView+Help into QuickHelpView/ --- Malachite.xcodeproj/project.pbxproj | 186 ++++- Malachite/Localizable.xcstrings | 30 + Malachite/Utilities/MalachiteViewUtils.swift | 37 + .../DeveloperView+Internal.swift | 59 ++ .../DeveloperView+Settings.swift | 8 +- .../Views/DeveloperView/DeveloperView.swift | 17 +- Malachite/Views/MalachiteAboutView.swift | 16 +- .../Views/MalachiteCompatibilityView.swift | 16 +- .../QuickHelpView/QuickHelpView+About.swift | 27 + .../QuickHelpView/QuickHelpView+Photo.swift | 21 + .../QuickHelpView/QuickHelpView+Preview.swift | 21 + .../QuickHelpView+Resolution.swift | 21 + .../QuickHelpView/QuickHelpView+UI.swift | 23 + .../QuickHelpView+Watermarking.swift | 20 + .../Views/QuickHelpView/QuickHelpView.swift | 87 +++ .../SettingsView/SettingsView+About.swift | 59 ++ .../SettingsView/SettingsView+Help.swift | 143 ---- .../SettingsView/SettingsView+Photo.swift | 150 ++++ .../SettingsView/SettingsView+Preview.swift | 123 ++++ .../SettingsView+Resolution.swift | 185 +++++ .../Views/SettingsView/SettingsView+UI.swift | 190 +++++ .../SettingsView+Watermarking.swift | 71 ++ .../Views/SettingsView/SettingsView.swift | 684 +----------------- 23 files changed, 1315 insertions(+), 879 deletions(-) create mode 100644 Malachite/Views/DeveloperView/DeveloperView+Internal.swift create mode 100644 Malachite/Views/QuickHelpView/QuickHelpView+About.swift create mode 100644 Malachite/Views/QuickHelpView/QuickHelpView+Photo.swift create mode 100644 Malachite/Views/QuickHelpView/QuickHelpView+Preview.swift create mode 100644 Malachite/Views/QuickHelpView/QuickHelpView+Resolution.swift create mode 100644 Malachite/Views/QuickHelpView/QuickHelpView+UI.swift create mode 100644 Malachite/Views/QuickHelpView/QuickHelpView+Watermarking.swift create mode 100755 Malachite/Views/QuickHelpView/QuickHelpView.swift create mode 100644 Malachite/Views/SettingsView/SettingsView+About.swift delete mode 100755 Malachite/Views/SettingsView/SettingsView+Help.swift create mode 100644 Malachite/Views/SettingsView/SettingsView+Photo.swift create mode 100644 Malachite/Views/SettingsView/SettingsView+Preview.swift create mode 100644 Malachite/Views/SettingsView/SettingsView+Resolution.swift create mode 100644 Malachite/Views/SettingsView/SettingsView+UI.swift create mode 100644 Malachite/Views/SettingsView/SettingsView+Watermarking.swift diff --git a/Malachite.xcodeproj/project.pbxproj b/Malachite.xcodeproj/project.pbxproj index 6436d10..3666afe 100755 --- a/Malachite.xcodeproj/project.pbxproj +++ b/Malachite.xcodeproj/project.pbxproj @@ -21,7 +21,7 @@ 786DAE272E4F28A600BE3137 /* MalachiteAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1C2E4F28A600BE3137 /* MalachiteAboutView.swift */; }; 786DAE282E4F28A600BE3137 /* MalachiteCompatibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1D2E4F28A600BE3137 /* MalachiteCompatibilityView.swift */; }; 786DAE292E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1E2E4F28A600BE3137 /* MalachitePhotoPreview.swift */; }; - 786DAE2A2E4F28A600BE3137 /* SettingsView+Help.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1F2E4F28A600BE3137 /* SettingsView+Help.swift */; }; + 786DAE2A2E4F28A600BE3137 /* QuickHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1F2E4F28A600BE3137 /* QuickHelpView.swift */; }; 786DAE2B2E4F28A600BE3137 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE202E4F28A600BE3137 /* SettingsView.swift */; }; 786DAE2C2E4F28A600BE3137 /* MalachiteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE212E4F28A600BE3137 /* MalachiteView.swift */; }; 786DAE2D2E4F28A600BE3137 /* DeveloperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE172E4F28A600BE3137 /* DeveloperView.swift */; }; @@ -31,7 +31,7 @@ 786DAE312E4F28A600BE3137 /* MalachiteAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1C2E4F28A600BE3137 /* MalachiteAboutView.swift */; }; 786DAE322E4F28A600BE3137 /* MalachiteCompatibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1D2E4F28A600BE3137 /* MalachiteCompatibilityView.swift */; }; 786DAE332E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1E2E4F28A600BE3137 /* MalachitePhotoPreview.swift */; }; - 786DAE342E4F28A600BE3137 /* SettingsView+Help.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1F2E4F28A600BE3137 /* SettingsView+Help.swift */; }; + 786DAE342E4F28A600BE3137 /* QuickHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1F2E4F28A600BE3137 /* QuickHelpView.swift */; }; 786DAE352E4F28A600BE3137 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE202E4F28A600BE3137 /* SettingsView.swift */; }; 786DAE362E4F28A600BE3137 /* MalachiteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE212E4F28A600BE3137 /* MalachiteView.swift */; }; 786DAE372E4F28A600BE3137 /* DeveloperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE172E4F28A600BE3137 /* DeveloperView.swift */; }; @@ -41,7 +41,7 @@ 786DAE3B2E4F28A600BE3137 /* MalachiteAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1C2E4F28A600BE3137 /* MalachiteAboutView.swift */; }; 786DAE3C2E4F28A600BE3137 /* MalachiteCompatibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1D2E4F28A600BE3137 /* MalachiteCompatibilityView.swift */; }; 786DAE3D2E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1E2E4F28A600BE3137 /* MalachitePhotoPreview.swift */; }; - 786DAE3E2E4F28A600BE3137 /* SettingsView+Help.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1F2E4F28A600BE3137 /* SettingsView+Help.swift */; }; + 786DAE3E2E4F28A600BE3137 /* QuickHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1F2E4F28A600BE3137 /* QuickHelpView.swift */; }; 786DAE3F2E4F28A600BE3137 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE202E4F28A600BE3137 /* SettingsView.swift */; }; 786DAE402E4F28A600BE3137 /* MalachiteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE212E4F28A600BE3137 /* MalachiteView.swift */; }; 786DAE4C2E4F28B100BE3137 /* MalachitePreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE412E4F28B100BE3137 /* MalachitePreferences.swift */; }; @@ -82,6 +82,45 @@ 786DAE7D2E4F28BF00BE3137 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE792E4F28BF00BE3137 /* ContentView.swift */; }; 786DAE7E2E4F28BF00BE3137 /* MalachiteWatchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE7B2E4F28BF00BE3137 /* MalachiteWatchApp.swift */; }; 787B1CAA2B8B095E000AFECC /* Malachite.docc in Sources */ = {isa = PBXBuildFile; fileRef = 787B1CA92B8B095E000AFECC /* Malachite.docc */; }; + 788262602E513080000085AC /* SettingsView+About.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882625F2E51307B000085AC /* SettingsView+About.swift */; }; + 788262612E513080000085AC /* SettingsView+About.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882625F2E51307B000085AC /* SettingsView+About.swift */; }; + 788262622E513080000085AC /* SettingsView+About.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882625F2E51307B000085AC /* SettingsView+About.swift */; }; + 788262642E51309C000085AC /* SettingsView+Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262632E513094000085AC /* SettingsView+Preview.swift */; }; + 788262652E51309C000085AC /* SettingsView+Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262632E513094000085AC /* SettingsView+Preview.swift */; }; + 788262662E51309C000085AC /* SettingsView+Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262632E513094000085AC /* SettingsView+Preview.swift */; }; + 7882626C2E5130C1000085AC /* SettingsView+Resolution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882626B2E5130BB000085AC /* SettingsView+Resolution.swift */; }; + 7882626D2E5130C1000085AC /* SettingsView+Resolution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882626B2E5130BB000085AC /* SettingsView+Resolution.swift */; }; + 7882626E2E5130C1000085AC /* SettingsView+Resolution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882626B2E5130BB000085AC /* SettingsView+Resolution.swift */; }; + 788262702E5130CA000085AC /* SettingsView+Photo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882626F2E5130C6000085AC /* SettingsView+Photo.swift */; }; + 788262712E5130CA000085AC /* SettingsView+Photo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882626F2E5130C6000085AC /* SettingsView+Photo.swift */; }; + 788262722E5130CA000085AC /* SettingsView+Photo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882626F2E5130C6000085AC /* SettingsView+Photo.swift */; }; + 788262742E5130D4000085AC /* SettingsView+Watermarking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262732E5130CF000085AC /* SettingsView+Watermarking.swift */; }; + 788262752E5130D4000085AC /* SettingsView+Watermarking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262732E5130CF000085AC /* SettingsView+Watermarking.swift */; }; + 788262762E5130D4000085AC /* SettingsView+Watermarking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262732E5130CF000085AC /* SettingsView+Watermarking.swift */; }; + 788262782E5130DB000085AC /* SettingsView+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262772E5130D8000085AC /* SettingsView+UI.swift */; }; + 788262792E5130DB000085AC /* SettingsView+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262772E5130D8000085AC /* SettingsView+UI.swift */; }; + 7882627A2E5130DB000085AC /* SettingsView+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262772E5130D8000085AC /* SettingsView+UI.swift */; }; + 7882627C2E514750000085AC /* DeveloperView+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882627B2E514749000085AC /* DeveloperView+Internal.swift */; }; + 7882627D2E514750000085AC /* DeveloperView+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882627B2E514749000085AC /* DeveloperView+Internal.swift */; }; + 7882627E2E514750000085AC /* DeveloperView+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882627B2E514749000085AC /* DeveloperView+Internal.swift */; }; + 788262852E514F12000085AC /* QuickHelpView+About.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262842E514F0C000085AC /* QuickHelpView+About.swift */; }; + 788262862E514F12000085AC /* QuickHelpView+About.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262842E514F0C000085AC /* QuickHelpView+About.swift */; }; + 788262872E514F12000085AC /* QuickHelpView+About.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262842E514F0C000085AC /* QuickHelpView+About.swift */; }; + 788262892E514F18000085AC /* QuickHelpView+Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262882E514F14000085AC /* QuickHelpView+Preview.swift */; }; + 7882628A2E514F18000085AC /* QuickHelpView+Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262882E514F14000085AC /* QuickHelpView+Preview.swift */; }; + 7882628B2E514F18000085AC /* QuickHelpView+Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262882E514F14000085AC /* QuickHelpView+Preview.swift */; }; + 7882628D2E514F25000085AC /* QuickHelpView+Resolution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882628C2E514F1F000085AC /* QuickHelpView+Resolution.swift */; }; + 7882628E2E514F25000085AC /* QuickHelpView+Resolution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882628C2E514F1F000085AC /* QuickHelpView+Resolution.swift */; }; + 7882628F2E514F25000085AC /* QuickHelpView+Resolution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882628C2E514F1F000085AC /* QuickHelpView+Resolution.swift */; }; + 788262912E514F2D000085AC /* QuickHelpView+Photo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262902E514F28000085AC /* QuickHelpView+Photo.swift */; }; + 788262922E514F2D000085AC /* QuickHelpView+Photo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262902E514F28000085AC /* QuickHelpView+Photo.swift */; }; + 788262932E514F2D000085AC /* QuickHelpView+Photo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262902E514F28000085AC /* QuickHelpView+Photo.swift */; }; + 788262952E514F38000085AC /* QuickHelpView+Watermarking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262942E514F31000085AC /* QuickHelpView+Watermarking.swift */; }; + 788262962E514F38000085AC /* QuickHelpView+Watermarking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262942E514F31000085AC /* QuickHelpView+Watermarking.swift */; }; + 788262972E514F38000085AC /* QuickHelpView+Watermarking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262942E514F31000085AC /* QuickHelpView+Watermarking.swift */; }; + 788262992E514F3F000085AC /* QuickHelpView+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262982E514F3B000085AC /* QuickHelpView+UI.swift */; }; + 7882629A2E514F3F000085AC /* QuickHelpView+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262982E514F3B000085AC /* QuickHelpView+UI.swift */; }; + 7882629B2E514F3F000085AC /* QuickHelpView+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262982E514F3B000085AC /* QuickHelpView+UI.swift */; }; 78917F5C2B99AE72005E10FA /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78917F5B2B99AE72005E10FA /* WidgetKit.framework */; }; 78917F5E2B99AE72005E10FA /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78917F5D2B99AE72005E10FA /* SwiftUI.framework */; }; 78917F692B99AE73005E10FA /* WidgetBundle.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 78917F592B99AE72005E10FA /* WidgetBundle.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -196,7 +235,7 @@ 786DAE1C2E4F28A600BE3137 /* MalachiteAboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteAboutView.swift; sourceTree = ""; }; 786DAE1D2E4F28A600BE3137 /* MalachiteCompatibilityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteCompatibilityView.swift; sourceTree = ""; }; 786DAE1E2E4F28A600BE3137 /* MalachitePhotoPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachitePhotoPreview.swift; sourceTree = ""; }; - 786DAE1F2E4F28A600BE3137 /* SettingsView+Help.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsView+Help.swift"; sourceTree = ""; }; + 786DAE1F2E4F28A600BE3137 /* QuickHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickHelpView.swift; sourceTree = ""; }; 786DAE202E4F28A600BE3137 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 786DAE212E4F28A600BE3137 /* MalachiteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteView.swift; sourceTree = ""; }; 786DAE412E4F28B100BE3137 /* MalachitePreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachitePreferences.swift; sourceTree = ""; }; @@ -219,6 +258,19 @@ 786DAE7B2E4F28BF00BE3137 /* MalachiteWatchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteWatchApp.swift; sourceTree = ""; }; 787B1CA92B8B095E000AFECC /* Malachite.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = Malachite.docc; sourceTree = ""; }; 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = Malachite/Localizable.xcstrings; sourceTree = SOURCE_ROOT; }; + 7882625F2E51307B000085AC /* SettingsView+About.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsView+About.swift"; sourceTree = ""; }; + 788262632E513094000085AC /* SettingsView+Preview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsView+Preview.swift"; sourceTree = ""; }; + 7882626B2E5130BB000085AC /* SettingsView+Resolution.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsView+Resolution.swift"; sourceTree = ""; }; + 7882626F2E5130C6000085AC /* SettingsView+Photo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsView+Photo.swift"; sourceTree = ""; }; + 788262732E5130CF000085AC /* SettingsView+Watermarking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsView+Watermarking.swift"; sourceTree = ""; }; + 788262772E5130D8000085AC /* SettingsView+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsView+UI.swift"; sourceTree = ""; }; + 7882627B2E514749000085AC /* DeveloperView+Internal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DeveloperView+Internal.swift"; sourceTree = ""; }; + 788262842E514F0C000085AC /* QuickHelpView+About.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QuickHelpView+About.swift"; sourceTree = ""; }; + 788262882E514F14000085AC /* QuickHelpView+Preview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QuickHelpView+Preview.swift"; sourceTree = ""; }; + 7882628C2E514F1F000085AC /* QuickHelpView+Resolution.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QuickHelpView+Resolution.swift"; sourceTree = ""; }; + 788262902E514F28000085AC /* QuickHelpView+Photo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QuickHelpView+Photo.swift"; sourceTree = ""; }; + 788262942E514F31000085AC /* QuickHelpView+Watermarking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QuickHelpView+Watermarking.swift"; sourceTree = ""; }; + 788262982E514F3B000085AC /* QuickHelpView+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QuickHelpView+UI.swift"; sourceTree = ""; }; 788589912B8974680018E2DA /* Malachite.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Malachite.entitlements; sourceTree = ""; }; 78917F592B99AE72005E10FA /* WidgetBundle.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetBundle.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 78917F5B2B99AE72005E10FA /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; @@ -230,40 +282,43 @@ /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ - 786DADA02E4F283500BE3137 /* Exceptions for "Assets" folder in "Malachite" target */ = { + 7882625A2E512B3A000085AC /* Exceptions for "Assets" folder in "Malachite" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( - AppIcon.icon, - Assets.xcassets, + MalachiteIconMasked_Darwin24.png, + MalachiteIconMasked_Darwin25.png, ); target = 785F08482B12D41100244EB4 /* Malachite */; }; - 786DADA12E4F283500BE3137 /* Exceptions for "Assets" folder in "MalachiteWatch" target */ = { + 7882625B2E512B3A000085AC /* Exceptions for "Assets" folder in "MalachiteWatch" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( - AppIcon.icon, - Assets.xcassets, + MalachiteIconMasked_Darwin24.png, + MalachiteIconMasked_Darwin25.png, ); target = 78B72BA62E33282E002E2D4E /* MalachiteWatch */; }; - 786DADA22E4F283500BE3137 /* Exceptions for "Assets" folder in "WidgetBundle" target */ = { + 7882625C2E512B3A000085AC /* Exceptions for "Assets" folder in "WidgetBundle" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( - Assets.xcassets, + MalachiteIconMasked_Darwin24.png, + MalachiteIconMasked_Darwin25.png, ); target = 78917F582B99AE72005E10FA /* WidgetBundle */; }; - 786DADA32E4F283500BE3137 /* Exceptions for "Assets" folder in "WidgetBundleWatch" target */ = { + 7882625D2E512B3A000085AC /* Exceptions for "Assets" folder in "WidgetBundleWatch" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( - Assets.xcassets, + MalachiteIconMasked_Darwin24.png, + MalachiteIconMasked_Darwin25.png, ); target = 7837C1042E34C45B009396B0 /* WidgetBundleWatch */; }; - 786DADA42E4F283500BE3137 /* Exceptions for "Assets" folder in "CaptureBundle" target */ = { + 7882625E2E512B3A000085AC /* Exceptions for "Assets" folder in "CaptureBundle" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( - Assets.xcassets, + MalachiteIconMasked_Darwin24.png, + MalachiteIconMasked_Darwin25.png, ); target = 78A9DD602CD55551002C131D /* CaptureBundle */; }; @@ -283,11 +338,11 @@ 786DAD982E4F283500BE3137 /* Assets */ = { isa = PBXFileSystemSynchronizedRootGroup; exceptions = ( - 786DADA02E4F283500BE3137 /* Exceptions for "Assets" folder in "Malachite" target */, - 786DADA12E4F283500BE3137 /* Exceptions for "Assets" folder in "MalachiteWatch" target */, - 786DADA22E4F283500BE3137 /* Exceptions for "Assets" folder in "WidgetBundle" target */, - 786DADA32E4F283500BE3137 /* Exceptions for "Assets" folder in "WidgetBundleWatch" target */, - 786DADA42E4F283500BE3137 /* Exceptions for "Assets" folder in "CaptureBundle" target */, + 7882625A2E512B3A000085AC /* Exceptions for "Assets" folder in "Malachite" target */, + 7882625B2E512B3A000085AC /* Exceptions for "Assets" folder in "MalachiteWatch" target */, + 7882625C2E512B3A000085AC /* Exceptions for "Assets" folder in "WidgetBundle" target */, + 7882625D2E512B3A000085AC /* Exceptions for "Assets" folder in "WidgetBundleWatch" target */, + 7882625E2E512B3A000085AC /* Exceptions for "Assets" folder in "CaptureBundle" target */, ); path = Assets; sourceTree = ""; @@ -383,9 +438,10 @@ isa = PBXGroup; children = ( 786DAE172E4F28A600BE3137 /* DeveloperView.swift */, + 786DAE1A2E4F28A600BE3137 /* DeveloperView+Settings.swift */, + 7882627B2E514749000085AC /* DeveloperView+Internal.swift */, 786DAE182E4F28A600BE3137 /* DeveloperView+BuildInfo.swift */, 786DAE192E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift */, - 786DAE1A2E4F28A600BE3137 /* DeveloperView+Settings.swift */, ); path = DeveloperView; sourceTree = ""; @@ -393,12 +449,13 @@ 786DAE222E4F28A600BE3137 /* Views */ = { isa = PBXGroup; children = ( - 786DAE802E4F28D600BE3137 /* SettingsView */, - 786DAE1B2E4F28A600BE3137 /* DeveloperView */, + 7882627F2E514CA8000085AC /* QuickHelpView */, 786DAE1C2E4F28A600BE3137 /* MalachiteAboutView.swift */, 786DAE1D2E4F28A600BE3137 /* MalachiteCompatibilityView.swift */, 786DAE1E2E4F28A600BE3137 /* MalachitePhotoPreview.swift */, 786DAE212E4F28A600BE3137 /* MalachiteView.swift */, + 786DAE802E4F28D600BE3137 /* SettingsView */, + 786DAE1B2E4F28A600BE3137 /* DeveloperView */, ); path = Views; sourceTree = ""; @@ -461,11 +518,30 @@ isa = PBXGroup; children = ( 786DAE202E4F28A600BE3137 /* SettingsView.swift */, - 786DAE1F2E4F28A600BE3137 /* SettingsView+Help.swift */, + 7882625F2E51307B000085AC /* SettingsView+About.swift */, + 788262632E513094000085AC /* SettingsView+Preview.swift */, + 7882626B2E5130BB000085AC /* SettingsView+Resolution.swift */, + 7882626F2E5130C6000085AC /* SettingsView+Photo.swift */, + 788262732E5130CF000085AC /* SettingsView+Watermarking.swift */, + 788262772E5130D8000085AC /* SettingsView+UI.swift */, ); path = SettingsView; sourceTree = ""; }; + 7882627F2E514CA8000085AC /* QuickHelpView */ = { + isa = PBXGroup; + children = ( + 786DAE1F2E4F28A600BE3137 /* QuickHelpView.swift */, + 788262842E514F0C000085AC /* QuickHelpView+About.swift */, + 788262882E514F14000085AC /* QuickHelpView+Preview.swift */, + 7882628C2E514F1F000085AC /* QuickHelpView+Resolution.swift */, + 788262902E514F28000085AC /* QuickHelpView+Photo.swift */, + 788262942E514F31000085AC /* QuickHelpView+Watermarking.swift */, + 788262982E514F3B000085AC /* QuickHelpView+UI.swift */, + ); + path = QuickHelpView; + sourceTree = ""; + }; 78917F5A2B99AE72005E10FA /* Frameworks */ = { isa = PBXGroup; children = ( @@ -491,6 +567,9 @@ ); dependencies = ( ); + fileSystemSynchronizedGroups = ( + 786DAD982E4F283500BE3137 /* Assets */, + ); name = WidgetBundleWatch; productName = MalachiteWidgetBundle; productReference = 7837C1152E34C45B009396B0 /* WidgetBundleWatch.appex */; @@ -514,6 +593,9 @@ 78A9DD692CD55551002C131D /* PBXTargetDependency */, 78B72BB02E332830002E2D4E /* PBXTargetDependency */, ); + fileSystemSynchronizedGroups = ( + 786DAD982E4F283500BE3137 /* Assets */, + ); name = Malachite; productName = Malachite; productReference = 785F08492B12D41100244EB4 /* Malachite.app */; @@ -531,6 +613,9 @@ ); dependencies = ( ); + fileSystemSynchronizedGroups = ( + 786DAD982E4F283500BE3137 /* Assets */, + ); name = WidgetBundle; productName = MalachiteWidgetBundle; productReference = 78917F592B99AE72005E10FA /* WidgetBundle.appex */; @@ -548,6 +633,9 @@ ); dependencies = ( ); + fileSystemSynchronizedGroups = ( + 786DAD982E4F283500BE3137 /* Assets */, + ); name = CaptureBundle; packageProductDependencies = ( ); @@ -571,6 +659,9 @@ 7837C1022E34BD60009396B0 /* PBXTargetDependency */, 7837C1192E34C47D009396B0 /* PBXTargetDependency */, ); + fileSystemSynchronizedGroups = ( + 786DAD982E4F283500BE3137 /* Assets */, + ); name = MalachiteWatch; packageProductDependencies = ( ); @@ -728,25 +819,38 @@ files = ( 786DAE552E4F28B100BE3137 /* MalachitePreferences.swift in Sources */, 786DAE562E4F28B100BE3137 /* MalachitePreferencesUtils.swift in Sources */, + 7882628A2E514F18000085AC /* QuickHelpView+Preview.swift in Sources */, 786DAE572E4F28B100BE3137 /* MalachiteFunctionUtils.swift in Sources */, + 788262932E514F2D000085AC /* QuickHelpView+Photo.swift in Sources */, 786DAE582E4F28B100BE3137 /* MalachiteGameUtils.swift in Sources */, 786DAE592E4F28B100BE3137 /* MalachiteHapticUtils.swift in Sources */, 786DAE5A2E4F28B100BE3137 /* MalachiteIntentUtils.swift in Sources */, + 788262622E513080000085AC /* SettingsView+About.swift in Sources */, 786DAE5B2E4F28B100BE3137 /* MalachiteTooltipUtils.swift in Sources */, 786DAE5C2E4F28B100BE3137 /* MalachiteUtils.swift in Sources */, 786DAE5D2E4F28B100BE3137 /* MalachiteViewUtils.swift in Sources */, + 788262722E5130CA000085AC /* SettingsView+Photo.swift in Sources */, 786DAE2D2E4F28A600BE3137 /* DeveloperView.swift in Sources */, 786DAE2E2E4F28A600BE3137 /* DeveloperView+BuildInfo.swift in Sources */, + 7882627A2E5130DB000085AC /* SettingsView+UI.swift in Sources */, 786DAE2F2E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift in Sources */, 786DAE302E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */, 786DAE312E4F28A600BE3137 /* MalachiteAboutView.swift in Sources */, 786DAE322E4F28A600BE3137 /* MalachiteCompatibilityView.swift in Sources */, + 7882627E2E514750000085AC /* DeveloperView+Internal.swift in Sources */, 786DAE332E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */, - 786DAE342E4F28A600BE3137 /* SettingsView+Help.swift in Sources */, + 786DAE342E4F28A600BE3137 /* QuickHelpView.swift in Sources */, + 788262862E514F12000085AC /* QuickHelpView+About.swift in Sources */, + 7882626E2E5130C1000085AC /* SettingsView+Resolution.swift in Sources */, 786DAE352E4F28A600BE3137 /* SettingsView.swift in Sources */, + 7882628F2E514F25000085AC /* QuickHelpView+Resolution.swift in Sources */, + 7882629A2E514F3F000085AC /* QuickHelpView+UI.swift in Sources */, + 788262762E5130D4000085AC /* SettingsView+Watermarking.swift in Sources */, 786DAE362E4F28A600BE3137 /* MalachiteView.swift in Sources */, 785F084D2B12D41100244EB4 /* AppDelegate.swift in Sources */, + 788262962E514F38000085AC /* QuickHelpView+Watermarking.swift in Sources */, 787B1CAA2B8B095E000AFECC /* Malachite.docc in Sources */, + 788262662E51309C000085AC /* SettingsView+Preview.swift in Sources */, 785F084F2B12D41100244EB4 /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -757,25 +861,38 @@ files = ( 786DAE5E2E4F28B100BE3137 /* MalachitePreferences.swift in Sources */, 786DAE5F2E4F28B100BE3137 /* MalachitePreferencesUtils.swift in Sources */, + 788262892E514F18000085AC /* QuickHelpView+Preview.swift in Sources */, 786DAE602E4F28B100BE3137 /* MalachiteFunctionUtils.swift in Sources */, + 788262922E514F2D000085AC /* QuickHelpView+Photo.swift in Sources */, 786DAE612E4F28B100BE3137 /* MalachiteGameUtils.swift in Sources */, 786DAE622E4F28B100BE3137 /* MalachiteHapticUtils.swift in Sources */, 786DAE632E4F28B100BE3137 /* MalachiteIntentUtils.swift in Sources */, + 788262612E513080000085AC /* SettingsView+About.swift in Sources */, 786DAE642E4F28B100BE3137 /* MalachiteTooltipUtils.swift in Sources */, 786DAE652E4F28B100BE3137 /* MalachiteUtils.swift in Sources */, 786DAE662E4F28B100BE3137 /* MalachiteViewUtils.swift in Sources */, + 788262702E5130CA000085AC /* SettingsView+Photo.swift in Sources */, 786DAE372E4F28A600BE3137 /* DeveloperView.swift in Sources */, 786DAE382E4F28A600BE3137 /* DeveloperView+BuildInfo.swift in Sources */, + 788262792E5130DB000085AC /* SettingsView+UI.swift in Sources */, 786DAE392E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift in Sources */, 786DAE6D2E4F28B700BE3137 /* ControlCenterWidget.swift in Sources */, 786DAE6E2E4F28B700BE3137 /* LockScreenWidget.swift in Sources */, 786DAE6F2E4F28B700BE3137 /* MalachiteWidgetBundle.swift in Sources */, + 7882627D2E514750000085AC /* DeveloperView+Internal.swift in Sources */, 786DAE3A2E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */, 786DAE3B2E4F28A600BE3137 /* MalachiteAboutView.swift in Sources */, + 788262852E514F12000085AC /* QuickHelpView+About.swift in Sources */, + 7882626D2E5130C1000085AC /* SettingsView+Resolution.swift in Sources */, 786DAE3C2E4F28A600BE3137 /* MalachiteCompatibilityView.swift in Sources */, + 7882628E2E514F25000085AC /* QuickHelpView+Resolution.swift in Sources */, + 788262992E514F3F000085AC /* QuickHelpView+UI.swift in Sources */, + 788262742E5130D4000085AC /* SettingsView+Watermarking.swift in Sources */, 786DAE3D2E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */, - 786DAE3E2E4F28A600BE3137 /* SettingsView+Help.swift in Sources */, + 786DAE3E2E4F28A600BE3137 /* QuickHelpView.swift in Sources */, + 788262952E514F38000085AC /* QuickHelpView+Watermarking.swift in Sources */, 786DAE3F2E4F28A600BE3137 /* SettingsView.swift in Sources */, + 788262652E51309C000085AC /* SettingsView+Preview.swift in Sources */, 786DAE402E4F28A600BE3137 /* MalachiteView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -785,23 +902,36 @@ buildActionMask = 2147483647; files = ( 786DAE232E4F28A600BE3137 /* DeveloperView.swift in Sources */, + 788262782E5130DB000085AC /* SettingsView+UI.swift in Sources */, 786DAE242E4F28A600BE3137 /* DeveloperView+BuildInfo.swift in Sources */, 786DAE252E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift in Sources */, + 788262602E513080000085AC /* SettingsView+About.swift in Sources */, 786DAE4C2E4F28B100BE3137 /* MalachitePreferences.swift in Sources */, + 788262972E514F38000085AC /* QuickHelpView+Watermarking.swift in Sources */, + 788262752E5130D4000085AC /* SettingsView+Watermarking.swift in Sources */, + 7882627C2E514750000085AC /* DeveloperView+Internal.swift in Sources */, 786DAE4D2E4F28B100BE3137 /* MalachitePreferencesUtils.swift in Sources */, + 788262642E51309C000085AC /* SettingsView+Preview.swift in Sources */, 786DAE782E4F28B900BE3137 /* MalachiteCaptureBundle.swift in Sources */, 786DAE4E2E4F28B100BE3137 /* MalachiteFunctionUtils.swift in Sources */, + 7882629B2E514F3F000085AC /* QuickHelpView+UI.swift in Sources */, 786DAE4F2E4F28B100BE3137 /* MalachiteGameUtils.swift in Sources */, + 7882626C2E5130C1000085AC /* SettingsView+Resolution.swift in Sources */, 786DAE502E4F28B100BE3137 /* MalachiteHapticUtils.swift in Sources */, 786DAE512E4F28B100BE3137 /* MalachiteIntentUtils.swift in Sources */, 786DAE522E4F28B100BE3137 /* MalachiteTooltipUtils.swift in Sources */, 786DAE532E4F28B100BE3137 /* MalachiteUtils.swift in Sources */, 786DAE542E4F28B100BE3137 /* MalachiteViewUtils.swift in Sources */, + 788262872E514F12000085AC /* QuickHelpView+About.swift in Sources */, 786DAE262E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */, + 788262912E514F2D000085AC /* QuickHelpView+Photo.swift in Sources */, 786DAE272E4F28A600BE3137 /* MalachiteAboutView.swift in Sources */, 786DAE282E4F28A600BE3137 /* MalachiteCompatibilityView.swift in Sources */, 786DAE292E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */, - 786DAE2A2E4F28A600BE3137 /* SettingsView+Help.swift in Sources */, + 788262712E5130CA000085AC /* SettingsView+Photo.swift in Sources */, + 7882628D2E514F25000085AC /* QuickHelpView+Resolution.swift in Sources */, + 786DAE2A2E4F28A600BE3137 /* QuickHelpView.swift in Sources */, + 7882628B2E514F18000085AC /* QuickHelpView+Preview.swift in Sources */, 786DAE2B2E4F28A600BE3137 /* SettingsView.swift in Sources */, 786DAE2C2E4F28A600BE3137 /* MalachiteView.swift in Sources */, ); diff --git a/Malachite/Localizable.xcstrings b/Malachite/Localizable.xcstrings index 801efc4..46a6994 100755 --- a/Malachite/Localizable.xcstrings +++ b/Malachite/Localizable.xcstrings @@ -701,6 +701,16 @@ } } }, + "developer.header.internal" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Internal settings" + } + } + } + }, "developer.option.debug.breakapp" : { "localizations" : { "en" : { @@ -1536,6 +1546,26 @@ } } }, + "view.detail.compatibility" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lists various features and capabilities of Malachite that your device can take advantage of, and ones it can't." + } + } + } + }, + "view.detail.developer" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Extra settings and information, intended for people working on Malachite’s code." + } + } + } + }, "view.title.about" : { "localizations" : { "en" : { diff --git a/Malachite/Utilities/MalachiteViewUtils.swift b/Malachite/Utilities/MalachiteViewUtils.swift index b616363..133920d 100755 --- a/Malachite/Utilities/MalachiteViewUtils.swift +++ b/Malachite/Utilities/MalachiteViewUtils.swift @@ -282,6 +282,7 @@ struct MalachiteCompatibilityViewUtils: View { } } +// Really Eva? What the fuck is a "Nagivation" struct MalachiteNagivationViewUtils: View { let content: Content @@ -303,9 +304,45 @@ struct MalachiteNagivationViewUtils: View { } } +// temp, will be redone +struct MalachiteToolbarUtils: View { + let action: () -> Void + let image: String + let primary: Bool + + init( + action: @escaping (() -> Void), + image: String, + primary: Bool + ) { + self.action = action + self.image = image + self.primary = primary + } + + @ViewBuilder + var body: some View { + if #available(iOS 26.0, *) { + Button { + self.action() + } label: { + Image(systemName: image).tint(primary ? .primary : nil) + } + .buttonStyle(.glassProminent) + } else { + Button { + self.action() + } label: { + Image(systemName: "\(image).circle").tint(primary ? .primary : nil) + } + } + } +} + /// Needed for the alerts to properly display their localized strings extension String { var localized: String { return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "") } } + diff --git a/Malachite/Views/DeveloperView/DeveloperView+Internal.swift b/Malachite/Views/DeveloperView/DeveloperView+Internal.swift new file mode 100644 index 0000000..5404c9f --- /dev/null +++ b/Malachite/Views/DeveloperView/DeveloperView+Internal.swift @@ -0,0 +1,59 @@ +// +// DeveloperView+Internal.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension DeveloperView { + struct InternalSettings: View { + /// A State variable used for determining how many fingers are used for the settings gesture. + @State private var settingsGestureFingers = Int() + + var utilities: MalachiteClassesObject + + init( + utilities: MalachiteClassesObject + ) { + self.utilities = utilities + } + + var body: some View { + Section(header: Text("developer.header.internal")) { + MalachiteCellViewUtils( + icon: "hand.draw", + disabled: nil, + dangerous: false) + { + Picker("settings.option.ui.settingsgesture", selection: $settingsGestureFingers) { + Text("settings.option.ui.settingsgesture.1") + .tag(1) + Text("settings.option.ui.settingsgesture.2") + .tag(2) + Text("settings.option.ui.settingsgesture.3") + .tag(3) + } + } + } + .onAppear(perform: onAppear) + .onDisappear(perform: onDisappear) + .onChange(of: settingsGestureFingers) {_ in + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.settingsGestureNotification.name, object: nil) + + utilities.preferences.evaintrnl.settingsGesture = settingsGestureFingers + } + } + + func onAppear() { + settingsGestureFingers = utilities.preferences.evaintrnl.settingsGesture + } + + func onDisappear() { + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.settingsGestureNotification.name, object: nil) + + utilities.preferences.evaintrnl.settingsGesture = settingsGestureFingers + } + } +} diff --git a/Malachite/Views/DeveloperView/DeveloperView+Settings.swift b/Malachite/Views/DeveloperView/DeveloperView+Settings.swift index 6cabab0..b92335b 100644 --- a/Malachite/Views/DeveloperView/DeveloperView+Settings.swift +++ b/Malachite/Views/DeveloperView/DeveloperView+Settings.swift @@ -9,6 +9,10 @@ import SwiftUI extension DeveloperView { struct Settings: View { + @State private var debugLoggingUserDefaults = false + /// A State variable used for determining whether or not to literally break the app. + @State private var breakApp = false + var utilities: MalachiteClassesObject init( @@ -17,10 +21,6 @@ extension DeveloperView { self.utilities = utilities } - @State private var debugLoggingUserDefaults = false - /// A State variable used for determining whether or not to literally break the app. - @State private var breakApp = false - var body: some View { Section { MalachiteCellViewUtils( diff --git a/Malachite/Views/DeveloperView/DeveloperView.swift b/Malachite/Views/DeveloperView/DeveloperView.swift index 92974b6..79d2555 100644 --- a/Malachite/Views/DeveloperView/DeveloperView.swift +++ b/Malachite/Views/DeveloperView/DeveloperView.swift @@ -16,27 +16,14 @@ struct DeveloperView: View { var body: some View { Form { Settings(utilities: utilities) + if utilities.versionType == "INTERNAL" { InternalSettings(utilities: utilities)} BuildInfo(utilities: utilities) DeviceInfo(utilities: utilities) } .navigationTitle("view.title.developer") .toolbar(content: { ToolbarItemGroup(placement: .topBarTrailing) { - if #available(iOS 26.0, *) { - Button { - self.dismissAction() - } label: { - Image(systemName: "checkmark") - .tint(.primary) - } - .buttonStyle(.glassProminent) - } else { - Button { - self.dismissAction() - } label: { - Image(systemName: "checkmark.circle") - } - } + MalachiteToolbarUtils(action: self.dismissAction, image: "checkmark", primary: true) } }) } diff --git a/Malachite/Views/MalachiteAboutView.swift b/Malachite/Views/MalachiteAboutView.swift index aa11b37..fbbe248 100755 --- a/Malachite/Views/MalachiteAboutView.swift +++ b/Malachite/Views/MalachiteAboutView.swift @@ -75,21 +75,7 @@ struct MalachiteAboutView: View { .navigationTitle("view.title.about") .toolbar(content: { ToolbarItemGroup(placement: .topBarTrailing) { - if #available(iOS 26.0, *) { - Button { - self.dismissAction() - } label: { - Image(systemName: "checkmark") - .tint(.primary) - } - .buttonStyle(.glassProminent) - } else { - Button { - self.dismissAction() - } label: { - Image(systemName: "checkmark.circle") - } - } + MalachiteToolbarUtils(action: self.dismissAction, image: "checkmark", primary: true) } }) } diff --git a/Malachite/Views/MalachiteCompatibilityView.swift b/Malachite/Views/MalachiteCompatibilityView.swift index c3db225..e17f7aa 100755 --- a/Malachite/Views/MalachiteCompatibilityView.swift +++ b/Malachite/Views/MalachiteCompatibilityView.swift @@ -53,21 +53,7 @@ public struct MalachiteCompatibilityView: View { .navigationTitle("view.title.compatibility") .toolbar(content: { ToolbarItemGroup(placement: .topBarTrailing) { - if #available(iOS 26.0, *) { - Button { - self.dismissAction() - } label: { - Image(systemName: "checkmark") - .tint(.primary) - } - .buttonStyle(.glassProminent) - } else { - Button { - self.dismissAction() - } label: { - Image(systemName: "checkmark.circle") - } - } + MalachiteToolbarUtils(action: self.dismissAction, image: "checkmark", primary: true) } }) } diff --git a/Malachite/Views/QuickHelpView/QuickHelpView+About.swift b/Malachite/Views/QuickHelpView/QuickHelpView+About.swift new file mode 100644 index 0000000..6a40b83 --- /dev/null +++ b/Malachite/Views/QuickHelpView/QuickHelpView+About.swift @@ -0,0 +1,27 @@ +// +// QuickHelpView+About.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension QuickHelpView { + struct About: View { + var utilities = MalachiteClassesObject() + + init(utilities: MalachiteClassesObject ) { self.utilities = utilities } + + /// A variable to hold the about section. + var body: some View { + Section { + Builder(title: Text("view.title.about"), subtitle: Text("view.detail.about")) {} + if utilities.versionType == "INTERNAL" { + Builder(title: Text("view.title.compatibility"), subtitle: Text("view.detail.compatibility")) {} + Builder(title: Text("view.title.developer"), subtitle: Text("view.detail.developer")) {} + } + } + } + } +} diff --git a/Malachite/Views/QuickHelpView/QuickHelpView+Photo.swift b/Malachite/Views/QuickHelpView/QuickHelpView+Photo.swift new file mode 100644 index 0000000..19f1bf8 --- /dev/null +++ b/Malachite/Views/QuickHelpView/QuickHelpView+Photo.swift @@ -0,0 +1,21 @@ +// +// QuickHelpView+Photo.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension QuickHelpView { + struct Photo: View { + /// A variable to hold the photo settings section. + var body: some View { + Section(header: Text("settings.header.photo"), footer: Text("settings.footer.photo")) { + Builder(title: Text("settings.option.photo.fileformat"), subtitle: Text("settings.detail.photo.fileformat")) {} + Builder(title: Text("settings.option.photo.hdr"), subtitle: Text("settings.detail.photo.hdr")) {} + Builder(title: Text("settings.option.photo.continuous"), subtitle: Text("settings.detail.photo.continuous")) {} + } + } + } +} diff --git a/Malachite/Views/QuickHelpView/QuickHelpView+Preview.swift b/Malachite/Views/QuickHelpView/QuickHelpView+Preview.swift new file mode 100644 index 0000000..b4d2bb2 --- /dev/null +++ b/Malachite/Views/QuickHelpView/QuickHelpView+Preview.swift @@ -0,0 +1,21 @@ +// +// QuickHelpView+Preview.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension QuickHelpView { + struct Preview: View { + /// A variable to hold the preview settings section. + var body: some View { + Section(header: Text("settings.header.preview"), footer: Text("settings.footer.preview")) { + Builder(title: Text("settings.option.preview.aspect_ratio"), subtitle: Text("settings.detail.preview.aspect_ratio")) {} + Builder(title: Text("settings.option.preview.sbtlz"), subtitle: Text("settings.detail.preview.sbtlz")) {} + Builder(title: Text("settings.option.preview.zoom_maximum"), subtitle: Text("settings.detail.preview.zoom_maximum")) {} + } + } + } +} diff --git a/Malachite/Views/QuickHelpView/QuickHelpView+Resolution.swift b/Malachite/Views/QuickHelpView/QuickHelpView+Resolution.swift new file mode 100644 index 0000000..270de99 --- /dev/null +++ b/Malachite/Views/QuickHelpView/QuickHelpView+Resolution.swift @@ -0,0 +1,21 @@ +// +// QuickHelpView+Resolution.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension QuickHelpView { + struct Resolution: View { + /// A variable to hold the image resolution section. + var body: some View { + Section(header: Text("settings.header.resolution"), footer: Text("settings.footer.resolution")) { + Builder(title: Text("settings.option.resolution.ultrawide"), subtitle: Text("settings.detail.resolution.ultrawide")) {} + Builder(title: Text("settings.option.resolution.wide"), subtitle: Text("settings.detail.resolution.wide")) {} + Builder(title: Text("settings.option.resolution.telephoto"), subtitle: Text("settings.detail.resolution.telephoto")) {} + } + } + } +} diff --git a/Malachite/Views/QuickHelpView/QuickHelpView+UI.swift b/Malachite/Views/QuickHelpView/QuickHelpView+UI.swift new file mode 100644 index 0000000..aa91e21 --- /dev/null +++ b/Malachite/Views/QuickHelpView/QuickHelpView+UI.swift @@ -0,0 +1,23 @@ +// +// QuickHelpView+UI.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension QuickHelpView { + struct UserInterface: View { + /// A variable to hold the user interface settings section. + var body: some View { + Section(header: Text("settings.header.ui"), footer: Text("settings.footer.ui")) { + Builder(title: Text("settings.option.ui.tapgesture"), subtitle: Text("settings.detail.ui.tapgesture")) {} + Builder(title: Text("settings.option.ui.hiddengestures"), subtitle: Text("settings.detail.ui.hiddengestures")) {} + Builder(title: Text("settings.option.ui.idletimer"), subtitle: Text("settings.detail.ui.idletimer")) {} + Builder(title: Text("settings.option.ui.haptics"), subtitle: Text("settings.detail.ui.haptics")) {} + Builder(title: Text("settings.option.ui.hiddenonlaunch"), subtitle: Text("settings.detail.ui.hiddenonlaunch")) {} + } + } + } +} diff --git a/Malachite/Views/QuickHelpView/QuickHelpView+Watermarking.swift b/Malachite/Views/QuickHelpView/QuickHelpView+Watermarking.swift new file mode 100644 index 0000000..8942761 --- /dev/null +++ b/Malachite/Views/QuickHelpView/QuickHelpView+Watermarking.swift @@ -0,0 +1,20 @@ +// +// QuickHelpView+Watermarking.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension QuickHelpView { + struct Watermarking: View { + /// A variable to hold the watermark settings section. + var body: some View { + Section(header: Text("settings.header.watermark"), footer: Text("settings.footer.watermark")) { + Builder(title: Text("settings.option.watermark.enable"), subtitle: Text("settings.detail.watermark.enable")) {} + Builder(title: Text("settings.option.watermark.text"), subtitle: Text("settings.detail.watermark.text")) {} + } + } + } +} diff --git a/Malachite/Views/QuickHelpView/QuickHelpView.swift b/Malachite/Views/QuickHelpView/QuickHelpView.swift new file mode 100755 index 0000000..190da0f --- /dev/null +++ b/Malachite/Views/QuickHelpView/QuickHelpView.swift @@ -0,0 +1,87 @@ +// +// SettingsView+Help.swift +// Malachite +// +// Created by Eva Isabella Luna on 3/14/24. +// + +import SwiftUI + +struct QuickHelpView: View { + /// A State variable used for determining whether or not this view is being presented as a modal. + var dismissAction: (() -> Void) + /// A variable to hold the existing instance of ``MalachiteClassesObject``. + var utilities = MalachiteClassesObject() + + init( + utilities: MalachiteClassesObject, + dismissAction: @escaping (() -> Void) + ) { + self.utilities = utilities + self.dismissAction = dismissAction + } + + /// A variable used to hold the entire view. + var body: some View { + + Form { + About(utilities: utilities) + Preview() + Resolution() + Photo() + Watermarking() + UserInterface() + if utilities.versionType == "DEBUG" { Debugging() } + } + .navigationTitle("view.title.help") + .toolbar(content: { + ToolbarItemGroup(placement: .topBarTrailing) { + MalachiteToolbarUtils(action: self.dismissAction, image: "checkmark", primary: true) + } + }) + } + + @available(*, deprecated, message: "Debugging section is being replaced with the Developer link in the future") + struct Debugging: View { + /// A variable to hold the debug settings section. Only available with debug builds. + var body: some View { + Section(header: Text("developer.header.debug"), footer: Text("developer.footer.debug")) { + Builder(title: Text("developer.option.debug.logging.userdefaults"), subtitle: Text("developer.detail.debug.logging.userdefaults")) {} + Builder(title: Text("developer.option.debug.erase.userdefaults"), subtitle: Text("developer.detail.debug.erase.userdefaults")) {} + Builder(title: Text("developer.option.debug.erase.gamekit"), subtitle: Text("developer.detail.debug.erase.gamekit")) {} + } + } + } + + struct Builder: View { + var title: Text + var subtitle: Text + let content: Content? + + init( + title: Text, + subtitle: Text, + @ViewBuilder content: () -> Content? + ) { + self.title = title + self.subtitle = subtitle + self.content = content() ?? nil + } + + var body: some View { + VStack { + HStack { + title + .bold() + Spacer() + + } + HStack { + subtitle + .font(.footnote) + Spacer() + } + } + } + } +} diff --git a/Malachite/Views/SettingsView/SettingsView+About.swift b/Malachite/Views/SettingsView/SettingsView+About.swift new file mode 100644 index 0000000..2f9856e --- /dev/null +++ b/Malachite/Views/SettingsView/SettingsView+About.swift @@ -0,0 +1,59 @@ +// +// SettingsView+About.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension SettingsView { + struct About: View { + /// A State variable used for determining whether or not this view is being presented as a modal. + var dismissAction: (() -> Void) + /// A variable to hold the existing instance of ``MalachiteClassesObject``. + var utilities = MalachiteClassesObject() + + init( + utilities: MalachiteClassesObject, + dismissAction: @escaping (() -> Void) + ) { + self.utilities = utilities + self.dismissAction = dismissAction + } + + var body: some View { + Section { + MalachiteCellViewUtils( + icon: "info.circle", + disabled: nil, + dangerous: false) + { + NavigationLink(destination: MalachiteAboutView(dismissAction: dismissAction)) { + Text("view.title.about") + } + } + if utilities.versionType == "INTERNAL" { + MalachiteCellViewUtils( + icon: "checkmark.seal", + disabled: nil, + dangerous: false) + { + NavigationLink(destination: MalachiteCompatibilityView(dismissAction: dismissAction, utilities: utilities)) { + Text("view.title.compatibility") + } + } + MalachiteCellViewUtils( + icon: "wrench.and.screwdriver", + disabled: nil, + dangerous: false) + { + NavigationLink(destination: DeveloperView(dismissAction: dismissAction)) { + Text("view.title.developer") + } + } + } + } + } + } +} diff --git a/Malachite/Views/SettingsView/SettingsView+Help.swift b/Malachite/Views/SettingsView/SettingsView+Help.swift deleted file mode 100755 index e9701a7..0000000 --- a/Malachite/Views/SettingsView/SettingsView+Help.swift +++ /dev/null @@ -1,143 +0,0 @@ -// -// SettingsView+Help.swift -// Malachite -// -// Created by Eva Isabella Luna on 3/14/24. -// - -import SwiftUI - -extension SettingsView { - struct Help: View { - - /// A variable used to hold the function for dismissing with the toolbar item. - var dismissAction: (() -> Void) - - /// A variable used to hold the entire view. - var body: some View { - - Form { - aboutSection - previewSettingsSection - resolutionSettingsSection - photoSettingsSection - watermarkSettingsSection - uiSettingsSection - debugSettingsSection - } - .navigationTitle("view.title.help") - .toolbar(content: { - ToolbarItemGroup(placement: .topBarTrailing) { - if #available(iOS 26.0, *) { - Button { - self.dismissAction() - } label: { - Image(systemName: "checkmark") - .tint(.primary) - } - .buttonStyle(.glassProminent) - } else { - Button { - self.dismissAction() - } label: { - Image(systemName: "checkmark.circle") - } - } - } - }) - } - - /// A variable to hold the about section. - var aboutSection: some View { - Section { - Builder(title: Text("view.title.about"), subtitle: Text("view.detail.about")) {} - } - } - - /// A variable to hold the preview settings section. - var previewSettingsSection: some View { - Section(header: Text("settings.header.preview"), footer: Text("settings.footer.preview")) { - Builder(title: Text("settings.option.preview.aspect_ratio"), subtitle: Text("settings.detail.preview.aspect_ratio")) {} - Builder(title: Text("settings.option.preview.sbtlz"), subtitle: Text("settings.detail.preview.sbtlz")) {} - Builder(title: Text("settings.option.preview.zoom_maximum"), subtitle: Text("settings.detail.preview.zoom_maximum")) {} - } - } - - /// A variable to hold the image resolution section. - var resolutionSettingsSection: some View { - Section(header: Text("settings.header.resolution"), footer: Text("settings.footer.resolution")) { - Builder(title: Text("settings.option.resolution.ultrawide"), subtitle: Text("settings.detail.resolution.ultrawide")) {} - Builder(title: Text("settings.option.resolution.wide"), subtitle: Text("settings.detail.resolution.wide")) {} - Builder(title: Text("settings.option.resolution.telephoto"), subtitle: Text("settings.detail.resolution.telephoto")) {} - } - } - - /// A variable to hold the photo settings section. - var photoSettingsSection: some View { - Section(header: Text("settings.header.photo"), footer: Text("settings.footer.photo")) { - Builder(title: Text("settings.option.photo.fileformat"), subtitle: Text("settings.detail.photo.fileformat")) {} - Builder(title: Text("settings.option.photo.hdr"), subtitle: Text("settings.detail.photo.hdr")) {} - Builder(title: Text("settings.option.photo.continuous"), subtitle: Text("settings.detail.photo.continuous")) {} - } - } - - var uiSettingsSection: some View { - Section(header: Text("settings.header.ui"), footer: Text("settings.footer.ui")) { - Builder(title: Text("settings.option.ui.tapgesture"), subtitle: Text("settings.detail.ui.tapgesture")) {} - Builder(title: Text("settings.option.ui.hiddengestures"), subtitle: Text("settings.detail.ui.hiddengestures")) {} - Builder(title: Text("settings.option.ui.idletimer"), subtitle: Text("settings.detail.ui.idletimer")) {} - Builder(title: Text("settings.option.ui.haptics"), subtitle: Text("settings.detail.ui.haptics")) {} - Builder(title: Text("settings.option.ui.hiddenonlaunch"), subtitle: Text("settings.detail.ui.hiddenonlaunch")) {} - } - } - - /// A variable to hold the watermark settings section. - var watermarkSettingsSection: some View { - Section(header: Text("settings.header.watermark"), footer: Text("settings.footer.watermark")) { - Builder(title: Text("settings.option.watermark.enable"), subtitle: Text("settings.detail.watermark.enable")) {} - Builder(title: Text("settings.option.watermark.text"), subtitle: Text("settings.detail.watermark.text")) {} - } - } - - /// A variable to hold the debug settings section. Only available with debug builds. - var debugSettingsSection: some View { - Section(header: Text("developer.header.debug"), footer: Text("developer.footer.debug")) { - Builder(title: Text("developer.option.debug.logging.userdefaults"), subtitle: Text("developer.detail.debug.logging.userdefaults")) {} - Builder(title: Text("developer.option.debug.erase.userdefaults"), subtitle: Text("developer.detail.debug.erase.userdefaults")) {} - Builder(title: Text("developer.option.debug.erase.gamekit"), subtitle: Text("developer.detail.debug.erase.gamekit")) {} - } - } - - struct Builder: View { - var title: Text - var subtitle: Text - let content: Content? - - init( - title: Text, - subtitle: Text, - @ViewBuilder content: () -> Content? - ) { - self.title = title - self.subtitle = subtitle - self.content = content() ?? nil - } - - var body: some View { - VStack { - HStack { - title - .bold() - Spacer() - - } - HStack { - subtitle - .font(.footnote) - Spacer() - } - } - } - } - } -} diff --git a/Malachite/Views/SettingsView/SettingsView+Photo.swift b/Malachite/Views/SettingsView/SettingsView+Photo.swift new file mode 100644 index 0000000..bd52ad6 --- /dev/null +++ b/Malachite/Views/SettingsView/SettingsView+Photo.swift @@ -0,0 +1,150 @@ +// +// SettingsView+Photo.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension SettingsView { + struct Photo: View { + /// A State variable used for determining whether or not the device supports HDR capture in its current mode. + @State private var supportsHDR = Bool() + /// A State variable used for determining whether or not the device supports HEIC capture. + @State private var supportsHEIC = Bool() + /// A State variable used for presenting the user with a footer based on capabilities. + @State private var formatFooterText: String? + /// A State variable used for determining the active photo format. + @State private var photoFormat = Int() + /// A State variable used for determining whether or not to capture in HDR. + @State private var hdrSwitch = false + /// A State variable used for determining whether or not to enable continuous auto exposure. + @State private var continuousAEAF = Int() + /// A State variable used for determining whether or not this view is being presented as a modal. + var dismissAction: (() -> Void) + /// A variable to hold the existing instance of ``MalachiteClassesObject``. + var utilities = MalachiteClassesObject() + + init( + utilities: MalachiteClassesObject, + dismissAction: @escaping (() -> Void) + ) { + self.utilities = utilities + self.dismissAction = dismissAction + } + + var body: some View { + Section(header: Text("settings.header.photo"), footer: (formatFooterText != nil) ? Text(formatFooterText!) : nil) { + MalachiteCellViewUtils( + icon: "square.and.arrow.down", + disabled: !supportsHEIC, + dangerous: false) + { + Picker("settings.option.photo.fileformat", selection: $photoFormat) { + Text("settings.option.photo.fileformat.jpeg") + .tag(0) + Text("settings.option.photo.fileformat.heif") + .tag(1) + } + } + + MalachiteCellViewUtils( + icon: "camera.filters", + disabled: !supportsHDR, + dangerous: false) + { + Toggle("settings.option.photo.hdr", isOn: $hdrSwitch) + } + MalachiteCellViewUtils( + icon: "plus.viewfinder", + disabled: nil, + dangerous: false) + { + Picker("settings.option.photo.continuous", selection: $continuousAEAF) { + Text("settings.option.reusable.aeaf") + .tag(0) + Text("settings.option.reusable.af") + .tag(1) + Text("settings.option.reusable.ae") + .tag(2) + Text("settings.option.reusable.off") + .tag(3) + } + } + } + .onAppear(perform: self.onAppear) + .onDisappear(perform: self.onDisappear) + .onChange(of: photoFormat) {_ in + utilities.preferences.capture.format.jpeg = photoFormat == 0 ? true : false + utilities.preferences.capture.format.heic = photoFormat == 1 ? true : false + } + .onChange(of: hdrSwitch) { _ in + utilities.preferences.capture.hdr = hdrSwitch + } + .onChange(of: continuousAEAF) { _ in + switch continuousAEAF { + case 0: + utilities.preferences.capture.continuous = ["ae", "af"] + case 1: + utilities.preferences.capture.continuous = ["af"] + case 2: + utilities.preferences.capture.continuous = ["ae"] + default: + utilities.preferences.capture.continuous = ["off"] + } + + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.continousAEAFNotification.name, object: nil) + } + } + + func onAppear() { + supportsHDR = utilities.function.supportsHDR + supportsHEIC = utilities.function.supportsHEIC() + + if !supportsHEIC { + formatFooterText = "settings.footer.photo.heif".localized + } + + if !supportsHDR { + formatFooterText = (formatFooterText != nil) ? formatFooterText! + "settings.footer.photo.hdr".localized : "settings.footer.photo.hdr".localized + } + + hdrSwitch = utilities.preferences.capture.hdr + + // TODO: Better way to do this + photoFormat = utilities.preferences.capture.format.jpeg ? 0 : 1 + photoFormat = utilities.preferences.capture.format.heic ? 1 : 0 + + switch utilities.preferences.capture.continuous { + case ["ae", "af"]: + continuousAEAF = 0 + case ["af"]: + continuousAEAF = 1 + case ["ae"]: + continuousAEAF = 2 + default: + continuousAEAF = 3 + } + } + + func onDisappear() { + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.continousAEAFNotification.name, object: nil) + + utilities.preferences.capture.hdr = hdrSwitch + utilities.preferences.capture.format.jpeg = photoFormat == 0 ? true : false + utilities.preferences.capture.format.heic = photoFormat == 1 ? true : false + + switch continuousAEAF { + case 0: + utilities.preferences.capture.continuous = ["ae", "af"] + case 1: + utilities.preferences.capture.continuous = ["af"] + case 2: + utilities.preferences.capture.continuous = ["ae"] + default: + utilities.preferences.capture.continuous = ["off"] + } + } + } +} diff --git a/Malachite/Views/SettingsView/SettingsView+Preview.swift b/Malachite/Views/SettingsView/SettingsView+Preview.swift new file mode 100644 index 0000000..fc06bc6 --- /dev/null +++ b/Malachite/Views/SettingsView/SettingsView+Preview.swift @@ -0,0 +1,123 @@ +// +// SettingsView+Preview.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension SettingsView { + struct Preview: View { + /// A State variable used for determining the current aspect ratio for the ``cameraPreview``. + @State private var previewAspect = Int() + /// A State variable used for determining whether or not to stabilize the ``cameraPreview``. + @State private var shouldStabilize = Bool() + /// A State variable used for determining whether or not to skip opening ``MalachitePhotoPreview`` and save the photo. + @State private var shouldUseFastPath = Bool() + /// A State variable used for determining what the maximum zoom level for each camera should be. + @State private var zoomMaximum = Int() + /// A State variable used for determining whether or not this view is being presented as a modal. + var dismissAction: (() -> Void) + /// A variable to hold the existing instance of ``MalachiteClassesObject``. + var utilities = MalachiteClassesObject() + + init( + utilities: MalachiteClassesObject, + dismissAction: @escaping (() -> Void) + ) { + self.utilities = utilities + self.dismissAction = dismissAction + } + + /// A variable to hold the preview settings section. + var body: some View { + Section(header: Text("settings.header.preview")) { + MalachiteCellViewUtils( + icon: "aspectratio", + disabled: false, + dangerous: false) + { + Picker("settings.option.preview.aspect_ratio", selection: $previewAspect) { + Text("settings.option.preview.aspect_ratio.fit") + .tag(0) + Text("settings.option.preview.aspect_ratio.fill") + .tag(1) + } + } + MalachiteCellViewUtils( + icon: "level", + disabled: nil, + dangerous: false) + { + Toggle("settings.option.preview.sbtlz", isOn: $shouldStabilize) + } + // Testing things out + if utilities.versionType == "INTERNAL" { + MalachiteCellViewUtils( + icon: "forward", + disabled: nil, + dangerous: false) + { + Toggle("settings.option.preview.fastpath", isOn: $shouldUseFastPath) + } + } + MalachiteCellViewUtils( + icon: "plus.magnifyingglass", + disabled: nil, + dangerous: false) + { + Stepper { + Text("settings.option.preview.zoom_maximum") + } onIncrement: { + if zoomMaximum < 10 { zoomMaximum += 1 } + } onDecrement: { + if zoomMaximum > 5 { zoomMaximum -= 1 } + } + Text(String(format: "%lldx", Int(zoomMaximum))) + .font(.footnote) + .foregroundColor(.gray) + } + } + .onAppear(perform: self.onAppear) + .onDisappear(perform: self.onDisappear) + // Called when the preview's aspect ratio is changed. + .onChange(of: previewAspect) {_ in + utilities.preferences.preview.aspect = (previewAspect == 1) + + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.aspectFillNotification.name, object: nil) + } + // Called when the user enables/disables preview stabilization. + .onChange(of: shouldStabilize) {_ in + utilities.preferences.preview.stablize = shouldStabilize + + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.stabilizerNotification.name, object: nil) + } + // Called when the user enables/disables the fast path. + .onChange(of: shouldUseFastPath) {_ in + utilities.preferences.preview.fastPath = shouldUseFastPath + } + // Called when the maximum zoom level changes. + .onChange(of: zoomMaximum) {_ in + utilities.preferences.capture.maximumZoom = zoomMaximum + } + } + + func onAppear() { + shouldStabilize = utilities.preferences.preview.stablize + shouldUseFastPath = utilities.preferences.preview.fastPath + zoomMaximum = utilities.preferences.capture.maximumZoom + previewAspect = utilities.preferences.preview.aspect ? 1 : 0 + } + + func onDisappear() { + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.aspectFillNotification.name, object: nil) + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.stabilizerNotification.name, object: nil) + + utilities.preferences.preview.aspect = (previewAspect == 1) + utilities.preferences.preview.stablize = shouldStabilize + utilities.preferences.preview.fastPath = shouldUseFastPath + utilities.preferences.capture.maximumZoom = zoomMaximum + } + } +} diff --git a/Malachite/Views/SettingsView/SettingsView+Resolution.swift b/Malachite/Views/SettingsView/SettingsView+Resolution.swift new file mode 100644 index 0000000..d5d6bfe --- /dev/null +++ b/Malachite/Views/SettingsView/SettingsView+Resolution.swift @@ -0,0 +1,185 @@ +// +// SettingsView+Resolution.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension SettingsView { + struct Resolution: View { + /// A State variable used for determining what megapixel count the ultrawide camera should shoot in. + @State private var ultrawideMegapixelCount = Int() + /// A State variable used for determining what megapixel count the wide angle camera should shoot in. + @State private var wideMegapixelCount = Int() + /// A State variable used for determining what megapixel count the telephoto camera should shoot in. + @State private var telephotoMegapixelCount = Int() + /// A State variable used for determining whether or not this view is being presented as a modal. + var dismissAction: (() -> Void) + /// A variable to hold the existing instance of ``MalachiteClassesObject``. + var utilities = MalachiteClassesObject() + + init( + utilities: MalachiteClassesObject, + dismissAction: @escaping (() -> Void) + ) { + self.utilities = utilities + self.dismissAction = dismissAction + } + + var body: some View { + Section(header: Text("settings.header.resolution")) { + if utilities.preferences.ext.dictionary.isValid(dictionary: utilities.preferences.compatibility.ultrawide) { + MalachiteCellViewUtils( + icon: "camera.aperture", + disabled: utilities.preferences.ext.dictionary.getCount(dictionary: utilities.preferences.compatibility.ultrawide) == 1, + dangerous: false) + { + Picker("settings.option.resolution.ultrawide", selection: $ultrawideMegapixelCount) { + if let mp = utilities.preferences.compatibility.ultrawide["8"] { if mp { + Text("settings.option.resolution.8") + .tag(0) + } } + if let mp = utilities.preferences.compatibility.ultrawide["12"] { if mp { + Text("settings.option.resolution.12") + .tag(1) + } } + if let mp = utilities.preferences.compatibility.ultrawide["48"] { if mp { + Text("settings.option.resolution.48") + .tag(2) + } } + } + } + } + if utilities.preferences.ext.dictionary.isValid(dictionary: utilities.preferences.compatibility.wideangle) { + MalachiteCellViewUtils( + icon: "camera.aperture", + disabled: utilities.preferences.ext.dictionary.getCount(dictionary: utilities.preferences.compatibility.wideangle) == 1, + dangerous: false) + { + Picker("settings.option.resolution.wide", selection: $wideMegapixelCount) { + if let mp = utilities.preferences.compatibility.wideangle["8"] { if mp { + Text("settings.option.resolution.8") + .tag(0) + } } + if let mp = utilities.preferences.compatibility.wideangle["12"] { if mp { + Text("settings.option.resolution.12") + .tag(1) + } } + if let mp = utilities.preferences.compatibility.wideangle["48"] { if mp { + Text("settings.option.resolution.48") + .tag(2) + } } + } + } + } + if utilities.preferences.ext.dictionary.isValid(dictionary: utilities.preferences.compatibility.telephoto) { + MalachiteCellViewUtils( + icon: "camera.aperture", + disabled: utilities.preferences.ext.dictionary.getCount(dictionary: utilities.preferences.compatibility.telephoto) == 1, + dangerous: false) + { + Picker("settings.option.resolution.telephoto", selection: $telephotoMegapixelCount) { + if let mp = utilities.preferences.compatibility.telephoto["48"] { if mp { + Text("settings.option.resolution.48") + .tag(1) + } } + if let mp = utilities.preferences.compatibility.telephoto["12"] { if mp { + Text("settings.option.resolution.12") + .tag(0) + } } + } + } + } + } + .onAppear(perform: self.onAppear) + .onDisappear(perform: self.onDisappear) + .onChange(of: ultrawideMegapixelCount) { _ in + switch ultrawideMegapixelCount { + case 1: + utilities.preferences.capture.mp.ultrawide = 12 + case 2: + utilities.preferences.capture.mp.ultrawide = 48 + default: + utilities.preferences.capture.mp.ultrawide = 8 + } + } + .onChange(of: wideMegapixelCount) { _ in + switch wideMegapixelCount { + case 1: + utilities.preferences.capture.mp.wideangle = 12 + case 2: + utilities.preferences.capture.mp.wideangle = 48 + default: + utilities.preferences.capture.mp.wideangle = 8 + } + } + .onChange(of: telephotoMegapixelCount) { _ in + switch telephotoMegapixelCount { + case 1: + utilities.preferences.capture.mp.telephoto = 48 + default: + utilities.preferences.capture.mp.telephoto = 12 + } + } + } + + func onAppear() { + switch utilities.preferences.capture.mp.ultrawide { + case 12: + ultrawideMegapixelCount = 1 + case 48: + ultrawideMegapixelCount = 2 + default: + ultrawideMegapixelCount = 0 + } + + switch utilities.preferences.capture.mp.wideangle { + case 12: + wideMegapixelCount = 1 + case 48: + wideMegapixelCount = 2 + default: + wideMegapixelCount = 0 + } + + switch utilities.preferences.capture.mp.telephoto { + case 48: + telephotoMegapixelCount = 1 + default: + telephotoMegapixelCount = 0 + } + } + + func onDisappear() { + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.megaPixelSwitchNotification.name, object: nil) + + switch ultrawideMegapixelCount { + case 1: + utilities.preferences.capture.mp.ultrawide = 12 + case 2: + utilities.preferences.capture.mp.ultrawide = 48 + default: + utilities.preferences.capture.mp.ultrawide = 8 + } + + switch wideMegapixelCount { + case 1: + utilities.preferences.capture.mp.wideangle = 12 + case 2: + utilities.preferences.capture.mp.wideangle = 48 + default: + utilities.preferences.capture.mp.wideangle = 8 + } + + switch telephotoMegapixelCount { + case 1: + utilities.preferences.capture.mp.telephoto = 48 + default: + utilities.preferences.capture.mp.telephoto = 12 + } + } + } +} + diff --git a/Malachite/Views/SettingsView/SettingsView+UI.swift b/Malachite/Views/SettingsView/SettingsView+UI.swift new file mode 100644 index 0000000..b59bc0d --- /dev/null +++ b/Malachite/Views/SettingsView/SettingsView+UI.swift @@ -0,0 +1,190 @@ +// +// SettingsView+UI.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension SettingsView { + struct UserInterface: View { + /// A State variable used for determining whether or not to enable exposure and focus POI on tap and hold. + @State private var poiTapAndHold = Int() + /// A State variable used for determining whether or not to enable the system's auto locking APIs. + @State private var idleTimerDisabled = Bool() + /// A State variable used for determining whether or not to enabel haptics. + @State private var hapticsDisabled = Bool() + /// A State variable used for determining whether or not to start the app with the UI hidden. + @State private var appStartsUIHidden = Bool() + /// A State vairable used for determining whether or not to enable pinch to zoom and the tap gesture while the UI is hidden. + @State private var uiHiderGestures = Int() + /// A State variable used for determining whether or not this view is being presented as a modal. + var dismissAction: (() -> Void) + /// A variable to hold the existing instance of ``MalachiteClassesObject``. + var utilities = MalachiteClassesObject() + + init( + utilities: MalachiteClassesObject, + dismissAction: @escaping (() -> Void) + ) { + self.utilities = utilities + self.dismissAction = dismissAction + } + + var body: some View { + Section(header: Text("settings.header.ui")) { + MalachiteCellViewUtils( + icon: "hand.tap", + disabled: nil, + dangerous: false) + { + Picker("settings.option.ui.tapgesture", selection: $poiTapAndHold) { + Text("settings.option.reusable.aeaf") + .tag(0) + Text("settings.option.reusable.af") + .tag(1) + Text("settings.option.reusable.ae") + .tag(2) + Text("settings.option.reusable.off") + .tag(3) + } + } + MalachiteCellViewUtils( + icon: "hand.raised.slash", + disabled: nil, + dangerous: false) + { + Picker("settings.option.ui.hiddengestures", selection: $uiHiderGestures) { + Text("settings.option.reusable.pinchzoomtapandhold") + .tag(0) + Text("settings.option.reusable.pinchzoom") + .tag(1) + Text("settings.option.reusable.tapandhold") + .tag(2) + Text("settings.option.reusable.off") + .tag(3) + } + } + MalachiteCellViewUtils( + icon: "clock", + disabled: nil, + dangerous: false) + { + Toggle("settings.option.ui.idletimer", isOn: $idleTimerDisabled) + } + MalachiteCellViewUtils( + icon: "iphone.radiowaves.left.and.right", + disabled: nil, + dangerous: false) + { + Toggle("settings.option.ui.haptics", isOn: $hapticsDisabled) + } + MalachiteCellViewUtils( + icon: "eye.slash", + disabled: nil, + dangerous: false) + { + Toggle("settings.option.ui.hiddenonlaunch", isOn: $appStartsUIHidden) + } + } + .onAppear(perform: self.onAppear) + .onDisappear(perform: self.onDisappear) + .onChange(of: poiTapAndHold) {_ in + switch poiTapAndHold { + case 0: + utilities.preferences.userInterface.tapAndHold = ["ae", "af"] + case 1: + utilities.preferences.userInterface.tapAndHold = ["af"] + case 2: + utilities.preferences.userInterface.tapAndHold = ["ae"] + default: + utilities.preferences.userInterface.tapAndHold = ["off"] + } + } + .onChange(of: uiHiderGestures) {_ in + switch uiHiderGestures { + case 0: + utilities.preferences.userInterface.hiddenControls = ["zoom", "tah"] + case 1: + utilities.preferences.userInterface.hiddenControls = ["zoom"] + case 2: + utilities.preferences.userInterface.hiddenControls = ["tah"] + default: + utilities.preferences.userInterface.hiddenControls = ["off"] + } + + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.aeafTapGestureNotification.name, object: nil) + } + .onChange(of: idleTimerDisabled) { _ in + utilities.preferences.userInterface.idleTimerDisabled = idleTimerDisabled + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.idleTimerNotification.name, object: nil) + } + .onChange(of: hapticsDisabled) { _ in + utilities.preferences.userInterface.hapticFeedback = hapticsDisabled + } + .onChange(of: appStartsUIHidden) { _ in + utilities.preferences.userInterface.appLaunch = appStartsUIHidden + } + } + + func onAppear() { + idleTimerDisabled = utilities.preferences.userInterface.idleTimerDisabled + hapticsDisabled = utilities.preferences.userInterface.hapticFeedback + appStartsUIHidden = utilities.preferences.userInterface.appLaunch + + switch utilities.preferences.userInterface.tapAndHold { + case ["ae", "af"]: + poiTapAndHold = 0 + case ["af"]: + poiTapAndHold = 1 + case ["ae"]: + poiTapAndHold = 2 + default: + poiTapAndHold = 3 + } + + switch utilities.preferences.userInterface.hiddenControls { + case ["zoom", "tah"]: + uiHiderGestures = 0 + case ["zoom"]: + uiHiderGestures = 1 + case ["tah"]: + uiHiderGestures = 2 + default: + uiHiderGestures = 3 + } + } + + func onDisappear() { + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.aeafTapGestureNotification.name, object: nil) + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.idleTimerNotification.name, object: nil) + + utilities.preferences.userInterface.idleTimerDisabled = idleTimerDisabled + utilities.preferences.userInterface.hapticFeedback = hapticsDisabled + utilities.preferences.userInterface.appLaunch = appStartsUIHidden + + switch poiTapAndHold { + case 0: + utilities.preferences.userInterface.tapAndHold = ["ae", "af"] + case 1: + utilities.preferences.userInterface.tapAndHold = ["af"] + case 2: + utilities.preferences.userInterface.tapAndHold = ["ae"] + default: + utilities.preferences.userInterface.tapAndHold = ["off"] + } + + switch uiHiderGestures { + case 0: + utilities.preferences.userInterface.hiddenControls = ["zoom", "tah"] + case 1: + utilities.preferences.userInterface.hiddenControls = ["zoom"] + case 2: + utilities.preferences.userInterface.hiddenControls = ["tah"] + default: + utilities.preferences.userInterface.hiddenControls = ["off"] + } + } + } +} diff --git a/Malachite/Views/SettingsView/SettingsView+Watermarking.swift b/Malachite/Views/SettingsView/SettingsView+Watermarking.swift new file mode 100644 index 0000000..bd0fbb6 --- /dev/null +++ b/Malachite/Views/SettingsView/SettingsView+Watermarking.swift @@ -0,0 +1,71 @@ +// +// SettingsView+Watermarking.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension SettingsView { + struct Watermarking: View { + /// A State variable used for determining whether or not watermarking is enabled. + @State private var watermarkSwitch = false + /// A State variable used for determining the current watermark string. + @State private var watermarkText = String() + /// A State variable used for determining whether or not this view is being presented as a modal. + var dismissAction: (() -> Void) + /// A variable to hold the existing instance of ``MalachiteClassesObject``. + var utilities = MalachiteClassesObject() + + init( + utilities: MalachiteClassesObject, + dismissAction: @escaping (() -> Void) + ) { + self.utilities = utilities + self.dismissAction = dismissAction + } + + var body: some View { + Section(header: Text("settings.header.watermark")) { + MalachiteCellViewUtils( + icon: "textformat", + disabled: nil, + dangerous: false) + { + Toggle("settings.option.watermark.enable", isOn: $watermarkSwitch) + } + + MalachiteCellViewUtils( + icon: "signature", + disabled: nil, + dangerous: false) + { + Text("settings.option.watermark.text") + TextField("settings.option.watermark.text.placeholder", text: $watermarkText) + .multilineTextAlignment(.trailing) + .autocorrectionDisabled() + .keyboardType(.twitter) + } + } + .onAppear(perform: self.onAppear) + .onDisappear(perform: self.onDisappear) + .onChange(of: watermarkSwitch) {_ in + utilities.preferences.watermark.enabled = watermarkSwitch + } + .onChange(of: watermarkText) {_ in + utilities.preferences.watermark.text = watermarkText.isEmpty ? "Shot with Malachite" : String(watermarkText.prefix(65)) + } + } + + func onAppear() { + watermarkText = utilities.preferences.watermark.text + watermarkSwitch = utilities.preferences.watermark.enabled + } + + func onDisappear() { + utilities.preferences.watermark.enabled = watermarkSwitch + utilities.preferences.watermark.text = watermarkText.isEmpty ? "Shot with Malachite" : String(watermarkText.prefix(65)) + } + } +} diff --git a/Malachite/Views/SettingsView/SettingsView.swift b/Malachite/Views/SettingsView/SettingsView.swift index 6f9cf7a..98c1561 100755 --- a/Malachite/Views/SettingsView/SettingsView.swift +++ b/Malachite/Views/SettingsView/SettingsView.swift @@ -8,56 +8,10 @@ import SwiftUI struct SettingsView: View { - /// A State variable used for determining whether or not watermarking is enabled. - @State private var watermarkSwitch = false - /// A State variable used for determining the current watermark string. - @State private var watermarkText = String() - /// A State variable used for determining the active photo format. - @State private var photoFormat = Int() - /// A State variable used for determining the current aspect ratio for the ``cameraPreview``. - @State private var previewAspect = Int() - /// A State variable used for determining whether or not to stabilize the ``cameraPreview``. - @State private var shouldStabilize = Bool() - /// A State variable used for determining whether or not to skip opening ``MalachitePhotoPreview`` and save the photo. - @State private var shouldUseFastPath = Bool() - /// A State variable used for determining what the maximum zoom level for each camera should be. - @State private var zoomMaximum = Int() - /// A State variable used for determining whether or not to capture in HDR. - @State private var hdrSwitch = false - /// A State variable used for determining whether or not to enable continuous auto exposure. - @State private var continuousAEAF = Int() - /// A State variable used for determining how many fingers are used for the settings gesture. - @State private var settingsGestureFingers = Int() - /// A State variable used for determining whether or not to enable exposure and focus POI on tap and hold. - @State private var poiTapAndHold = Int() - /// A State variable used for determining whether or not to enable the system's auto locking APIs. - @State private var idleTimerDisabled = Bool() - /// A State variable used for determining whether or not to enabel haptics. - @State private var hapticsDisabled = Bool() - /// A State variable used for determining whether or not to start the app with the UI hidden. - @State private var appStartsUIHidden = Bool() - /// A State vairable used for determining whether or not to enable pinch to zoom and the tap gesture while the UI is hidden. - @State private var uiHiderGestures = Int() - /// A State variable used for determining whether or not the device supports HDR capture in its current mode. - @State private var supportsHDR = Bool() - /// A State variable used for determining whether or not the device supports HEIC capture. - @State private var supportsHEIC = Bool() - /// A State variable used for presenting the user with a footer based on capabilities. - @State private var formatFooterText: String? /// A State variable used for determining whether or not debug logging UserDefaults is enabled. @State private var debugLoggingUserDefaults = false /// A State variable used for determining whether or not to literally break the app. @State private var breakApp = false - /// A State variable used for determining what megapixel count the ultrawide camera should shoot in. - @State private var ultrawideMegapixelCount = Int() - /// A State variable used for determining what megapixel count the wide angle camera should shoot in. - @State private var wideMegapixelCount = Int() - /// A State variable used for determining what megapixel count the telephoto camera should shoot in. - @State private var telephotoMegapixelCount = Int() - /// A State variable used for determining whether or not a view is being presented. - @State var presentingAboutModal = false - /// A State variable used for determining whether or not a view is being presented. - @State var presentingCompatibilityModal = false /// A variable to hold the existing instance of ``MalachiteClassesObject``. var utilities = MalachiteClassesObject() /// A variable used to hold the function for dismissing with the toolbar item. @@ -74,631 +28,43 @@ struct SettingsView: View { - Toolbar item for dismissing the view. */ var body: some View { - MalachiteNagivationViewUtils() { guts } - } - - var guts: some View { - Form { - aboutSection - previewSettingsSection - resolutionSettingsSection - photoSettingsSection - watermarkSettingsSection - uiSettingsSection - if utilities.versionType == "DEBUG" { - DeveloperView.Settings(utilities: utilities) - } - } - .onAppear { onAppear() } - .onDisappear { onDisappear() } - .navigationTitle("view.title.settings") - .toolbar(content: { - ToolbarItemGroup(placement: .topBarLeading) { - NavigationLink(destination: Help(dismissAction: dismissAction)) { - if #available(iOS 26.0, *) { - Image(systemName: "questionmark.circle") - } else { - Image(systemName: "questionmark.circle").tint(.primary) - } - } - } - ToolbarItemGroup(placement: .topBarTrailing) { - if #available(iOS 26.0, *) { - Button { - self.dismissAction() - } label: { - Image(systemName: "checkmark") - .tint(.primary) - } - .buttonStyle(.glassProminent) - } else { - Button { - self.dismissAction() - } label: { - Image(systemName: "checkmark.circle") - } - } - } - }) - } - - /// A variable to hold the about section. - var aboutSection: some View { - Section { - MalachiteCellViewUtils( - icon: "info.circle", - disabled: nil, - dangerous: false) - { - NavigationLink(destination: MalachiteAboutView(dismissAction: dismissAction)) { - Text("view.title.about") - } - } - if utilities.versionType == "INTERNAL" { - MalachiteCellViewUtils( - icon: "checkmark.seal", - disabled: nil, - dangerous: false) - { - NavigationLink(destination: MalachiteCompatibilityView(dismissAction: dismissAction, utilities: utilities)) { - Text("view.title.compatibility") - } - } - MalachiteCellViewUtils( - icon: "wrench.and.screwdriver", - disabled: nil, - dangerous: false) - { - NavigationLink(destination: DeveloperView(dismissAction: dismissAction)) { - Text("view.title.developer") - } - } - } - } - } - - /// A variable to hold the preview settings section. - var previewSettingsSection: some View { - Section(header: Text("settings.header.preview")) { - MalachiteCellViewUtils( - icon: "aspectratio", - disabled: false, - dangerous: false) - { - Picker("settings.option.preview.aspect_ratio", selection: $previewAspect) { - Text("settings.option.preview.aspect_ratio.fit") - .tag(0) - Text("settings.option.preview.aspect_ratio.fill") - .tag(1) - } - } - MalachiteCellViewUtils( - icon: "level", - disabled: nil, - dangerous: false) - { - Toggle("settings.option.preview.sbtlz", isOn: $shouldStabilize) - } - // Testing things out - if utilities.versionType == "INTERNAL" { - MalachiteCellViewUtils( - icon: "forward", - disabled: nil, - dangerous: false) - { - Toggle("settings.option.preview.fastpath", isOn: $shouldUseFastPath) - } - } - MalachiteCellViewUtils( - icon: "plus.magnifyingglass", - disabled: nil, - dangerous: false) - { - Stepper { - Text("settings.option.preview.zoom_maximum") - } onIncrement: { - if zoomMaximum < 10 { zoomMaximum += 1 } - } onDecrement: { - if zoomMaximum > 5 { zoomMaximum -= 1 } - } - Text(String(format: "%lldx", Int(zoomMaximum))) - .font(.footnote) - .foregroundColor(.gray) - } - } - .onChange(of: previewAspect) {_ in - utilities.preferences.preview.aspect = (previewAspect == 1) - - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.aspectFillNotification.name, object: nil) - } - .onChange(of: shouldStabilize) {_ in - utilities.preferences.preview.stablize = shouldStabilize - - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.stabilizerNotification.name, object: nil) - } - .onChange(of: shouldUseFastPath) {_ in - utilities.preferences.preview.fastPath = shouldUseFastPath - } - .onChange(of: zoomMaximum) {_ in - utilities.preferences.capture.maximumZoom = zoomMaximum - } - } - - /// A variable to hold the image resolution section. - var resolutionSettingsSection: some View { - Section(header: Text("settings.header.resolution")) { - if utilities.preferences.ext.dictionary.isValid(dictionary: utilities.preferences.compatibility.ultrawide) { - MalachiteCellViewUtils( - icon: "camera.aperture", - disabled: utilities.preferences.ext.dictionary.getCount(dictionary: utilities.preferences.compatibility.ultrawide) == 1, - dangerous: false) - { - Picker("settings.option.resolution.ultrawide", selection: $ultrawideMegapixelCount) { - if let mp = utilities.preferences.compatibility.ultrawide["8"] { if mp { - Text("settings.option.resolution.8") - .tag(0) - } } - if let mp = utilities.preferences.compatibility.ultrawide["12"] { if mp { - Text("settings.option.resolution.12") - .tag(1) - } } - if let mp = utilities.preferences.compatibility.ultrawide["48"] { if mp { - Text("settings.option.resolution.48") - .tag(2) - } } - } - } - } - if utilities.preferences.ext.dictionary.isValid(dictionary: utilities.preferences.compatibility.wideangle) { - MalachiteCellViewUtils( - icon: "camera.aperture", - disabled: utilities.preferences.ext.dictionary.getCount(dictionary: utilities.preferences.compatibility.wideangle) == 1, - dangerous: false) - { - Picker("settings.option.resolution.wide", selection: $wideMegapixelCount) { - if let mp = utilities.preferences.compatibility.wideangle["8"] { if mp { - Text("settings.option.resolution.8") - .tag(0) - } } - if let mp = utilities.preferences.compatibility.wideangle["12"] { if mp { - Text("settings.option.resolution.12") - .tag(1) - } } - if let mp = utilities.preferences.compatibility.wideangle["48"] { if mp { - Text("settings.option.resolution.48") - .tag(2) - } } + MalachiteNagivationViewUtils() { + Form { + About(utilities: utilities, dismissAction: dismissAction) + Preview(utilities: utilities, dismissAction: dismissAction) + Resolution(utilities: utilities, dismissAction: dismissAction) + Photo(utilities: utilities, dismissAction: dismissAction) + Watermarking(utilities: utilities, dismissAction: dismissAction) + UserInterface(utilities: utilities, dismissAction: dismissAction) + if utilities.versionType == "DEBUG" { DeveloperView.Settings(utilities: utilities) } + } + .onAppear { onAppear() } + .onDisappear { onDisappear() } + .navigationTitle("view.title.settings") + .toolbar( +content: { + ToolbarItemGroup(placement: .topBarLeading) { + NavigationLink(destination: QuickHelpView(utilities: utilities, dismissAction: dismissAction)) { + if #available(iOS 26.0, *) { + Image(systemName: "questionmark.circle") + } else { + Image(systemName: "questionmark.circle").tint(.primary) + } } } - } - if utilities.preferences.ext.dictionary.isValid(dictionary: utilities.preferences.compatibility.telephoto) { - MalachiteCellViewUtils( - icon: "camera.aperture", - disabled: utilities.preferences.ext.dictionary.getCount(dictionary: utilities.preferences.compatibility.telephoto) == 1, - dangerous: false) - { - Picker("settings.option.resolution.telephoto", selection: $telephotoMegapixelCount) { - if let mp = utilities.preferences.compatibility.wideangle["48"] { if mp { - Text("settings.option.resolution.12") - .tag(0) - } } - } + ToolbarItemGroup(placement: .topBarTrailing) { + MalachiteToolbarUtils(action: self.dismissAction, image: "checkmark", primary: true) } - } - } - .onChange(of: ultrawideMegapixelCount) { _ in - switch ultrawideMegapixelCount { - case 1: - utilities.preferences.capture.mp.ultrawide = 12 - case 2: - utilities.preferences.capture.mp.ultrawide = 48 - default: - utilities.preferences.capture.mp.ultrawide = 8 - } - } - .onChange(of: wideMegapixelCount) { _ in - switch wideMegapixelCount { - case 1: - utilities.preferences.capture.mp.wideangle = 12 - case 2: - utilities.preferences.capture.mp.wideangle = 48 - default: - utilities.preferences.capture.mp.wideangle = 8 - } - } - .onChange(of: telephotoMegapixelCount) { _ in - switch telephotoMegapixelCount { - default: - utilities.preferences.capture.mp.telephoto = 12 - } - } - } - - /// A variable to hold the photo settings section. - var photoSettingsSection: some View { - Section(header: Text("settings.header.photo"), footer: (formatFooterText != nil) ? Text(formatFooterText!) : nil) { - MalachiteCellViewUtils( - icon: "square.and.arrow.down", - disabled: !supportsHEIC, - dangerous: false) - { - Picker("settings.option.photo.fileformat", selection: $photoFormat) { - Text("settings.option.photo.fileformat.jpeg") - .tag(0) - Text("settings.option.photo.fileformat.heif") - .tag(1) - } - } - - MalachiteCellViewUtils( - icon: "camera.filters", - disabled: !supportsHDR, - dangerous: false) - { - Toggle("settings.option.photo.hdr", isOn: $hdrSwitch) - } - MalachiteCellViewUtils( - icon: "plus.viewfinder", - disabled: nil, - dangerous: false) - { - Picker("settings.option.photo.continuous", selection: $continuousAEAF) { - Text("settings.option.reusable.aeaf") - .tag(0) - Text("settings.option.reusable.af") - .tag(1) - Text("settings.option.reusable.ae") - .tag(2) - Text("settings.option.reusable.off") - .tag(3) - } - } - } - .onChange(of: photoFormat) {_ in - utilities.preferences.capture.format.jpeg = photoFormat == 0 ? true : false - utilities.preferences.capture.format.heic = photoFormat == 1 ? true : false - } - .onChange(of: hdrSwitch) { _ in - utilities.preferences.capture.hdr = hdrSwitch - } - .onChange(of: continuousAEAF) { _ in - switch continuousAEAF { - case 0: - utilities.preferences.capture.continuous = ["ae", "af"] - case 1: - utilities.preferences.capture.continuous = ["af"] - case 2: - utilities.preferences.capture.continuous = ["ae"] - default: - utilities.preferences.capture.continuous = ["off"] - } - - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.continousAEAFNotification.name, object: nil) - } - } - - /// A variable to hold the watermark settings section. - var watermarkSettingsSection: some View { - Section(header: Text("settings.header.watermark")) { - MalachiteCellViewUtils( - icon: "textformat", - disabled: nil, - dangerous: false) - { - Toggle("settings.option.watermark.enable", isOn: $watermarkSwitch) - } - - MalachiteCellViewUtils( - icon: "signature", - disabled: nil, - dangerous: false) - { - Text("settings.option.watermark.text") - TextField("settings.option.watermark.text.placeholder", text: $watermarkText) - .multilineTextAlignment(.trailing) - .autocorrectionDisabled() - .keyboardType(.twitter) - } - } - .onChange(of: watermarkSwitch) {_ in - utilities.preferences.watermark.enabled = watermarkSwitch - } - .onChange(of: watermarkText) {_ in - utilities.preferences.watermark.text = watermarkText.isEmpty ? "Shot with Malachite" : String(watermarkText.prefix(65)) - } - } - - /// A variable to hold settings related to the user interface - var uiSettingsSection: some View { - Section(header: Text("settings.header.ui")) { - MalachiteCellViewUtils( - icon: "hand.tap", - disabled: nil, - dangerous: false) - { - Picker("settings.option.ui.tapgesture", selection: $poiTapAndHold) { - Text("settings.option.reusable.aeaf") - .tag(0) - Text("settings.option.reusable.af") - .tag(1) - Text("settings.option.reusable.ae") - .tag(2) - Text("settings.option.reusable.off") - .tag(3) - } - } - if utilities.versionType == "INTERNAL" { - MalachiteCellViewUtils( - icon: "hand.draw", - disabled: nil, - dangerous: false) - { - Picker("settings.option.ui.settingsgesture", selection: $settingsGestureFingers) { - Text("settings.option.ui.settingsgesture.1") - .tag(1) - Text("settings.option.ui.settingsgesture.2") - .tag(2) - Text("settings.option.ui.settingsgesture.3") - .tag(3) - } - } - } - MalachiteCellViewUtils( - icon: "hand.raised.slash", - disabled: nil, - dangerous: false) - { - Picker("settings.option.ui.hiddengestures", selection: $uiHiderGestures) { - Text("settings.option.reusable.pinchzoomtapandhold") - .tag(0) - Text("settings.option.reusable.pinchzoom") - .tag(1) - Text("settings.option.reusable.tapandhold") - .tag(2) - Text("settings.option.reusable.off") - .tag(3) - } - } - MalachiteCellViewUtils( - icon: "clock", - disabled: nil, - dangerous: false) - { - Toggle("settings.option.ui.idletimer", isOn: $idleTimerDisabled) - } - MalachiteCellViewUtils( - icon: "iphone.radiowaves.left.and.right", - disabled: nil, - dangerous: false) - { - Toggle("settings.option.ui.haptics", isOn: $hapticsDisabled) - } - MalachiteCellViewUtils( - icon: "eye.slash", - disabled: nil, - dangerous: false) - { - Toggle("settings.option.ui.hiddenonlaunch", isOn: $appStartsUIHidden) - } - } - .onChange(of: poiTapAndHold) {_ in - switch poiTapAndHold { - case 0: - utilities.preferences.userInterface.tapAndHold = ["ae", "af"] - case 1: - utilities.preferences.userInterface.tapAndHold = ["af"] - case 2: - utilities.preferences.userInterface.tapAndHold = ["ae"] - default: - utilities.preferences.userInterface.tapAndHold = ["off"] - } - } - .onChange(of: settingsGestureFingers) {_ in - utilities.preferences.evaintrnl.settingsGesture = settingsGestureFingers - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.settingsGestureNotification.name, object: nil) - } - .onChange(of: uiHiderGestures) {_ in - switch uiHiderGestures { - case 0: - utilities.preferences.userInterface.hiddenControls = ["zoom", "tah"] - case 1: - utilities.preferences.userInterface.hiddenControls = ["zoom"] - case 2: - utilities.preferences.userInterface.hiddenControls = ["tah"] - default: - utilities.preferences.userInterface.hiddenControls = ["off"] - } - - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.aeafTapGestureNotification.name, object: nil) - } - .onChange(of: idleTimerDisabled) { _ in - utilities.preferences.userInterface.idleTimerDisabled = idleTimerDisabled - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.idleTimerNotification.name, object: nil) - } - .onChange(of: hapticsDisabled) { _ in - utilities.preferences.userInterface.hapticFeedback = hapticsDisabled - } - .onChange(of: appStartsUIHidden) { _ in - utilities.preferences.userInterface.appLaunch = appStartsUIHidden + }) } } func onAppear() { - supportsHDR = utilities.function.supportsHDR - supportsHEIC = utilities.function.supportsHEIC() - - if !supportsHEIC { - formatFooterText = "settings.footer.photo.heif".localized - } - - if !supportsHDR { - formatFooterText = (formatFooterText != nil) ? formatFooterText! + "settings.footer.photo.hdr".localized : "settings.footer.photo.hdr".localized - } - - shouldStabilize = utilities.preferences.preview.stablize - shouldUseFastPath = utilities.preferences.preview.fastPath - zoomMaximum = utilities.preferences.capture.maximumZoom - previewAspect = utilities.preferences.preview.aspect ? 1 : 0 - - switch utilities.preferences.capture.mp.ultrawide { - case 12: - ultrawideMegapixelCount = 1 - case 48: - ultrawideMegapixelCount = 2 - default: - ultrawideMegapixelCount = 0 - } - - switch utilities.preferences.capture.mp.wideangle { - case 12: - wideMegapixelCount = 1 - case 48: - wideMegapixelCount = 2 - default: - wideMegapixelCount = 0 - } - - switch utilities.preferences.capture.mp.telephoto { - default: - telephotoMegapixelCount = 0 - } - - hdrSwitch = utilities.preferences.capture.hdr - - // TODO: Better way to do this - photoFormat = utilities.preferences.capture.format.jpeg ? 0 : 1 - photoFormat = utilities.preferences.capture.format.heic ? 1 : 0 - - switch utilities.preferences.capture.continuous { - case ["ae", "af"]: - continuousAEAF = 0 - case ["af"]: - continuousAEAF = 1 - case ["ae"]: - continuousAEAF = 2 - default: - continuousAEAF = 3 - } - - watermarkText = utilities.preferences.watermark.text - watermarkSwitch = utilities.preferences.watermark.enabled - - settingsGestureFingers = utilities.preferences.evaintrnl.settingsGesture - idleTimerDisabled = utilities.preferences.userInterface.idleTimerDisabled - hapticsDisabled = utilities.preferences.userInterface.hapticFeedback - appStartsUIHidden = utilities.preferences.userInterface.appLaunch - - switch utilities.preferences.userInterface.tapAndHold { - case ["ae", "af"]: - poiTapAndHold = 0 - case ["af"]: - poiTapAndHold = 1 - case ["ae"]: - poiTapAndHold = 2 - default: - poiTapAndHold = 3 - } - - switch utilities.preferences.userInterface.hiddenControls { - case ["zoom", "tah"]: - uiHiderGestures = 0 - case ["zoom"]: - uiHiderGestures = 1 - case ["tah"]: - uiHiderGestures = 2 - default: - uiHiderGestures = 3 - } - debugLoggingUserDefaults = utilities.preferences.debug.logging.preferences breakApp = utilities.preferences.debug.breakApp } func onDisappear() { - utilities.preferences.preview.aspect = (previewAspect == 1) - utilities.preferences.preview.stablize = shouldStabilize - utilities.preferences.preview.fastPath = shouldUseFastPath - utilities.preferences.capture.maximumZoom = zoomMaximum - - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.aspectFillNotification.name, object: nil) - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.stabilizerNotification.name, object: nil) - - switch ultrawideMegapixelCount { - case 1: - utilities.preferences.capture.mp.ultrawide = 12 - case 2: - utilities.preferences.capture.mp.ultrawide = 48 - default: - utilities.preferences.capture.mp.ultrawide = 8 - } - - switch wideMegapixelCount { - case 1: - utilities.preferences.capture.mp.wideangle = 12 - case 2: - utilities.preferences.capture.mp.wideangle = 48 - default: - utilities.preferences.capture.mp.wideangle = 8 - } - - switch telephotoMegapixelCount { - default: - utilities.preferences.capture.mp.telephoto = 12 - } - - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.megaPixelSwitchNotification.name, object: nil) - - utilities.preferences.capture.hdr = hdrSwitch - utilities.preferences.capture.format.jpeg = photoFormat == 0 ? true : false - utilities.preferences.capture.format.heic = photoFormat == 1 ? true : false - - switch continuousAEAF { - case 0: - utilities.preferences.capture.continuous = ["ae", "af"] - case 1: - utilities.preferences.capture.continuous = ["af"] - case 2: - utilities.preferences.capture.continuous = ["ae"] - default: - utilities.preferences.capture.continuous = ["off"] - } - - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.continousAEAFNotification.name, object: nil) - - utilities.preferences.watermark.enabled = watermarkSwitch - utilities.preferences.watermark.text = watermarkText.isEmpty ? "Shot with Malachite" : String(watermarkText.prefix(65)) - - utilities.preferences.evaintrnl.settingsGesture = settingsGestureFingers - utilities.preferences.userInterface.idleTimerDisabled = idleTimerDisabled - utilities.preferences.userInterface.hapticFeedback = hapticsDisabled - utilities.preferences.userInterface.appLaunch = appStartsUIHidden - - switch poiTapAndHold { - case 0: - utilities.preferences.userInterface.tapAndHold = ["ae", "af"] - case 1: - utilities.preferences.userInterface.tapAndHold = ["af"] - case 2: - utilities.preferences.userInterface.tapAndHold = ["ae"] - default: - utilities.preferences.userInterface.tapAndHold = ["off"] - } - - switch uiHiderGestures { - case 0: - utilities.preferences.userInterface.hiddenControls = ["zoom", "tah"] - case 1: - utilities.preferences.userInterface.hiddenControls = ["zoom"] - case 2: - utilities.preferences.userInterface.hiddenControls = ["tah"] - default: - utilities.preferences.userInterface.hiddenControls = ["off"] - } - - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.settingsGestureNotification.name, object: nil) - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.aeafTapGestureNotification.name, object: nil) - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.idleTimerNotification.name, object: nil) - utilities.preferences.debug.logging.preferences = debugLoggingUserDefaults utilities.preferences.debug.breakApp = breakApp } From 36ea3d3768dc4500ce6167eedc9d02adde1fafd2 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Sat, 16 Aug 2025 17:51:27 -0600 Subject: [PATCH 05/66] Massive changes - Refactor SettingsView into multiple files - Refactor SettingsView+Help into QuickHelpView/ - Move internal settings to DeveloperView+Internal - Add Quick Help entries for Compatibility and Developer --- Malachite.xcodeproj/project.pbxproj | 186 ++++- Malachite/Localizable.xcstrings | 30 + Malachite/Utilities/MalachiteViewUtils.swift | 37 + .../DeveloperView+Internal.swift | 59 ++ .../DeveloperView+Settings.swift | 8 +- .../Views/DeveloperView/DeveloperView.swift | 17 +- Malachite/Views/MalachiteAboutView.swift | 16 +- .../Views/MalachiteCompatibilityView.swift | 16 +- .../QuickHelpView/QuickHelpView+About.swift | 27 + .../QuickHelpView/QuickHelpView+Photo.swift | 21 + .../QuickHelpView/QuickHelpView+Preview.swift | 21 + .../QuickHelpView+Resolution.swift | 21 + .../QuickHelpView/QuickHelpView+UI.swift | 23 + .../QuickHelpView+Watermarking.swift | 20 + .../Views/QuickHelpView/QuickHelpView.swift | 87 +++ .../SettingsView/SettingsView+About.swift | 59 ++ .../SettingsView/SettingsView+Help.swift | 143 ---- .../SettingsView/SettingsView+Photo.swift | 150 ++++ .../SettingsView/SettingsView+Preview.swift | 123 ++++ .../SettingsView+Resolution.swift | 185 +++++ .../Views/SettingsView/SettingsView+UI.swift | 190 +++++ .../SettingsView+Watermarking.swift | 71 ++ .../Views/SettingsView/SettingsView.swift | 684 +----------------- 23 files changed, 1315 insertions(+), 879 deletions(-) create mode 100644 Malachite/Views/DeveloperView/DeveloperView+Internal.swift create mode 100644 Malachite/Views/QuickHelpView/QuickHelpView+About.swift create mode 100644 Malachite/Views/QuickHelpView/QuickHelpView+Photo.swift create mode 100644 Malachite/Views/QuickHelpView/QuickHelpView+Preview.swift create mode 100644 Malachite/Views/QuickHelpView/QuickHelpView+Resolution.swift create mode 100644 Malachite/Views/QuickHelpView/QuickHelpView+UI.swift create mode 100644 Malachite/Views/QuickHelpView/QuickHelpView+Watermarking.swift create mode 100755 Malachite/Views/QuickHelpView/QuickHelpView.swift create mode 100644 Malachite/Views/SettingsView/SettingsView+About.swift delete mode 100755 Malachite/Views/SettingsView/SettingsView+Help.swift create mode 100644 Malachite/Views/SettingsView/SettingsView+Photo.swift create mode 100644 Malachite/Views/SettingsView/SettingsView+Preview.swift create mode 100644 Malachite/Views/SettingsView/SettingsView+Resolution.swift create mode 100644 Malachite/Views/SettingsView/SettingsView+UI.swift create mode 100644 Malachite/Views/SettingsView/SettingsView+Watermarking.swift diff --git a/Malachite.xcodeproj/project.pbxproj b/Malachite.xcodeproj/project.pbxproj index 6436d10..3666afe 100755 --- a/Malachite.xcodeproj/project.pbxproj +++ b/Malachite.xcodeproj/project.pbxproj @@ -21,7 +21,7 @@ 786DAE272E4F28A600BE3137 /* MalachiteAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1C2E4F28A600BE3137 /* MalachiteAboutView.swift */; }; 786DAE282E4F28A600BE3137 /* MalachiteCompatibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1D2E4F28A600BE3137 /* MalachiteCompatibilityView.swift */; }; 786DAE292E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1E2E4F28A600BE3137 /* MalachitePhotoPreview.swift */; }; - 786DAE2A2E4F28A600BE3137 /* SettingsView+Help.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1F2E4F28A600BE3137 /* SettingsView+Help.swift */; }; + 786DAE2A2E4F28A600BE3137 /* QuickHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1F2E4F28A600BE3137 /* QuickHelpView.swift */; }; 786DAE2B2E4F28A600BE3137 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE202E4F28A600BE3137 /* SettingsView.swift */; }; 786DAE2C2E4F28A600BE3137 /* MalachiteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE212E4F28A600BE3137 /* MalachiteView.swift */; }; 786DAE2D2E4F28A600BE3137 /* DeveloperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE172E4F28A600BE3137 /* DeveloperView.swift */; }; @@ -31,7 +31,7 @@ 786DAE312E4F28A600BE3137 /* MalachiteAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1C2E4F28A600BE3137 /* MalachiteAboutView.swift */; }; 786DAE322E4F28A600BE3137 /* MalachiteCompatibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1D2E4F28A600BE3137 /* MalachiteCompatibilityView.swift */; }; 786DAE332E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1E2E4F28A600BE3137 /* MalachitePhotoPreview.swift */; }; - 786DAE342E4F28A600BE3137 /* SettingsView+Help.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1F2E4F28A600BE3137 /* SettingsView+Help.swift */; }; + 786DAE342E4F28A600BE3137 /* QuickHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1F2E4F28A600BE3137 /* QuickHelpView.swift */; }; 786DAE352E4F28A600BE3137 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE202E4F28A600BE3137 /* SettingsView.swift */; }; 786DAE362E4F28A600BE3137 /* MalachiteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE212E4F28A600BE3137 /* MalachiteView.swift */; }; 786DAE372E4F28A600BE3137 /* DeveloperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE172E4F28A600BE3137 /* DeveloperView.swift */; }; @@ -41,7 +41,7 @@ 786DAE3B2E4F28A600BE3137 /* MalachiteAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1C2E4F28A600BE3137 /* MalachiteAboutView.swift */; }; 786DAE3C2E4F28A600BE3137 /* MalachiteCompatibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1D2E4F28A600BE3137 /* MalachiteCompatibilityView.swift */; }; 786DAE3D2E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1E2E4F28A600BE3137 /* MalachitePhotoPreview.swift */; }; - 786DAE3E2E4F28A600BE3137 /* SettingsView+Help.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1F2E4F28A600BE3137 /* SettingsView+Help.swift */; }; + 786DAE3E2E4F28A600BE3137 /* QuickHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1F2E4F28A600BE3137 /* QuickHelpView.swift */; }; 786DAE3F2E4F28A600BE3137 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE202E4F28A600BE3137 /* SettingsView.swift */; }; 786DAE402E4F28A600BE3137 /* MalachiteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE212E4F28A600BE3137 /* MalachiteView.swift */; }; 786DAE4C2E4F28B100BE3137 /* MalachitePreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE412E4F28B100BE3137 /* MalachitePreferences.swift */; }; @@ -82,6 +82,45 @@ 786DAE7D2E4F28BF00BE3137 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE792E4F28BF00BE3137 /* ContentView.swift */; }; 786DAE7E2E4F28BF00BE3137 /* MalachiteWatchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE7B2E4F28BF00BE3137 /* MalachiteWatchApp.swift */; }; 787B1CAA2B8B095E000AFECC /* Malachite.docc in Sources */ = {isa = PBXBuildFile; fileRef = 787B1CA92B8B095E000AFECC /* Malachite.docc */; }; + 788262602E513080000085AC /* SettingsView+About.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882625F2E51307B000085AC /* SettingsView+About.swift */; }; + 788262612E513080000085AC /* SettingsView+About.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882625F2E51307B000085AC /* SettingsView+About.swift */; }; + 788262622E513080000085AC /* SettingsView+About.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882625F2E51307B000085AC /* SettingsView+About.swift */; }; + 788262642E51309C000085AC /* SettingsView+Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262632E513094000085AC /* SettingsView+Preview.swift */; }; + 788262652E51309C000085AC /* SettingsView+Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262632E513094000085AC /* SettingsView+Preview.swift */; }; + 788262662E51309C000085AC /* SettingsView+Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262632E513094000085AC /* SettingsView+Preview.swift */; }; + 7882626C2E5130C1000085AC /* SettingsView+Resolution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882626B2E5130BB000085AC /* SettingsView+Resolution.swift */; }; + 7882626D2E5130C1000085AC /* SettingsView+Resolution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882626B2E5130BB000085AC /* SettingsView+Resolution.swift */; }; + 7882626E2E5130C1000085AC /* SettingsView+Resolution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882626B2E5130BB000085AC /* SettingsView+Resolution.swift */; }; + 788262702E5130CA000085AC /* SettingsView+Photo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882626F2E5130C6000085AC /* SettingsView+Photo.swift */; }; + 788262712E5130CA000085AC /* SettingsView+Photo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882626F2E5130C6000085AC /* SettingsView+Photo.swift */; }; + 788262722E5130CA000085AC /* SettingsView+Photo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882626F2E5130C6000085AC /* SettingsView+Photo.swift */; }; + 788262742E5130D4000085AC /* SettingsView+Watermarking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262732E5130CF000085AC /* SettingsView+Watermarking.swift */; }; + 788262752E5130D4000085AC /* SettingsView+Watermarking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262732E5130CF000085AC /* SettingsView+Watermarking.swift */; }; + 788262762E5130D4000085AC /* SettingsView+Watermarking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262732E5130CF000085AC /* SettingsView+Watermarking.swift */; }; + 788262782E5130DB000085AC /* SettingsView+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262772E5130D8000085AC /* SettingsView+UI.swift */; }; + 788262792E5130DB000085AC /* SettingsView+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262772E5130D8000085AC /* SettingsView+UI.swift */; }; + 7882627A2E5130DB000085AC /* SettingsView+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262772E5130D8000085AC /* SettingsView+UI.swift */; }; + 7882627C2E514750000085AC /* DeveloperView+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882627B2E514749000085AC /* DeveloperView+Internal.swift */; }; + 7882627D2E514750000085AC /* DeveloperView+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882627B2E514749000085AC /* DeveloperView+Internal.swift */; }; + 7882627E2E514750000085AC /* DeveloperView+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882627B2E514749000085AC /* DeveloperView+Internal.swift */; }; + 788262852E514F12000085AC /* QuickHelpView+About.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262842E514F0C000085AC /* QuickHelpView+About.swift */; }; + 788262862E514F12000085AC /* QuickHelpView+About.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262842E514F0C000085AC /* QuickHelpView+About.swift */; }; + 788262872E514F12000085AC /* QuickHelpView+About.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262842E514F0C000085AC /* QuickHelpView+About.swift */; }; + 788262892E514F18000085AC /* QuickHelpView+Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262882E514F14000085AC /* QuickHelpView+Preview.swift */; }; + 7882628A2E514F18000085AC /* QuickHelpView+Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262882E514F14000085AC /* QuickHelpView+Preview.swift */; }; + 7882628B2E514F18000085AC /* QuickHelpView+Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262882E514F14000085AC /* QuickHelpView+Preview.swift */; }; + 7882628D2E514F25000085AC /* QuickHelpView+Resolution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882628C2E514F1F000085AC /* QuickHelpView+Resolution.swift */; }; + 7882628E2E514F25000085AC /* QuickHelpView+Resolution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882628C2E514F1F000085AC /* QuickHelpView+Resolution.swift */; }; + 7882628F2E514F25000085AC /* QuickHelpView+Resolution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882628C2E514F1F000085AC /* QuickHelpView+Resolution.swift */; }; + 788262912E514F2D000085AC /* QuickHelpView+Photo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262902E514F28000085AC /* QuickHelpView+Photo.swift */; }; + 788262922E514F2D000085AC /* QuickHelpView+Photo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262902E514F28000085AC /* QuickHelpView+Photo.swift */; }; + 788262932E514F2D000085AC /* QuickHelpView+Photo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262902E514F28000085AC /* QuickHelpView+Photo.swift */; }; + 788262952E514F38000085AC /* QuickHelpView+Watermarking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262942E514F31000085AC /* QuickHelpView+Watermarking.swift */; }; + 788262962E514F38000085AC /* QuickHelpView+Watermarking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262942E514F31000085AC /* QuickHelpView+Watermarking.swift */; }; + 788262972E514F38000085AC /* QuickHelpView+Watermarking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262942E514F31000085AC /* QuickHelpView+Watermarking.swift */; }; + 788262992E514F3F000085AC /* QuickHelpView+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262982E514F3B000085AC /* QuickHelpView+UI.swift */; }; + 7882629A2E514F3F000085AC /* QuickHelpView+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262982E514F3B000085AC /* QuickHelpView+UI.swift */; }; + 7882629B2E514F3F000085AC /* QuickHelpView+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262982E514F3B000085AC /* QuickHelpView+UI.swift */; }; 78917F5C2B99AE72005E10FA /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78917F5B2B99AE72005E10FA /* WidgetKit.framework */; }; 78917F5E2B99AE72005E10FA /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78917F5D2B99AE72005E10FA /* SwiftUI.framework */; }; 78917F692B99AE73005E10FA /* WidgetBundle.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 78917F592B99AE72005E10FA /* WidgetBundle.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -196,7 +235,7 @@ 786DAE1C2E4F28A600BE3137 /* MalachiteAboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteAboutView.swift; sourceTree = ""; }; 786DAE1D2E4F28A600BE3137 /* MalachiteCompatibilityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteCompatibilityView.swift; sourceTree = ""; }; 786DAE1E2E4F28A600BE3137 /* MalachitePhotoPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachitePhotoPreview.swift; sourceTree = ""; }; - 786DAE1F2E4F28A600BE3137 /* SettingsView+Help.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsView+Help.swift"; sourceTree = ""; }; + 786DAE1F2E4F28A600BE3137 /* QuickHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickHelpView.swift; sourceTree = ""; }; 786DAE202E4F28A600BE3137 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 786DAE212E4F28A600BE3137 /* MalachiteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteView.swift; sourceTree = ""; }; 786DAE412E4F28B100BE3137 /* MalachitePreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachitePreferences.swift; sourceTree = ""; }; @@ -219,6 +258,19 @@ 786DAE7B2E4F28BF00BE3137 /* MalachiteWatchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteWatchApp.swift; sourceTree = ""; }; 787B1CA92B8B095E000AFECC /* Malachite.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = Malachite.docc; sourceTree = ""; }; 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = Malachite/Localizable.xcstrings; sourceTree = SOURCE_ROOT; }; + 7882625F2E51307B000085AC /* SettingsView+About.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsView+About.swift"; sourceTree = ""; }; + 788262632E513094000085AC /* SettingsView+Preview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsView+Preview.swift"; sourceTree = ""; }; + 7882626B2E5130BB000085AC /* SettingsView+Resolution.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsView+Resolution.swift"; sourceTree = ""; }; + 7882626F2E5130C6000085AC /* SettingsView+Photo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsView+Photo.swift"; sourceTree = ""; }; + 788262732E5130CF000085AC /* SettingsView+Watermarking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsView+Watermarking.swift"; sourceTree = ""; }; + 788262772E5130D8000085AC /* SettingsView+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsView+UI.swift"; sourceTree = ""; }; + 7882627B2E514749000085AC /* DeveloperView+Internal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DeveloperView+Internal.swift"; sourceTree = ""; }; + 788262842E514F0C000085AC /* QuickHelpView+About.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QuickHelpView+About.swift"; sourceTree = ""; }; + 788262882E514F14000085AC /* QuickHelpView+Preview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QuickHelpView+Preview.swift"; sourceTree = ""; }; + 7882628C2E514F1F000085AC /* QuickHelpView+Resolution.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QuickHelpView+Resolution.swift"; sourceTree = ""; }; + 788262902E514F28000085AC /* QuickHelpView+Photo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QuickHelpView+Photo.swift"; sourceTree = ""; }; + 788262942E514F31000085AC /* QuickHelpView+Watermarking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QuickHelpView+Watermarking.swift"; sourceTree = ""; }; + 788262982E514F3B000085AC /* QuickHelpView+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QuickHelpView+UI.swift"; sourceTree = ""; }; 788589912B8974680018E2DA /* Malachite.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Malachite.entitlements; sourceTree = ""; }; 78917F592B99AE72005E10FA /* WidgetBundle.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetBundle.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 78917F5B2B99AE72005E10FA /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; @@ -230,40 +282,43 @@ /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ - 786DADA02E4F283500BE3137 /* Exceptions for "Assets" folder in "Malachite" target */ = { + 7882625A2E512B3A000085AC /* Exceptions for "Assets" folder in "Malachite" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( - AppIcon.icon, - Assets.xcassets, + MalachiteIconMasked_Darwin24.png, + MalachiteIconMasked_Darwin25.png, ); target = 785F08482B12D41100244EB4 /* Malachite */; }; - 786DADA12E4F283500BE3137 /* Exceptions for "Assets" folder in "MalachiteWatch" target */ = { + 7882625B2E512B3A000085AC /* Exceptions for "Assets" folder in "MalachiteWatch" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( - AppIcon.icon, - Assets.xcassets, + MalachiteIconMasked_Darwin24.png, + MalachiteIconMasked_Darwin25.png, ); target = 78B72BA62E33282E002E2D4E /* MalachiteWatch */; }; - 786DADA22E4F283500BE3137 /* Exceptions for "Assets" folder in "WidgetBundle" target */ = { + 7882625C2E512B3A000085AC /* Exceptions for "Assets" folder in "WidgetBundle" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( - Assets.xcassets, + MalachiteIconMasked_Darwin24.png, + MalachiteIconMasked_Darwin25.png, ); target = 78917F582B99AE72005E10FA /* WidgetBundle */; }; - 786DADA32E4F283500BE3137 /* Exceptions for "Assets" folder in "WidgetBundleWatch" target */ = { + 7882625D2E512B3A000085AC /* Exceptions for "Assets" folder in "WidgetBundleWatch" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( - Assets.xcassets, + MalachiteIconMasked_Darwin24.png, + MalachiteIconMasked_Darwin25.png, ); target = 7837C1042E34C45B009396B0 /* WidgetBundleWatch */; }; - 786DADA42E4F283500BE3137 /* Exceptions for "Assets" folder in "CaptureBundle" target */ = { + 7882625E2E512B3A000085AC /* Exceptions for "Assets" folder in "CaptureBundle" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( - Assets.xcassets, + MalachiteIconMasked_Darwin24.png, + MalachiteIconMasked_Darwin25.png, ); target = 78A9DD602CD55551002C131D /* CaptureBundle */; }; @@ -283,11 +338,11 @@ 786DAD982E4F283500BE3137 /* Assets */ = { isa = PBXFileSystemSynchronizedRootGroup; exceptions = ( - 786DADA02E4F283500BE3137 /* Exceptions for "Assets" folder in "Malachite" target */, - 786DADA12E4F283500BE3137 /* Exceptions for "Assets" folder in "MalachiteWatch" target */, - 786DADA22E4F283500BE3137 /* Exceptions for "Assets" folder in "WidgetBundle" target */, - 786DADA32E4F283500BE3137 /* Exceptions for "Assets" folder in "WidgetBundleWatch" target */, - 786DADA42E4F283500BE3137 /* Exceptions for "Assets" folder in "CaptureBundle" target */, + 7882625A2E512B3A000085AC /* Exceptions for "Assets" folder in "Malachite" target */, + 7882625B2E512B3A000085AC /* Exceptions for "Assets" folder in "MalachiteWatch" target */, + 7882625C2E512B3A000085AC /* Exceptions for "Assets" folder in "WidgetBundle" target */, + 7882625D2E512B3A000085AC /* Exceptions for "Assets" folder in "WidgetBundleWatch" target */, + 7882625E2E512B3A000085AC /* Exceptions for "Assets" folder in "CaptureBundle" target */, ); path = Assets; sourceTree = ""; @@ -383,9 +438,10 @@ isa = PBXGroup; children = ( 786DAE172E4F28A600BE3137 /* DeveloperView.swift */, + 786DAE1A2E4F28A600BE3137 /* DeveloperView+Settings.swift */, + 7882627B2E514749000085AC /* DeveloperView+Internal.swift */, 786DAE182E4F28A600BE3137 /* DeveloperView+BuildInfo.swift */, 786DAE192E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift */, - 786DAE1A2E4F28A600BE3137 /* DeveloperView+Settings.swift */, ); path = DeveloperView; sourceTree = ""; @@ -393,12 +449,13 @@ 786DAE222E4F28A600BE3137 /* Views */ = { isa = PBXGroup; children = ( - 786DAE802E4F28D600BE3137 /* SettingsView */, - 786DAE1B2E4F28A600BE3137 /* DeveloperView */, + 7882627F2E514CA8000085AC /* QuickHelpView */, 786DAE1C2E4F28A600BE3137 /* MalachiteAboutView.swift */, 786DAE1D2E4F28A600BE3137 /* MalachiteCompatibilityView.swift */, 786DAE1E2E4F28A600BE3137 /* MalachitePhotoPreview.swift */, 786DAE212E4F28A600BE3137 /* MalachiteView.swift */, + 786DAE802E4F28D600BE3137 /* SettingsView */, + 786DAE1B2E4F28A600BE3137 /* DeveloperView */, ); path = Views; sourceTree = ""; @@ -461,11 +518,30 @@ isa = PBXGroup; children = ( 786DAE202E4F28A600BE3137 /* SettingsView.swift */, - 786DAE1F2E4F28A600BE3137 /* SettingsView+Help.swift */, + 7882625F2E51307B000085AC /* SettingsView+About.swift */, + 788262632E513094000085AC /* SettingsView+Preview.swift */, + 7882626B2E5130BB000085AC /* SettingsView+Resolution.swift */, + 7882626F2E5130C6000085AC /* SettingsView+Photo.swift */, + 788262732E5130CF000085AC /* SettingsView+Watermarking.swift */, + 788262772E5130D8000085AC /* SettingsView+UI.swift */, ); path = SettingsView; sourceTree = ""; }; + 7882627F2E514CA8000085AC /* QuickHelpView */ = { + isa = PBXGroup; + children = ( + 786DAE1F2E4F28A600BE3137 /* QuickHelpView.swift */, + 788262842E514F0C000085AC /* QuickHelpView+About.swift */, + 788262882E514F14000085AC /* QuickHelpView+Preview.swift */, + 7882628C2E514F1F000085AC /* QuickHelpView+Resolution.swift */, + 788262902E514F28000085AC /* QuickHelpView+Photo.swift */, + 788262942E514F31000085AC /* QuickHelpView+Watermarking.swift */, + 788262982E514F3B000085AC /* QuickHelpView+UI.swift */, + ); + path = QuickHelpView; + sourceTree = ""; + }; 78917F5A2B99AE72005E10FA /* Frameworks */ = { isa = PBXGroup; children = ( @@ -491,6 +567,9 @@ ); dependencies = ( ); + fileSystemSynchronizedGroups = ( + 786DAD982E4F283500BE3137 /* Assets */, + ); name = WidgetBundleWatch; productName = MalachiteWidgetBundle; productReference = 7837C1152E34C45B009396B0 /* WidgetBundleWatch.appex */; @@ -514,6 +593,9 @@ 78A9DD692CD55551002C131D /* PBXTargetDependency */, 78B72BB02E332830002E2D4E /* PBXTargetDependency */, ); + fileSystemSynchronizedGroups = ( + 786DAD982E4F283500BE3137 /* Assets */, + ); name = Malachite; productName = Malachite; productReference = 785F08492B12D41100244EB4 /* Malachite.app */; @@ -531,6 +613,9 @@ ); dependencies = ( ); + fileSystemSynchronizedGroups = ( + 786DAD982E4F283500BE3137 /* Assets */, + ); name = WidgetBundle; productName = MalachiteWidgetBundle; productReference = 78917F592B99AE72005E10FA /* WidgetBundle.appex */; @@ -548,6 +633,9 @@ ); dependencies = ( ); + fileSystemSynchronizedGroups = ( + 786DAD982E4F283500BE3137 /* Assets */, + ); name = CaptureBundle; packageProductDependencies = ( ); @@ -571,6 +659,9 @@ 7837C1022E34BD60009396B0 /* PBXTargetDependency */, 7837C1192E34C47D009396B0 /* PBXTargetDependency */, ); + fileSystemSynchronizedGroups = ( + 786DAD982E4F283500BE3137 /* Assets */, + ); name = MalachiteWatch; packageProductDependencies = ( ); @@ -728,25 +819,38 @@ files = ( 786DAE552E4F28B100BE3137 /* MalachitePreferences.swift in Sources */, 786DAE562E4F28B100BE3137 /* MalachitePreferencesUtils.swift in Sources */, + 7882628A2E514F18000085AC /* QuickHelpView+Preview.swift in Sources */, 786DAE572E4F28B100BE3137 /* MalachiteFunctionUtils.swift in Sources */, + 788262932E514F2D000085AC /* QuickHelpView+Photo.swift in Sources */, 786DAE582E4F28B100BE3137 /* MalachiteGameUtils.swift in Sources */, 786DAE592E4F28B100BE3137 /* MalachiteHapticUtils.swift in Sources */, 786DAE5A2E4F28B100BE3137 /* MalachiteIntentUtils.swift in Sources */, + 788262622E513080000085AC /* SettingsView+About.swift in Sources */, 786DAE5B2E4F28B100BE3137 /* MalachiteTooltipUtils.swift in Sources */, 786DAE5C2E4F28B100BE3137 /* MalachiteUtils.swift in Sources */, 786DAE5D2E4F28B100BE3137 /* MalachiteViewUtils.swift in Sources */, + 788262722E5130CA000085AC /* SettingsView+Photo.swift in Sources */, 786DAE2D2E4F28A600BE3137 /* DeveloperView.swift in Sources */, 786DAE2E2E4F28A600BE3137 /* DeveloperView+BuildInfo.swift in Sources */, + 7882627A2E5130DB000085AC /* SettingsView+UI.swift in Sources */, 786DAE2F2E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift in Sources */, 786DAE302E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */, 786DAE312E4F28A600BE3137 /* MalachiteAboutView.swift in Sources */, 786DAE322E4F28A600BE3137 /* MalachiteCompatibilityView.swift in Sources */, + 7882627E2E514750000085AC /* DeveloperView+Internal.swift in Sources */, 786DAE332E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */, - 786DAE342E4F28A600BE3137 /* SettingsView+Help.swift in Sources */, + 786DAE342E4F28A600BE3137 /* QuickHelpView.swift in Sources */, + 788262862E514F12000085AC /* QuickHelpView+About.swift in Sources */, + 7882626E2E5130C1000085AC /* SettingsView+Resolution.swift in Sources */, 786DAE352E4F28A600BE3137 /* SettingsView.swift in Sources */, + 7882628F2E514F25000085AC /* QuickHelpView+Resolution.swift in Sources */, + 7882629A2E514F3F000085AC /* QuickHelpView+UI.swift in Sources */, + 788262762E5130D4000085AC /* SettingsView+Watermarking.swift in Sources */, 786DAE362E4F28A600BE3137 /* MalachiteView.swift in Sources */, 785F084D2B12D41100244EB4 /* AppDelegate.swift in Sources */, + 788262962E514F38000085AC /* QuickHelpView+Watermarking.swift in Sources */, 787B1CAA2B8B095E000AFECC /* Malachite.docc in Sources */, + 788262662E51309C000085AC /* SettingsView+Preview.swift in Sources */, 785F084F2B12D41100244EB4 /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -757,25 +861,38 @@ files = ( 786DAE5E2E4F28B100BE3137 /* MalachitePreferences.swift in Sources */, 786DAE5F2E4F28B100BE3137 /* MalachitePreferencesUtils.swift in Sources */, + 788262892E514F18000085AC /* QuickHelpView+Preview.swift in Sources */, 786DAE602E4F28B100BE3137 /* MalachiteFunctionUtils.swift in Sources */, + 788262922E514F2D000085AC /* QuickHelpView+Photo.swift in Sources */, 786DAE612E4F28B100BE3137 /* MalachiteGameUtils.swift in Sources */, 786DAE622E4F28B100BE3137 /* MalachiteHapticUtils.swift in Sources */, 786DAE632E4F28B100BE3137 /* MalachiteIntentUtils.swift in Sources */, + 788262612E513080000085AC /* SettingsView+About.swift in Sources */, 786DAE642E4F28B100BE3137 /* MalachiteTooltipUtils.swift in Sources */, 786DAE652E4F28B100BE3137 /* MalachiteUtils.swift in Sources */, 786DAE662E4F28B100BE3137 /* MalachiteViewUtils.swift in Sources */, + 788262702E5130CA000085AC /* SettingsView+Photo.swift in Sources */, 786DAE372E4F28A600BE3137 /* DeveloperView.swift in Sources */, 786DAE382E4F28A600BE3137 /* DeveloperView+BuildInfo.swift in Sources */, + 788262792E5130DB000085AC /* SettingsView+UI.swift in Sources */, 786DAE392E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift in Sources */, 786DAE6D2E4F28B700BE3137 /* ControlCenterWidget.swift in Sources */, 786DAE6E2E4F28B700BE3137 /* LockScreenWidget.swift in Sources */, 786DAE6F2E4F28B700BE3137 /* MalachiteWidgetBundle.swift in Sources */, + 7882627D2E514750000085AC /* DeveloperView+Internal.swift in Sources */, 786DAE3A2E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */, 786DAE3B2E4F28A600BE3137 /* MalachiteAboutView.swift in Sources */, + 788262852E514F12000085AC /* QuickHelpView+About.swift in Sources */, + 7882626D2E5130C1000085AC /* SettingsView+Resolution.swift in Sources */, 786DAE3C2E4F28A600BE3137 /* MalachiteCompatibilityView.swift in Sources */, + 7882628E2E514F25000085AC /* QuickHelpView+Resolution.swift in Sources */, + 788262992E514F3F000085AC /* QuickHelpView+UI.swift in Sources */, + 788262742E5130D4000085AC /* SettingsView+Watermarking.swift in Sources */, 786DAE3D2E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */, - 786DAE3E2E4F28A600BE3137 /* SettingsView+Help.swift in Sources */, + 786DAE3E2E4F28A600BE3137 /* QuickHelpView.swift in Sources */, + 788262952E514F38000085AC /* QuickHelpView+Watermarking.swift in Sources */, 786DAE3F2E4F28A600BE3137 /* SettingsView.swift in Sources */, + 788262652E51309C000085AC /* SettingsView+Preview.swift in Sources */, 786DAE402E4F28A600BE3137 /* MalachiteView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -785,23 +902,36 @@ buildActionMask = 2147483647; files = ( 786DAE232E4F28A600BE3137 /* DeveloperView.swift in Sources */, + 788262782E5130DB000085AC /* SettingsView+UI.swift in Sources */, 786DAE242E4F28A600BE3137 /* DeveloperView+BuildInfo.swift in Sources */, 786DAE252E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift in Sources */, + 788262602E513080000085AC /* SettingsView+About.swift in Sources */, 786DAE4C2E4F28B100BE3137 /* MalachitePreferences.swift in Sources */, + 788262972E514F38000085AC /* QuickHelpView+Watermarking.swift in Sources */, + 788262752E5130D4000085AC /* SettingsView+Watermarking.swift in Sources */, + 7882627C2E514750000085AC /* DeveloperView+Internal.swift in Sources */, 786DAE4D2E4F28B100BE3137 /* MalachitePreferencesUtils.swift in Sources */, + 788262642E51309C000085AC /* SettingsView+Preview.swift in Sources */, 786DAE782E4F28B900BE3137 /* MalachiteCaptureBundle.swift in Sources */, 786DAE4E2E4F28B100BE3137 /* MalachiteFunctionUtils.swift in Sources */, + 7882629B2E514F3F000085AC /* QuickHelpView+UI.swift in Sources */, 786DAE4F2E4F28B100BE3137 /* MalachiteGameUtils.swift in Sources */, + 7882626C2E5130C1000085AC /* SettingsView+Resolution.swift in Sources */, 786DAE502E4F28B100BE3137 /* MalachiteHapticUtils.swift in Sources */, 786DAE512E4F28B100BE3137 /* MalachiteIntentUtils.swift in Sources */, 786DAE522E4F28B100BE3137 /* MalachiteTooltipUtils.swift in Sources */, 786DAE532E4F28B100BE3137 /* MalachiteUtils.swift in Sources */, 786DAE542E4F28B100BE3137 /* MalachiteViewUtils.swift in Sources */, + 788262872E514F12000085AC /* QuickHelpView+About.swift in Sources */, 786DAE262E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */, + 788262912E514F2D000085AC /* QuickHelpView+Photo.swift in Sources */, 786DAE272E4F28A600BE3137 /* MalachiteAboutView.swift in Sources */, 786DAE282E4F28A600BE3137 /* MalachiteCompatibilityView.swift in Sources */, 786DAE292E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */, - 786DAE2A2E4F28A600BE3137 /* SettingsView+Help.swift in Sources */, + 788262712E5130CA000085AC /* SettingsView+Photo.swift in Sources */, + 7882628D2E514F25000085AC /* QuickHelpView+Resolution.swift in Sources */, + 786DAE2A2E4F28A600BE3137 /* QuickHelpView.swift in Sources */, + 7882628B2E514F18000085AC /* QuickHelpView+Preview.swift in Sources */, 786DAE2B2E4F28A600BE3137 /* SettingsView.swift in Sources */, 786DAE2C2E4F28A600BE3137 /* MalachiteView.swift in Sources */, ); diff --git a/Malachite/Localizable.xcstrings b/Malachite/Localizable.xcstrings index 801efc4..46a6994 100755 --- a/Malachite/Localizable.xcstrings +++ b/Malachite/Localizable.xcstrings @@ -701,6 +701,16 @@ } } }, + "developer.header.internal" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Internal settings" + } + } + } + }, "developer.option.debug.breakapp" : { "localizations" : { "en" : { @@ -1536,6 +1546,26 @@ } } }, + "view.detail.compatibility" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lists various features and capabilities of Malachite that your device can take advantage of, and ones it can't." + } + } + } + }, + "view.detail.developer" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Extra settings and information, intended for people working on Malachite’s code." + } + } + } + }, "view.title.about" : { "localizations" : { "en" : { diff --git a/Malachite/Utilities/MalachiteViewUtils.swift b/Malachite/Utilities/MalachiteViewUtils.swift index b616363..133920d 100755 --- a/Malachite/Utilities/MalachiteViewUtils.swift +++ b/Malachite/Utilities/MalachiteViewUtils.swift @@ -282,6 +282,7 @@ struct MalachiteCompatibilityViewUtils: View { } } +// Really Eva? What the fuck is a "Nagivation" struct MalachiteNagivationViewUtils: View { let content: Content @@ -303,9 +304,45 @@ struct MalachiteNagivationViewUtils: View { } } +// temp, will be redone +struct MalachiteToolbarUtils: View { + let action: () -> Void + let image: String + let primary: Bool + + init( + action: @escaping (() -> Void), + image: String, + primary: Bool + ) { + self.action = action + self.image = image + self.primary = primary + } + + @ViewBuilder + var body: some View { + if #available(iOS 26.0, *) { + Button { + self.action() + } label: { + Image(systemName: image).tint(primary ? .primary : nil) + } + .buttonStyle(.glassProminent) + } else { + Button { + self.action() + } label: { + Image(systemName: "\(image).circle").tint(primary ? .primary : nil) + } + } + } +} + /// Needed for the alerts to properly display their localized strings extension String { var localized: String { return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "") } } + diff --git a/Malachite/Views/DeveloperView/DeveloperView+Internal.swift b/Malachite/Views/DeveloperView/DeveloperView+Internal.swift new file mode 100644 index 0000000..5404c9f --- /dev/null +++ b/Malachite/Views/DeveloperView/DeveloperView+Internal.swift @@ -0,0 +1,59 @@ +// +// DeveloperView+Internal.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension DeveloperView { + struct InternalSettings: View { + /// A State variable used for determining how many fingers are used for the settings gesture. + @State private var settingsGestureFingers = Int() + + var utilities: MalachiteClassesObject + + init( + utilities: MalachiteClassesObject + ) { + self.utilities = utilities + } + + var body: some View { + Section(header: Text("developer.header.internal")) { + MalachiteCellViewUtils( + icon: "hand.draw", + disabled: nil, + dangerous: false) + { + Picker("settings.option.ui.settingsgesture", selection: $settingsGestureFingers) { + Text("settings.option.ui.settingsgesture.1") + .tag(1) + Text("settings.option.ui.settingsgesture.2") + .tag(2) + Text("settings.option.ui.settingsgesture.3") + .tag(3) + } + } + } + .onAppear(perform: onAppear) + .onDisappear(perform: onDisappear) + .onChange(of: settingsGestureFingers) {_ in + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.settingsGestureNotification.name, object: nil) + + utilities.preferences.evaintrnl.settingsGesture = settingsGestureFingers + } + } + + func onAppear() { + settingsGestureFingers = utilities.preferences.evaintrnl.settingsGesture + } + + func onDisappear() { + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.settingsGestureNotification.name, object: nil) + + utilities.preferences.evaintrnl.settingsGesture = settingsGestureFingers + } + } +} diff --git a/Malachite/Views/DeveloperView/DeveloperView+Settings.swift b/Malachite/Views/DeveloperView/DeveloperView+Settings.swift index 6cabab0..b92335b 100644 --- a/Malachite/Views/DeveloperView/DeveloperView+Settings.swift +++ b/Malachite/Views/DeveloperView/DeveloperView+Settings.swift @@ -9,6 +9,10 @@ import SwiftUI extension DeveloperView { struct Settings: View { + @State private var debugLoggingUserDefaults = false + /// A State variable used for determining whether or not to literally break the app. + @State private var breakApp = false + var utilities: MalachiteClassesObject init( @@ -17,10 +21,6 @@ extension DeveloperView { self.utilities = utilities } - @State private var debugLoggingUserDefaults = false - /// A State variable used for determining whether or not to literally break the app. - @State private var breakApp = false - var body: some View { Section { MalachiteCellViewUtils( diff --git a/Malachite/Views/DeveloperView/DeveloperView.swift b/Malachite/Views/DeveloperView/DeveloperView.swift index 92974b6..79d2555 100644 --- a/Malachite/Views/DeveloperView/DeveloperView.swift +++ b/Malachite/Views/DeveloperView/DeveloperView.swift @@ -16,27 +16,14 @@ struct DeveloperView: View { var body: some View { Form { Settings(utilities: utilities) + if utilities.versionType == "INTERNAL" { InternalSettings(utilities: utilities)} BuildInfo(utilities: utilities) DeviceInfo(utilities: utilities) } .navigationTitle("view.title.developer") .toolbar(content: { ToolbarItemGroup(placement: .topBarTrailing) { - if #available(iOS 26.0, *) { - Button { - self.dismissAction() - } label: { - Image(systemName: "checkmark") - .tint(.primary) - } - .buttonStyle(.glassProminent) - } else { - Button { - self.dismissAction() - } label: { - Image(systemName: "checkmark.circle") - } - } + MalachiteToolbarUtils(action: self.dismissAction, image: "checkmark", primary: true) } }) } diff --git a/Malachite/Views/MalachiteAboutView.swift b/Malachite/Views/MalachiteAboutView.swift index aa11b37..fbbe248 100755 --- a/Malachite/Views/MalachiteAboutView.swift +++ b/Malachite/Views/MalachiteAboutView.swift @@ -75,21 +75,7 @@ struct MalachiteAboutView: View { .navigationTitle("view.title.about") .toolbar(content: { ToolbarItemGroup(placement: .topBarTrailing) { - if #available(iOS 26.0, *) { - Button { - self.dismissAction() - } label: { - Image(systemName: "checkmark") - .tint(.primary) - } - .buttonStyle(.glassProminent) - } else { - Button { - self.dismissAction() - } label: { - Image(systemName: "checkmark.circle") - } - } + MalachiteToolbarUtils(action: self.dismissAction, image: "checkmark", primary: true) } }) } diff --git a/Malachite/Views/MalachiteCompatibilityView.swift b/Malachite/Views/MalachiteCompatibilityView.swift index c3db225..e17f7aa 100755 --- a/Malachite/Views/MalachiteCompatibilityView.swift +++ b/Malachite/Views/MalachiteCompatibilityView.swift @@ -53,21 +53,7 @@ public struct MalachiteCompatibilityView: View { .navigationTitle("view.title.compatibility") .toolbar(content: { ToolbarItemGroup(placement: .topBarTrailing) { - if #available(iOS 26.0, *) { - Button { - self.dismissAction() - } label: { - Image(systemName: "checkmark") - .tint(.primary) - } - .buttonStyle(.glassProminent) - } else { - Button { - self.dismissAction() - } label: { - Image(systemName: "checkmark.circle") - } - } + MalachiteToolbarUtils(action: self.dismissAction, image: "checkmark", primary: true) } }) } diff --git a/Malachite/Views/QuickHelpView/QuickHelpView+About.swift b/Malachite/Views/QuickHelpView/QuickHelpView+About.swift new file mode 100644 index 0000000..6a40b83 --- /dev/null +++ b/Malachite/Views/QuickHelpView/QuickHelpView+About.swift @@ -0,0 +1,27 @@ +// +// QuickHelpView+About.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension QuickHelpView { + struct About: View { + var utilities = MalachiteClassesObject() + + init(utilities: MalachiteClassesObject ) { self.utilities = utilities } + + /// A variable to hold the about section. + var body: some View { + Section { + Builder(title: Text("view.title.about"), subtitle: Text("view.detail.about")) {} + if utilities.versionType == "INTERNAL" { + Builder(title: Text("view.title.compatibility"), subtitle: Text("view.detail.compatibility")) {} + Builder(title: Text("view.title.developer"), subtitle: Text("view.detail.developer")) {} + } + } + } + } +} diff --git a/Malachite/Views/QuickHelpView/QuickHelpView+Photo.swift b/Malachite/Views/QuickHelpView/QuickHelpView+Photo.swift new file mode 100644 index 0000000..19f1bf8 --- /dev/null +++ b/Malachite/Views/QuickHelpView/QuickHelpView+Photo.swift @@ -0,0 +1,21 @@ +// +// QuickHelpView+Photo.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension QuickHelpView { + struct Photo: View { + /// A variable to hold the photo settings section. + var body: some View { + Section(header: Text("settings.header.photo"), footer: Text("settings.footer.photo")) { + Builder(title: Text("settings.option.photo.fileformat"), subtitle: Text("settings.detail.photo.fileformat")) {} + Builder(title: Text("settings.option.photo.hdr"), subtitle: Text("settings.detail.photo.hdr")) {} + Builder(title: Text("settings.option.photo.continuous"), subtitle: Text("settings.detail.photo.continuous")) {} + } + } + } +} diff --git a/Malachite/Views/QuickHelpView/QuickHelpView+Preview.swift b/Malachite/Views/QuickHelpView/QuickHelpView+Preview.swift new file mode 100644 index 0000000..b4d2bb2 --- /dev/null +++ b/Malachite/Views/QuickHelpView/QuickHelpView+Preview.swift @@ -0,0 +1,21 @@ +// +// QuickHelpView+Preview.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension QuickHelpView { + struct Preview: View { + /// A variable to hold the preview settings section. + var body: some View { + Section(header: Text("settings.header.preview"), footer: Text("settings.footer.preview")) { + Builder(title: Text("settings.option.preview.aspect_ratio"), subtitle: Text("settings.detail.preview.aspect_ratio")) {} + Builder(title: Text("settings.option.preview.sbtlz"), subtitle: Text("settings.detail.preview.sbtlz")) {} + Builder(title: Text("settings.option.preview.zoom_maximum"), subtitle: Text("settings.detail.preview.zoom_maximum")) {} + } + } + } +} diff --git a/Malachite/Views/QuickHelpView/QuickHelpView+Resolution.swift b/Malachite/Views/QuickHelpView/QuickHelpView+Resolution.swift new file mode 100644 index 0000000..270de99 --- /dev/null +++ b/Malachite/Views/QuickHelpView/QuickHelpView+Resolution.swift @@ -0,0 +1,21 @@ +// +// QuickHelpView+Resolution.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension QuickHelpView { + struct Resolution: View { + /// A variable to hold the image resolution section. + var body: some View { + Section(header: Text("settings.header.resolution"), footer: Text("settings.footer.resolution")) { + Builder(title: Text("settings.option.resolution.ultrawide"), subtitle: Text("settings.detail.resolution.ultrawide")) {} + Builder(title: Text("settings.option.resolution.wide"), subtitle: Text("settings.detail.resolution.wide")) {} + Builder(title: Text("settings.option.resolution.telephoto"), subtitle: Text("settings.detail.resolution.telephoto")) {} + } + } + } +} diff --git a/Malachite/Views/QuickHelpView/QuickHelpView+UI.swift b/Malachite/Views/QuickHelpView/QuickHelpView+UI.swift new file mode 100644 index 0000000..aa91e21 --- /dev/null +++ b/Malachite/Views/QuickHelpView/QuickHelpView+UI.swift @@ -0,0 +1,23 @@ +// +// QuickHelpView+UI.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension QuickHelpView { + struct UserInterface: View { + /// A variable to hold the user interface settings section. + var body: some View { + Section(header: Text("settings.header.ui"), footer: Text("settings.footer.ui")) { + Builder(title: Text("settings.option.ui.tapgesture"), subtitle: Text("settings.detail.ui.tapgesture")) {} + Builder(title: Text("settings.option.ui.hiddengestures"), subtitle: Text("settings.detail.ui.hiddengestures")) {} + Builder(title: Text("settings.option.ui.idletimer"), subtitle: Text("settings.detail.ui.idletimer")) {} + Builder(title: Text("settings.option.ui.haptics"), subtitle: Text("settings.detail.ui.haptics")) {} + Builder(title: Text("settings.option.ui.hiddenonlaunch"), subtitle: Text("settings.detail.ui.hiddenonlaunch")) {} + } + } + } +} diff --git a/Malachite/Views/QuickHelpView/QuickHelpView+Watermarking.swift b/Malachite/Views/QuickHelpView/QuickHelpView+Watermarking.swift new file mode 100644 index 0000000..8942761 --- /dev/null +++ b/Malachite/Views/QuickHelpView/QuickHelpView+Watermarking.swift @@ -0,0 +1,20 @@ +// +// QuickHelpView+Watermarking.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension QuickHelpView { + struct Watermarking: View { + /// A variable to hold the watermark settings section. + var body: some View { + Section(header: Text("settings.header.watermark"), footer: Text("settings.footer.watermark")) { + Builder(title: Text("settings.option.watermark.enable"), subtitle: Text("settings.detail.watermark.enable")) {} + Builder(title: Text("settings.option.watermark.text"), subtitle: Text("settings.detail.watermark.text")) {} + } + } + } +} diff --git a/Malachite/Views/QuickHelpView/QuickHelpView.swift b/Malachite/Views/QuickHelpView/QuickHelpView.swift new file mode 100755 index 0000000..190da0f --- /dev/null +++ b/Malachite/Views/QuickHelpView/QuickHelpView.swift @@ -0,0 +1,87 @@ +// +// SettingsView+Help.swift +// Malachite +// +// Created by Eva Isabella Luna on 3/14/24. +// + +import SwiftUI + +struct QuickHelpView: View { + /// A State variable used for determining whether or not this view is being presented as a modal. + var dismissAction: (() -> Void) + /// A variable to hold the existing instance of ``MalachiteClassesObject``. + var utilities = MalachiteClassesObject() + + init( + utilities: MalachiteClassesObject, + dismissAction: @escaping (() -> Void) + ) { + self.utilities = utilities + self.dismissAction = dismissAction + } + + /// A variable used to hold the entire view. + var body: some View { + + Form { + About(utilities: utilities) + Preview() + Resolution() + Photo() + Watermarking() + UserInterface() + if utilities.versionType == "DEBUG" { Debugging() } + } + .navigationTitle("view.title.help") + .toolbar(content: { + ToolbarItemGroup(placement: .topBarTrailing) { + MalachiteToolbarUtils(action: self.dismissAction, image: "checkmark", primary: true) + } + }) + } + + @available(*, deprecated, message: "Debugging section is being replaced with the Developer link in the future") + struct Debugging: View { + /// A variable to hold the debug settings section. Only available with debug builds. + var body: some View { + Section(header: Text("developer.header.debug"), footer: Text("developer.footer.debug")) { + Builder(title: Text("developer.option.debug.logging.userdefaults"), subtitle: Text("developer.detail.debug.logging.userdefaults")) {} + Builder(title: Text("developer.option.debug.erase.userdefaults"), subtitle: Text("developer.detail.debug.erase.userdefaults")) {} + Builder(title: Text("developer.option.debug.erase.gamekit"), subtitle: Text("developer.detail.debug.erase.gamekit")) {} + } + } + } + + struct Builder: View { + var title: Text + var subtitle: Text + let content: Content? + + init( + title: Text, + subtitle: Text, + @ViewBuilder content: () -> Content? + ) { + self.title = title + self.subtitle = subtitle + self.content = content() ?? nil + } + + var body: some View { + VStack { + HStack { + title + .bold() + Spacer() + + } + HStack { + subtitle + .font(.footnote) + Spacer() + } + } + } + } +} diff --git a/Malachite/Views/SettingsView/SettingsView+About.swift b/Malachite/Views/SettingsView/SettingsView+About.swift new file mode 100644 index 0000000..2f9856e --- /dev/null +++ b/Malachite/Views/SettingsView/SettingsView+About.swift @@ -0,0 +1,59 @@ +// +// SettingsView+About.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension SettingsView { + struct About: View { + /// A State variable used for determining whether or not this view is being presented as a modal. + var dismissAction: (() -> Void) + /// A variable to hold the existing instance of ``MalachiteClassesObject``. + var utilities = MalachiteClassesObject() + + init( + utilities: MalachiteClassesObject, + dismissAction: @escaping (() -> Void) + ) { + self.utilities = utilities + self.dismissAction = dismissAction + } + + var body: some View { + Section { + MalachiteCellViewUtils( + icon: "info.circle", + disabled: nil, + dangerous: false) + { + NavigationLink(destination: MalachiteAboutView(dismissAction: dismissAction)) { + Text("view.title.about") + } + } + if utilities.versionType == "INTERNAL" { + MalachiteCellViewUtils( + icon: "checkmark.seal", + disabled: nil, + dangerous: false) + { + NavigationLink(destination: MalachiteCompatibilityView(dismissAction: dismissAction, utilities: utilities)) { + Text("view.title.compatibility") + } + } + MalachiteCellViewUtils( + icon: "wrench.and.screwdriver", + disabled: nil, + dangerous: false) + { + NavigationLink(destination: DeveloperView(dismissAction: dismissAction)) { + Text("view.title.developer") + } + } + } + } + } + } +} diff --git a/Malachite/Views/SettingsView/SettingsView+Help.swift b/Malachite/Views/SettingsView/SettingsView+Help.swift deleted file mode 100755 index e9701a7..0000000 --- a/Malachite/Views/SettingsView/SettingsView+Help.swift +++ /dev/null @@ -1,143 +0,0 @@ -// -// SettingsView+Help.swift -// Malachite -// -// Created by Eva Isabella Luna on 3/14/24. -// - -import SwiftUI - -extension SettingsView { - struct Help: View { - - /// A variable used to hold the function for dismissing with the toolbar item. - var dismissAction: (() -> Void) - - /// A variable used to hold the entire view. - var body: some View { - - Form { - aboutSection - previewSettingsSection - resolutionSettingsSection - photoSettingsSection - watermarkSettingsSection - uiSettingsSection - debugSettingsSection - } - .navigationTitle("view.title.help") - .toolbar(content: { - ToolbarItemGroup(placement: .topBarTrailing) { - if #available(iOS 26.0, *) { - Button { - self.dismissAction() - } label: { - Image(systemName: "checkmark") - .tint(.primary) - } - .buttonStyle(.glassProminent) - } else { - Button { - self.dismissAction() - } label: { - Image(systemName: "checkmark.circle") - } - } - } - }) - } - - /// A variable to hold the about section. - var aboutSection: some View { - Section { - Builder(title: Text("view.title.about"), subtitle: Text("view.detail.about")) {} - } - } - - /// A variable to hold the preview settings section. - var previewSettingsSection: some View { - Section(header: Text("settings.header.preview"), footer: Text("settings.footer.preview")) { - Builder(title: Text("settings.option.preview.aspect_ratio"), subtitle: Text("settings.detail.preview.aspect_ratio")) {} - Builder(title: Text("settings.option.preview.sbtlz"), subtitle: Text("settings.detail.preview.sbtlz")) {} - Builder(title: Text("settings.option.preview.zoom_maximum"), subtitle: Text("settings.detail.preview.zoom_maximum")) {} - } - } - - /// A variable to hold the image resolution section. - var resolutionSettingsSection: some View { - Section(header: Text("settings.header.resolution"), footer: Text("settings.footer.resolution")) { - Builder(title: Text("settings.option.resolution.ultrawide"), subtitle: Text("settings.detail.resolution.ultrawide")) {} - Builder(title: Text("settings.option.resolution.wide"), subtitle: Text("settings.detail.resolution.wide")) {} - Builder(title: Text("settings.option.resolution.telephoto"), subtitle: Text("settings.detail.resolution.telephoto")) {} - } - } - - /// A variable to hold the photo settings section. - var photoSettingsSection: some View { - Section(header: Text("settings.header.photo"), footer: Text("settings.footer.photo")) { - Builder(title: Text("settings.option.photo.fileformat"), subtitle: Text("settings.detail.photo.fileformat")) {} - Builder(title: Text("settings.option.photo.hdr"), subtitle: Text("settings.detail.photo.hdr")) {} - Builder(title: Text("settings.option.photo.continuous"), subtitle: Text("settings.detail.photo.continuous")) {} - } - } - - var uiSettingsSection: some View { - Section(header: Text("settings.header.ui"), footer: Text("settings.footer.ui")) { - Builder(title: Text("settings.option.ui.tapgesture"), subtitle: Text("settings.detail.ui.tapgesture")) {} - Builder(title: Text("settings.option.ui.hiddengestures"), subtitle: Text("settings.detail.ui.hiddengestures")) {} - Builder(title: Text("settings.option.ui.idletimer"), subtitle: Text("settings.detail.ui.idletimer")) {} - Builder(title: Text("settings.option.ui.haptics"), subtitle: Text("settings.detail.ui.haptics")) {} - Builder(title: Text("settings.option.ui.hiddenonlaunch"), subtitle: Text("settings.detail.ui.hiddenonlaunch")) {} - } - } - - /// A variable to hold the watermark settings section. - var watermarkSettingsSection: some View { - Section(header: Text("settings.header.watermark"), footer: Text("settings.footer.watermark")) { - Builder(title: Text("settings.option.watermark.enable"), subtitle: Text("settings.detail.watermark.enable")) {} - Builder(title: Text("settings.option.watermark.text"), subtitle: Text("settings.detail.watermark.text")) {} - } - } - - /// A variable to hold the debug settings section. Only available with debug builds. - var debugSettingsSection: some View { - Section(header: Text("developer.header.debug"), footer: Text("developer.footer.debug")) { - Builder(title: Text("developer.option.debug.logging.userdefaults"), subtitle: Text("developer.detail.debug.logging.userdefaults")) {} - Builder(title: Text("developer.option.debug.erase.userdefaults"), subtitle: Text("developer.detail.debug.erase.userdefaults")) {} - Builder(title: Text("developer.option.debug.erase.gamekit"), subtitle: Text("developer.detail.debug.erase.gamekit")) {} - } - } - - struct Builder: View { - var title: Text - var subtitle: Text - let content: Content? - - init( - title: Text, - subtitle: Text, - @ViewBuilder content: () -> Content? - ) { - self.title = title - self.subtitle = subtitle - self.content = content() ?? nil - } - - var body: some View { - VStack { - HStack { - title - .bold() - Spacer() - - } - HStack { - subtitle - .font(.footnote) - Spacer() - } - } - } - } - } -} diff --git a/Malachite/Views/SettingsView/SettingsView+Photo.swift b/Malachite/Views/SettingsView/SettingsView+Photo.swift new file mode 100644 index 0000000..bd52ad6 --- /dev/null +++ b/Malachite/Views/SettingsView/SettingsView+Photo.swift @@ -0,0 +1,150 @@ +// +// SettingsView+Photo.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension SettingsView { + struct Photo: View { + /// A State variable used for determining whether or not the device supports HDR capture in its current mode. + @State private var supportsHDR = Bool() + /// A State variable used for determining whether or not the device supports HEIC capture. + @State private var supportsHEIC = Bool() + /// A State variable used for presenting the user with a footer based on capabilities. + @State private var formatFooterText: String? + /// A State variable used for determining the active photo format. + @State private var photoFormat = Int() + /// A State variable used for determining whether or not to capture in HDR. + @State private var hdrSwitch = false + /// A State variable used for determining whether or not to enable continuous auto exposure. + @State private var continuousAEAF = Int() + /// A State variable used for determining whether or not this view is being presented as a modal. + var dismissAction: (() -> Void) + /// A variable to hold the existing instance of ``MalachiteClassesObject``. + var utilities = MalachiteClassesObject() + + init( + utilities: MalachiteClassesObject, + dismissAction: @escaping (() -> Void) + ) { + self.utilities = utilities + self.dismissAction = dismissAction + } + + var body: some View { + Section(header: Text("settings.header.photo"), footer: (formatFooterText != nil) ? Text(formatFooterText!) : nil) { + MalachiteCellViewUtils( + icon: "square.and.arrow.down", + disabled: !supportsHEIC, + dangerous: false) + { + Picker("settings.option.photo.fileformat", selection: $photoFormat) { + Text("settings.option.photo.fileformat.jpeg") + .tag(0) + Text("settings.option.photo.fileformat.heif") + .tag(1) + } + } + + MalachiteCellViewUtils( + icon: "camera.filters", + disabled: !supportsHDR, + dangerous: false) + { + Toggle("settings.option.photo.hdr", isOn: $hdrSwitch) + } + MalachiteCellViewUtils( + icon: "plus.viewfinder", + disabled: nil, + dangerous: false) + { + Picker("settings.option.photo.continuous", selection: $continuousAEAF) { + Text("settings.option.reusable.aeaf") + .tag(0) + Text("settings.option.reusable.af") + .tag(1) + Text("settings.option.reusable.ae") + .tag(2) + Text("settings.option.reusable.off") + .tag(3) + } + } + } + .onAppear(perform: self.onAppear) + .onDisappear(perform: self.onDisappear) + .onChange(of: photoFormat) {_ in + utilities.preferences.capture.format.jpeg = photoFormat == 0 ? true : false + utilities.preferences.capture.format.heic = photoFormat == 1 ? true : false + } + .onChange(of: hdrSwitch) { _ in + utilities.preferences.capture.hdr = hdrSwitch + } + .onChange(of: continuousAEAF) { _ in + switch continuousAEAF { + case 0: + utilities.preferences.capture.continuous = ["ae", "af"] + case 1: + utilities.preferences.capture.continuous = ["af"] + case 2: + utilities.preferences.capture.continuous = ["ae"] + default: + utilities.preferences.capture.continuous = ["off"] + } + + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.continousAEAFNotification.name, object: nil) + } + } + + func onAppear() { + supportsHDR = utilities.function.supportsHDR + supportsHEIC = utilities.function.supportsHEIC() + + if !supportsHEIC { + formatFooterText = "settings.footer.photo.heif".localized + } + + if !supportsHDR { + formatFooterText = (formatFooterText != nil) ? formatFooterText! + "settings.footer.photo.hdr".localized : "settings.footer.photo.hdr".localized + } + + hdrSwitch = utilities.preferences.capture.hdr + + // TODO: Better way to do this + photoFormat = utilities.preferences.capture.format.jpeg ? 0 : 1 + photoFormat = utilities.preferences.capture.format.heic ? 1 : 0 + + switch utilities.preferences.capture.continuous { + case ["ae", "af"]: + continuousAEAF = 0 + case ["af"]: + continuousAEAF = 1 + case ["ae"]: + continuousAEAF = 2 + default: + continuousAEAF = 3 + } + } + + func onDisappear() { + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.continousAEAFNotification.name, object: nil) + + utilities.preferences.capture.hdr = hdrSwitch + utilities.preferences.capture.format.jpeg = photoFormat == 0 ? true : false + utilities.preferences.capture.format.heic = photoFormat == 1 ? true : false + + switch continuousAEAF { + case 0: + utilities.preferences.capture.continuous = ["ae", "af"] + case 1: + utilities.preferences.capture.continuous = ["af"] + case 2: + utilities.preferences.capture.continuous = ["ae"] + default: + utilities.preferences.capture.continuous = ["off"] + } + } + } +} diff --git a/Malachite/Views/SettingsView/SettingsView+Preview.swift b/Malachite/Views/SettingsView/SettingsView+Preview.swift new file mode 100644 index 0000000..fc06bc6 --- /dev/null +++ b/Malachite/Views/SettingsView/SettingsView+Preview.swift @@ -0,0 +1,123 @@ +// +// SettingsView+Preview.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension SettingsView { + struct Preview: View { + /// A State variable used for determining the current aspect ratio for the ``cameraPreview``. + @State private var previewAspect = Int() + /// A State variable used for determining whether or not to stabilize the ``cameraPreview``. + @State private var shouldStabilize = Bool() + /// A State variable used for determining whether or not to skip opening ``MalachitePhotoPreview`` and save the photo. + @State private var shouldUseFastPath = Bool() + /// A State variable used for determining what the maximum zoom level for each camera should be. + @State private var zoomMaximum = Int() + /// A State variable used for determining whether or not this view is being presented as a modal. + var dismissAction: (() -> Void) + /// A variable to hold the existing instance of ``MalachiteClassesObject``. + var utilities = MalachiteClassesObject() + + init( + utilities: MalachiteClassesObject, + dismissAction: @escaping (() -> Void) + ) { + self.utilities = utilities + self.dismissAction = dismissAction + } + + /// A variable to hold the preview settings section. + var body: some View { + Section(header: Text("settings.header.preview")) { + MalachiteCellViewUtils( + icon: "aspectratio", + disabled: false, + dangerous: false) + { + Picker("settings.option.preview.aspect_ratio", selection: $previewAspect) { + Text("settings.option.preview.aspect_ratio.fit") + .tag(0) + Text("settings.option.preview.aspect_ratio.fill") + .tag(1) + } + } + MalachiteCellViewUtils( + icon: "level", + disabled: nil, + dangerous: false) + { + Toggle("settings.option.preview.sbtlz", isOn: $shouldStabilize) + } + // Testing things out + if utilities.versionType == "INTERNAL" { + MalachiteCellViewUtils( + icon: "forward", + disabled: nil, + dangerous: false) + { + Toggle("settings.option.preview.fastpath", isOn: $shouldUseFastPath) + } + } + MalachiteCellViewUtils( + icon: "plus.magnifyingglass", + disabled: nil, + dangerous: false) + { + Stepper { + Text("settings.option.preview.zoom_maximum") + } onIncrement: { + if zoomMaximum < 10 { zoomMaximum += 1 } + } onDecrement: { + if zoomMaximum > 5 { zoomMaximum -= 1 } + } + Text(String(format: "%lldx", Int(zoomMaximum))) + .font(.footnote) + .foregroundColor(.gray) + } + } + .onAppear(perform: self.onAppear) + .onDisappear(perform: self.onDisappear) + // Called when the preview's aspect ratio is changed. + .onChange(of: previewAspect) {_ in + utilities.preferences.preview.aspect = (previewAspect == 1) + + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.aspectFillNotification.name, object: nil) + } + // Called when the user enables/disables preview stabilization. + .onChange(of: shouldStabilize) {_ in + utilities.preferences.preview.stablize = shouldStabilize + + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.stabilizerNotification.name, object: nil) + } + // Called when the user enables/disables the fast path. + .onChange(of: shouldUseFastPath) {_ in + utilities.preferences.preview.fastPath = shouldUseFastPath + } + // Called when the maximum zoom level changes. + .onChange(of: zoomMaximum) {_ in + utilities.preferences.capture.maximumZoom = zoomMaximum + } + } + + func onAppear() { + shouldStabilize = utilities.preferences.preview.stablize + shouldUseFastPath = utilities.preferences.preview.fastPath + zoomMaximum = utilities.preferences.capture.maximumZoom + previewAspect = utilities.preferences.preview.aspect ? 1 : 0 + } + + func onDisappear() { + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.aspectFillNotification.name, object: nil) + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.stabilizerNotification.name, object: nil) + + utilities.preferences.preview.aspect = (previewAspect == 1) + utilities.preferences.preview.stablize = shouldStabilize + utilities.preferences.preview.fastPath = shouldUseFastPath + utilities.preferences.capture.maximumZoom = zoomMaximum + } + } +} diff --git a/Malachite/Views/SettingsView/SettingsView+Resolution.swift b/Malachite/Views/SettingsView/SettingsView+Resolution.swift new file mode 100644 index 0000000..d5d6bfe --- /dev/null +++ b/Malachite/Views/SettingsView/SettingsView+Resolution.swift @@ -0,0 +1,185 @@ +// +// SettingsView+Resolution.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension SettingsView { + struct Resolution: View { + /// A State variable used for determining what megapixel count the ultrawide camera should shoot in. + @State private var ultrawideMegapixelCount = Int() + /// A State variable used for determining what megapixel count the wide angle camera should shoot in. + @State private var wideMegapixelCount = Int() + /// A State variable used for determining what megapixel count the telephoto camera should shoot in. + @State private var telephotoMegapixelCount = Int() + /// A State variable used for determining whether or not this view is being presented as a modal. + var dismissAction: (() -> Void) + /// A variable to hold the existing instance of ``MalachiteClassesObject``. + var utilities = MalachiteClassesObject() + + init( + utilities: MalachiteClassesObject, + dismissAction: @escaping (() -> Void) + ) { + self.utilities = utilities + self.dismissAction = dismissAction + } + + var body: some View { + Section(header: Text("settings.header.resolution")) { + if utilities.preferences.ext.dictionary.isValid(dictionary: utilities.preferences.compatibility.ultrawide) { + MalachiteCellViewUtils( + icon: "camera.aperture", + disabled: utilities.preferences.ext.dictionary.getCount(dictionary: utilities.preferences.compatibility.ultrawide) == 1, + dangerous: false) + { + Picker("settings.option.resolution.ultrawide", selection: $ultrawideMegapixelCount) { + if let mp = utilities.preferences.compatibility.ultrawide["8"] { if mp { + Text("settings.option.resolution.8") + .tag(0) + } } + if let mp = utilities.preferences.compatibility.ultrawide["12"] { if mp { + Text("settings.option.resolution.12") + .tag(1) + } } + if let mp = utilities.preferences.compatibility.ultrawide["48"] { if mp { + Text("settings.option.resolution.48") + .tag(2) + } } + } + } + } + if utilities.preferences.ext.dictionary.isValid(dictionary: utilities.preferences.compatibility.wideangle) { + MalachiteCellViewUtils( + icon: "camera.aperture", + disabled: utilities.preferences.ext.dictionary.getCount(dictionary: utilities.preferences.compatibility.wideangle) == 1, + dangerous: false) + { + Picker("settings.option.resolution.wide", selection: $wideMegapixelCount) { + if let mp = utilities.preferences.compatibility.wideangle["8"] { if mp { + Text("settings.option.resolution.8") + .tag(0) + } } + if let mp = utilities.preferences.compatibility.wideangle["12"] { if mp { + Text("settings.option.resolution.12") + .tag(1) + } } + if let mp = utilities.preferences.compatibility.wideangle["48"] { if mp { + Text("settings.option.resolution.48") + .tag(2) + } } + } + } + } + if utilities.preferences.ext.dictionary.isValid(dictionary: utilities.preferences.compatibility.telephoto) { + MalachiteCellViewUtils( + icon: "camera.aperture", + disabled: utilities.preferences.ext.dictionary.getCount(dictionary: utilities.preferences.compatibility.telephoto) == 1, + dangerous: false) + { + Picker("settings.option.resolution.telephoto", selection: $telephotoMegapixelCount) { + if let mp = utilities.preferences.compatibility.telephoto["48"] { if mp { + Text("settings.option.resolution.48") + .tag(1) + } } + if let mp = utilities.preferences.compatibility.telephoto["12"] { if mp { + Text("settings.option.resolution.12") + .tag(0) + } } + } + } + } + } + .onAppear(perform: self.onAppear) + .onDisappear(perform: self.onDisappear) + .onChange(of: ultrawideMegapixelCount) { _ in + switch ultrawideMegapixelCount { + case 1: + utilities.preferences.capture.mp.ultrawide = 12 + case 2: + utilities.preferences.capture.mp.ultrawide = 48 + default: + utilities.preferences.capture.mp.ultrawide = 8 + } + } + .onChange(of: wideMegapixelCount) { _ in + switch wideMegapixelCount { + case 1: + utilities.preferences.capture.mp.wideangle = 12 + case 2: + utilities.preferences.capture.mp.wideangle = 48 + default: + utilities.preferences.capture.mp.wideangle = 8 + } + } + .onChange(of: telephotoMegapixelCount) { _ in + switch telephotoMegapixelCount { + case 1: + utilities.preferences.capture.mp.telephoto = 48 + default: + utilities.preferences.capture.mp.telephoto = 12 + } + } + } + + func onAppear() { + switch utilities.preferences.capture.mp.ultrawide { + case 12: + ultrawideMegapixelCount = 1 + case 48: + ultrawideMegapixelCount = 2 + default: + ultrawideMegapixelCount = 0 + } + + switch utilities.preferences.capture.mp.wideangle { + case 12: + wideMegapixelCount = 1 + case 48: + wideMegapixelCount = 2 + default: + wideMegapixelCount = 0 + } + + switch utilities.preferences.capture.mp.telephoto { + case 48: + telephotoMegapixelCount = 1 + default: + telephotoMegapixelCount = 0 + } + } + + func onDisappear() { + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.megaPixelSwitchNotification.name, object: nil) + + switch ultrawideMegapixelCount { + case 1: + utilities.preferences.capture.mp.ultrawide = 12 + case 2: + utilities.preferences.capture.mp.ultrawide = 48 + default: + utilities.preferences.capture.mp.ultrawide = 8 + } + + switch wideMegapixelCount { + case 1: + utilities.preferences.capture.mp.wideangle = 12 + case 2: + utilities.preferences.capture.mp.wideangle = 48 + default: + utilities.preferences.capture.mp.wideangle = 8 + } + + switch telephotoMegapixelCount { + case 1: + utilities.preferences.capture.mp.telephoto = 48 + default: + utilities.preferences.capture.mp.telephoto = 12 + } + } + } +} + diff --git a/Malachite/Views/SettingsView/SettingsView+UI.swift b/Malachite/Views/SettingsView/SettingsView+UI.swift new file mode 100644 index 0000000..b59bc0d --- /dev/null +++ b/Malachite/Views/SettingsView/SettingsView+UI.swift @@ -0,0 +1,190 @@ +// +// SettingsView+UI.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension SettingsView { + struct UserInterface: View { + /// A State variable used for determining whether or not to enable exposure and focus POI on tap and hold. + @State private var poiTapAndHold = Int() + /// A State variable used for determining whether or not to enable the system's auto locking APIs. + @State private var idleTimerDisabled = Bool() + /// A State variable used for determining whether or not to enabel haptics. + @State private var hapticsDisabled = Bool() + /// A State variable used for determining whether or not to start the app with the UI hidden. + @State private var appStartsUIHidden = Bool() + /// A State vairable used for determining whether or not to enable pinch to zoom and the tap gesture while the UI is hidden. + @State private var uiHiderGestures = Int() + /// A State variable used for determining whether or not this view is being presented as a modal. + var dismissAction: (() -> Void) + /// A variable to hold the existing instance of ``MalachiteClassesObject``. + var utilities = MalachiteClassesObject() + + init( + utilities: MalachiteClassesObject, + dismissAction: @escaping (() -> Void) + ) { + self.utilities = utilities + self.dismissAction = dismissAction + } + + var body: some View { + Section(header: Text("settings.header.ui")) { + MalachiteCellViewUtils( + icon: "hand.tap", + disabled: nil, + dangerous: false) + { + Picker("settings.option.ui.tapgesture", selection: $poiTapAndHold) { + Text("settings.option.reusable.aeaf") + .tag(0) + Text("settings.option.reusable.af") + .tag(1) + Text("settings.option.reusable.ae") + .tag(2) + Text("settings.option.reusable.off") + .tag(3) + } + } + MalachiteCellViewUtils( + icon: "hand.raised.slash", + disabled: nil, + dangerous: false) + { + Picker("settings.option.ui.hiddengestures", selection: $uiHiderGestures) { + Text("settings.option.reusable.pinchzoomtapandhold") + .tag(0) + Text("settings.option.reusable.pinchzoom") + .tag(1) + Text("settings.option.reusable.tapandhold") + .tag(2) + Text("settings.option.reusable.off") + .tag(3) + } + } + MalachiteCellViewUtils( + icon: "clock", + disabled: nil, + dangerous: false) + { + Toggle("settings.option.ui.idletimer", isOn: $idleTimerDisabled) + } + MalachiteCellViewUtils( + icon: "iphone.radiowaves.left.and.right", + disabled: nil, + dangerous: false) + { + Toggle("settings.option.ui.haptics", isOn: $hapticsDisabled) + } + MalachiteCellViewUtils( + icon: "eye.slash", + disabled: nil, + dangerous: false) + { + Toggle("settings.option.ui.hiddenonlaunch", isOn: $appStartsUIHidden) + } + } + .onAppear(perform: self.onAppear) + .onDisappear(perform: self.onDisappear) + .onChange(of: poiTapAndHold) {_ in + switch poiTapAndHold { + case 0: + utilities.preferences.userInterface.tapAndHold = ["ae", "af"] + case 1: + utilities.preferences.userInterface.tapAndHold = ["af"] + case 2: + utilities.preferences.userInterface.tapAndHold = ["ae"] + default: + utilities.preferences.userInterface.tapAndHold = ["off"] + } + } + .onChange(of: uiHiderGestures) {_ in + switch uiHiderGestures { + case 0: + utilities.preferences.userInterface.hiddenControls = ["zoom", "tah"] + case 1: + utilities.preferences.userInterface.hiddenControls = ["zoom"] + case 2: + utilities.preferences.userInterface.hiddenControls = ["tah"] + default: + utilities.preferences.userInterface.hiddenControls = ["off"] + } + + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.aeafTapGestureNotification.name, object: nil) + } + .onChange(of: idleTimerDisabled) { _ in + utilities.preferences.userInterface.idleTimerDisabled = idleTimerDisabled + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.idleTimerNotification.name, object: nil) + } + .onChange(of: hapticsDisabled) { _ in + utilities.preferences.userInterface.hapticFeedback = hapticsDisabled + } + .onChange(of: appStartsUIHidden) { _ in + utilities.preferences.userInterface.appLaunch = appStartsUIHidden + } + } + + func onAppear() { + idleTimerDisabled = utilities.preferences.userInterface.idleTimerDisabled + hapticsDisabled = utilities.preferences.userInterface.hapticFeedback + appStartsUIHidden = utilities.preferences.userInterface.appLaunch + + switch utilities.preferences.userInterface.tapAndHold { + case ["ae", "af"]: + poiTapAndHold = 0 + case ["af"]: + poiTapAndHold = 1 + case ["ae"]: + poiTapAndHold = 2 + default: + poiTapAndHold = 3 + } + + switch utilities.preferences.userInterface.hiddenControls { + case ["zoom", "tah"]: + uiHiderGestures = 0 + case ["zoom"]: + uiHiderGestures = 1 + case ["tah"]: + uiHiderGestures = 2 + default: + uiHiderGestures = 3 + } + } + + func onDisappear() { + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.aeafTapGestureNotification.name, object: nil) + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.idleTimerNotification.name, object: nil) + + utilities.preferences.userInterface.idleTimerDisabled = idleTimerDisabled + utilities.preferences.userInterface.hapticFeedback = hapticsDisabled + utilities.preferences.userInterface.appLaunch = appStartsUIHidden + + switch poiTapAndHold { + case 0: + utilities.preferences.userInterface.tapAndHold = ["ae", "af"] + case 1: + utilities.preferences.userInterface.tapAndHold = ["af"] + case 2: + utilities.preferences.userInterface.tapAndHold = ["ae"] + default: + utilities.preferences.userInterface.tapAndHold = ["off"] + } + + switch uiHiderGestures { + case 0: + utilities.preferences.userInterface.hiddenControls = ["zoom", "tah"] + case 1: + utilities.preferences.userInterface.hiddenControls = ["zoom"] + case 2: + utilities.preferences.userInterface.hiddenControls = ["tah"] + default: + utilities.preferences.userInterface.hiddenControls = ["off"] + } + } + } +} diff --git a/Malachite/Views/SettingsView/SettingsView+Watermarking.swift b/Malachite/Views/SettingsView/SettingsView+Watermarking.swift new file mode 100644 index 0000000..bd0fbb6 --- /dev/null +++ b/Malachite/Views/SettingsView/SettingsView+Watermarking.swift @@ -0,0 +1,71 @@ +// +// SettingsView+Watermarking.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension SettingsView { + struct Watermarking: View { + /// A State variable used for determining whether or not watermarking is enabled. + @State private var watermarkSwitch = false + /// A State variable used for determining the current watermark string. + @State private var watermarkText = String() + /// A State variable used for determining whether or not this view is being presented as a modal. + var dismissAction: (() -> Void) + /// A variable to hold the existing instance of ``MalachiteClassesObject``. + var utilities = MalachiteClassesObject() + + init( + utilities: MalachiteClassesObject, + dismissAction: @escaping (() -> Void) + ) { + self.utilities = utilities + self.dismissAction = dismissAction + } + + var body: some View { + Section(header: Text("settings.header.watermark")) { + MalachiteCellViewUtils( + icon: "textformat", + disabled: nil, + dangerous: false) + { + Toggle("settings.option.watermark.enable", isOn: $watermarkSwitch) + } + + MalachiteCellViewUtils( + icon: "signature", + disabled: nil, + dangerous: false) + { + Text("settings.option.watermark.text") + TextField("settings.option.watermark.text.placeholder", text: $watermarkText) + .multilineTextAlignment(.trailing) + .autocorrectionDisabled() + .keyboardType(.twitter) + } + } + .onAppear(perform: self.onAppear) + .onDisappear(perform: self.onDisappear) + .onChange(of: watermarkSwitch) {_ in + utilities.preferences.watermark.enabled = watermarkSwitch + } + .onChange(of: watermarkText) {_ in + utilities.preferences.watermark.text = watermarkText.isEmpty ? "Shot with Malachite" : String(watermarkText.prefix(65)) + } + } + + func onAppear() { + watermarkText = utilities.preferences.watermark.text + watermarkSwitch = utilities.preferences.watermark.enabled + } + + func onDisappear() { + utilities.preferences.watermark.enabled = watermarkSwitch + utilities.preferences.watermark.text = watermarkText.isEmpty ? "Shot with Malachite" : String(watermarkText.prefix(65)) + } + } +} diff --git a/Malachite/Views/SettingsView/SettingsView.swift b/Malachite/Views/SettingsView/SettingsView.swift index 6f9cf7a..98c1561 100755 --- a/Malachite/Views/SettingsView/SettingsView.swift +++ b/Malachite/Views/SettingsView/SettingsView.swift @@ -8,56 +8,10 @@ import SwiftUI struct SettingsView: View { - /// A State variable used for determining whether or not watermarking is enabled. - @State private var watermarkSwitch = false - /// A State variable used for determining the current watermark string. - @State private var watermarkText = String() - /// A State variable used for determining the active photo format. - @State private var photoFormat = Int() - /// A State variable used for determining the current aspect ratio for the ``cameraPreview``. - @State private var previewAspect = Int() - /// A State variable used for determining whether or not to stabilize the ``cameraPreview``. - @State private var shouldStabilize = Bool() - /// A State variable used for determining whether or not to skip opening ``MalachitePhotoPreview`` and save the photo. - @State private var shouldUseFastPath = Bool() - /// A State variable used for determining what the maximum zoom level for each camera should be. - @State private var zoomMaximum = Int() - /// A State variable used for determining whether or not to capture in HDR. - @State private var hdrSwitch = false - /// A State variable used for determining whether or not to enable continuous auto exposure. - @State private var continuousAEAF = Int() - /// A State variable used for determining how many fingers are used for the settings gesture. - @State private var settingsGestureFingers = Int() - /// A State variable used for determining whether or not to enable exposure and focus POI on tap and hold. - @State private var poiTapAndHold = Int() - /// A State variable used for determining whether or not to enable the system's auto locking APIs. - @State private var idleTimerDisabled = Bool() - /// A State variable used for determining whether or not to enabel haptics. - @State private var hapticsDisabled = Bool() - /// A State variable used for determining whether or not to start the app with the UI hidden. - @State private var appStartsUIHidden = Bool() - /// A State vairable used for determining whether or not to enable pinch to zoom and the tap gesture while the UI is hidden. - @State private var uiHiderGestures = Int() - /// A State variable used for determining whether or not the device supports HDR capture in its current mode. - @State private var supportsHDR = Bool() - /// A State variable used for determining whether or not the device supports HEIC capture. - @State private var supportsHEIC = Bool() - /// A State variable used for presenting the user with a footer based on capabilities. - @State private var formatFooterText: String? /// A State variable used for determining whether or not debug logging UserDefaults is enabled. @State private var debugLoggingUserDefaults = false /// A State variable used for determining whether or not to literally break the app. @State private var breakApp = false - /// A State variable used for determining what megapixel count the ultrawide camera should shoot in. - @State private var ultrawideMegapixelCount = Int() - /// A State variable used for determining what megapixel count the wide angle camera should shoot in. - @State private var wideMegapixelCount = Int() - /// A State variable used for determining what megapixel count the telephoto camera should shoot in. - @State private var telephotoMegapixelCount = Int() - /// A State variable used for determining whether or not a view is being presented. - @State var presentingAboutModal = false - /// A State variable used for determining whether or not a view is being presented. - @State var presentingCompatibilityModal = false /// A variable to hold the existing instance of ``MalachiteClassesObject``. var utilities = MalachiteClassesObject() /// A variable used to hold the function for dismissing with the toolbar item. @@ -74,631 +28,43 @@ struct SettingsView: View { - Toolbar item for dismissing the view. */ var body: some View { - MalachiteNagivationViewUtils() { guts } - } - - var guts: some View { - Form { - aboutSection - previewSettingsSection - resolutionSettingsSection - photoSettingsSection - watermarkSettingsSection - uiSettingsSection - if utilities.versionType == "DEBUG" { - DeveloperView.Settings(utilities: utilities) - } - } - .onAppear { onAppear() } - .onDisappear { onDisappear() } - .navigationTitle("view.title.settings") - .toolbar(content: { - ToolbarItemGroup(placement: .topBarLeading) { - NavigationLink(destination: Help(dismissAction: dismissAction)) { - if #available(iOS 26.0, *) { - Image(systemName: "questionmark.circle") - } else { - Image(systemName: "questionmark.circle").tint(.primary) - } - } - } - ToolbarItemGroup(placement: .topBarTrailing) { - if #available(iOS 26.0, *) { - Button { - self.dismissAction() - } label: { - Image(systemName: "checkmark") - .tint(.primary) - } - .buttonStyle(.glassProminent) - } else { - Button { - self.dismissAction() - } label: { - Image(systemName: "checkmark.circle") - } - } - } - }) - } - - /// A variable to hold the about section. - var aboutSection: some View { - Section { - MalachiteCellViewUtils( - icon: "info.circle", - disabled: nil, - dangerous: false) - { - NavigationLink(destination: MalachiteAboutView(dismissAction: dismissAction)) { - Text("view.title.about") - } - } - if utilities.versionType == "INTERNAL" { - MalachiteCellViewUtils( - icon: "checkmark.seal", - disabled: nil, - dangerous: false) - { - NavigationLink(destination: MalachiteCompatibilityView(dismissAction: dismissAction, utilities: utilities)) { - Text("view.title.compatibility") - } - } - MalachiteCellViewUtils( - icon: "wrench.and.screwdriver", - disabled: nil, - dangerous: false) - { - NavigationLink(destination: DeveloperView(dismissAction: dismissAction)) { - Text("view.title.developer") - } - } - } - } - } - - /// A variable to hold the preview settings section. - var previewSettingsSection: some View { - Section(header: Text("settings.header.preview")) { - MalachiteCellViewUtils( - icon: "aspectratio", - disabled: false, - dangerous: false) - { - Picker("settings.option.preview.aspect_ratio", selection: $previewAspect) { - Text("settings.option.preview.aspect_ratio.fit") - .tag(0) - Text("settings.option.preview.aspect_ratio.fill") - .tag(1) - } - } - MalachiteCellViewUtils( - icon: "level", - disabled: nil, - dangerous: false) - { - Toggle("settings.option.preview.sbtlz", isOn: $shouldStabilize) - } - // Testing things out - if utilities.versionType == "INTERNAL" { - MalachiteCellViewUtils( - icon: "forward", - disabled: nil, - dangerous: false) - { - Toggle("settings.option.preview.fastpath", isOn: $shouldUseFastPath) - } - } - MalachiteCellViewUtils( - icon: "plus.magnifyingglass", - disabled: nil, - dangerous: false) - { - Stepper { - Text("settings.option.preview.zoom_maximum") - } onIncrement: { - if zoomMaximum < 10 { zoomMaximum += 1 } - } onDecrement: { - if zoomMaximum > 5 { zoomMaximum -= 1 } - } - Text(String(format: "%lldx", Int(zoomMaximum))) - .font(.footnote) - .foregroundColor(.gray) - } - } - .onChange(of: previewAspect) {_ in - utilities.preferences.preview.aspect = (previewAspect == 1) - - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.aspectFillNotification.name, object: nil) - } - .onChange(of: shouldStabilize) {_ in - utilities.preferences.preview.stablize = shouldStabilize - - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.stabilizerNotification.name, object: nil) - } - .onChange(of: shouldUseFastPath) {_ in - utilities.preferences.preview.fastPath = shouldUseFastPath - } - .onChange(of: zoomMaximum) {_ in - utilities.preferences.capture.maximumZoom = zoomMaximum - } - } - - /// A variable to hold the image resolution section. - var resolutionSettingsSection: some View { - Section(header: Text("settings.header.resolution")) { - if utilities.preferences.ext.dictionary.isValid(dictionary: utilities.preferences.compatibility.ultrawide) { - MalachiteCellViewUtils( - icon: "camera.aperture", - disabled: utilities.preferences.ext.dictionary.getCount(dictionary: utilities.preferences.compatibility.ultrawide) == 1, - dangerous: false) - { - Picker("settings.option.resolution.ultrawide", selection: $ultrawideMegapixelCount) { - if let mp = utilities.preferences.compatibility.ultrawide["8"] { if mp { - Text("settings.option.resolution.8") - .tag(0) - } } - if let mp = utilities.preferences.compatibility.ultrawide["12"] { if mp { - Text("settings.option.resolution.12") - .tag(1) - } } - if let mp = utilities.preferences.compatibility.ultrawide["48"] { if mp { - Text("settings.option.resolution.48") - .tag(2) - } } - } - } - } - if utilities.preferences.ext.dictionary.isValid(dictionary: utilities.preferences.compatibility.wideangle) { - MalachiteCellViewUtils( - icon: "camera.aperture", - disabled: utilities.preferences.ext.dictionary.getCount(dictionary: utilities.preferences.compatibility.wideangle) == 1, - dangerous: false) - { - Picker("settings.option.resolution.wide", selection: $wideMegapixelCount) { - if let mp = utilities.preferences.compatibility.wideangle["8"] { if mp { - Text("settings.option.resolution.8") - .tag(0) - } } - if let mp = utilities.preferences.compatibility.wideangle["12"] { if mp { - Text("settings.option.resolution.12") - .tag(1) - } } - if let mp = utilities.preferences.compatibility.wideangle["48"] { if mp { - Text("settings.option.resolution.48") - .tag(2) - } } + MalachiteNagivationViewUtils() { + Form { + About(utilities: utilities, dismissAction: dismissAction) + Preview(utilities: utilities, dismissAction: dismissAction) + Resolution(utilities: utilities, dismissAction: dismissAction) + Photo(utilities: utilities, dismissAction: dismissAction) + Watermarking(utilities: utilities, dismissAction: dismissAction) + UserInterface(utilities: utilities, dismissAction: dismissAction) + if utilities.versionType == "DEBUG" { DeveloperView.Settings(utilities: utilities) } + } + .onAppear { onAppear() } + .onDisappear { onDisappear() } + .navigationTitle("view.title.settings") + .toolbar( +content: { + ToolbarItemGroup(placement: .topBarLeading) { + NavigationLink(destination: QuickHelpView(utilities: utilities, dismissAction: dismissAction)) { + if #available(iOS 26.0, *) { + Image(systemName: "questionmark.circle") + } else { + Image(systemName: "questionmark.circle").tint(.primary) + } } } - } - if utilities.preferences.ext.dictionary.isValid(dictionary: utilities.preferences.compatibility.telephoto) { - MalachiteCellViewUtils( - icon: "camera.aperture", - disabled: utilities.preferences.ext.dictionary.getCount(dictionary: utilities.preferences.compatibility.telephoto) == 1, - dangerous: false) - { - Picker("settings.option.resolution.telephoto", selection: $telephotoMegapixelCount) { - if let mp = utilities.preferences.compatibility.wideangle["48"] { if mp { - Text("settings.option.resolution.12") - .tag(0) - } } - } + ToolbarItemGroup(placement: .topBarTrailing) { + MalachiteToolbarUtils(action: self.dismissAction, image: "checkmark", primary: true) } - } - } - .onChange(of: ultrawideMegapixelCount) { _ in - switch ultrawideMegapixelCount { - case 1: - utilities.preferences.capture.mp.ultrawide = 12 - case 2: - utilities.preferences.capture.mp.ultrawide = 48 - default: - utilities.preferences.capture.mp.ultrawide = 8 - } - } - .onChange(of: wideMegapixelCount) { _ in - switch wideMegapixelCount { - case 1: - utilities.preferences.capture.mp.wideangle = 12 - case 2: - utilities.preferences.capture.mp.wideangle = 48 - default: - utilities.preferences.capture.mp.wideangle = 8 - } - } - .onChange(of: telephotoMegapixelCount) { _ in - switch telephotoMegapixelCount { - default: - utilities.preferences.capture.mp.telephoto = 12 - } - } - } - - /// A variable to hold the photo settings section. - var photoSettingsSection: some View { - Section(header: Text("settings.header.photo"), footer: (formatFooterText != nil) ? Text(formatFooterText!) : nil) { - MalachiteCellViewUtils( - icon: "square.and.arrow.down", - disabled: !supportsHEIC, - dangerous: false) - { - Picker("settings.option.photo.fileformat", selection: $photoFormat) { - Text("settings.option.photo.fileformat.jpeg") - .tag(0) - Text("settings.option.photo.fileformat.heif") - .tag(1) - } - } - - MalachiteCellViewUtils( - icon: "camera.filters", - disabled: !supportsHDR, - dangerous: false) - { - Toggle("settings.option.photo.hdr", isOn: $hdrSwitch) - } - MalachiteCellViewUtils( - icon: "plus.viewfinder", - disabled: nil, - dangerous: false) - { - Picker("settings.option.photo.continuous", selection: $continuousAEAF) { - Text("settings.option.reusable.aeaf") - .tag(0) - Text("settings.option.reusable.af") - .tag(1) - Text("settings.option.reusable.ae") - .tag(2) - Text("settings.option.reusable.off") - .tag(3) - } - } - } - .onChange(of: photoFormat) {_ in - utilities.preferences.capture.format.jpeg = photoFormat == 0 ? true : false - utilities.preferences.capture.format.heic = photoFormat == 1 ? true : false - } - .onChange(of: hdrSwitch) { _ in - utilities.preferences.capture.hdr = hdrSwitch - } - .onChange(of: continuousAEAF) { _ in - switch continuousAEAF { - case 0: - utilities.preferences.capture.continuous = ["ae", "af"] - case 1: - utilities.preferences.capture.continuous = ["af"] - case 2: - utilities.preferences.capture.continuous = ["ae"] - default: - utilities.preferences.capture.continuous = ["off"] - } - - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.continousAEAFNotification.name, object: nil) - } - } - - /// A variable to hold the watermark settings section. - var watermarkSettingsSection: some View { - Section(header: Text("settings.header.watermark")) { - MalachiteCellViewUtils( - icon: "textformat", - disabled: nil, - dangerous: false) - { - Toggle("settings.option.watermark.enable", isOn: $watermarkSwitch) - } - - MalachiteCellViewUtils( - icon: "signature", - disabled: nil, - dangerous: false) - { - Text("settings.option.watermark.text") - TextField("settings.option.watermark.text.placeholder", text: $watermarkText) - .multilineTextAlignment(.trailing) - .autocorrectionDisabled() - .keyboardType(.twitter) - } - } - .onChange(of: watermarkSwitch) {_ in - utilities.preferences.watermark.enabled = watermarkSwitch - } - .onChange(of: watermarkText) {_ in - utilities.preferences.watermark.text = watermarkText.isEmpty ? "Shot with Malachite" : String(watermarkText.prefix(65)) - } - } - - /// A variable to hold settings related to the user interface - var uiSettingsSection: some View { - Section(header: Text("settings.header.ui")) { - MalachiteCellViewUtils( - icon: "hand.tap", - disabled: nil, - dangerous: false) - { - Picker("settings.option.ui.tapgesture", selection: $poiTapAndHold) { - Text("settings.option.reusable.aeaf") - .tag(0) - Text("settings.option.reusable.af") - .tag(1) - Text("settings.option.reusable.ae") - .tag(2) - Text("settings.option.reusable.off") - .tag(3) - } - } - if utilities.versionType == "INTERNAL" { - MalachiteCellViewUtils( - icon: "hand.draw", - disabled: nil, - dangerous: false) - { - Picker("settings.option.ui.settingsgesture", selection: $settingsGestureFingers) { - Text("settings.option.ui.settingsgesture.1") - .tag(1) - Text("settings.option.ui.settingsgesture.2") - .tag(2) - Text("settings.option.ui.settingsgesture.3") - .tag(3) - } - } - } - MalachiteCellViewUtils( - icon: "hand.raised.slash", - disabled: nil, - dangerous: false) - { - Picker("settings.option.ui.hiddengestures", selection: $uiHiderGestures) { - Text("settings.option.reusable.pinchzoomtapandhold") - .tag(0) - Text("settings.option.reusable.pinchzoom") - .tag(1) - Text("settings.option.reusable.tapandhold") - .tag(2) - Text("settings.option.reusable.off") - .tag(3) - } - } - MalachiteCellViewUtils( - icon: "clock", - disabled: nil, - dangerous: false) - { - Toggle("settings.option.ui.idletimer", isOn: $idleTimerDisabled) - } - MalachiteCellViewUtils( - icon: "iphone.radiowaves.left.and.right", - disabled: nil, - dangerous: false) - { - Toggle("settings.option.ui.haptics", isOn: $hapticsDisabled) - } - MalachiteCellViewUtils( - icon: "eye.slash", - disabled: nil, - dangerous: false) - { - Toggle("settings.option.ui.hiddenonlaunch", isOn: $appStartsUIHidden) - } - } - .onChange(of: poiTapAndHold) {_ in - switch poiTapAndHold { - case 0: - utilities.preferences.userInterface.tapAndHold = ["ae", "af"] - case 1: - utilities.preferences.userInterface.tapAndHold = ["af"] - case 2: - utilities.preferences.userInterface.tapAndHold = ["ae"] - default: - utilities.preferences.userInterface.tapAndHold = ["off"] - } - } - .onChange(of: settingsGestureFingers) {_ in - utilities.preferences.evaintrnl.settingsGesture = settingsGestureFingers - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.settingsGestureNotification.name, object: nil) - } - .onChange(of: uiHiderGestures) {_ in - switch uiHiderGestures { - case 0: - utilities.preferences.userInterface.hiddenControls = ["zoom", "tah"] - case 1: - utilities.preferences.userInterface.hiddenControls = ["zoom"] - case 2: - utilities.preferences.userInterface.hiddenControls = ["tah"] - default: - utilities.preferences.userInterface.hiddenControls = ["off"] - } - - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.aeafTapGestureNotification.name, object: nil) - } - .onChange(of: idleTimerDisabled) { _ in - utilities.preferences.userInterface.idleTimerDisabled = idleTimerDisabled - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.idleTimerNotification.name, object: nil) - } - .onChange(of: hapticsDisabled) { _ in - utilities.preferences.userInterface.hapticFeedback = hapticsDisabled - } - .onChange(of: appStartsUIHidden) { _ in - utilities.preferences.userInterface.appLaunch = appStartsUIHidden + }) } } func onAppear() { - supportsHDR = utilities.function.supportsHDR - supportsHEIC = utilities.function.supportsHEIC() - - if !supportsHEIC { - formatFooterText = "settings.footer.photo.heif".localized - } - - if !supportsHDR { - formatFooterText = (formatFooterText != nil) ? formatFooterText! + "settings.footer.photo.hdr".localized : "settings.footer.photo.hdr".localized - } - - shouldStabilize = utilities.preferences.preview.stablize - shouldUseFastPath = utilities.preferences.preview.fastPath - zoomMaximum = utilities.preferences.capture.maximumZoom - previewAspect = utilities.preferences.preview.aspect ? 1 : 0 - - switch utilities.preferences.capture.mp.ultrawide { - case 12: - ultrawideMegapixelCount = 1 - case 48: - ultrawideMegapixelCount = 2 - default: - ultrawideMegapixelCount = 0 - } - - switch utilities.preferences.capture.mp.wideangle { - case 12: - wideMegapixelCount = 1 - case 48: - wideMegapixelCount = 2 - default: - wideMegapixelCount = 0 - } - - switch utilities.preferences.capture.mp.telephoto { - default: - telephotoMegapixelCount = 0 - } - - hdrSwitch = utilities.preferences.capture.hdr - - // TODO: Better way to do this - photoFormat = utilities.preferences.capture.format.jpeg ? 0 : 1 - photoFormat = utilities.preferences.capture.format.heic ? 1 : 0 - - switch utilities.preferences.capture.continuous { - case ["ae", "af"]: - continuousAEAF = 0 - case ["af"]: - continuousAEAF = 1 - case ["ae"]: - continuousAEAF = 2 - default: - continuousAEAF = 3 - } - - watermarkText = utilities.preferences.watermark.text - watermarkSwitch = utilities.preferences.watermark.enabled - - settingsGestureFingers = utilities.preferences.evaintrnl.settingsGesture - idleTimerDisabled = utilities.preferences.userInterface.idleTimerDisabled - hapticsDisabled = utilities.preferences.userInterface.hapticFeedback - appStartsUIHidden = utilities.preferences.userInterface.appLaunch - - switch utilities.preferences.userInterface.tapAndHold { - case ["ae", "af"]: - poiTapAndHold = 0 - case ["af"]: - poiTapAndHold = 1 - case ["ae"]: - poiTapAndHold = 2 - default: - poiTapAndHold = 3 - } - - switch utilities.preferences.userInterface.hiddenControls { - case ["zoom", "tah"]: - uiHiderGestures = 0 - case ["zoom"]: - uiHiderGestures = 1 - case ["tah"]: - uiHiderGestures = 2 - default: - uiHiderGestures = 3 - } - debugLoggingUserDefaults = utilities.preferences.debug.logging.preferences breakApp = utilities.preferences.debug.breakApp } func onDisappear() { - utilities.preferences.preview.aspect = (previewAspect == 1) - utilities.preferences.preview.stablize = shouldStabilize - utilities.preferences.preview.fastPath = shouldUseFastPath - utilities.preferences.capture.maximumZoom = zoomMaximum - - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.aspectFillNotification.name, object: nil) - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.stabilizerNotification.name, object: nil) - - switch ultrawideMegapixelCount { - case 1: - utilities.preferences.capture.mp.ultrawide = 12 - case 2: - utilities.preferences.capture.mp.ultrawide = 48 - default: - utilities.preferences.capture.mp.ultrawide = 8 - } - - switch wideMegapixelCount { - case 1: - utilities.preferences.capture.mp.wideangle = 12 - case 2: - utilities.preferences.capture.mp.wideangle = 48 - default: - utilities.preferences.capture.mp.wideangle = 8 - } - - switch telephotoMegapixelCount { - default: - utilities.preferences.capture.mp.telephoto = 12 - } - - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.megaPixelSwitchNotification.name, object: nil) - - utilities.preferences.capture.hdr = hdrSwitch - utilities.preferences.capture.format.jpeg = photoFormat == 0 ? true : false - utilities.preferences.capture.format.heic = photoFormat == 1 ? true : false - - switch continuousAEAF { - case 0: - utilities.preferences.capture.continuous = ["ae", "af"] - case 1: - utilities.preferences.capture.continuous = ["af"] - case 2: - utilities.preferences.capture.continuous = ["ae"] - default: - utilities.preferences.capture.continuous = ["off"] - } - - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.continousAEAFNotification.name, object: nil) - - utilities.preferences.watermark.enabled = watermarkSwitch - utilities.preferences.watermark.text = watermarkText.isEmpty ? "Shot with Malachite" : String(watermarkText.prefix(65)) - - utilities.preferences.evaintrnl.settingsGesture = settingsGestureFingers - utilities.preferences.userInterface.idleTimerDisabled = idleTimerDisabled - utilities.preferences.userInterface.hapticFeedback = hapticsDisabled - utilities.preferences.userInterface.appLaunch = appStartsUIHidden - - switch poiTapAndHold { - case 0: - utilities.preferences.userInterface.tapAndHold = ["ae", "af"] - case 1: - utilities.preferences.userInterface.tapAndHold = ["af"] - case 2: - utilities.preferences.userInterface.tapAndHold = ["ae"] - default: - utilities.preferences.userInterface.tapAndHold = ["off"] - } - - switch uiHiderGestures { - case 0: - utilities.preferences.userInterface.hiddenControls = ["zoom", "tah"] - case 1: - utilities.preferences.userInterface.hiddenControls = ["zoom"] - case 2: - utilities.preferences.userInterface.hiddenControls = ["tah"] - default: - utilities.preferences.userInterface.hiddenControls = ["off"] - } - - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.settingsGestureNotification.name, object: nil) - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.aeafTapGestureNotification.name, object: nil) - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.idleTimerNotification.name, object: nil) - utilities.preferences.debug.logging.preferences = debugLoggingUserDefaults utilities.preferences.debug.breakApp = breakApp } From b745cae4c1098a2e7ef1b1121b8c6c6ed3e76402 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Sun, 17 Aug 2025 01:51:35 -0600 Subject: [PATCH 06/66] Fix iOS 15/16 crash on launch So the availability clause isn't working... what. (cherry picked from commit 4bf97b9c9f64efe2efe2c3e359e9ff037ba76bfc) --- Malachite/Views/MalachiteView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Malachite/Views/MalachiteView.swift b/Malachite/Views/MalachiteView.swift index cfc3379..6b02c10 100755 --- a/Malachite/Views/MalachiteView.swift +++ b/Malachite/Views/MalachiteView.swift @@ -216,7 +216,7 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A utilities.debugNSLog("[Camera Input] Getting current camera system capabilities") var camerasToDiscover: [AVCaptureDevice.DeviceType] = [] - if #available(iOS 17.0, *) { camerasToDiscover = [.builtInUltraWideCamera, .builtInWideAngleCamera, .builtInTelephotoCamera, .continuityCamera, .external] } + if #available(iOS 17.0, *) { camerasToDiscover = [.builtInUltraWideCamera, .builtInWideAngleCamera, .builtInTelephotoCamera ] } else { camerasToDiscover = [.builtInUltraWideCamera, .builtInWideAngleCamera, .builtInTelephotoCamera] } utilities.debugNSLog("[Camera Input] Discovering available cameras") From 871f5a18b035e54219863959ff25e734f02f6d31 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Sun, 17 Aug 2025 02:58:48 -0600 Subject: [PATCH 07/66] Refactor CompatibilityView and AboutView - Added a few things to the DeveloperView - Rename some localizations and functions - Put CompatibilityView into sections for more clarity - Switch to isFlashAvailable from hasFlash - Remove some unnecessary code, formatting changes --- Malachite.xcodeproj/project.pbxproj | 156 +++++++----- Malachite/Localizable.xcstrings | 119 ++++++++- Malachite/Utilities/MalachiteViewUtils.swift | 2 +- .../Preferences/MalachitePreferences.swift | 1 - .../Views/AboutView/AboutView+Credits.swift | 92 +++++++ .../Views/AboutView/AboutView+Eggs.swift | 65 +++++ .../Views/AboutView/AboutView+Info.swift | 78 ++++++ .../Views/AboutView/AboutView+Story.swift | 18 ++ Malachite/Views/AboutView/AboutView.swift | 41 +++ .../CompatibilityView+Camera.swift | 35 +++ .../CompatibilityView+Format.swift | 28 ++ .../CompatibilityView+Resolutions.swift | 41 +++ .../CompatibilityView/CompatibilityView.swift | 51 ++++ .../DeveloperView+BuildInfo.swift | 48 +--- .../DeveloperView+DeviceInfo.swift | 32 ++- .../Views/DeveloperView/DeveloperView.swift | 14 + Malachite/Views/MalachiteAboutView.swift | 241 ------------------ .../Views/MalachiteCompatibilityView.swift | 61 ----- Malachite/Views/MalachiteView.swift | 5 +- .../QuickHelpView/QuickHelpView+About.swift | 6 +- .../QuickHelpView/QuickHelpView+Photo.swift | 6 +- .../QuickHelpView/QuickHelpView+Preview.swift | 6 +- .../QuickHelpView+Resolution.swift | 16 +- .../QuickHelpView/QuickHelpView+UI.swift | 10 +- .../QuickHelpView+Watermarking.swift | 4 +- .../Views/QuickHelpView/QuickHelpView.swift | 16 +- .../SettingsView/SettingsView+About.swift | 4 +- .../Views/SettingsView/SettingsView.swift | 3 +- MalachiteWatch/ContentView.swift | 15 +- 29 files changed, 750 insertions(+), 464 deletions(-) create mode 100644 Malachite/Views/AboutView/AboutView+Credits.swift create mode 100644 Malachite/Views/AboutView/AboutView+Eggs.swift create mode 100644 Malachite/Views/AboutView/AboutView+Info.swift create mode 100644 Malachite/Views/AboutView/AboutView+Story.swift create mode 100755 Malachite/Views/AboutView/AboutView.swift create mode 100644 Malachite/Views/CompatibilityView/CompatibilityView+Camera.swift create mode 100644 Malachite/Views/CompatibilityView/CompatibilityView+Format.swift create mode 100644 Malachite/Views/CompatibilityView/CompatibilityView+Resolutions.swift create mode 100755 Malachite/Views/CompatibilityView/CompatibilityView.swift delete mode 100755 Malachite/Views/MalachiteAboutView.swift delete mode 100755 Malachite/Views/MalachiteCompatibilityView.swift diff --git a/Malachite.xcodeproj/project.pbxproj b/Malachite.xcodeproj/project.pbxproj index 3666afe..7e2cac2 100755 --- a/Malachite.xcodeproj/project.pbxproj +++ b/Malachite.xcodeproj/project.pbxproj @@ -18,8 +18,8 @@ 786DAE242E4F28A600BE3137 /* DeveloperView+BuildInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE182E4F28A600BE3137 /* DeveloperView+BuildInfo.swift */; }; 786DAE252E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE192E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift */; }; 786DAE262E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1A2E4F28A600BE3137 /* DeveloperView+Settings.swift */; }; - 786DAE272E4F28A600BE3137 /* MalachiteAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1C2E4F28A600BE3137 /* MalachiteAboutView.swift */; }; - 786DAE282E4F28A600BE3137 /* MalachiteCompatibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1D2E4F28A600BE3137 /* MalachiteCompatibilityView.swift */; }; + 786DAE272E4F28A600BE3137 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1C2E4F28A600BE3137 /* AboutView.swift */; }; + 786DAE282E4F28A600BE3137 /* CompatibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1D2E4F28A600BE3137 /* CompatibilityView.swift */; }; 786DAE292E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1E2E4F28A600BE3137 /* MalachitePhotoPreview.swift */; }; 786DAE2A2E4F28A600BE3137 /* QuickHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1F2E4F28A600BE3137 /* QuickHelpView.swift */; }; 786DAE2B2E4F28A600BE3137 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE202E4F28A600BE3137 /* SettingsView.swift */; }; @@ -28,8 +28,8 @@ 786DAE2E2E4F28A600BE3137 /* DeveloperView+BuildInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE182E4F28A600BE3137 /* DeveloperView+BuildInfo.swift */; }; 786DAE2F2E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE192E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift */; }; 786DAE302E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1A2E4F28A600BE3137 /* DeveloperView+Settings.swift */; }; - 786DAE312E4F28A600BE3137 /* MalachiteAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1C2E4F28A600BE3137 /* MalachiteAboutView.swift */; }; - 786DAE322E4F28A600BE3137 /* MalachiteCompatibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1D2E4F28A600BE3137 /* MalachiteCompatibilityView.swift */; }; + 786DAE312E4F28A600BE3137 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1C2E4F28A600BE3137 /* AboutView.swift */; }; + 786DAE322E4F28A600BE3137 /* CompatibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1D2E4F28A600BE3137 /* CompatibilityView.swift */; }; 786DAE332E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1E2E4F28A600BE3137 /* MalachitePhotoPreview.swift */; }; 786DAE342E4F28A600BE3137 /* QuickHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1F2E4F28A600BE3137 /* QuickHelpView.swift */; }; 786DAE352E4F28A600BE3137 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE202E4F28A600BE3137 /* SettingsView.swift */; }; @@ -38,8 +38,8 @@ 786DAE382E4F28A600BE3137 /* DeveloperView+BuildInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE182E4F28A600BE3137 /* DeveloperView+BuildInfo.swift */; }; 786DAE392E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE192E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift */; }; 786DAE3A2E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1A2E4F28A600BE3137 /* DeveloperView+Settings.swift */; }; - 786DAE3B2E4F28A600BE3137 /* MalachiteAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1C2E4F28A600BE3137 /* MalachiteAboutView.swift */; }; - 786DAE3C2E4F28A600BE3137 /* MalachiteCompatibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1D2E4F28A600BE3137 /* MalachiteCompatibilityView.swift */; }; + 786DAE3B2E4F28A600BE3137 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1C2E4F28A600BE3137 /* AboutView.swift */; }; + 786DAE3C2E4F28A600BE3137 /* CompatibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1D2E4F28A600BE3137 /* CompatibilityView.swift */; }; 786DAE3D2E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1E2E4F28A600BE3137 /* MalachitePhotoPreview.swift */; }; 786DAE3E2E4F28A600BE3137 /* QuickHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1F2E4F28A600BE3137 /* QuickHelpView.swift */; }; 786DAE3F2E4F28A600BE3137 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE202E4F28A600BE3137 /* SettingsView.swift */; }; @@ -121,6 +121,18 @@ 788262992E514F3F000085AC /* QuickHelpView+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262982E514F3B000085AC /* QuickHelpView+UI.swift */; }; 7882629A2E514F3F000085AC /* QuickHelpView+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262982E514F3B000085AC /* QuickHelpView+UI.swift */; }; 7882629B2E514F3F000085AC /* QuickHelpView+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262982E514F3B000085AC /* QuickHelpView+UI.swift */; }; + 788262BA2E5170F4000085AC /* AboutView+Info.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262B92E5170EF000085AC /* AboutView+Info.swift */; }; + 788262BB2E5170F4000085AC /* AboutView+Info.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262B92E5170EF000085AC /* AboutView+Info.swift */; }; + 788262BC2E5170F4000085AC /* AboutView+Info.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262B92E5170EF000085AC /* AboutView+Info.swift */; }; + 788262DA2E5171F0000085AC /* AboutView+Story.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262D92E5171EC000085AC /* AboutView+Story.swift */; }; + 788262DB2E5171F0000085AC /* AboutView+Story.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262D92E5171EC000085AC /* AboutView+Story.swift */; }; + 788262DC2E5171F0000085AC /* AboutView+Story.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262D92E5171EC000085AC /* AboutView+Story.swift */; }; + 788262DE2E517252000085AC /* AboutView+Eggs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262DD2E51724C000085AC /* AboutView+Eggs.swift */; }; + 788262DF2E517252000085AC /* AboutView+Eggs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262DD2E51724C000085AC /* AboutView+Eggs.swift */; }; + 788262E02E517252000085AC /* AboutView+Eggs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262DD2E51724C000085AC /* AboutView+Eggs.swift */; }; + 788262E22E5172E8000085AC /* AboutView+Credits.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262E12E5172E3000085AC /* AboutView+Credits.swift */; }; + 788262E32E5172E8000085AC /* AboutView+Credits.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262E12E5172E3000085AC /* AboutView+Credits.swift */; }; + 788262E42E5172E8000085AC /* AboutView+Credits.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262E12E5172E3000085AC /* AboutView+Credits.swift */; }; 78917F5C2B99AE72005E10FA /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78917F5B2B99AE72005E10FA /* WidgetKit.framework */; }; 78917F5E2B99AE72005E10FA /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78917F5D2B99AE72005E10FA /* SwiftUI.framework */; }; 78917F692B99AE73005E10FA /* WidgetBundle.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 78917F592B99AE72005E10FA /* WidgetBundle.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -129,6 +141,15 @@ 78A9DE752CD5AE5F002C131D /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */; }; 78B72BB12E332830002E2D4E /* MalachiteWatch.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 78B72BA72E33282E002E2D4E /* MalachiteWatch.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 78CAC36A2CCD99B600A35AE8 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */; }; + 78E170772E51C97B009BEF2F /* CompatibilityView+Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E170762E51C971009BEF2F /* CompatibilityView+Camera.swift */; }; + 78E170782E51C97B009BEF2F /* CompatibilityView+Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E170762E51C971009BEF2F /* CompatibilityView+Camera.swift */; }; + 78E170792E51C97B009BEF2F /* CompatibilityView+Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E170762E51C971009BEF2F /* CompatibilityView+Camera.swift */; }; + 78E1707B2E51CC63009BEF2F /* CompatibilityView+Resolutions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E1707A2E51CC5C009BEF2F /* CompatibilityView+Resolutions.swift */; }; + 78E1707C2E51CC63009BEF2F /* CompatibilityView+Resolutions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E1707A2E51CC5C009BEF2F /* CompatibilityView+Resolutions.swift */; }; + 78E1707D2E51CC63009BEF2F /* CompatibilityView+Resolutions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E1707A2E51CC5C009BEF2F /* CompatibilityView+Resolutions.swift */; }; + 78E1707F2E51CE45009BEF2F /* CompatibilityView+Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E1707E2E51CE3D009BEF2F /* CompatibilityView+Format.swift */; }; + 78E170802E51CE45009BEF2F /* CompatibilityView+Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E1707E2E51CE3D009BEF2F /* CompatibilityView+Format.swift */; }; + 78E170812E51CE45009BEF2F /* CompatibilityView+Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E1707E2E51CE3D009BEF2F /* CompatibilityView+Format.swift */; }; 78EC5D5F2E4EC110005044E2 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */; }; /* End PBXBuildFile section */ @@ -232,8 +253,8 @@ 786DAE182E4F28A600BE3137 /* DeveloperView+BuildInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DeveloperView+BuildInfo.swift"; sourceTree = ""; }; 786DAE192E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DeveloperView+DeviceInfo.swift"; sourceTree = ""; }; 786DAE1A2E4F28A600BE3137 /* DeveloperView+Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DeveloperView+Settings.swift"; sourceTree = ""; }; - 786DAE1C2E4F28A600BE3137 /* MalachiteAboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteAboutView.swift; sourceTree = ""; }; - 786DAE1D2E4F28A600BE3137 /* MalachiteCompatibilityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteCompatibilityView.swift; sourceTree = ""; }; + 786DAE1C2E4F28A600BE3137 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; + 786DAE1D2E4F28A600BE3137 /* CompatibilityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompatibilityView.swift; sourceTree = ""; }; 786DAE1E2E4F28A600BE3137 /* MalachitePhotoPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachitePhotoPreview.swift; sourceTree = ""; }; 786DAE1F2E4F28A600BE3137 /* QuickHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickHelpView.swift; sourceTree = ""; }; 786DAE202E4F28A600BE3137 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; @@ -271,6 +292,10 @@ 788262902E514F28000085AC /* QuickHelpView+Photo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QuickHelpView+Photo.swift"; sourceTree = ""; }; 788262942E514F31000085AC /* QuickHelpView+Watermarking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QuickHelpView+Watermarking.swift"; sourceTree = ""; }; 788262982E514F3B000085AC /* QuickHelpView+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QuickHelpView+UI.swift"; sourceTree = ""; }; + 788262B92E5170EF000085AC /* AboutView+Info.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AboutView+Info.swift"; sourceTree = ""; }; + 788262D92E5171EC000085AC /* AboutView+Story.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AboutView+Story.swift"; sourceTree = ""; }; + 788262DD2E51724C000085AC /* AboutView+Eggs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AboutView+Eggs.swift"; sourceTree = ""; }; + 788262E12E5172E3000085AC /* AboutView+Credits.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AboutView+Credits.swift"; sourceTree = ""; }; 788589912B8974680018E2DA /* Malachite.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Malachite.entitlements; sourceTree = ""; }; 78917F592B99AE72005E10FA /* WidgetBundle.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetBundle.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 78917F5B2B99AE72005E10FA /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; @@ -279,51 +304,11 @@ 78A9DE302CD581F1002C131D /* LockedCameraCapture.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LockedCameraCapture.framework; path = System/Library/Frameworks/LockedCameraCapture.framework; sourceTree = SDKROOT; }; 78B72BA72E33282E002E2D4E /* MalachiteWatch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MalachiteWatch.app; sourceTree = BUILT_PRODUCTS_DIR; }; 78E142C92DD426260016B3DB /* Codesigning.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Codesigning.xcconfig; sourceTree = ""; }; + 78E170762E51C971009BEF2F /* CompatibilityView+Camera.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CompatibilityView+Camera.swift"; sourceTree = ""; }; + 78E1707A2E51CC5C009BEF2F /* CompatibilityView+Resolutions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CompatibilityView+Resolutions.swift"; sourceTree = ""; }; + 78E1707E2E51CE3D009BEF2F /* CompatibilityView+Format.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CompatibilityView+Format.swift"; sourceTree = ""; }; /* End PBXFileReference section */ -/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ - 7882625A2E512B3A000085AC /* Exceptions for "Assets" folder in "Malachite" target */ = { - isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - membershipExceptions = ( - MalachiteIconMasked_Darwin24.png, - MalachiteIconMasked_Darwin25.png, - ); - target = 785F08482B12D41100244EB4 /* Malachite */; - }; - 7882625B2E512B3A000085AC /* Exceptions for "Assets" folder in "MalachiteWatch" target */ = { - isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - membershipExceptions = ( - MalachiteIconMasked_Darwin24.png, - MalachiteIconMasked_Darwin25.png, - ); - target = 78B72BA62E33282E002E2D4E /* MalachiteWatch */; - }; - 7882625C2E512B3A000085AC /* Exceptions for "Assets" folder in "WidgetBundle" target */ = { - isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - membershipExceptions = ( - MalachiteIconMasked_Darwin24.png, - MalachiteIconMasked_Darwin25.png, - ); - target = 78917F582B99AE72005E10FA /* WidgetBundle */; - }; - 7882625D2E512B3A000085AC /* Exceptions for "Assets" folder in "WidgetBundleWatch" target */ = { - isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - membershipExceptions = ( - MalachiteIconMasked_Darwin24.png, - MalachiteIconMasked_Darwin25.png, - ); - target = 7837C1042E34C45B009396B0 /* WidgetBundleWatch */; - }; - 7882625E2E512B3A000085AC /* Exceptions for "Assets" folder in "CaptureBundle" target */ = { - isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - membershipExceptions = ( - MalachiteIconMasked_Darwin24.png, - MalachiteIconMasked_Darwin25.png, - ); - target = 78A9DD602CD55551002C131D /* CaptureBundle */; - }; -/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ - /* Begin PBXFileSystemSynchronizedRootGroup section */ 786DAD902E4F282E00BE3137 /* .github */ = { isa = PBXFileSystemSynchronizedRootGroup; @@ -337,13 +322,6 @@ }; 786DAD982E4F283500BE3137 /* Assets */ = { isa = PBXFileSystemSynchronizedRootGroup; - exceptions = ( - 7882625A2E512B3A000085AC /* Exceptions for "Assets" folder in "Malachite" target */, - 7882625B2E512B3A000085AC /* Exceptions for "Assets" folder in "MalachiteWatch" target */, - 7882625C2E512B3A000085AC /* Exceptions for "Assets" folder in "WidgetBundle" target */, - 7882625D2E512B3A000085AC /* Exceptions for "Assets" folder in "WidgetBundleWatch" target */, - 7882625E2E512B3A000085AC /* Exceptions for "Assets" folder in "CaptureBundle" target */, - ); path = Assets; sourceTree = ""; }; @@ -449,12 +427,12 @@ 786DAE222E4F28A600BE3137 /* Views */ = { isa = PBXGroup; children = ( - 7882627F2E514CA8000085AC /* QuickHelpView */, - 786DAE1C2E4F28A600BE3137 /* MalachiteAboutView.swift */, - 786DAE1D2E4F28A600BE3137 /* MalachiteCompatibilityView.swift */, + 78E170752E51C312009BEF2F /* CompatibilityView */, 786DAE1E2E4F28A600BE3137 /* MalachitePhotoPreview.swift */, 786DAE212E4F28A600BE3137 /* MalachiteView.swift */, + 788262B82E517076000085AC /* AboutView */, 786DAE802E4F28D600BE3137 /* SettingsView */, + 7882627F2E514CA8000085AC /* QuickHelpView */, 786DAE1B2E4F28A600BE3137 /* DeveloperView */, ); path = Views; @@ -542,6 +520,18 @@ path = QuickHelpView; sourceTree = ""; }; + 788262B82E517076000085AC /* AboutView */ = { + isa = PBXGroup; + children = ( + 786DAE1C2E4F28A600BE3137 /* AboutView.swift */, + 788262B92E5170EF000085AC /* AboutView+Info.swift */, + 788262D92E5171EC000085AC /* AboutView+Story.swift */, + 788262E12E5172E3000085AC /* AboutView+Credits.swift */, + 788262DD2E51724C000085AC /* AboutView+Eggs.swift */, + ); + path = AboutView; + sourceTree = ""; + }; 78917F5A2B99AE72005E10FA /* Frameworks */ = { isa = PBXGroup; children = ( @@ -552,6 +542,17 @@ name = Frameworks; sourceTree = ""; }; + 78E170752E51C312009BEF2F /* CompatibilityView */ = { + isa = PBXGroup; + children = ( + 786DAE1D2E4F28A600BE3137 /* CompatibilityView.swift */, + 78E170762E51C971009BEF2F /* CompatibilityView+Camera.swift */, + 78E1707A2E51CC5C009BEF2F /* CompatibilityView+Resolutions.swift */, + 78E1707E2E51CE3D009BEF2F /* CompatibilityView+Format.swift */, + ); + path = CompatibilityView; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -817,13 +818,18 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 788262BB2E5170F4000085AC /* AboutView+Info.swift in Sources */, + 78E1707D2E51CC63009BEF2F /* CompatibilityView+Resolutions.swift in Sources */, + 788262DA2E5171F0000085AC /* AboutView+Story.swift in Sources */, 786DAE552E4F28B100BE3137 /* MalachitePreferences.swift in Sources */, 786DAE562E4F28B100BE3137 /* MalachitePreferencesUtils.swift in Sources */, 7882628A2E514F18000085AC /* QuickHelpView+Preview.swift in Sources */, + 788262E32E5172E8000085AC /* AboutView+Credits.swift in Sources */, 786DAE572E4F28B100BE3137 /* MalachiteFunctionUtils.swift in Sources */, 788262932E514F2D000085AC /* QuickHelpView+Photo.swift in Sources */, 786DAE582E4F28B100BE3137 /* MalachiteGameUtils.swift in Sources */, 786DAE592E4F28B100BE3137 /* MalachiteHapticUtils.swift in Sources */, + 78E170772E51C97B009BEF2F /* CompatibilityView+Camera.swift in Sources */, 786DAE5A2E4F28B100BE3137 /* MalachiteIntentUtils.swift in Sources */, 788262622E513080000085AC /* SettingsView+About.swift in Sources */, 786DAE5B2E4F28B100BE3137 /* MalachiteTooltipUtils.swift in Sources */, @@ -835,8 +841,8 @@ 7882627A2E5130DB000085AC /* SettingsView+UI.swift in Sources */, 786DAE2F2E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift in Sources */, 786DAE302E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */, - 786DAE312E4F28A600BE3137 /* MalachiteAboutView.swift in Sources */, - 786DAE322E4F28A600BE3137 /* MalachiteCompatibilityView.swift in Sources */, + 786DAE312E4F28A600BE3137 /* AboutView.swift in Sources */, + 786DAE322E4F28A600BE3137 /* CompatibilityView.swift in Sources */, 7882627E2E514750000085AC /* DeveloperView+Internal.swift in Sources */, 786DAE332E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */, 786DAE342E4F28A600BE3137 /* QuickHelpView.swift in Sources */, @@ -851,6 +857,8 @@ 788262962E514F38000085AC /* QuickHelpView+Watermarking.swift in Sources */, 787B1CAA2B8B095E000AFECC /* Malachite.docc in Sources */, 788262662E51309C000085AC /* SettingsView+Preview.swift in Sources */, + 788262E02E517252000085AC /* AboutView+Eggs.swift in Sources */, + 78E1707F2E51CE45009BEF2F /* CompatibilityView+Format.swift in Sources */, 785F084F2B12D41100244EB4 /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -859,13 +867,18 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 788262BC2E5170F4000085AC /* AboutView+Info.swift in Sources */, + 78E1707B2E51CC63009BEF2F /* CompatibilityView+Resolutions.swift in Sources */, + 788262DB2E5171F0000085AC /* AboutView+Story.swift in Sources */, 786DAE5E2E4F28B100BE3137 /* MalachitePreferences.swift in Sources */, 786DAE5F2E4F28B100BE3137 /* MalachitePreferencesUtils.swift in Sources */, 788262892E514F18000085AC /* QuickHelpView+Preview.swift in Sources */, + 788262E22E5172E8000085AC /* AboutView+Credits.swift in Sources */, 786DAE602E4F28B100BE3137 /* MalachiteFunctionUtils.swift in Sources */, 788262922E514F2D000085AC /* QuickHelpView+Photo.swift in Sources */, 786DAE612E4F28B100BE3137 /* MalachiteGameUtils.swift in Sources */, 786DAE622E4F28B100BE3137 /* MalachiteHapticUtils.swift in Sources */, + 78E170792E51C97B009BEF2F /* CompatibilityView+Camera.swift in Sources */, 786DAE632E4F28B100BE3137 /* MalachiteIntentUtils.swift in Sources */, 788262612E513080000085AC /* SettingsView+About.swift in Sources */, 786DAE642E4F28B100BE3137 /* MalachiteTooltipUtils.swift in Sources */, @@ -881,10 +894,10 @@ 786DAE6F2E4F28B700BE3137 /* MalachiteWidgetBundle.swift in Sources */, 7882627D2E514750000085AC /* DeveloperView+Internal.swift in Sources */, 786DAE3A2E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */, - 786DAE3B2E4F28A600BE3137 /* MalachiteAboutView.swift in Sources */, + 786DAE3B2E4F28A600BE3137 /* AboutView.swift in Sources */, 788262852E514F12000085AC /* QuickHelpView+About.swift in Sources */, 7882626D2E5130C1000085AC /* SettingsView+Resolution.swift in Sources */, - 786DAE3C2E4F28A600BE3137 /* MalachiteCompatibilityView.swift in Sources */, + 786DAE3C2E4F28A600BE3137 /* CompatibilityView.swift in Sources */, 7882628E2E514F25000085AC /* QuickHelpView+Resolution.swift in Sources */, 788262992E514F3F000085AC /* QuickHelpView+UI.swift in Sources */, 788262742E5130D4000085AC /* SettingsView+Watermarking.swift in Sources */, @@ -893,6 +906,8 @@ 788262952E514F38000085AC /* QuickHelpView+Watermarking.swift in Sources */, 786DAE3F2E4F28A600BE3137 /* SettingsView.swift in Sources */, 788262652E51309C000085AC /* SettingsView+Preview.swift in Sources */, + 788262DE2E517252000085AC /* AboutView+Eggs.swift in Sources */, + 78E170812E51CE45009BEF2F /* CompatibilityView+Format.swift in Sources */, 786DAE402E4F28A600BE3137 /* MalachiteView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -905,12 +920,15 @@ 788262782E5130DB000085AC /* SettingsView+UI.swift in Sources */, 786DAE242E4F28A600BE3137 /* DeveloperView+BuildInfo.swift in Sources */, 786DAE252E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift in Sources */, + 78E170782E51C97B009BEF2F /* CompatibilityView+Camera.swift in Sources */, 788262602E513080000085AC /* SettingsView+About.swift in Sources */, 786DAE4C2E4F28B100BE3137 /* MalachitePreferences.swift in Sources */, + 788262DF2E517252000085AC /* AboutView+Eggs.swift in Sources */, 788262972E514F38000085AC /* QuickHelpView+Watermarking.swift in Sources */, 788262752E5130D4000085AC /* SettingsView+Watermarking.swift in Sources */, 7882627C2E514750000085AC /* DeveloperView+Internal.swift in Sources */, 786DAE4D2E4F28B100BE3137 /* MalachitePreferencesUtils.swift in Sources */, + 788262DC2E5171F0000085AC /* AboutView+Story.swift in Sources */, 788262642E51309C000085AC /* SettingsView+Preview.swift in Sources */, 786DAE782E4F28B900BE3137 /* MalachiteCaptureBundle.swift in Sources */, 786DAE4E2E4F28B100BE3137 /* MalachiteFunctionUtils.swift in Sources */, @@ -919,16 +937,20 @@ 7882626C2E5130C1000085AC /* SettingsView+Resolution.swift in Sources */, 786DAE502E4F28B100BE3137 /* MalachiteHapticUtils.swift in Sources */, 786DAE512E4F28B100BE3137 /* MalachiteIntentUtils.swift in Sources */, + 788262E42E5172E8000085AC /* AboutView+Credits.swift in Sources */, 786DAE522E4F28B100BE3137 /* MalachiteTooltipUtils.swift in Sources */, 786DAE532E4F28B100BE3137 /* MalachiteUtils.swift in Sources */, + 788262BA2E5170F4000085AC /* AboutView+Info.swift in Sources */, 786DAE542E4F28B100BE3137 /* MalachiteViewUtils.swift in Sources */, 788262872E514F12000085AC /* QuickHelpView+About.swift in Sources */, 786DAE262E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */, 788262912E514F2D000085AC /* QuickHelpView+Photo.swift in Sources */, - 786DAE272E4F28A600BE3137 /* MalachiteAboutView.swift in Sources */, - 786DAE282E4F28A600BE3137 /* MalachiteCompatibilityView.swift in Sources */, + 786DAE272E4F28A600BE3137 /* AboutView.swift in Sources */, + 78E1707C2E51CC63009BEF2F /* CompatibilityView+Resolutions.swift in Sources */, + 786DAE282E4F28A600BE3137 /* CompatibilityView.swift in Sources */, 786DAE292E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */, 788262712E5130CA000085AC /* SettingsView+Photo.swift in Sources */, + 78E170802E51CE45009BEF2F /* CompatibilityView+Format.swift in Sources */, 7882628D2E514F25000085AC /* QuickHelpView+Resolution.swift in Sources */, 786DAE2A2E4F28A600BE3137 /* QuickHelpView.swift in Sources */, 7882628B2E514F18000085AC /* QuickHelpView+Preview.swift in Sources */, diff --git a/Malachite/Localizable.xcstrings b/Malachite/Localizable.xcstrings index 46a6994..a9c719d 100755 --- a/Malachite/Localizable.xcstrings +++ b/Malachite/Localizable.xcstrings @@ -400,6 +400,36 @@ } } }, + "compatibility.header.cameras" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Camera support" + } + } + } + }, + "compatibility.header.format" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Format support" + } + } + } + }, + "compatibility.header.resolution" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Resolution support" + } + } + } + }, "compatibility.note" : { "localizations" : { "en" : { @@ -608,7 +638,18 @@ } } }, - "compatibility.title.no.telephoto.unsupported" : { + "compatibility.title.telephoto" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Telephoto camera detected" + } + } + } + }, + "compatibility.title.telephoto.unsupported" : { "extractionState" : "manual", "localizations" : { "en" : { @@ -619,7 +660,18 @@ } } }, - "compatibility.title.no.ultrawide.unsupported" : { + "compatibility.title.ultrawide" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ultra wide camera detected" + } + } + } + }, + "compatibility.title.ultrawide.unsupported" : { "extractionState" : "manual", "localizations" : { "en" : { @@ -630,7 +682,18 @@ } } }, - "compatibility.title.no.wide.unsupported" : { + "compatibility.title.wide" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wide angle camera detected" + } + } + } + }, + "compatibility.title.wide.unsupported" : { "extractionState" : "manual", "localizations" : { "en" : { @@ -681,6 +744,16 @@ } } }, + "developer.footer.device" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Device information is shown for informational and debugging purposes only and does not leave your device unless you choose to export a log file from this screen." + } + } + } + }, "developer.header.debug" : { "localizations" : { "en" : { @@ -691,6 +764,16 @@ } } }, + "developer.header.device" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Device information" + } + } + } + }, "developer.header.info" : { "localizations" : { "en" : { @@ -751,6 +834,36 @@ } } }, + "developer.option.device_build" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "OS build number" + } + } + } + }, + "developer.option.device_model" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Device model" + } + } + } + }, + "developer.option.device_version" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "OS version" + } + } + } + }, "developer.option.version_branch" : { "localizations" : { "en" : { diff --git a/Malachite/Utilities/MalachiteViewUtils.swift b/Malachite/Utilities/MalachiteViewUtils.swift index 133920d..260ab1b 100755 --- a/Malachite/Utilities/MalachiteViewUtils.swift +++ b/Malachite/Utilities/MalachiteViewUtils.swift @@ -333,7 +333,7 @@ struct MalachiteToolbarUtils: View { Button { self.action() } label: { - Image(systemName: "\(image).circle").tint(primary ? .primary : nil) + Image(systemName: "\(image).circle").tint(!primary ? .primary : nil) } } } diff --git a/Malachite/Utilities/Preferences/MalachitePreferences.swift b/Malachite/Utilities/Preferences/MalachitePreferences.swift index eb12f11..f31b19f 100755 --- a/Malachite/Utilities/Preferences/MalachitePreferences.swift +++ b/Malachite/Utilities/Preferences/MalachitePreferences.swift @@ -7,7 +7,6 @@ import Foundation import AppIntents -// TODO: Rename a bunch of these preferences to be more concise in their meaning struct MalachitePreferences_AppContext: Codable, IntentResult { var value: Never? diff --git a/Malachite/Views/AboutView/AboutView+Credits.swift b/Malachite/Views/AboutView/AboutView+Credits.swift new file mode 100644 index 0000000..4af9c2c --- /dev/null +++ b/Malachite/Views/AboutView/AboutView+Credits.swift @@ -0,0 +1,92 @@ +// +// AboutView+Credits.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension AboutView { + struct Credits: View { + private struct AppIcon { + let id = UUID() + let name: String + let description: String + let image: String + let symbol: Bool + let icon: String? + let achievement: String? + } + /// A variable used to determine the currently available app icons. + private var appIcons = [ + AppIcon(name: "crystall1nedev", description: "about.credits.crystall1nedev", image: "crystall1nedev", symbol: false, icon: nil, achievement: "icon.default"), + AppIcon(name: "ThatStella7922", description: "about.credits.thatstella7922", image: "thatstella7922", symbol: false, icon: "thatsniceguy", achievement: "icon.wifey"), + AppIcon(name: "ASentientBot", description: "about.credits.asentientbot", image: "asentientbot", symbol: false, icon: "asb_approved", achievement: "icon.marimo"), + AppIcon(name: "The Sanctuary Discord", description: "about.credits.discord", image: "", symbol: true, icon: nil, achievement: nil), + AppIcon(name: "Apple", description: "about.credits.apple", image: "applelogo", symbol: true, icon: nil, achievement: nil) + ] + /// A variable to hold the existing instance of ``MalachiteClassesObject``. + var utilities = MalachiteClassesObject() + + init( + utilities: MalachiteClassesObject, + ) { + self.utilities = utilities + } + + var body: some View { + Section(header: Text("about.header.credits")) { + ForEach(appIcons, id: \.id) {appIcon in + HStack { + VStack { + HStack { + Text(appIcon.name) + .font(.title2) + .bold() + Spacer() + } + HStack { + Text(LocalizedStringKey(appIcon.description)) + Spacer() + } + } + Spacer() + Button { + utilities.debugNSLog("[App Icon] Changing to \(appIcon.icon ?? "default")") +#if MAIN_APP + UIApplication.shared.setAlternateIconName(appIcon.icon) { (error) in + if let error = error { + print("Failed request to update the app’s icon: \(error)") + } + } +#endif + if utilities.games.gameCenterEnabled && appIcon.achievement != nil { + DispatchQueue.global(qos: .background).async { [self] in + let iconAchievement = utilities.games.achievements.pullAchievement(achievementName: appIcon.achievement!) + iconAchievement.percentComplete = 100 + utilities.games.achievements.pushAchievement(achievementBody: iconAchievement) + } + } + } label: { + Text("") + } + if !appIcon.symbol { + Image(appIcon.image) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxWidth: 40, alignment: .trailing) + .clipShape(Circle()) + } else { + Image(systemName: appIcon.image) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxWidth: 30, alignment: .trailing) + .padding(.trailing, 5) + } + } + } + } + } + } +} diff --git a/Malachite/Views/AboutView/AboutView+Eggs.swift b/Malachite/Views/AboutView/AboutView+Eggs.swift new file mode 100644 index 0000000..a99f649 --- /dev/null +++ b/Malachite/Views/AboutView/AboutView+Eggs.swift @@ -0,0 +1,65 @@ +// +// AboutView+Eggs.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension AboutView { + struct Eggs: View { + /// A State variable used for determining whether or not to enable Game Center integration. + @State private var gamekitSwitch = false + /// A State variable used for determining whether or not to uncap the exposure slider. + @State private var exposureUnlimiterSwitch = false + /// A variable to hold the existing instance of ``MalachiteClassesObject``. + var utilities = MalachiteClassesObject() + + init( + utilities: MalachiteClassesObject, + ) { + self.utilities = utilities + } + + var body: some View { + Section(header: Text("about.header.special")) { + MalachiteCellViewUtils( + icon: "sun.max", + disabled: nil, + dangerous: false) + { + Toggle("settings.option.photo.max_exposure", isOn: $exposureUnlimiterSwitch) + } + if utilities.preferences.general.gamekit.found { + MalachiteCellViewUtils( + icon: "gamecontroller", + disabled: nil, + dangerous: true) + { + Toggle("", isOn: $gamekitSwitch) + } + } + } + .onChange(of: gamekitSwitch) {_ in + utilities.preferences.general.gamekit.enabled = gamekitSwitch + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.gameCenterEnabledNotification.name, object: nil) + } + .onChange(of: exposureUnlimiterSwitch) { _ in + utilities.debugNSLog("[Settings View] Lol") + utilities.preferences.capture.unlimitedISO = exposureUnlimiterSwitch + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.exposureLimitNotification.name, object: nil) + } + .onAppear() { + gamekitSwitch = utilities.preferences.general.gamekit.enabled + exposureUnlimiterSwitch = utilities.preferences.capture.unlimitedISO + } + .onDisappear() { + utilities.preferences.general.gamekit.enabled = gamekitSwitch + utilities.preferences.capture.unlimitedISO = exposureUnlimiterSwitch + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.exposureLimitNotification.name, object: nil) + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.gameCenterEnabledNotification.name, object: nil) + } + } + } +} diff --git a/Malachite/Views/AboutView/AboutView+Info.swift b/Malachite/Views/AboutView/AboutView+Info.swift new file mode 100644 index 0000000..179c914 --- /dev/null +++ b/Malachite/Views/AboutView/AboutView+Info.swift @@ -0,0 +1,78 @@ +// +// AboutView+Info.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension AboutView { + struct Info: View { + /// A variable to hold the existing instance of ``MalachiteClassesObject``. + var utilities = MalachiteClassesObject() + + init( + utilities: MalachiteClassesObject, + ) { + self.utilities = utilities + } + + var body: some View { + Section(footer: (utilities.versionType == "INTERNAL") ? footer : nil){ + HStack { + VStack { + HStack { + Text("appname") + .font(.largeTitle) + .bold() + Spacer() + } + HStack { + Text("\(utilities.versionMajor).\(utilities.versionMinor).\(utilities.versionMinor)") + .font(.footnote) + .frame(alignment: .leading) + Spacer() + } + } + Spacer() + Button { + if utilities.versionType == "INTERNAL" { + utilities.preferences.ext.showGameKitOptionInAbout(in: &utilities.preferences) + } + } label: { + if #available(iOS 26.0, *) { + Image("icon26") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxWidth: 80, alignment: .trailing) + .clipShape(RoundedRectangle(cornerRadius: 17)) + } else { + Image("icon") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxWidth: 80, alignment: .trailing) + .clipShape(RoundedRectangle(cornerRadius: 17)) + } + } + } + Text("about.description") + Text("about.author_note") + .bold() + } + } + + var footer: some View { + VStack { + if utilities.versionType == "DEBUG" { + HStack { + Text("\(utilities.versionType) - \(utilities.versionHash) - \(utilities.versionDate)") + .font(.footnote) + .frame(alignment: .leading) + Spacer() + } + } + } + } + } +} diff --git a/Malachite/Views/AboutView/AboutView+Story.swift b/Malachite/Views/AboutView/AboutView+Story.swift new file mode 100644 index 0000000..dcec130 --- /dev/null +++ b/Malachite/Views/AboutView/AboutView+Story.swift @@ -0,0 +1,18 @@ +// +// AboutView+Story.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/16/25. +// + +import SwiftUI + +extension AboutView { + struct Story: View { + var body: some View { + Section(header: Text("about.header.story")) { + Text("about.story") + } + } + } +} diff --git a/Malachite/Views/AboutView/AboutView.swift b/Malachite/Views/AboutView/AboutView.swift new file mode 100755 index 0000000..20b13ca --- /dev/null +++ b/Malachite/Views/AboutView/AboutView.swift @@ -0,0 +1,41 @@ +// +// AboutView.swift +// Malachite +// +// Created by Eva Isabella Luna on 2/18/24. +// + +import SwiftUI +import GameKit + +struct AboutView: View { + /// A State variable used for determining whether or not this view is being presented as a modal. + var dismissAction: (() -> Void) + /// A variable to hold the existing instance of ``MalachiteClassesObject``. + var utilities = MalachiteClassesObject() + + /** + A variable used to hold the entire view. + + SwiftUI is weird... + Currently holds: + - Other variables to avoid type counting time issues. + - Handles initialization of variables required to show current settings. + - Navigation title of "About Malachite" + - Toolbar item for dismissing the view + */ + var body: some View { + Form { + Info(utilities: utilities) + Story() + Credits(utilities: utilities) + Eggs(utilities: utilities) + } + .navigationTitle("view.title.about") + .toolbar(content: { + ToolbarItemGroup(placement: .topBarTrailing) { + MalachiteToolbarUtils(action: self.dismissAction, image: "checkmark", primary: true) + } + }) + } +} diff --git a/Malachite/Views/CompatibilityView/CompatibilityView+Camera.swift b/Malachite/Views/CompatibilityView/CompatibilityView+Camera.swift new file mode 100644 index 0000000..0ccf88f --- /dev/null +++ b/Malachite/Views/CompatibilityView/CompatibilityView+Camera.swift @@ -0,0 +1,35 @@ +// +// CompatibilityView+Camera.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/17/25. +// + +import SwiftUI + +extension CompatibilityView { + struct Cameras: View { + /// A variable to hold the existing instance of ``MalachiteClassesObject``. + var utilities = MalachiteClassesObject() + + init( + utilities: MalachiteClassesObject, + ) { + self.utilities = utilities + } + + var body: some View { + Section(header: Text("compatibility.header.cameras")) { + MalachiteCompatibilityViewUtils( + title: "compatibility.title.ultrawide", + available: utilities.preferences.ext.dictionary.isValid(dictionary: utilities.preferences.compatibility.ultrawide) ? true : false) + MalachiteCompatibilityViewUtils( + title: "compatibility.title.wide", + available: utilities.preferences.ext.dictionary.isValid(dictionary: utilities.preferences.compatibility.wideangle) ? true : false) + MalachiteCompatibilityViewUtils( + title: "compatibility.title.telephoto", + available: utilities.preferences.ext.dictionary.isValid(dictionary: utilities.preferences.compatibility.telephoto) ? true : false) + } + } + } +} diff --git a/Malachite/Views/CompatibilityView/CompatibilityView+Format.swift b/Malachite/Views/CompatibilityView/CompatibilityView+Format.swift new file mode 100644 index 0000000..5409014 --- /dev/null +++ b/Malachite/Views/CompatibilityView/CompatibilityView+Format.swift @@ -0,0 +1,28 @@ +// +// CompatibilityView+Format.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/17/25. +// + +import SwiftUI + +extension CompatibilityView { + struct Formats: View { + /// A variable to hold the existing instance of ``MalachiteClassesObject``. + var utilities = MalachiteClassesObject() + + init( + utilities: MalachiteClassesObject, + ) { + self.utilities = utilities + } + + var body: some View { + Section(header: Text("compatibility.header.format")) { + MalachiteCompatibilityViewUtils(title: "compatibility.title.jpeg", available: utilities.preferences.compatibility.jpeg) + MalachiteCompatibilityViewUtils(title: "compatibility.title.heif", available: utilities.preferences.compatibility.heic) + } + } + } +} diff --git a/Malachite/Views/CompatibilityView/CompatibilityView+Resolutions.swift b/Malachite/Views/CompatibilityView/CompatibilityView+Resolutions.swift new file mode 100644 index 0000000..d6bb838 --- /dev/null +++ b/Malachite/Views/CompatibilityView/CompatibilityView+Resolutions.swift @@ -0,0 +1,41 @@ +// +// CompatibilityView+Resolutions.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/17/25. +// + +import SwiftUI + +extension CompatibilityView { + struct Resolutions: View { + /// A variable to hold the existing instance of ``MalachiteClassesObject``. + var utilities = MalachiteClassesObject() + + init( + utilities: MalachiteClassesObject, + ) { + self.utilities = utilities + } + + var body: some View { + Section(header: Text("compatibility.header.resolution")) { + if utilities.preferences.ext.dictionary.isValid(dictionary: utilities.preferences.compatibility.ultrawide) { + // Ultra wide megapixel capabilities + MalachiteCompatibilityViewUtils(title: "compatibility.title.12mp.ultrawide", available: utilities.preferences.compatibility.ultrawide["12"] ?? false) + MalachiteCompatibilityViewUtils(title: "compatibility.title.48mp.ultrawide", available: utilities.preferences.compatibility.ultrawide["48"] ?? false) + } + if utilities.preferences.ext.dictionary.isValid(dictionary: utilities.preferences.compatibility.wideangle) { + // Wide angle megapixel capabilities + MalachiteCompatibilityViewUtils(title: "compatibility.title.8mp.wide", available: utilities.preferences.compatibility.wideangle["8"] ?? false) + MalachiteCompatibilityViewUtils(title: "compatibility.title.12mp.wide", available: utilities.preferences.compatibility.wideangle["12"] ?? false) + MalachiteCompatibilityViewUtils(title: "compatibility.title.48mp.wide", available: utilities.preferences.compatibility.wideangle["48"] ?? false) + } + if utilities.preferences.ext.dictionary.isValid(dictionary: utilities.preferences.compatibility.telephoto) { + // Telephoto megapixel capabilities + MalachiteCompatibilityViewUtils(title: "compatibility.title.12mp.telephoto", available: utilities.preferences.compatibility.telephoto["12"] ?? false) + } + } + } + } +} diff --git a/Malachite/Views/CompatibilityView/CompatibilityView.swift b/Malachite/Views/CompatibilityView/CompatibilityView.swift new file mode 100755 index 0000000..cfbcf72 --- /dev/null +++ b/Malachite/Views/CompatibilityView/CompatibilityView.swift @@ -0,0 +1,51 @@ +// +// CompatibilityView.swift +// Malachite +// +// Created by Eva Isabella Luna on 10/21/24. +// + +import SwiftUI + +public struct CompatibilityView: View { + /// A State variable used for determining whether or not this view is being presented as a modal. + var dismissAction: (() -> Void) + /// A variable to hold the existing instance of ``MalachiteClassesObject``. + var utilities = MalachiteClassesObject() + + public var body: some View { + Form { + Section { + Text("compatibility.note") + } + Cameras(utilities: utilities) + Resolutions(utilities: utilities) + Formats(utilities: utilities) + Misc(utilities: utilities) + } + .navigationTitle("view.title.compatibility") + .toolbar(content: { + ToolbarItemGroup(placement: .topBarTrailing) { + MalachiteToolbarUtils(action: self.dismissAction, image: "checkmark", primary: true) + } + }) + } + + @available(*, deprecated, message: "Avoid using the Misc structure. When possible, build into a more descriptive structure.") + struct Misc: View { + /// A variable to hold the existing instance of ``MalachiteClassesObject``. + var utilities = MalachiteClassesObject() + + init( + utilities: MalachiteClassesObject, + ) { + self.utilities = utilities + } + + var body: some View { + Section { + MalachiteCompatibilityViewUtils(title: "compatibility.title.hdr", available: utilities.preferences.compatibility.hdr) + } + } + } +} diff --git a/Malachite/Views/DeveloperView/DeveloperView+BuildInfo.swift b/Malachite/Views/DeveloperView/DeveloperView+BuildInfo.swift index 3c9c436..6d379c9 100644 --- a/Malachite/Views/DeveloperView/DeveloperView+BuildInfo.swift +++ b/Malachite/Views/DeveloperView/DeveloperView+BuildInfo.swift @@ -20,50 +20,14 @@ extension DeveloperView { var body: some View { Section(header: Text("developer.header.info")) { if utilities.versionType == "DEBUG" || utilities.versionType == "INTERNAL" { - HStack { - Text("developer.option.version_type") - .frame(alignment: .leading) - Spacer() - Text(utilities.versionType) - .frame(alignment: .trailing) - } - HStack { - Text("developer.option.version_branch") - .frame(alignment: .leading) - Spacer() - Text(utilities.versionBranch) - .frame(alignment: .trailing) - } - HStack { - Text("developer.option.version_hash") - .frame(alignment: .leading) - Spacer() - Text(utilities.versionHash) - .frame(alignment: .trailing) - } - HStack { - Text("developer.option.version_date") - .frame(alignment: .leading) - Spacer() - Text(utilities.versionDate) - .frame(alignment: .trailing) - } + createBuildInformation(label: "developer.option.version_type", value: utilities.versionType) + createBuildInformation(label: "developer.option.version_branch", value: utilities.versionBranch) + createBuildInformation(label: "developer.option.version_hash", value: utilities.versionHash) + createBuildInformation(label: "developer.option.version_date", value: utilities.versionDate) } if utilities.versionType == "INTERNAL" { - HStack { - Text("developer.option.version_user") - .frame(alignment: .leading) - Spacer() - Text(utilities.versionUser) - .frame(alignment: .trailing) - } - HStack { - Text("developer.option.version_host") - .frame(alignment: .leading) - Spacer() - Text(utilities.versionHost) - .frame(alignment: .trailing) - } + createBuildInformation(label: "developer.option.version_user", value: utilities.versionUser) + createBuildInformation(label: "developer.option.version_host", value: utilities.versionHost) } } } diff --git a/Malachite/Views/DeveloperView/DeveloperView+DeviceInfo.swift b/Malachite/Views/DeveloperView/DeveloperView+DeviceInfo.swift index 90d2e8b..b696569 100644 --- a/Malachite/Views/DeveloperView/DeveloperView+DeviceInfo.swift +++ b/Malachite/Views/DeveloperView/DeveloperView+DeviceInfo.swift @@ -6,6 +6,7 @@ // import SwiftUI +import Foundation extension DeveloperView { struct DeviceInfo: View { @@ -18,7 +19,36 @@ extension DeveloperView { } var body: some View { - Text("") + Section(header: Text("developer.header.device"), footer: Text("developer.footer.device")) { + createBuildInformation(label: "developer.option.device_model", value: utilities.preferences.general.deviceModel) + createBuildInformation(label: "developer.option.device_version", value: getCurrentOSVersion()) + createBuildInformation(label: "developer.option.device_build", value: getCurrentOSBuild()) + } + } + + @available(*, deprecated, message: "Will be renamed in MalachiteKit.") + func getCurrentOSVersion() -> String { + var osVersion: String + + // Doing it like this to possibly add "iOS" "macOS" "watchOS" in the future + osVersion = ProcessInfo.processInfo.operatingSystemVersion.majorVersion.description + osVersion += "." + osVersion += ProcessInfo.processInfo.operatingSystemVersion.minorVersion.description + osVersion += "." + osVersion += ProcessInfo.processInfo.operatingSystemVersion.patchVersion.description + + return osVersion + } + + @available(*, deprecated, message: "Will be renamed in MalachiteKit.") + func getCurrentOSBuild() -> String { + var size = 0 + sysctlbyname("kern.osversion", nil, &size, nil, 0) + var buffer = [CChar](repeating: 0, count: size) + let result = sysctlbyname("kern.osversion", &buffer, &size, nil, 0) + if result == 0 { return String(cString: buffer) } + + return "Unknown" } } } diff --git a/Malachite/Views/DeveloperView/DeveloperView.swift b/Malachite/Views/DeveloperView/DeveloperView.swift index 79d2555..4c37e07 100644 --- a/Malachite/Views/DeveloperView/DeveloperView.swift +++ b/Malachite/Views/DeveloperView/DeveloperView.swift @@ -27,4 +27,18 @@ struct DeveloperView: View { } }) } + + struct createBuildInformation: View { + var label: LocalizedStringKey + var value: String + var body: some View { + HStack { + Text(label) + .frame(alignment: .leading) + Spacer() + Text(value) + .frame(alignment: .trailing) + } + } + } } diff --git a/Malachite/Views/MalachiteAboutView.swift b/Malachite/Views/MalachiteAboutView.swift deleted file mode 100755 index fbbe248..0000000 --- a/Malachite/Views/MalachiteAboutView.swift +++ /dev/null @@ -1,241 +0,0 @@ -// -// MalachiteAboutView.swift -// Malachite -// -// Created by Eva Isabella Luna on 2/18/24. -// - -import SwiftUI -import GameKit - -private struct AppIcon { - let id = UUID() - let name: String - let description: String - let image: String - let symbol: Bool - let icon: String? - let achievement: String? -} - -struct MalachiteAboutView: View { - /// A State variable used for determining whether or not this view is being presented as a modal. - var dismissAction: (() -> Void) - /// A State variable used for determining whether or not to enable Game Center integration. - @State private var gamekitSwitch = false - /// A State variable used for determining whether or not to uncap the exposure slider. - @State private var exposureUnlimiterSwitch = false - - /// A variable used to determine the currently available app icons. - private let appIcons = [ - AppIcon(name: "crystall1nedev", description: "about.credits.crystall1nedev", image: "crystall1nedev", symbol: false, icon: nil, achievement: "icon.default"), - AppIcon(name: "ThatStella7922", description: "about.credits.thatstella7922", image: "thatstella7922", symbol: false, icon: "thatsniceguy", achievement: "icon.wifey"), - AppIcon(name: "ASentientBot", description: "about.credits.asentientbot", image: "asentientbot", symbol: false, icon: "asb_approved", achievement: "icon.marimo"), - AppIcon(name: "The Sanctuary Discord", description: "about.credits.discord", image: "", symbol: true, icon: nil, achievement: nil), - AppIcon(name: "Apple", description: "about.credits.apple", image: "applelogo", symbol: true, icon: nil, achievement: nil) - ] - - /// A variable to hold the existing instance of ``MalachiteClassesObject``. - var utilities = MalachiteClassesObject() - /// A variable used to hold the function for dismissing with the toolbar item. - ///var dismissAction: (() -> Void) - - /** - A variable used to hold the entire view. - - SwiftUI is weird... - Currently holds: - - Other variables to avoid type counting time issues. - - Handles initialization of variables required to show current settings. - - Navigation title of "About Malachite" - - Toolbar item for dismissing the view - */ - var body: some View { - MalachiteNagivationViewUtils() { guts } - } - - var guts: some View { - Form { - aboutSection - storySection - creditsSection - funniesSection - - } - .onAppear() { - gamekitSwitch = utilities.preferences.general.gamekit.enabled - exposureUnlimiterSwitch = utilities.preferences.capture.unlimitedISO - } - .onDisappear() { - utilities.preferences.general.gamekit.enabled = gamekitSwitch - utilities.preferences.capture.unlimitedISO = exposureUnlimiterSwitch - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.exposureLimitNotification.name, object: nil) - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.gameCenterEnabledNotification.name, object: nil) - } - .navigationTitle("view.title.about") - .toolbar(content: { - ToolbarItemGroup(placement: .topBarTrailing) { - MalachiteToolbarUtils(action: self.dismissAction, image: "checkmark", primary: true) - } - }) - } - - /// A variable for the about section. - var aboutSection: some View { - Section(footer: aboutSectionFooter){ - HStack { - VStack { - HStack { - Text("appname") - .font(.largeTitle) - .bold() - Spacer() - } - HStack { - Text("\(utilities.versionMajor).\(utilities.versionMinor).\(utilities.versionMinor)") - .font(.footnote) - .frame(alignment: .leading) - Spacer() - } - } - Spacer() - Button { - if utilities.versionType == "INTERNAL" { - utilities.preferences.ext.showGameKitOptionInAbout(in: &utilities.preferences) - } - } label: { - if #available(iOS 26.0, *) { - Image("icon26") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(maxWidth: 80, alignment: .trailing) - .clipShape(RoundedRectangle(cornerRadius: 17)) - } else { - Image("icon") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(maxWidth: 80, alignment: .trailing) - .clipShape(RoundedRectangle(cornerRadius: 17)) - } - } - } - Text("about.description") - Text("about.author_note") - .bold() - } - } - - var aboutSectionFooter: some View { - VStack { - if utilities.versionType == "DEBUG" { - HStack { - Text("\(utilities.versionType) - \(utilities.versionHash) - \(utilities.versionDate)") - .font(.footnote) - .frame(alignment: .leading) - Spacer() - } - } - } - } - - /// A variable for the story section. - var storySection: some View { - Section(header: Text("about.header.story")) { - Text("about.story") - } - } - - /// A variable for the credits section. - var creditsSection : some View { - Section(header: Text("about.header.credits")) { - ForEach(appIcons, id: \.id) {appIcon in - HStack { - VStack { - HStack { - Text(appIcon.name) - .font(.title2) - .bold() - Spacer() - } - HStack { - Text(LocalizedStringKey(appIcon.description)) - Spacer() - } - } - Spacer() - Button { - utilities.debugNSLog("[App Icon] Changing to \(appIcon.icon ?? "default")") - #if MAIN_APP - UIApplication.shared.setAlternateIconName(appIcon.icon) { (error) in - if let error = error { - print("Failed request to update the app’s icon: \(error)") - } - } - #endif - if utilities.games.gameCenterEnabled && appIcon.achievement != nil { - DispatchQueue.global(qos: .background).async { [self] in - let iconAchievement = utilities.games.achievements.pullAchievement(achievementName: appIcon.achievement!) - iconAchievement.percentComplete = 100 - utilities.games.achievements.pushAchievement(achievementBody: iconAchievement) - } - } - } label: { - Text("") - } - if !appIcon.symbol { - Image(appIcon.image) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(maxWidth: 40, alignment: .trailing) - .clipShape(Circle()) - } else { - if #available(iOS 16.0, *) { - Image(systemName: appIcon.image) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(maxWidth: 30, alignment: .trailing) - .padding(.trailing, 5) - } else { - Image(systemName: appIcon.image) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(maxWidth: 30, alignment: .trailing) - .padding(.trailing, 5) - } - } - } - } - } - } - - /// A variable for the funnies section. - var funniesSection : some View { - Section(header: Text("about.header.special")) { - MalachiteCellViewUtils( - icon: "sun.max", - disabled: nil, - dangerous: false) - { - Toggle("settings.option.photo.max_exposure", isOn: $exposureUnlimiterSwitch) - } - if utilities.preferences.general.gamekit.found { - MalachiteCellViewUtils( - icon: "gamecontroller", - disabled: nil, - dangerous: true) - { - Toggle("", isOn: $gamekitSwitch) - } - } - } - .onChange(of: gamekitSwitch) {_ in - utilities.preferences.general.gamekit.enabled = gamekitSwitch - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.gameCenterEnabledNotification.name, object: nil) - } - .onChange(of: exposureUnlimiterSwitch) { _ in - utilities.debugNSLog("[Settings View] Lol") - utilities.preferences.capture.unlimitedISO = exposureUnlimiterSwitch - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.exposureLimitNotification.name, object: nil) - } - } -} diff --git a/Malachite/Views/MalachiteCompatibilityView.swift b/Malachite/Views/MalachiteCompatibilityView.swift deleted file mode 100755 index e17f7aa..0000000 --- a/Malachite/Views/MalachiteCompatibilityView.swift +++ /dev/null @@ -1,61 +0,0 @@ -// -// MalachiteCompatibilityView.swift -// Malachite -// -// Created by Eva Isabella Luna on 10/21/24. -// - -import SwiftUI - -public struct MalachiteCompatibilityView: View { - /// A State variable used for determining whether or not this view is being presented as a modal. - var dismissAction: (() -> Void) - /// A variable to hold the existing instance of ``MalachiteClassesObject``. - var utilities = MalachiteClassesObject() - - public var body: some View { - MalachiteNagivationViewUtils() { - Form { - Section { - Text("compatibility.note") - } - Section { - if utilities.preferences.ext.dictionary.isValid(dictionary: utilities.preferences.compatibility.ultrawide) { - // Ultra wide megapixel capabilities - MalachiteCompatibilityViewUtils(title: "compatibility.title.12mp.ultrawide", available: utilities.preferences.compatibility.ultrawide["12"] ?? false) - MalachiteCompatibilityViewUtils(title: "compatibility.title.48mp.ultrawide", available: utilities.preferences.compatibility.ultrawide["48"] ?? false) - } else { - MalachiteCompatibilityViewUtils(title: "compatibility.title.no.ultrawide", available: false) - } - if utilities.preferences.ext.dictionary.isValid(dictionary: utilities.preferences.compatibility.wideangle) { - // Wide angle megapixel capabilities - MalachiteCompatibilityViewUtils(title: "compatibility.title.8mp.wide", available: utilities.preferences.compatibility.wideangle["8"] ?? false) - MalachiteCompatibilityViewUtils(title: "compatibility.title.12mp.wide", available: utilities.preferences.compatibility.wideangle["12"] ?? false) - MalachiteCompatibilityViewUtils(title: "compatibility.title.48mp.wide", available: utilities.preferences.compatibility.wideangle["48"] ?? false) - } else { - MalachiteCompatibilityViewUtils(title: "compatibility.title.no.wide", available: false) - } - if utilities.preferences.ext.dictionary.isValid(dictionary: utilities.preferences.compatibility.telephoto) { - // Telephoto megapixel capabilities - MalachiteCompatibilityViewUtils(title: "compatibility.title.12mp.telephoto", available: utilities.preferences.compatibility.telephoto["12"] ?? false) - } else { - MalachiteCompatibilityViewUtils(title: "compatibility.title.no.telephoto", available: false) - } - - // JPEG, HEIF - MalachiteCompatibilityViewUtils(title: "compatibility.title.jpeg", available: utilities.preferences.compatibility.jpeg) - MalachiteCompatibilityViewUtils(title: "compatibility.title.heif", available: utilities.preferences.compatibility.heic) - - // HDR - MalachiteCompatibilityViewUtils(title: "compatibility.title.hdr", available: utilities.preferences.compatibility.hdr) - } - } - .navigationTitle("view.title.compatibility") - .toolbar(content: { - ToolbarItemGroup(placement: .topBarTrailing) { - MalachiteToolbarUtils(action: self.dismissAction, image: "checkmark", primary: true) - } - }) - } - } -} diff --git a/Malachite/Views/MalachiteView.swift b/Malachite/Views/MalachiteView.swift index 6b02c10..b41b549 100755 --- a/Malachite/Views/MalachiteView.swift +++ b/Malachite/Views/MalachiteView.swift @@ -235,7 +235,7 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A if self.availableRearCameras.first != nil { photoOutput = AVCapturePhotoOutput() - if #available(iOS 16.0, *) {} else { photoOutput.isHighResolutionCaptureEnabled = true } + if #unavailable(iOS 16.0) { photoOutput.isHighResolutionCaptureEnabled = true } photoOutput.maxPhotoQualityPrioritization = .quality cameraSession?.sessionPreset = AVCaptureSession.Preset.photo cameraSession?.addOutput(photoOutput) @@ -822,8 +822,7 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A /// Function to toggle the flashlight's on state. @objc func runFlashlightToggle() { - // TODO: change to isFlashAvailable. hasFlash doesn't mean it can currently be used - guard let flashlight = selectedDevice?.hasFlash else { return } + guard let flashlight = selectedDevice?.isFlashAvailable else { return } if flashlight && !utilities.preferences.debug.breakApp { utilities.function.toggleFlash(captureDevice: &selectedDevice!, flashlightButton: flashlightButton, diff --git a/Malachite/Views/QuickHelpView/QuickHelpView+About.swift b/Malachite/Views/QuickHelpView/QuickHelpView+About.swift index 6a40b83..06d63a5 100644 --- a/Malachite/Views/QuickHelpView/QuickHelpView+About.swift +++ b/Malachite/Views/QuickHelpView/QuickHelpView+About.swift @@ -16,10 +16,10 @@ extension QuickHelpView { /// A variable to hold the about section. var body: some View { Section { - Builder(title: Text("view.title.about"), subtitle: Text("view.detail.about")) {} + createQuickHelpRow(title: Text("view.title.about"), subtitle: Text("view.detail.about")) if utilities.versionType == "INTERNAL" { - Builder(title: Text("view.title.compatibility"), subtitle: Text("view.detail.compatibility")) {} - Builder(title: Text("view.title.developer"), subtitle: Text("view.detail.developer")) {} + createQuickHelpRow(title: Text("view.title.compatibility"), subtitle: Text("view.detail.compatibility")) + createQuickHelpRow(title: Text("view.title.developer"), subtitle: Text("view.detail.developer")) } } } diff --git a/Malachite/Views/QuickHelpView/QuickHelpView+Photo.swift b/Malachite/Views/QuickHelpView/QuickHelpView+Photo.swift index 19f1bf8..abe311f 100644 --- a/Malachite/Views/QuickHelpView/QuickHelpView+Photo.swift +++ b/Malachite/Views/QuickHelpView/QuickHelpView+Photo.swift @@ -12,9 +12,9 @@ extension QuickHelpView { /// A variable to hold the photo settings section. var body: some View { Section(header: Text("settings.header.photo"), footer: Text("settings.footer.photo")) { - Builder(title: Text("settings.option.photo.fileformat"), subtitle: Text("settings.detail.photo.fileformat")) {} - Builder(title: Text("settings.option.photo.hdr"), subtitle: Text("settings.detail.photo.hdr")) {} - Builder(title: Text("settings.option.photo.continuous"), subtitle: Text("settings.detail.photo.continuous")) {} + createQuickHelpRow(title: Text("settings.option.photo.fileformat"), subtitle: Text("settings.detail.photo.fileformat")) + createQuickHelpRow(title: Text("settings.option.photo.hdr"), subtitle: Text("settings.detail.photo.hdr")) + createQuickHelpRow(title: Text("settings.option.photo.continuous"), subtitle: Text("settings.detail.photo.continuous")) } } } diff --git a/Malachite/Views/QuickHelpView/QuickHelpView+Preview.swift b/Malachite/Views/QuickHelpView/QuickHelpView+Preview.swift index b4d2bb2..0314c7d 100644 --- a/Malachite/Views/QuickHelpView/QuickHelpView+Preview.swift +++ b/Malachite/Views/QuickHelpView/QuickHelpView+Preview.swift @@ -12,9 +12,9 @@ extension QuickHelpView { /// A variable to hold the preview settings section. var body: some View { Section(header: Text("settings.header.preview"), footer: Text("settings.footer.preview")) { - Builder(title: Text("settings.option.preview.aspect_ratio"), subtitle: Text("settings.detail.preview.aspect_ratio")) {} - Builder(title: Text("settings.option.preview.sbtlz"), subtitle: Text("settings.detail.preview.sbtlz")) {} - Builder(title: Text("settings.option.preview.zoom_maximum"), subtitle: Text("settings.detail.preview.zoom_maximum")) {} + createQuickHelpRow(title: Text("settings.option.preview.aspect_ratio"), subtitle: Text("settings.detail.preview.aspect_ratio")) + createQuickHelpRow(title: Text("settings.option.preview.sbtlz"), subtitle: Text("settings.detail.preview.sbtlz")) + createQuickHelpRow(title: Text("settings.option.preview.zoom_maximum"), subtitle: Text("settings.detail.preview.zoom_maximum")) } } } diff --git a/Malachite/Views/QuickHelpView/QuickHelpView+Resolution.swift b/Malachite/Views/QuickHelpView/QuickHelpView+Resolution.swift index 270de99..7c157f2 100644 --- a/Malachite/Views/QuickHelpView/QuickHelpView+Resolution.swift +++ b/Malachite/Views/QuickHelpView/QuickHelpView+Resolution.swift @@ -9,12 +9,22 @@ import SwiftUI extension QuickHelpView { struct Resolution: View { + var utilities = MalachiteClassesObject() + + init(utilities: MalachiteClassesObject ) { self.utilities = utilities } + /// A variable to hold the image resolution section. var body: some View { Section(header: Text("settings.header.resolution"), footer: Text("settings.footer.resolution")) { - Builder(title: Text("settings.option.resolution.ultrawide"), subtitle: Text("settings.detail.resolution.ultrawide")) {} - Builder(title: Text("settings.option.resolution.wide"), subtitle: Text("settings.detail.resolution.wide")) {} - Builder(title: Text("settings.option.resolution.telephoto"), subtitle: Text("settings.detail.resolution.telephoto")) {} + if utilities.preferences.ext.dictionary.isValid(dictionary: utilities.preferences.compatibility.ultrawide) { + createQuickHelpRow(title: Text("settings.option.resolution.ultrawide"), subtitle: Text("settings.detail.resolution.ultrawide")) + } + if utilities.preferences.ext.dictionary.isValid(dictionary: utilities.preferences.compatibility.wideangle) { + createQuickHelpRow(title: Text("settings.option.resolution.wide"), subtitle: Text("settings.detail.resolution.wide")) + } + if utilities.preferences.ext.dictionary.isValid(dictionary: utilities.preferences.compatibility.telephoto) { + createQuickHelpRow(title: Text("settings.option.resolution.telephoto"), subtitle: Text("settings.detail.resolution.telephoto")) + } } } } diff --git a/Malachite/Views/QuickHelpView/QuickHelpView+UI.swift b/Malachite/Views/QuickHelpView/QuickHelpView+UI.swift index aa91e21..269a2be 100644 --- a/Malachite/Views/QuickHelpView/QuickHelpView+UI.swift +++ b/Malachite/Views/QuickHelpView/QuickHelpView+UI.swift @@ -12,11 +12,11 @@ extension QuickHelpView { /// A variable to hold the user interface settings section. var body: some View { Section(header: Text("settings.header.ui"), footer: Text("settings.footer.ui")) { - Builder(title: Text("settings.option.ui.tapgesture"), subtitle: Text("settings.detail.ui.tapgesture")) {} - Builder(title: Text("settings.option.ui.hiddengestures"), subtitle: Text("settings.detail.ui.hiddengestures")) {} - Builder(title: Text("settings.option.ui.idletimer"), subtitle: Text("settings.detail.ui.idletimer")) {} - Builder(title: Text("settings.option.ui.haptics"), subtitle: Text("settings.detail.ui.haptics")) {} - Builder(title: Text("settings.option.ui.hiddenonlaunch"), subtitle: Text("settings.detail.ui.hiddenonlaunch")) {} + createQuickHelpRow(title: Text("settings.option.ui.tapgesture"), subtitle: Text("settings.detail.ui.tapgesture")) + createQuickHelpRow(title: Text("settings.option.ui.hiddengestures"), subtitle: Text("settings.detail.ui.hiddengestures")) + createQuickHelpRow(title: Text("settings.option.ui.idletimer"), subtitle: Text("settings.detail.ui.idletimer")) + createQuickHelpRow(title: Text("settings.option.ui.haptics"), subtitle: Text("settings.detail.ui.haptics")) + createQuickHelpRow(title: Text("settings.option.ui.hiddenonlaunch"), subtitle: Text("settings.detail.ui.hiddenonlaunch")) } } } diff --git a/Malachite/Views/QuickHelpView/QuickHelpView+Watermarking.swift b/Malachite/Views/QuickHelpView/QuickHelpView+Watermarking.swift index 8942761..18585fb 100644 --- a/Malachite/Views/QuickHelpView/QuickHelpView+Watermarking.swift +++ b/Malachite/Views/QuickHelpView/QuickHelpView+Watermarking.swift @@ -12,8 +12,8 @@ extension QuickHelpView { /// A variable to hold the watermark settings section. var body: some View { Section(header: Text("settings.header.watermark"), footer: Text("settings.footer.watermark")) { - Builder(title: Text("settings.option.watermark.enable"), subtitle: Text("settings.detail.watermark.enable")) {} - Builder(title: Text("settings.option.watermark.text"), subtitle: Text("settings.detail.watermark.text")) {} + createQuickHelpRow(title: Text("settings.option.watermark.enable"), subtitle: Text("settings.detail.watermark.enable")) + createQuickHelpRow(title: Text("settings.option.watermark.text"), subtitle: Text("settings.detail.watermark.text")) } } } diff --git a/Malachite/Views/QuickHelpView/QuickHelpView.swift b/Malachite/Views/QuickHelpView/QuickHelpView.swift index 190da0f..acdd81e 100755 --- a/Malachite/Views/QuickHelpView/QuickHelpView.swift +++ b/Malachite/Views/QuickHelpView/QuickHelpView.swift @@ -23,11 +23,10 @@ struct QuickHelpView: View { /// A variable used to hold the entire view. var body: some View { - Form { About(utilities: utilities) Preview() - Resolution() + Resolution(utilities: utilities) Photo() Watermarking() UserInterface() @@ -46,26 +45,23 @@ struct QuickHelpView: View { /// A variable to hold the debug settings section. Only available with debug builds. var body: some View { Section(header: Text("developer.header.debug"), footer: Text("developer.footer.debug")) { - Builder(title: Text("developer.option.debug.logging.userdefaults"), subtitle: Text("developer.detail.debug.logging.userdefaults")) {} - Builder(title: Text("developer.option.debug.erase.userdefaults"), subtitle: Text("developer.detail.debug.erase.userdefaults")) {} - Builder(title: Text("developer.option.debug.erase.gamekit"), subtitle: Text("developer.detail.debug.erase.gamekit")) {} + createQuickHelpRow(title: Text("developer.option.debug.logging.userdefaults"), subtitle: Text("developer.detail.debug.logging.userdefaults")) + createQuickHelpRow(title: Text("developer.option.debug.erase.userdefaults"), subtitle: Text("developer.detail.debug.erase.userdefaults")) + createQuickHelpRow(title: Text("developer.option.debug.erase.gamekit"), subtitle: Text("developer.detail.debug.erase.gamekit")) } } } - struct Builder: View { + struct createQuickHelpRow: View { var title: Text var subtitle: Text - let content: Content? init( title: Text, - subtitle: Text, - @ViewBuilder content: () -> Content? + subtitle: Text ) { self.title = title self.subtitle = subtitle - self.content = content() ?? nil } var body: some View { diff --git a/Malachite/Views/SettingsView/SettingsView+About.swift b/Malachite/Views/SettingsView/SettingsView+About.swift index 2f9856e..6952c90 100644 --- a/Malachite/Views/SettingsView/SettingsView+About.swift +++ b/Malachite/Views/SettingsView/SettingsView+About.swift @@ -29,7 +29,7 @@ extension SettingsView { disabled: nil, dangerous: false) { - NavigationLink(destination: MalachiteAboutView(dismissAction: dismissAction)) { + NavigationLink(destination: AboutView(dismissAction: dismissAction)) { Text("view.title.about") } } @@ -39,7 +39,7 @@ extension SettingsView { disabled: nil, dangerous: false) { - NavigationLink(destination: MalachiteCompatibilityView(dismissAction: dismissAction, utilities: utilities)) { + NavigationLink(destination: CompatibilityView(dismissAction: dismissAction, utilities: utilities)) { Text("view.title.compatibility") } } diff --git a/Malachite/Views/SettingsView/SettingsView.swift b/Malachite/Views/SettingsView/SettingsView.swift index 98c1561..f550739 100755 --- a/Malachite/Views/SettingsView/SettingsView.swift +++ b/Malachite/Views/SettingsView/SettingsView.swift @@ -41,8 +41,7 @@ struct SettingsView: View { .onAppear { onAppear() } .onDisappear { onDisappear() } .navigationTitle("view.title.settings") - .toolbar( -content: { + .toolbar(content: { ToolbarItemGroup(placement: .topBarLeading) { NavigationLink(destination: QuickHelpView(utilities: utilities, dismissAction: dismissAction)) { if #available(iOS 26.0, *) { diff --git a/MalachiteWatch/ContentView.swift b/MalachiteWatch/ContentView.swift index 7233ba0..f7a8301 100644 --- a/MalachiteWatch/ContentView.swift +++ b/MalachiteWatch/ContentView.swift @@ -10,17 +10,10 @@ import SwiftUI struct ContentView: View { var body: some View { VStack { - if #available(watchOS 8.0, *) { - Image(systemName: "globe") - .imageScale(.large) - .foregroundStyle(.tint) - Text("Hello, world!") - } else { - Image(systemName: "globe") - .imageScale(.large) - .foregroundColor(.accentColor) - Text("Hello, world!") - } + Image(systemName: "globe") + .imageScale(.large) + .foregroundStyle(.tint) + Text("Hello, world!") } .padding() } From 695eeb64671880cad1911ae8837737f32fff15c5 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Wed, 20 Aug 2025 03:30:28 -0600 Subject: [PATCH 08/66] Zoom controls bugfix, various warnings --- Malachite/Utilities/MalachiteFunctionUtils.swift | 11 +++++++---- Malachite/Views/MalachiteView.swift | 8 +++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Malachite/Utilities/MalachiteFunctionUtils.swift b/Malachite/Utilities/MalachiteFunctionUtils.swift index 5cd61ae..5590bac 100755 --- a/Malachite/Utilities/MalachiteFunctionUtils.swift +++ b/Malachite/Utilities/MalachiteFunctionUtils.swift @@ -51,7 +51,7 @@ public class MalachiteFunctionUtils : NSObject { } /// Function that handles pinch to zoom. - public func zoom(sender pinch: UIPinchGestureRecognizer, floater float: CGFloat, captureDevice device: inout AVCaptureDevice, lastZoomFactor zoomFactor: inout CGFloat, hapticClass haptic: MalachiteHapticUtils) { + public func zoom(sender pinch: UIPinchGestureRecognizer, floater float: inout CGFloat, captureDevice device: inout AVCaptureDevice, lastZoomFactor zoomFactor: inout CGFloat, hapticClass haptic: MalachiteHapticUtils) { func minMaxZoom(_ factor: CGFloat) -> CGFloat { return min(min(max(factor, 1.0), CGFloat(MalachitePreferencesUtils.shared.preferences.capture.maximumZoom)), device.activeFormat.videoMaxZoomFactor) } @@ -67,16 +67,19 @@ public class MalachiteFunctionUtils : NSObject { } } - update(scale: minMaxZoom(float * zoomFactor)) + let newScaleFactor = minMaxZoom(float * zoomFactor) + update(scale: newScaleFactor) switch pinch.state { case .began: haptic.triggerMediumHaptic() fallthrough case .changed: - update(scale: minMaxZoom(float * zoomFactor)) + update(scale: newScaleFactor) case .ended: - update(scale: minMaxZoom(float * zoomFactor)) + zoomFactor = minMaxZoom(newScaleFactor) + float = minMaxZoom(newScaleFactor) + update(scale: zoomFactor) haptic.triggerMediumHaptic() default: break } diff --git a/Malachite/Views/MalachiteView.swift b/Malachite/Views/MalachiteView.swift index b41b549..81e5f49 100755 --- a/Malachite/Views/MalachiteView.swift +++ b/Malachite/Views/MalachiteView.swift @@ -74,7 +74,7 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A /// A `Bool` that determines whether or not the app is still initializing. Uses for tasks that should only be run once at the start of Malachite. var initRun = true /// A `CGFloat` that temporarily holds the zoom factor. - var zoomFloater: CGFloat? + var zoomFloater = CGFloat() /// A `Float` that temporarily holds the focus factor. var focusFloater: Float? /// A `Float` that temporarily holds the level of flash brightness to use. @@ -710,6 +710,7 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A /// Function to switch cameras and attach new inputs to ``cameraSession``, and set settings based on the `activeFormat` of ``selectedDevice``. @objc func runInputSwitch() { + #warning("fix this breaking after one camera switch") cameraSession?.beginConfiguration() if (self.availableRearCameras.count < 2 || utilities.preferences.debug.breakApp) && !self.initRun { utilities.debugNSLog("[Camera Input] Only one AVCaptureDevice is available to use, showing error") @@ -766,13 +767,14 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A let systemBiasSlider = AVCaptureSystemExposureBiasSlider(device: selectedDevice!) + #warning("malachitekit should properly sync this with the zoom slider") let zoomSlider = AVCaptureSlider("Zoom", symbolName: "plus.viewfinder", in: 1.0...Float(MalachiteClassesObject().preferences.capture.maximumZoom)) zoomSlider.setActionQueue(utilities.sessionQueue) { position in self.zoomFloater = CGFloat(position) self.runZoomController() - self.zoomFloater = nil } + #warning("same as above") let focusSlider = AVCaptureSlider("Focus", symbolName: "scope", in: 0.0...1.0) focusSlider.setActionQueue(utilities.sessionQueue) { position in self.focusFloater = position @@ -914,7 +916,7 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A /// Function to zoom in and out with ``zoomRecognizer``. @objc func runZoomController() { utilities.function.zoom(sender: zoomRecognizer, - floater: zoomFloater ?? zoomRecognizer.scale, + floater: &zoomFloater, captureDevice: &selectedDevice!, lastZoomFactor: &lastZoomFactor, hapticClass: utilities.haptics) From 15225599d27a4365e93b81627ac0793280cc108d Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Sun, 24 Aug 2025 21:17:12 -0600 Subject: [PATCH 09/66] Fixed pinch to zoom Camera Control support for focus and zoom have been disabled for everyone as I work on keeping Camera Control + gestures synched. --- Malachite/Utilities/MalachiteFunctionUtils.swift | 9 +++------ Malachite/Views/MalachiteView.swift | 8 +++++++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Malachite/Utilities/MalachiteFunctionUtils.swift b/Malachite/Utilities/MalachiteFunctionUtils.swift index 5590bac..ee12456 100755 --- a/Malachite/Utilities/MalachiteFunctionUtils.swift +++ b/Malachite/Utilities/MalachiteFunctionUtils.swift @@ -75,8 +75,11 @@ public class MalachiteFunctionUtils : NSObject { haptic.triggerMediumHaptic() fallthrough case .changed: + let newScaleFactor = minMaxZoom(pinch.scale * zoomFactor) + float = minMaxZoom(newScaleFactor) update(scale: newScaleFactor) case .ended: + let newScaleFactor = minMaxZoom(pinch.scale * zoomFactor) zoomFactor = minMaxZoom(newScaleFactor) float = minMaxZoom(newScaleFactor) update(scale: zoomFactor) @@ -229,7 +232,6 @@ public class MalachiteFunctionUtils : NSObject { /// Function that handles connecting and disconnecting cameras, and changing format properties. public func switchInput(session: inout AVCaptureSession, cameras: [AVCaptureDevice], device: inout AVCaptureDevice?, output: inout AVCapturePhotoOutput, input: inout AVCaptureDeviceInput?, button: UIButton, firstRun: inout Bool){ - DispatchQueue.main.async { button.isUserInteractionEnabled = false } MalachiteClassesObject().debugNSLog("[Camera Input] Getting ready to configure session") if !firstRun { @@ -322,11 +324,6 @@ public class MalachiteFunctionUtils : NSObject { MalachiteClassesObject().debugNSLog("[Camera Input] Attached input, finishing configuration") if session.canAddInput(input!) { session.addInput(input!) } switchInputMegapixels(device: device!, photoOutput: output) - if !Thread.isMainThread { - DispatchQueue.main.async { button.isUserInteractionEnabled = true } - } else { - button.isUserInteractionEnabled = true - } } @available(iOS 18.0, *) diff --git a/Malachite/Views/MalachiteView.swift b/Malachite/Views/MalachiteView.swift index 81e5f49..71fc129 100755 --- a/Malachite/Views/MalachiteView.swift +++ b/Malachite/Views/MalachiteView.swift @@ -712,6 +712,7 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A @objc func runInputSwitch() { #warning("fix this breaking after one camera switch") cameraSession?.beginConfiguration() + cameraButton.isUserInteractionEnabled = false if (self.availableRearCameras.count < 2 || utilities.preferences.debug.breakApp) && !self.initRun { utilities.debugNSLog("[Camera Input] Only one AVCaptureDevice is available to use, showing error") let alert = UIAlertController(title: "alert.title.camera_switch".localized, message: "alert.detail.camera_switch".localized, preferredStyle: .actionSheet) @@ -723,6 +724,7 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A self.utilities.debugNSLog("[Camera Input] Dialog has been dismissed") })) self.present(alert, animated: true, completion: nil) + cameraButton.isUserInteractionEnabled = true return } @@ -759,6 +761,8 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A DispatchQueue.main.async() { [self] in utilities.tooltips.zoomTooltipFlow(button: currentCamera, viewForBounds: view, camera: selectedDevice) } + + cameraButton.isUserInteractionEnabled = true } @available(iOS 18.0, *) @@ -768,11 +772,12 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A let systemBiasSlider = AVCaptureSystemExposureBiasSlider(device: selectedDevice!) #warning("malachitekit should properly sync this with the zoom slider") - let zoomSlider = AVCaptureSlider("Zoom", symbolName: "plus.viewfinder", in: 1.0...Float(MalachiteClassesObject().preferences.capture.maximumZoom)) + let zoomSlider = AVCaptureSlider("Zoom", symbolName: "plus.viewfinder", in: 0.5...Float(MalachiteClassesObject().preferences.capture.maximumZoom)) zoomSlider.setActionQueue(utilities.sessionQueue) { position in self.zoomFloater = CGFloat(position) self.runZoomController() } + zoomSlider.isEnabled = false #warning("same as above") let focusSlider = AVCaptureSlider("Focus", symbolName: "scope", in: 0.0...1.0) @@ -781,6 +786,7 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A self.runManualFocusController() self.focusFloater = nil } + focusSlider.isEnabled = false let cameraSwitcher = AVCaptureIndexPicker("Cameras", symbolName: "camera.fill", localizedIndexTitles: self.availableRearCameras.map { $0.localizedName } ) cameraSwitcher.selectedIndex = self.availableRearCameras.firstIndex(of: self.selectedDevice!)! From 58f91bf9a59dd34f7e31d00449b6565a76f0feea Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Sun, 24 Aug 2025 21:17:12 -0600 Subject: [PATCH 10/66] Fixed pinch to zoom v2 --- Malachite/Utilities/MalachiteFunctionUtils.swift | 4 +++- Malachite/Views/MalachiteView.swift | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Malachite/Utilities/MalachiteFunctionUtils.swift b/Malachite/Utilities/MalachiteFunctionUtils.swift index ee12456..31d83b2 100755 --- a/Malachite/Utilities/MalachiteFunctionUtils.swift +++ b/Malachite/Utilities/MalachiteFunctionUtils.swift @@ -67,7 +67,9 @@ public class MalachiteFunctionUtils : NSObject { } } - let newScaleFactor = minMaxZoom(float * zoomFactor) + MalachiteClassesObject().debugNSLog("[Zoom] Pinch scale: \(pinch.scale), float: \(float), zoomFactor: \(zoomFactor)") + if pinch.scale != 1.0 { float = pinch.scale * zoomFactor } + let newScaleFactor = minMaxZoom(float) update(scale: newScaleFactor) switch pinch.state { diff --git a/Malachite/Views/MalachiteView.swift b/Malachite/Views/MalachiteView.swift index 71fc129..a7b9c51 100755 --- a/Malachite/Views/MalachiteView.swift +++ b/Malachite/Views/MalachiteView.swift @@ -137,6 +137,7 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A return nil } }() + /// A `Bool` that determines whether or not the user interface is currently hidden to the user. var uiIsHidden = false @@ -772,12 +773,12 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A let systemBiasSlider = AVCaptureSystemExposureBiasSlider(device: selectedDevice!) #warning("malachitekit should properly sync this with the zoom slider") - let zoomSlider = AVCaptureSlider("Zoom", symbolName: "plus.viewfinder", in: 0.5...Float(MalachiteClassesObject().preferences.capture.maximumZoom)) + let zoomSlider = AVCaptureSlider("Zoom", symbolName: "plus.viewfinder", in: 1.0...Float(MalachiteClassesObject().preferences.capture.maximumZoom)) + zoomSlider.prominentValues = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0 ] zoomSlider.setActionQueue(utilities.sessionQueue) { position in self.zoomFloater = CGFloat(position) self.runZoomController() } - zoomSlider.isEnabled = false #warning("same as above") let focusSlider = AVCaptureSlider("Focus", symbolName: "scope", in: 0.0...1.0) @@ -786,7 +787,6 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A self.runManualFocusController() self.focusFloater = nil } - focusSlider.isEnabled = false let cameraSwitcher = AVCaptureIndexPicker("Cameras", symbolName: "camera.fill", localizedIndexTitles: self.availableRearCameras.map { $0.localizedName } ) cameraSwitcher.selectedIndex = self.availableRearCameras.firstIndex(of: self.selectedDevice!)! From bd2a94295bd36bffeb13d7bc4c59c4ce30eaa10d Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Tue, 26 Aug 2025 02:06:56 -0600 Subject: [PATCH 11/66] Add option to disable debugNSLog() on debug builds Internal builds still have it force enabled --- Malachite/Localizable.xcstrings | 52 ++++++++++---- Malachite/Utilities/MalachiteUtils.swift | 2 +- .../MalachitePreferences.swift | 63 +---------------- .../MalachitePreferencesUtils.swift | 4 +- .../Preferences+Extension.swift | 70 +++++++++++++++++++ .../DeveloperView+Settings.swift | 30 +++++--- .../Views/DeveloperView/DeveloperView.swift | 9 +++ .../Views/QuickHelpView/QuickHelpView.swift | 37 ++++++++-- 8 files changed, 179 insertions(+), 88 deletions(-) rename Malachite/Utilities/{Preferences => PreferenceUtils}/MalachitePreferences.swift (54%) rename Malachite/Utilities/{Preferences => PreferenceUtils}/MalachitePreferencesUtils.swift (98%) create mode 100644 Malachite/Utilities/PreferenceUtils/Preferences+Extension.swift diff --git a/Malachite/Localizable.xcstrings b/Malachite/Localizable.xcstrings index a9c719d..4da26bd 100755 --- a/Malachite/Localizable.xcstrings +++ b/Malachite/Localizable.xcstrings @@ -704,32 +704,52 @@ } } }, + "developer.detail.debug.breakapp" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "DEBUG: UI testing. Disables the ability to do anything other than open Settings." + } + } + } + }, "developer.detail.debug.erase.gamekit" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "DEBUG: This option destroys all GameKit related data for the current user. " + "value" : "DEBUG: Erases all local GameKit data for the current user." + } + } + } + }, + "developer.detail.debug.erase.preferences" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "DEBUG: Erases preferences.plist in Malachite’s home directory." } } } }, - "developer.detail.debug.erase.userdefaults" : { + "developer.detail.debug.logging.preferences" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "DEBUG: This option destroys all UserDefaults keys." + "value" : "DEBUG: Print preferences.plist to unified logging system." } } } }, - "developer.detail.debug.logging.userdefaults" : { + "developer.detail.debug.logging.unified" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "DEBUG: This option dumps UserDefaults on launch." + "value" : "DEBUG: This option controls whether or not to enable the debugNSLog() function. INTERNAL builds cannot disable this." } } } @@ -739,7 +759,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "DEBUG: These options are meant for debugging purposes. " + "value" : "DEBUG: Options meant for debugging purposes. " } } } @@ -814,22 +834,32 @@ } } }, - "developer.option.debug.erase.userdefaults" : { + "developer.option.debug.erase.preferences" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erase preferences.plist" + } + } + } + }, + "developer.option.debug.logging.preferences" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Erase preferences" + "value" : "Log preferences.plist to ULS" } } } }, - "developer.option.debug.logging.userdefaults" : { + "developer.option.debug.logging.unified" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Log preferences to console" + "value" : "More verbose logging" } } } @@ -925,7 +955,6 @@ } }, "flash.off" : { - "comment" : "Off", "localizations" : { "en" : { "stringUnit" : { @@ -936,7 +965,6 @@ } }, "flash.on" : { - "comment" : "On", "localizations" : { "en" : { "stringUnit" : { diff --git a/Malachite/Utilities/MalachiteUtils.swift b/Malachite/Utilities/MalachiteUtils.swift index 1bbdbf4..370869b 100755 --- a/Malachite/Utilities/MalachiteUtils.swift +++ b/Malachite/Utilities/MalachiteUtils.swift @@ -68,7 +68,7 @@ public class MalachiteClassesObject : NSObject { /// A function to only log in DEBUG and INTERNAL builds public func debugNSLog(_ format: String, file: String = #file, line: Int = #line, function: String = #function) { - if self.versionType == "DEBUG" || self.versionType == "INTERNAL" { + if (self.versionType == "DEBUG" && self.preferences.debug.logging.unified) || self.versionType == "INTERNAL" { Foundation.NSLog("[\(NSString(string: file).lastPathComponent):\(line)] [\(function)] \(format)") } } diff --git a/Malachite/Utilities/Preferences/MalachitePreferences.swift b/Malachite/Utilities/PreferenceUtils/MalachitePreferences.swift similarity index 54% rename from Malachite/Utilities/Preferences/MalachitePreferences.swift rename to Malachite/Utilities/PreferenceUtils/MalachitePreferences.swift index f31b19f..527ca95 100755 --- a/Malachite/Utilities/Preferences/MalachitePreferences.swift +++ b/Malachite/Utilities/PreferenceUtils/MalachitePreferences.swift @@ -103,6 +103,7 @@ struct MalachitePreferences: Codable { struct debug_loggingPreferences: Codable { var preferences: Bool + var unified: Bool } } @@ -112,65 +113,3 @@ struct MalachitePreferences: Codable { var settingsGesture: Int } } - -extension MalachitePreferences { - - var ext: Utils { return Utils() } - class Utils { - var gameKitButton = 0 - /// Shows the GameKit enable switch in About settings. - public func showGameKitOptionInAbout(in preferences: inout MalachitePreferences) -> Void { - MalachiteClassesObject().debugNSLog("04F807A163D50211A2456C3460EACFACCBC5BF436AFC268F0DBAA529") - if gameKitButton < 7 { - gameKitButton += 1 - } else { - preferences.general.gamekit.alerted = true - exit(11) - } - } - var dictionary: ELDictionary { return ELDictionary() } - class ELDictionary { - public func isValid(dictionary: Dictionary) -> Bool { - return dictionary["invalid"] as? Bool == false ? false : true - } - - public func getCount(dictionary: Dictionary) -> Int { - return dictionary.count - } - } - var deviceModel: DeviceModel { return DeviceModel() } - class DeviceModel { - public func get() -> String { - var systemInfo = utsname() - uname(&systemInfo) - let machineMirror = Mirror(reflecting: systemInfo.machine) - let identifier = machineMirror.children.reduce("") { identifier, element in - guard let value = element.value as? Int8, value != 0 else { return identifier } - return identifier + String(UnicodeScalar(UInt8(value))) - } - - return identifier - } - - public func isSameDevice(in preferences: inout MalachitePreferences) -> Bool { - if get() == preferences.general.deviceModel { return true } - return false - } - } - - public func runPhotoCounter() { - let value = MalachiteClassesObject().preferences.general.photoCount - if value < UINT64_MAX { // I still want to see someone reach this - MalachiteClassesObject().preferences.general.photoCount += 1 - } else { - MalachiteClassesObject().debugNSLog("[Preferences] what") - MalachiteClassesObject().preferences.general.photoCount = 0 - } - } - - public func resetPreferences() { - if MalachitePreferencesUtils().writePreferences(MalachitePreferencesUtils().initPreferences()) { print("[Preferences] Successfully wiped preferences. Relaunch to ensure.") } - } - } -} - diff --git a/Malachite/Utilities/Preferences/MalachitePreferencesUtils.swift b/Malachite/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift similarity index 98% rename from Malachite/Utilities/Preferences/MalachitePreferencesUtils.swift rename to Malachite/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift index 90641f0..1aaffff 100755 --- a/Malachite/Utilities/Preferences/MalachitePreferencesUtils.swift +++ b/Malachite/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift @@ -147,6 +147,7 @@ class MalachitePreferencesUtils { if let debugPreferences = oldPreferences["debug"] as? [ String: AnyObject ] { currentPreferences.debug.logging.preferences = debugPreferences["logging"]?["preferences"] as? Bool ?? false currentPreferences.debug.breakApp = false + currentPreferences.debug.logging.unified = true } if let evaintrnlPreferences = oldPreferences["evaintrnl"] as? [ String: AnyObject ] { @@ -215,7 +216,8 @@ class MalachitePreferencesUtils { ), debug: MalachitePreferences.debugPreferences( logging: MalachitePreferences.debugPreferences.debug_loggingPreferences( - preferences: false + preferences: false, + unified: true ), breakApp: false ), diff --git a/Malachite/Utilities/PreferenceUtils/Preferences+Extension.swift b/Malachite/Utilities/PreferenceUtils/Preferences+Extension.swift new file mode 100644 index 0000000..cb3f0c4 --- /dev/null +++ b/Malachite/Utilities/PreferenceUtils/Preferences+Extension.swift @@ -0,0 +1,70 @@ +// +// Preferences+Extension.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/26/25. +// + +import Foundation + +extension MalachitePreferences { + + var ext: Utils { return Utils() } + class Utils { + var gameKitButton = 0 + /// Shows the GameKit enable switch in About settings. + public func showGameKitOptionInAbout(in preferences: inout MalachitePreferences) -> Void { + MalachiteClassesObject().debugNSLog("04F807A163D50211A2456C3460EACFACCBC5BF436AFC268F0DBAA529") + if gameKitButton < 7 { + gameKitButton += 1 + } else { + preferences.general.gamekit.alerted = true + exit(11) + } + } + var dictionary: ELDictionary { return ELDictionary() } + class ELDictionary { + public func isValid(dictionary: Dictionary) -> Bool { + return dictionary["invalid"] as? Bool == false ? false : true + } + + public func getCount(dictionary: Dictionary) -> Int { + return dictionary.count + } + } + var deviceModel: DeviceModel { return DeviceModel() } + class DeviceModel { + public func get() -> String { + var systemInfo = utsname() + uname(&systemInfo) + let machineMirror = Mirror(reflecting: systemInfo.machine) + let identifier = machineMirror.children.reduce("") { identifier, element in + guard let value = element.value as? Int8, value != 0 else { return identifier } + return identifier + String(UnicodeScalar(UInt8(value))) + } + + return identifier + } + + public func isSameDevice(in preferences: inout MalachitePreferences) -> Bool { + if get() == preferences.general.deviceModel { return true } + return false + } + } + + public func runPhotoCounter() { + let value = MalachiteClassesObject().preferences.general.photoCount + if value < UINT64_MAX { // I still want to see someone reach this + MalachiteClassesObject().preferences.general.photoCount += 1 + } else { + MalachiteClassesObject().debugNSLog("[Preferences] what") + MalachiteClassesObject().preferences.general.photoCount = 0 + } + } + + public func resetPreferences() { + if MalachitePreferencesUtils().writePreferences(MalachitePreferencesUtils().initPreferences()) { print("[Preferences] Successfully wiped preferences. Relaunch to ensure.") } + } + } +} + diff --git a/Malachite/Views/DeveloperView/DeveloperView+Settings.swift b/Malachite/Views/DeveloperView/DeveloperView+Settings.swift index b92335b..4087a98 100644 --- a/Malachite/Views/DeveloperView/DeveloperView+Settings.swift +++ b/Malachite/Views/DeveloperView/DeveloperView+Settings.swift @@ -9,7 +9,8 @@ import SwiftUI extension DeveloperView { struct Settings: View { - @State private var debugLoggingUserDefaults = false + @State private var debugLoggingUnified = false + @State private var debugLoggingPreferences = false /// A State variable used for determining whether or not to literally break the app. @State private var breakApp = false @@ -25,10 +26,17 @@ extension DeveloperView { Section { MalachiteCellViewUtils( icon: "text.redaction", + disabled: utilities.versionType == "INTERNAL", + dangerous: false) + { + Toggle("developer.option.debug.logging.unified", isOn: $debugLoggingUnified) + } + MalachiteCellViewUtils( + icon: "slider.horizontal.3", disabled: nil, dangerous: false) { - Toggle("developer.option.debug.logging.userdefaults", isOn: $debugLoggingUserDefaults) + Toggle("developer.option.debug.logging.preferences", isOn: $debugLoggingPreferences) } MalachiteCellViewUtils( icon: "iphone.slash", @@ -47,10 +55,10 @@ extension DeveloperView { utilities.preferences.ext.resetPreferences() } label: { if #available(iOS 17.0, *) { - Text("developer.option.debug.erase.userdefaults") + Text("developer.option.debug.erase.preferences") .foregroundStyle(.red) } else { - Text("developer.option.debug.erase.userdefaults") + Text("developer.option.debug.erase.preferences") .foregroundColor(.red) } } @@ -78,17 +86,23 @@ extension DeveloperView { } } .onAppear { - debugLoggingUserDefaults = utilities.preferences.debug.logging.preferences + if utilities.versionType == "INTERNAL" { debugLoggingUnified = true } + else { debugLoggingUnified = utilities.preferences.debug.logging.unified } + debugLoggingPreferences = utilities.preferences.debug.logging.preferences breakApp = utilities.preferences.debug.breakApp } - .onChange(of: debugLoggingUserDefaults) {_ in - utilities.preferences.debug.logging.preferences = debugLoggingUserDefaults + .onChange(of: debugLoggingPreferences) {_ in + utilities.preferences.debug.logging.preferences = debugLoggingPreferences + } + .onChange(of: debugLoggingUnified) {_ in + utilities.preferences.debug.logging.unified = debugLoggingUnified } .onChange(of: breakApp) {_ in utilities.preferences.debug.breakApp = breakApp } .onDisappear { - utilities.preferences.debug.logging.preferences = debugLoggingUserDefaults + utilities.preferences.debug.logging.unified = debugLoggingUnified + utilities.preferences.debug.logging.preferences = debugLoggingPreferences utilities.preferences.debug.breakApp = breakApp } } diff --git a/Malachite/Views/DeveloperView/DeveloperView.swift b/Malachite/Views/DeveloperView/DeveloperView.swift index 4c37e07..e0e6e74 100644 --- a/Malachite/Views/DeveloperView/DeveloperView.swift +++ b/Malachite/Views/DeveloperView/DeveloperView.swift @@ -22,6 +22,15 @@ struct DeveloperView: View { } .navigationTitle("view.title.developer") .toolbar(content: { + ToolbarItemGroup(placement: .topBarLeading) { + NavigationLink(destination: QuickHelpViewDeveloper(utilities: utilities, dismissAction: dismissAction)) { + if #available(iOS 26.0, *) { + Image(systemName: "questionmark.circle") + } else { + Image(systemName: "questionmark.circle").tint(.primary) + } + } + } ToolbarItemGroup(placement: .topBarTrailing) { MalachiteToolbarUtils(action: self.dismissAction, image: "checkmark", primary: true) } diff --git a/Malachite/Views/QuickHelpView/QuickHelpView.swift b/Malachite/Views/QuickHelpView/QuickHelpView.swift index acdd81e..355c20a 100755 --- a/Malachite/Views/QuickHelpView/QuickHelpView.swift +++ b/Malachite/Views/QuickHelpView/QuickHelpView.swift @@ -30,6 +30,7 @@ struct QuickHelpView: View { Photo() Watermarking() UserInterface() + #warning("Remove this once DeveloperView is out of INTERNAL ring") if utilities.versionType == "DEBUG" { Debugging() } } .navigationTitle("view.title.help") @@ -40,13 +41,14 @@ struct QuickHelpView: View { }) } - @available(*, deprecated, message: "Debugging section is being replaced with the Developer link in the future") struct Debugging: View { - /// A variable to hold the debug settings section. Only available with debug builds. + /// A variable to hold the debug settings section. Only available with debug and internal builds. var body: some View { Section(header: Text("developer.header.debug"), footer: Text("developer.footer.debug")) { - createQuickHelpRow(title: Text("developer.option.debug.logging.userdefaults"), subtitle: Text("developer.detail.debug.logging.userdefaults")) - createQuickHelpRow(title: Text("developer.option.debug.erase.userdefaults"), subtitle: Text("developer.detail.debug.erase.userdefaults")) + createQuickHelpRow(title: Text("developer.option.debug.logging.unified"), subtitle: Text("developer.detail.debug.logging.unified")) + createQuickHelpRow(title: Text("developer.option.debug.logging.preferences"), subtitle: Text("developer.detail.debug.logging.preferences")) + createQuickHelpRow(title: Text("developer.option.debug.breakapp"), subtitle: Text("developer.detail.debug.breakapp")) + createQuickHelpRow(title: Text("developer.option.debug.erase.preferences"), subtitle: Text("developer.detail.debug.erase.preferences")) createQuickHelpRow(title: Text("developer.option.debug.erase.gamekit"), subtitle: Text("developer.detail.debug.erase.gamekit")) } } @@ -81,3 +83,30 @@ struct QuickHelpView: View { } } } + +struct QuickHelpViewDeveloper: View { + /// A State variable used for determining whether or not this view is being presented as a modal. + var dismissAction: (() -> Void) + /// A variable to hold the existing instance of ``MalachiteClassesObject``. + var utilities = MalachiteClassesObject() + + init( + utilities: MalachiteClassesObject, + dismissAction: @escaping (() -> Void) + ) { + self.utilities = utilities + self.dismissAction = dismissAction + } + + var body: some View { + Form { + QuickHelpView.Debugging() + } + .navigationTitle("view.title.help") + .toolbar(content: { + ToolbarItemGroup(placement: .topBarTrailing) { + MalachiteToolbarUtils(action: self.dismissAction, image: "checkmark", primary: true) + } + }) + } +} From afb0b3ba94ef4d41d6cef77924c82d912db5b1f7 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Fri, 29 Aug 2025 20:14:30 -0600 Subject: [PATCH 12/66] Refactor MalachiteView -> CameraView, pt. 1 Took care of most of the setupView() function, updated PhotoPreviewView use the functions I created there as well. This commit also fixes generating iOS 18 and older app icons using an (obviously undocumented) asset catalog compiler flag, and switches NSLog a bit to show proper debug information. --- Malachite.xcodeproj/project.pbxproj | 113 ++++++++-- .../MalachiteCaptureBundle.swift | 8 +- Malachite/Localizable.xcstrings | 5 + Malachite/SceneDelegate.swift | 2 +- Malachite/Utilities/MalachiteUtils.swift | 15 +- Malachite/Utilities/MalachiteViewUtils.swift | 112 +++++++--- .../CameraView/CameraView+Controls.swift | 119 ++++++++++ .../Views/CameraView/CameraView+Preview.swift | 7 + .../CameraView.swift} | 206 ++---------------- .../Views/DeveloperView/DeveloperView.swift | 21 +- .../PhotoPreviewView+Controls.swift | 66 ++++++ .../PhotoPreviewView.swift} | 53 +---- .../Views/SettingsView/SettingsView.swift | 58 ++--- 13 files changed, 465 insertions(+), 320 deletions(-) create mode 100644 Malachite/Views/CameraView/CameraView+Controls.swift create mode 100644 Malachite/Views/CameraView/CameraView+Preview.swift rename Malachite/Views/{MalachiteView.swift => CameraView/CameraView.swift} (80%) create mode 100644 Malachite/Views/PhotoPreviewView/PhotoPreviewView+Controls.swift rename Malachite/Views/{MalachitePhotoPreview.swift => PhotoPreviewView/PhotoPreviewView.swift} (84%) diff --git a/Malachite.xcodeproj/project.pbxproj b/Malachite.xcodeproj/project.pbxproj index 7e2cac2..b68e4b0 100755 --- a/Malachite.xcodeproj/project.pbxproj +++ b/Malachite.xcodeproj/project.pbxproj @@ -11,6 +11,9 @@ 7837C10C2E34C45B009396B0 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78917F5B2B99AE72005E10FA /* WidgetKit.framework */; }; 7837C10E2E34C45B009396B0 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */; }; 7837C1172E34C47D009396B0 /* WidgetBundleWatch.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 7837C1152E34C45B009396B0 /* WidgetBundleWatch.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 784EDDD42E5EE52600DFF370 /* PhotoPreviewView+Controls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 784EDDD32E5EE52100DFF370 /* PhotoPreviewView+Controls.swift */; }; + 784EDDD52E5EE52600DFF370 /* PhotoPreviewView+Controls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 784EDDD32E5EE52100DFF370 /* PhotoPreviewView+Controls.swift */; }; + 784EDDD62E5EE52600DFF370 /* PhotoPreviewView+Controls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 784EDDD32E5EE52100DFF370 /* PhotoPreviewView+Controls.swift */; }; 785F084D2B12D41100244EB4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 785F084C2B12D41100244EB4 /* AppDelegate.swift */; }; 785F084F2B12D41100244EB4 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 785F084E2B12D41100244EB4 /* SceneDelegate.swift */; }; 785F08592B12D41300244EB4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 785F08572B12D41300244EB4 /* LaunchScreen.storyboard */; }; @@ -20,30 +23,30 @@ 786DAE262E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1A2E4F28A600BE3137 /* DeveloperView+Settings.swift */; }; 786DAE272E4F28A600BE3137 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1C2E4F28A600BE3137 /* AboutView.swift */; }; 786DAE282E4F28A600BE3137 /* CompatibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1D2E4F28A600BE3137 /* CompatibilityView.swift */; }; - 786DAE292E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1E2E4F28A600BE3137 /* MalachitePhotoPreview.swift */; }; + 786DAE292E4F28A600BE3137 /* PhotoPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1E2E4F28A600BE3137 /* PhotoPreviewView.swift */; }; 786DAE2A2E4F28A600BE3137 /* QuickHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1F2E4F28A600BE3137 /* QuickHelpView.swift */; }; 786DAE2B2E4F28A600BE3137 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE202E4F28A600BE3137 /* SettingsView.swift */; }; - 786DAE2C2E4F28A600BE3137 /* MalachiteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE212E4F28A600BE3137 /* MalachiteView.swift */; }; + 786DAE2C2E4F28A600BE3137 /* CameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE212E4F28A600BE3137 /* CameraView.swift */; }; 786DAE2D2E4F28A600BE3137 /* DeveloperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE172E4F28A600BE3137 /* DeveloperView.swift */; }; 786DAE2E2E4F28A600BE3137 /* DeveloperView+BuildInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE182E4F28A600BE3137 /* DeveloperView+BuildInfo.swift */; }; 786DAE2F2E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE192E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift */; }; 786DAE302E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1A2E4F28A600BE3137 /* DeveloperView+Settings.swift */; }; 786DAE312E4F28A600BE3137 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1C2E4F28A600BE3137 /* AboutView.swift */; }; 786DAE322E4F28A600BE3137 /* CompatibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1D2E4F28A600BE3137 /* CompatibilityView.swift */; }; - 786DAE332E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1E2E4F28A600BE3137 /* MalachitePhotoPreview.swift */; }; + 786DAE332E4F28A600BE3137 /* PhotoPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1E2E4F28A600BE3137 /* PhotoPreviewView.swift */; }; 786DAE342E4F28A600BE3137 /* QuickHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1F2E4F28A600BE3137 /* QuickHelpView.swift */; }; 786DAE352E4F28A600BE3137 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE202E4F28A600BE3137 /* SettingsView.swift */; }; - 786DAE362E4F28A600BE3137 /* MalachiteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE212E4F28A600BE3137 /* MalachiteView.swift */; }; + 786DAE362E4F28A600BE3137 /* CameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE212E4F28A600BE3137 /* CameraView.swift */; }; 786DAE372E4F28A600BE3137 /* DeveloperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE172E4F28A600BE3137 /* DeveloperView.swift */; }; 786DAE382E4F28A600BE3137 /* DeveloperView+BuildInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE182E4F28A600BE3137 /* DeveloperView+BuildInfo.swift */; }; 786DAE392E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE192E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift */; }; 786DAE3A2E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1A2E4F28A600BE3137 /* DeveloperView+Settings.swift */; }; 786DAE3B2E4F28A600BE3137 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1C2E4F28A600BE3137 /* AboutView.swift */; }; 786DAE3C2E4F28A600BE3137 /* CompatibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1D2E4F28A600BE3137 /* CompatibilityView.swift */; }; - 786DAE3D2E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1E2E4F28A600BE3137 /* MalachitePhotoPreview.swift */; }; + 786DAE3D2E4F28A600BE3137 /* PhotoPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1E2E4F28A600BE3137 /* PhotoPreviewView.swift */; }; 786DAE3E2E4F28A600BE3137 /* QuickHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE1F2E4F28A600BE3137 /* QuickHelpView.swift */; }; 786DAE3F2E4F28A600BE3137 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE202E4F28A600BE3137 /* SettingsView.swift */; }; - 786DAE402E4F28A600BE3137 /* MalachiteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE212E4F28A600BE3137 /* MalachiteView.swift */; }; + 786DAE402E4F28A600BE3137 /* CameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE212E4F28A600BE3137 /* CameraView.swift */; }; 786DAE4C2E4F28B100BE3137 /* MalachitePreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE412E4F28B100BE3137 /* MalachitePreferences.swift */; }; 786DAE4D2E4F28B100BE3137 /* MalachitePreferencesUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE422E4F28B100BE3137 /* MalachitePreferencesUtils.swift */; }; 786DAE4E2E4F28B100BE3137 /* MalachiteFunctionUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE442E4F28B100BE3137 /* MalachiteFunctionUtils.swift */; }; @@ -139,6 +142,15 @@ 78A9DD6A2CD55551002C131D /* CaptureBundle.appex in Embed ExtensionKit Extensions */ = {isa = PBXBuildFile; fileRef = 78A9DD612CD55551002C131D /* CaptureBundle.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 78A9DE312CD581F1002C131D /* LockedCameraCapture.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78A9DE302CD581F1002C131D /* LockedCameraCapture.framework */; }; 78A9DE752CD5AE5F002C131D /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */; }; + 78AF68682E5D97E500BB9D5C /* CameraView+Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AF68672E5D97E000BB9D5C /* CameraView+Preview.swift */; }; + 78AF68692E5D97E500BB9D5C /* CameraView+Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AF68672E5D97E000BB9D5C /* CameraView+Preview.swift */; }; + 78AF686A2E5D97E500BB9D5C /* CameraView+Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AF68672E5D97E000BB9D5C /* CameraView+Preview.swift */; }; + 78AF686C2E5D97EC00BB9D5C /* CameraView+Controls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AF686B2E5D97E800BB9D5C /* CameraView+Controls.swift */; }; + 78AF686D2E5D97EC00BB9D5C /* CameraView+Controls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AF686B2E5D97E800BB9D5C /* CameraView+Controls.swift */; }; + 78AF686E2E5D97EC00BB9D5C /* CameraView+Controls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AF686B2E5D97E800BB9D5C /* CameraView+Controls.swift */; }; + 78AF687D2E5D99EF00BB9D5C /* Preferences+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AF687C2E5D99E300BB9D5C /* Preferences+Extension.swift */; }; + 78AF687E2E5D99EF00BB9D5C /* Preferences+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AF687C2E5D99E300BB9D5C /* Preferences+Extension.swift */; }; + 78AF687F2E5D99EF00BB9D5C /* Preferences+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AF687C2E5D99E300BB9D5C /* Preferences+Extension.swift */; }; 78B72BB12E332830002E2D4E /* MalachiteWatch.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 78B72BA72E33282E002E2D4E /* MalachiteWatch.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 78CAC36A2CCD99B600A35AE8 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */; }; 78E170772E51C97B009BEF2F /* CompatibilityView+Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E170762E51C971009BEF2F /* CompatibilityView+Camera.swift */; }; @@ -243,6 +255,7 @@ 782FC7752B2DAFAB007709C1 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; 7837C1152E34C45B009396B0 /* WidgetBundleWatch.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetBundleWatch.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 78474D2B2D7AC879006FBB96 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = ""; }; + 784EDDD32E5EE52100DFF370 /* PhotoPreviewView+Controls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PhotoPreviewView+Controls.swift"; sourceTree = ""; }; 78562BC22B450A7600920160 /* PRIVACY_POLICY.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = PRIVACY_POLICY.md; sourceTree = SOURCE_ROOT; }; 785F08492B12D41100244EB4 /* Malachite.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Malachite.app; sourceTree = BUILT_PRODUCTS_DIR; }; 785F084C2B12D41100244EB4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -255,10 +268,10 @@ 786DAE1A2E4F28A600BE3137 /* DeveloperView+Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DeveloperView+Settings.swift"; sourceTree = ""; }; 786DAE1C2E4F28A600BE3137 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; 786DAE1D2E4F28A600BE3137 /* CompatibilityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompatibilityView.swift; sourceTree = ""; }; - 786DAE1E2E4F28A600BE3137 /* MalachitePhotoPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachitePhotoPreview.swift; sourceTree = ""; }; + 786DAE1E2E4F28A600BE3137 /* PhotoPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoPreviewView.swift; sourceTree = ""; }; 786DAE1F2E4F28A600BE3137 /* QuickHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickHelpView.swift; sourceTree = ""; }; 786DAE202E4F28A600BE3137 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; - 786DAE212E4F28A600BE3137 /* MalachiteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteView.swift; sourceTree = ""; }; + 786DAE212E4F28A600BE3137 /* CameraView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraView.swift; sourceTree = ""; }; 786DAE412E4F28B100BE3137 /* MalachitePreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachitePreferences.swift; sourceTree = ""; }; 786DAE422E4F28B100BE3137 /* MalachitePreferencesUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachitePreferencesUtils.swift; sourceTree = ""; }; 786DAE442E4F28B100BE3137 /* MalachiteFunctionUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteFunctionUtils.swift; sourceTree = ""; }; @@ -302,6 +315,9 @@ 78917F5D2B99AE72005E10FA /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; 78A9DD612CD55551002C131D /* CaptureBundle.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.extensionkit-extension"; includeInIndex = 0; path = CaptureBundle.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 78A9DE302CD581F1002C131D /* LockedCameraCapture.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LockedCameraCapture.framework; path = System/Library/Frameworks/LockedCameraCapture.framework; sourceTree = SDKROOT; }; + 78AF68672E5D97E000BB9D5C /* CameraView+Preview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraView+Preview.swift"; sourceTree = ""; }; + 78AF686B2E5D97E800BB9D5C /* CameraView+Controls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraView+Controls.swift"; sourceTree = ""; }; + 78AF687C2E5D99E300BB9D5C /* Preferences+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Preferences+Extension.swift"; sourceTree = ""; }; 78B72BA72E33282E002E2D4E /* MalachiteWatch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MalachiteWatch.app; sourceTree = BUILT_PRODUCTS_DIR; }; 78E142C92DD426260016B3DB /* Codesigning.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Codesigning.xcconfig; sourceTree = ""; }; 78E170762E51C971009BEF2F /* CompatibilityView+Camera.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CompatibilityView+Camera.swift"; sourceTree = ""; }; @@ -427,9 +443,9 @@ 786DAE222E4F28A600BE3137 /* Views */ = { isa = PBXGroup; children = ( + 78AF68662E5D8E9D00BB9D5C /* PhotoPreviewView */, + 78AF68652E5D8E9200BB9D5C /* CameraView */, 78E170752E51C312009BEF2F /* CompatibilityView */, - 786DAE1E2E4F28A600BE3137 /* MalachitePhotoPreview.swift */, - 786DAE212E4F28A600BE3137 /* MalachiteView.swift */, 788262B82E517076000085AC /* AboutView */, 786DAE802E4F28D600BE3137 /* SettingsView */, 7882627F2E514CA8000085AC /* QuickHelpView */, @@ -438,19 +454,20 @@ path = Views; sourceTree = ""; }; - 786DAE432E4F28B100BE3137 /* Preferences */ = { + 786DAE432E4F28B100BE3137 /* PreferenceUtils */ = { isa = PBXGroup; children = ( + 78AF687C2E5D99E300BB9D5C /* Preferences+Extension.swift */, 786DAE412E4F28B100BE3137 /* MalachitePreferences.swift */, 786DAE422E4F28B100BE3137 /* MalachitePreferencesUtils.swift */, ); - path = Preferences; + path = PreferenceUtils; sourceTree = ""; }; 786DAE4B2E4F28B100BE3137 /* Utilities */ = { isa = PBXGroup; children = ( - 786DAE432E4F28B100BE3137 /* Preferences */, + 786DAE432E4F28B100BE3137 /* PreferenceUtils */, 786DAE442E4F28B100BE3137 /* MalachiteFunctionUtils.swift */, 786DAE452E4F28B100BE3137 /* MalachiteGameUtils.swift */, 786DAE462E4F28B100BE3137 /* MalachiteHapticUtils.swift */, @@ -542,6 +559,25 @@ name = Frameworks; sourceTree = ""; }; + 78AF68652E5D8E9200BB9D5C /* CameraView */ = { + isa = PBXGroup; + children = ( + 786DAE212E4F28A600BE3137 /* CameraView.swift */, + 78AF686B2E5D97E800BB9D5C /* CameraView+Controls.swift */, + 78AF68672E5D97E000BB9D5C /* CameraView+Preview.swift */, + ); + path = CameraView; + sourceTree = ""; + }; + 78AF68662E5D8E9D00BB9D5C /* PhotoPreviewView */ = { + isa = PBXGroup; + children = ( + 784EDDD32E5EE52100DFF370 /* PhotoPreviewView+Controls.swift */, + 786DAE1E2E4F28A600BE3137 /* PhotoPreviewView.swift */, + ); + path = PhotoPreviewView; + sourceTree = ""; + }; 78E170752E51C312009BEF2F /* CompatibilityView */ = { isa = PBXGroup; children = ( @@ -821,8 +857,10 @@ 788262BB2E5170F4000085AC /* AboutView+Info.swift in Sources */, 78E1707D2E51CC63009BEF2F /* CompatibilityView+Resolutions.swift in Sources */, 788262DA2E5171F0000085AC /* AboutView+Story.swift in Sources */, + 78AF686C2E5D97EC00BB9D5C /* CameraView+Controls.swift in Sources */, 786DAE552E4F28B100BE3137 /* MalachitePreferences.swift in Sources */, 786DAE562E4F28B100BE3137 /* MalachitePreferencesUtils.swift in Sources */, + 78AF68682E5D97E500BB9D5C /* CameraView+Preview.swift in Sources */, 7882628A2E514F18000085AC /* QuickHelpView+Preview.swift in Sources */, 788262E32E5172E8000085AC /* AboutView+Credits.swift in Sources */, 786DAE572E4F28B100BE3137 /* MalachiteFunctionUtils.swift in Sources */, @@ -841,18 +879,20 @@ 7882627A2E5130DB000085AC /* SettingsView+UI.swift in Sources */, 786DAE2F2E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift in Sources */, 786DAE302E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */, + 78AF687E2E5D99EF00BB9D5C /* Preferences+Extension.swift in Sources */, 786DAE312E4F28A600BE3137 /* AboutView.swift in Sources */, 786DAE322E4F28A600BE3137 /* CompatibilityView.swift in Sources */, 7882627E2E514750000085AC /* DeveloperView+Internal.swift in Sources */, - 786DAE332E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */, + 786DAE332E4F28A600BE3137 /* PhotoPreviewView.swift in Sources */, 786DAE342E4F28A600BE3137 /* QuickHelpView.swift in Sources */, + 784EDDD62E5EE52600DFF370 /* PhotoPreviewView+Controls.swift in Sources */, 788262862E514F12000085AC /* QuickHelpView+About.swift in Sources */, 7882626E2E5130C1000085AC /* SettingsView+Resolution.swift in Sources */, 786DAE352E4F28A600BE3137 /* SettingsView.swift in Sources */, 7882628F2E514F25000085AC /* QuickHelpView+Resolution.swift in Sources */, 7882629A2E514F3F000085AC /* QuickHelpView+UI.swift in Sources */, 788262762E5130D4000085AC /* SettingsView+Watermarking.swift in Sources */, - 786DAE362E4F28A600BE3137 /* MalachiteView.swift in Sources */, + 786DAE362E4F28A600BE3137 /* CameraView.swift in Sources */, 785F084D2B12D41100244EB4 /* AppDelegate.swift in Sources */, 788262962E514F38000085AC /* QuickHelpView+Watermarking.swift in Sources */, 787B1CAA2B8B095E000AFECC /* Malachite.docc in Sources */, @@ -870,8 +910,10 @@ 788262BC2E5170F4000085AC /* AboutView+Info.swift in Sources */, 78E1707B2E51CC63009BEF2F /* CompatibilityView+Resolutions.swift in Sources */, 788262DB2E5171F0000085AC /* AboutView+Story.swift in Sources */, + 78AF686E2E5D97EC00BB9D5C /* CameraView+Controls.swift in Sources */, 786DAE5E2E4F28B100BE3137 /* MalachitePreferences.swift in Sources */, 786DAE5F2E4F28B100BE3137 /* MalachitePreferencesUtils.swift in Sources */, + 78AF68692E5D97E500BB9D5C /* CameraView+Preview.swift in Sources */, 788262892E514F18000085AC /* QuickHelpView+Preview.swift in Sources */, 788262E22E5172E8000085AC /* AboutView+Credits.swift in Sources */, 786DAE602E4F28B100BE3137 /* MalachiteFunctionUtils.swift in Sources */, @@ -890,25 +932,27 @@ 788262792E5130DB000085AC /* SettingsView+UI.swift in Sources */, 786DAE392E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift in Sources */, 786DAE6D2E4F28B700BE3137 /* ControlCenterWidget.swift in Sources */, + 78AF687D2E5D99EF00BB9D5C /* Preferences+Extension.swift in Sources */, 786DAE6E2E4F28B700BE3137 /* LockScreenWidget.swift in Sources */, 786DAE6F2E4F28B700BE3137 /* MalachiteWidgetBundle.swift in Sources */, 7882627D2E514750000085AC /* DeveloperView+Internal.swift in Sources */, 786DAE3A2E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */, 786DAE3B2E4F28A600BE3137 /* AboutView.swift in Sources */, + 784EDDD42E5EE52600DFF370 /* PhotoPreviewView+Controls.swift in Sources */, 788262852E514F12000085AC /* QuickHelpView+About.swift in Sources */, 7882626D2E5130C1000085AC /* SettingsView+Resolution.swift in Sources */, 786DAE3C2E4F28A600BE3137 /* CompatibilityView.swift in Sources */, 7882628E2E514F25000085AC /* QuickHelpView+Resolution.swift in Sources */, 788262992E514F3F000085AC /* QuickHelpView+UI.swift in Sources */, 788262742E5130D4000085AC /* SettingsView+Watermarking.swift in Sources */, - 786DAE3D2E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */, + 786DAE3D2E4F28A600BE3137 /* PhotoPreviewView.swift in Sources */, 786DAE3E2E4F28A600BE3137 /* QuickHelpView.swift in Sources */, 788262952E514F38000085AC /* QuickHelpView+Watermarking.swift in Sources */, 786DAE3F2E4F28A600BE3137 /* SettingsView.swift in Sources */, 788262652E51309C000085AC /* SettingsView+Preview.swift in Sources */, 788262DE2E517252000085AC /* AboutView+Eggs.swift in Sources */, 78E170812E51CE45009BEF2F /* CompatibilityView+Format.swift in Sources */, - 786DAE402E4F28A600BE3137 /* MalachiteView.swift in Sources */, + 786DAE402E4F28A600BE3137 /* CameraView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -929,12 +973,14 @@ 7882627C2E514750000085AC /* DeveloperView+Internal.swift in Sources */, 786DAE4D2E4F28B100BE3137 /* MalachitePreferencesUtils.swift in Sources */, 788262DC2E5171F0000085AC /* AboutView+Story.swift in Sources */, + 78AF686D2E5D97EC00BB9D5C /* CameraView+Controls.swift in Sources */, 788262642E51309C000085AC /* SettingsView+Preview.swift in Sources */, 786DAE782E4F28B900BE3137 /* MalachiteCaptureBundle.swift in Sources */, 786DAE4E2E4F28B100BE3137 /* MalachiteFunctionUtils.swift in Sources */, 7882629B2E514F3F000085AC /* QuickHelpView+UI.swift in Sources */, 786DAE4F2E4F28B100BE3137 /* MalachiteGameUtils.swift in Sources */, 7882626C2E5130C1000085AC /* SettingsView+Resolution.swift in Sources */, + 78AF686A2E5D97E500BB9D5C /* CameraView+Preview.swift in Sources */, 786DAE502E4F28B100BE3137 /* MalachiteHapticUtils.swift in Sources */, 786DAE512E4F28B100BE3137 /* MalachiteIntentUtils.swift in Sources */, 788262E42E5172E8000085AC /* AboutView+Credits.swift in Sources */, @@ -944,18 +990,20 @@ 786DAE542E4F28B100BE3137 /* MalachiteViewUtils.swift in Sources */, 788262872E514F12000085AC /* QuickHelpView+About.swift in Sources */, 786DAE262E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */, + 784EDDD52E5EE52600DFF370 /* PhotoPreviewView+Controls.swift in Sources */, 788262912E514F2D000085AC /* QuickHelpView+Photo.swift in Sources */, 786DAE272E4F28A600BE3137 /* AboutView.swift in Sources */, 78E1707C2E51CC63009BEF2F /* CompatibilityView+Resolutions.swift in Sources */, 786DAE282E4F28A600BE3137 /* CompatibilityView.swift in Sources */, - 786DAE292E4F28A600BE3137 /* MalachitePhotoPreview.swift in Sources */, + 78AF687F2E5D99EF00BB9D5C /* Preferences+Extension.swift in Sources */, + 786DAE292E4F28A600BE3137 /* PhotoPreviewView.swift in Sources */, 788262712E5130CA000085AC /* SettingsView+Photo.swift in Sources */, 78E170802E51CE45009BEF2F /* CompatibilityView+Format.swift in Sources */, 7882628D2E514F25000085AC /* QuickHelpView+Resolution.swift in Sources */, 786DAE2A2E4F28A600BE3137 /* QuickHelpView.swift in Sources */, 7882628B2E514F18000085AC /* QuickHelpView+Preview.swift in Sources */, 786DAE2B2E4F28A600BE3137 /* SettingsView.swift in Sources */, - 786DAE2C2E4F28A600BE3137 /* MalachiteView.swift in Sources */, + 786DAE2C2E4F28A600BE3137 /* CameraView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1015,7 +1063,9 @@ baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; @@ -1056,7 +1106,9 @@ baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; @@ -1097,7 +1149,9 @@ baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; @@ -1266,6 +1320,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; CODE_SIGN_ENTITLEMENTS = Malachite/Malachite.entitlements; CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 1; @@ -1325,6 +1380,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; CODE_SIGN_ENTITLEMENTS = Malachite/Malachite.entitlements; CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 1; @@ -1378,7 +1434,9 @@ baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; @@ -1418,7 +1476,9 @@ baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; @@ -1457,6 +1517,8 @@ isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; @@ -1495,6 +1557,8 @@ isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; @@ -1533,6 +1597,8 @@ isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; @@ -1572,6 +1638,8 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; @@ -1611,6 +1679,8 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; @@ -1650,6 +1720,8 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; @@ -1759,6 +1831,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; CODE_SIGN_ENTITLEMENTS = Malachite/Malachite.entitlements; CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 1; @@ -1815,7 +1888,9 @@ baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; diff --git a/Malachite/CaptureBundle/MalachiteCaptureBundle.swift b/Malachite/CaptureBundle/MalachiteCaptureBundle.swift index 8dcd335..e6e4477 100755 --- a/Malachite/CaptureBundle/MalachiteCaptureBundle.swift +++ b/Malachite/CaptureBundle/MalachiteCaptureBundle.swift @@ -21,7 +21,7 @@ struct MalachiteCaptureBundle: LockedCameraCaptureExtension { } struct MalachiteCaptureBundleViewFinder: UIViewControllerRepresentable { - typealias UIViewControllerType = MalachiteView + typealias UIViewControllerType = CameraView // Apple's sample LockedCameraCapture code let session: LockedCameraCaptureSession @@ -31,10 +31,10 @@ struct MalachiteCaptureBundleViewFinder: UIViewControllerRepresentable { self.session = session } - func makeUIViewController(context: Self.Context) -> MalachiteView { - return MalachiteView() + func makeUIViewController(context: Self.Context) -> CameraView { + return CameraView() } - func updateUIViewController(_ uiViewController: MalachiteView, context: Self.Context) { + func updateUIViewController(_ uiViewController: CameraView, context: Self.Context) { } } diff --git a/Malachite/Localizable.xcstrings b/Malachite/Localizable.xcstrings index 4da26bd..056e3f0 100755 --- a/Malachite/Localizable.xcstrings +++ b/Malachite/Localizable.xcstrings @@ -1628,6 +1628,7 @@ } }, "uibutton.close.title" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1638,6 +1639,7 @@ } }, "uibutton.exposure.title" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1648,6 +1650,7 @@ } }, "uibutton.focus.title" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1658,6 +1661,7 @@ } }, "uibutton.save.title" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1668,6 +1672,7 @@ } }, "uibutton.share.title" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { diff --git a/Malachite/SceneDelegate.swift b/Malachite/SceneDelegate.swift index c391cd8..2a37978 100755 --- a/Malachite/SceneDelegate.swift +++ b/Malachite/SceneDelegate.swift @@ -12,7 +12,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } window = UIWindow(windowScene: windowScene) - window?.rootViewController = MalachiteView() + window?.rootViewController = CameraView() window?.makeKeyAndVisible() } } diff --git a/Malachite/Utilities/MalachiteUtils.swift b/Malachite/Utilities/MalachiteUtils.swift index 370869b..172eafe 100755 --- a/Malachite/Utilities/MalachiteUtils.swift +++ b/Malachite/Utilities/MalachiteUtils.swift @@ -62,20 +62,23 @@ public class MalachiteClassesObject : NSObject { /// A function to only log in INTERNAL builds public func internalNSLog(_ format: String, file: String = #file, line: Int = #line, function: String = #function) { if self.versionType == "INTERNAL" { - Foundation.NSLog("[\(file):\(line)] [\(function)] [INTERNAL] \(format)") + Foundation.NSLog("[\(file):\(line)] [\(function)] \(format)") } } - /// A function to only log in DEBUG and INTERNAL builds + /// A function that calls ``debugNSLog`` on DEBUG and ``internalNSLog`` on INTERNAL. public func debugNSLog(_ format: String, file: String = #file, line: Int = #line, function: String = #function) { - if (self.versionType == "DEBUG" && self.preferences.debug.logging.unified) || self.versionType == "INTERNAL" { + if self.versionType == "INTERNAL" { self.internalNSLog(format, file: file, line: line, function: function) } + else if (self.versionType == "DEBUG" && self.preferences.debug.logging.unified) { Foundation.NSLog("[\(NSString(string: file).lastPathComponent):\(line)] [\(function)] \(format)") } } - /// Literally just regular NSLog, here for consistency - public func NSLog(_ format: String) { - Foundation.NSLog(format) + /// A function that calls regular NSLog on RELEASE, ``debugNSLog`` on DEBUG, and ``internalNSLog`` on INTERNAL. + public func NSLog(_ format: String, file: String = #file, line: Int = #line, function: String = #function) { + if self.versionType == "INTERNAL" { self.internalNSLog(format, file: file, line: line, function: function) } + else if self.versionType == "DEBUG" { self.debugNSLog(format, file: file, line: line, function: function) } + else { Foundation.NSLog(format) } } } diff --git a/Malachite/Utilities/MalachiteViewUtils.swift b/Malachite/Utilities/MalachiteViewUtils.swift index 260ab1b..b1a105e 100755 --- a/Malachite/Utilities/MalachiteViewUtils.swift +++ b/Malachite/Utilities/MalachiteViewUtils.swift @@ -12,13 +12,13 @@ import SwiftUI public class MalachiteViewUtils : NSObject { /// Function that returns a buttons for the user interface. - public func returnProperButton(symbolName name: String, cornerRadius corners: CGFloat, viewForBounds view: UIView, hapticClass haptic: MalachiteHapticUtils?) -> UIButton { + public func createAndAddButtonToView(symbolName: String, delegate: UIViewController, view: UIView, utilities: MalachiteClassesObject, action: Selector, dimensions: [ CGFloat ], constraints: MalachiteViewUtils.buttonBuilder.constraints) -> UIButton { let button = UIButton() - let buttonImage = UIImage(systemName: name)?.withRenderingMode(.alwaysTemplate) + let buttonImage = UIImage(systemName: symbolName)?.withRenderingMode(.alwaysTemplate) button.setImage(buttonImage, for: .normal) button.translatesAutoresizingMaskIntoConstraints = false button.layer.masksToBounds = true - button.layer.cornerRadius = corners + button.layer.cornerRadius = dimensions[0] button.bringSubviewToFront(button.imageView!) button.imageView?.clipsToBounds = false button.imageView?.contentMode = .center @@ -28,22 +28,62 @@ public class MalachiteViewUtils : NSObject { button.insertSubview(returnProperEffectView(viewForBounds: view, effect: UIBlurEffect(style: .systemThinMaterial)), at: 0) button.tintColor = UIColor(.primary) } - if haptic != nil { - button.addTarget(haptic, action: #selector(haptic!.buttonMediumHaptics(_:)), for: .touchUpInside) - } + button.isPointerInteractionEnabled = true button.pointerStyleProvider = { button, proposedEffect, proposedShape -> UIPointerStyle? in let parameters = UIPreviewParameters() - let shapePath = UIBezierPath(roundedRect: button.bounds, cornerRadius: corners) + let shapePath = UIBezierPath(roundedRect: button.bounds, cornerRadius: dimensions[0]) parameters.shadowPath = shapePath let preview = UITargetedPreview(view: proposedEffect.preview.view, parameters: parameters, target: proposedEffect.preview.target) let rect = button.convert(button.bounds, to: preview.target.container) - return UIPointerStyle(effect: .lift(preview), shape: .roundedRect(rect, radius: corners)) + return UIPointerStyle(effect: .lift(preview), shape: .roundedRect(rect, radius: dimensions[0])) + } + + button.addTarget(delegate, action: action, for: .touchUpInside) + button.addTarget(utilities.haptics, action: #selector(utilities.haptics.buttonMediumHaptics(_:)), for: .touchUpInside) + view.addSubview(button) + + var constraintsToAdd: [NSLayoutConstraint] = [ + button.widthAnchor.constraint(equalToConstant: dimensions[1]), + button.heightAnchor.constraint(equalToConstant: (dimensions.count > 2) ? dimensions[2] : dimensions[1]) + ] + + if let LXA = constraints.LXA, let LXC = constraints.LXC, let LXP = constraints.LXP { + if LXP { constraintsToAdd.append(button.leadingAnchor.constraint(equalTo: LXA, constant: LXC)) + } else { constraintsToAdd.append(button.trailingAnchor.constraint(equalTo: LXA, constant: LXC)) } + } + if let LYA = constraints.LYA, let LYC = constraints.LYC, let LYP = constraints.LYP { + if LYP { constraintsToAdd.append(button.bottomAnchor.constraint(equalTo: LYA, constant: LYC)) + } else { constraintsToAdd.append(button.topAnchor.constraint(equalTo: LYA, constant: LYC)) } } + if let CXA = constraints.CXA, let CXC = constraints.CXC { constraintsToAdd.append(button.centerXAnchor.constraint(equalTo: CXA, constant: CXC)) } + if let CYA = constraints.CYA, let CYC = constraints.CYC { constraintsToAdd.append(button.centerYAnchor.constraint(equalTo: CYA, constant: CYC)) } + + NSLayoutConstraint.activate(constraintsToAdd) + return button } + /// Function that returns a slider for the user interface. + public func createAndAddSliderToView(delegate: UIViewController, view: UIView, utilities: MalachiteClassesObject, action: Selector, dimensions: [ CGFloat ]) -> UISlider { + let slider = UISlider() + slider.translatesAutoresizingMaskIntoConstraints = false + slider.addTarget(delegate, action: action, for: .valueChanged) + slider.addTarget(utilities.haptics, action: #selector(utilities.haptics.buttonMediumHaptics(_:)), for: .touchUpInside) + view.addSubview(slider) + + + NSLayoutConstraint.activate([ + slider.widthAnchor.constraint(equalToConstant: dimensions[0]), + slider.heightAnchor.constraint(equalToConstant: dimensions[1]), + slider.centerYAnchor.constraint(equalTo: view.centerYAnchor), + slider.centerXAnchor.constraint(equalTo: view.trailingAnchor, constant: -105), + ]) + + return slider + } + /// Function that returns blurs for the user interface. public func returnProperEffectView(viewForBounds view: UIView, effect: UIVisualEffect) -> UIVisualEffectView { var effectView = UIVisualEffectView() @@ -150,6 +190,40 @@ public class MalachiteViewUtils : NSObject { let data = Data(base64Encoded: lobotomized, options: .ignoreUnknownCharacters) return UIImage(data: data!)! } + + public struct buttonBuilder { + let symbolName: String + let action: Selector + let dimensions: [ CGFloat ] + let constraints: constraints + let assign: (UIButton) -> Void + + public struct constraints { + let LXA: NSLayoutXAxisAnchor? + let LXC: CGFloat? + let LXP: Bool? + let LYA: NSLayoutYAxisAnchor? + let LYC: CGFloat? + let LYP: Bool? + let CXA: NSLayoutXAxisAnchor? + let CXC: CGFloat? + let CYA: NSLayoutYAxisAnchor? + let CYC: CGFloat? + } + } + + public struct sliderBuilder { + let action: Selector + let dimensions: [ CGFloat ] + let view: UIView + let assign: (UISlider) -> Void + } + + public struct tooltipBuilder { + let text: String + let anchor: CGFloat + let assign: (UILabel) -> Void + } } /// An extension for `UIDeviceOrientation` that properly orients the `cameraSession` @@ -282,28 +356,6 @@ struct MalachiteCompatibilityViewUtils: View { } } -// Really Eva? What the fuck is a "Nagivation" -struct MalachiteNagivationViewUtils: View { - let content: Content - - init( @ViewBuilder content: () -> Content ) { - self.content = content() - } - - var body: some View { - if #available(iOS 16.0, *) { - NavigationStack { - content - } - } else { - NavigationView { - content - } - .navigationViewStyle(.stack) - } - } -} - // temp, will be redone struct MalachiteToolbarUtils: View { let action: () -> Void diff --git a/Malachite/Views/CameraView/CameraView+Controls.swift b/Malachite/Views/CameraView/CameraView+Controls.swift new file mode 100644 index 0000000..af940c1 --- /dev/null +++ b/Malachite/Views/CameraView/CameraView+Controls.swift @@ -0,0 +1,119 @@ +// +// CameraView+Controls.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/26/25. +// + +import Foundation +import UIKit + +extension CameraView { + class controls: NSObject { + /// The existing instance of ``CameraView`` to act on. + var delegate = CameraView() + + init(delegate: CameraView) { self.delegate = delegate } + + func initButtons() { + var lockButtonsX = -80.0 + var lockButtonsY = 0.0 + + if delegate.view.frame.size.width >= 370 { + delegate.utilities.debugNSLog("[Initialization] Device screen is capable of displaying lock button inline") + lockButtonsX = -300.0 + } else { + // TODO: Make lock buttons not clip into other bars! + NSLog("[Initialization] Device screen is too small for inline lock button") + lockButtonsY = 70.0 + } + + let buttonConstraints: [MalachiteViewUtils.buttonBuilder.constraints] = [ + MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.leadingAnchor, LXC: 10.0, LXP: true, LYA: delegate.view.safeAreaLayoutGuide.bottomAnchor, LYC: -10.0, LYP: true, CXA: nil, CXC: nil, CYA: nil, CYC: nil), + MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: -10.0, LXP: false, LYA: delegate.view.safeAreaLayoutGuide.bottomAnchor, LYC: -10.0, LYP: true, CXA: nil, CXC: nil, CYA: nil, CYC: nil), + MalachiteViewUtils.buttonBuilder.constraints(LXA: nil, LXC: nil, LXP: nil, LYA: delegate.view.safeAreaLayoutGuide.bottomAnchor, LYC: -10.0, LYP: true, CXA: delegate.view.safeAreaLayoutGuide.centerXAnchor, CXC: 0.0, CYA: nil, CYC: nil), + MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: -10.0, LXP: false, LYA: delegate.view.safeAreaLayoutGuide.topAnchor, LYC: 10.0, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), + MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: 0.0, LXP: true, LYA: delegate.view.safeAreaLayoutGuide.topAnchor, LYC: 10.0, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), + MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: lockButtonsX, LXP: false, LYA: delegate.focusButton.topAnchor, LYC: lockButtonsY, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), + MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: -10.0, LXP: false, LYA: delegate.view.safeAreaLayoutGuide.topAnchor, LYC: 80.0, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), + MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: 0.0, LXP: true, LYA: delegate.view.safeAreaLayoutGuide.topAnchor, LYC: 80.0, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), + MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: lockButtonsX, LXP: false, LYA: delegate.exposureButton.topAnchor, LYC: lockButtonsY, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), + MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.leadingAnchor, LXC: 10.0, LXP: true, LYA: delegate.view.safeAreaLayoutGuide.bottomAnchor, LYC: -80.0, LYP: true, CXA: nil, CXC: nil, CYA: nil, CYC: nil), + MalachiteViewUtils.buttonBuilder.constraints(LXA: nil, LXC: nil, LXP: nil, LYA: nil, LYC: nil, LYP: nil, CXA: nil, CXC: nil, CYA: nil, CYC: nil), + MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.leadingAnchor, LXC: 10.0, LXP: true, LYA: delegate.view.safeAreaLayoutGuide.topAnchor, LYC: 10.0, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), + ] + + let buttonConfigs: [MalachiteViewUtils.buttonBuilder] = [ + MalachiteViewUtils.buttonBuilder(symbolName: "camera", action: #selector(delegate.runInputSwitch), dimensions: [ 30.0, 60.0 ], constraints: buttonConstraints[0], assign: { [self] button in delegate.cameraButton = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "flashlight.off.fill", action: #selector(delegate.runFlashlightToggle), dimensions: [ 30.0, 60.0 ], constraints: buttonConstraints[1], assign: { [self] button in delegate.flashlightButton = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "camera.aperture", action: #selector(delegate.runImageCapture), dimensions: [ 45.0, 90.0 ], constraints: buttonConstraints[2], assign: { [self] button in delegate.captureButton = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "scope", action: #selector(delegate.runManualFocusUIHider), dimensions: [ 30.0, 60.0 ], constraints: buttonConstraints[3], assign: { [self] button in delegate.focusButton = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "", action: #selector(delegate.stub), dimensions: [ 30.0, 210.0, 60.0 ], constraints: buttonConstraints[4], assign: { [self] button in delegate.focusSliderButton = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "lock.open", action: #selector(delegate.runManualFocusLockController), dimensions: [ 30.0, 60.0 ], constraints: buttonConstraints[5], assign: { [self] button in delegate.focusLockButton = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "plusminus", action: #selector(delegate.runManualExposureUIHider), dimensions: [ 30.0, 60.0 ], constraints: buttonConstraints[6], assign: { [self] button in delegate.exposureButton = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "", action: #selector(delegate.stub), dimensions: [ 30.0, 210.0, 60.0 ], constraints: buttonConstraints[7], assign: { [self] button in delegate.exposureSliderButton = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "lock.open", action: #selector(delegate.runManualExposureLockController), dimensions: [ 30.0, 60.0 ], constraints: buttonConstraints[8], assign: { [self] button in delegate.exposureLockButton = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "gear", action: #selector(delegate.presentSettingsView), dimensions: [ 30.0, 60.0 ], constraints: buttonConstraints[9], assign: { [self] button in delegate.settingsButton = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "", action: #selector(delegate.stub), dimensions: [ 30.0, 120.0 ], constraints: buttonConstraints[10], assign: { [self] button in delegate.aeafFeedback = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "", action: #selector(delegate.stub), dimensions: [ 30.0, 60.0 ], constraints: buttonConstraints[11], assign: { [self] button in delegate.currentCamera = button }), + ] + + for config in buttonConfigs { + let button = delegate.utilities.views.createAndAddButtonToView(symbolName: config.symbolName, delegate: delegate, view: delegate.view, utilities: delegate.utilities, action: config.action, dimensions: config.dimensions, constraints: config.constraints) + if delegate.utilities.preferences.userInterface.appLaunch { button.alpha = 0.0; delegate.uiIsHidden = true } + config.assign(button) + } + } + + func initSliders() { + let sliderConfigs: [MalachiteViewUtils.sliderBuilder] = [ + MalachiteViewUtils.sliderBuilder(action: #selector(delegate.runManualFocusController), dimensions: [ 180.0, 80.0 ], view: delegate.focusSliderButton, assign: { [self] slider in delegate.focusSlider = slider } ), + MalachiteViewUtils.sliderBuilder(action: #selector(delegate.runManualExposureController), dimensions: [ 180.0, 80.0 ], view: delegate.exposureSliderButton, assign: { [self] slider in delegate.exposureSlider = slider } ), + ] + + for config in sliderConfigs { + config.assign(delegate.utilities.views.createAndAddSliderToView(delegate: delegate, view: config.view, utilities: delegate.utilities, action: config.action, dimensions: config.dimensions)) + } + } + + func initRecognizers() { + delegate.zoomRecognizer = UIPinchGestureRecognizer(target: delegate, action:#selector(runZoomController)) + delegate.view.addGestureRecognizer(delegate.zoomRecognizer) + + if !delegate.utilities.preferences.userInterface.tapAndHold.contains("off") { + delegate.aeafRecognizer = UILongPressGestureRecognizer(target: delegate, action: #selector(runaeafController)) + delegate.view.addGestureRecognizer(delegate.aeafRecognizer) + } + + delegate.uiHiderRecognizer = UILongPressGestureRecognizer(target: delegate, action: #selector(runUIHider)) + delegate.uiHiderRecognizer.numberOfTouchesRequired = 2 + delegate.view.addGestureRecognizer(delegate.uiHiderRecognizer) + } + + func initTooltips(showLabels: Bool, showCamera: Bool) { + if showLabels { + let tooltipConfigs: [ MalachiteViewUtils.tooltipBuilder ] = [ + MalachiteViewUtils.tooltipBuilder(text: "uibutton.focus.title", anchor: 10, assign: { [self] label in delegate.focusTitle = label } ), + MalachiteViewUtils.tooltipBuilder(text: "uibutton.exposure.title", anchor: 80, assign: { [self] label in delegate.exposureTitle = label } ), + ] + + var labels: [ UILabel ] = [] + for config in tooltipConfigs { + labels.append(delegate.utilities.tooltips.returnLabelForTooltipFlows(viewForBounds: delegate.view, textForFlow: NSLocalizedString(config.text, comment: ""), anchorConstant: config.anchor)) + } + + delegate.utilities.tooltips.fadeOutTooltipFlow(labelsToFade: labels) + } + + if showCamera { delegate.utilities.tooltips.zoomTooltipFlow(button: delegate.currentCamera, viewForBounds: delegate.view, camera: delegate.selectedDevice) } + } + + func bringUpControlLayer() { + initButtons() + initSliders() + initRecognizers() + if !delegate.utilities.preferences.userInterface.appLaunch { initTooltips(showLabels: true, showCamera: true) } + } + } +} + diff --git a/Malachite/Views/CameraView/CameraView+Preview.swift b/Malachite/Views/CameraView/CameraView+Preview.swift new file mode 100644 index 0000000..3665ef9 --- /dev/null +++ b/Malachite/Views/CameraView/CameraView+Preview.swift @@ -0,0 +1,7 @@ +// +// CameraView+Preview.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/26/25. +// + diff --git a/Malachite/Views/MalachiteView.swift b/Malachite/Views/CameraView/CameraView.swift similarity index 80% rename from Malachite/Views/MalachiteView.swift rename to Malachite/Views/CameraView/CameraView.swift index a7b9c51..6ef4dad 100755 --- a/Malachite/Views/MalachiteView.swift +++ b/Malachite/Views/CameraView/CameraView.swift @@ -1,5 +1,5 @@ // -// ViewController.swift +// CameraView.swift // Malachite // // Created by Eva Isabella Luna on 11/25/23. @@ -14,25 +14,7 @@ import LockedCameraCapture import Photos import GameKit -struct MalachiteView_SwiftUIWrapped: UIViewControllerRepresentable { - let rootURL: URL? - typealias UIViewControllerType = MalachiteView - func makeUIViewController(context: Self.Context) -> MalachiteView { - return MalachiteView() - } - - func updateUIViewController(_ uiViewController: MalachiteView, context: Self.Context) { - } -} - -@available(iOS 18.0, *) -extension MalachiteView_SwiftUIWrapped { - init(_ session: LockedCameraCaptureSession) { - self.rootURL = session.sessionContentURL - } -} - -class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCapturePhotoCaptureDelegate, AVCaptureSessionControlsDelegate { +class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCapturePhotoCaptureDelegate, AVCaptureSessionControlsDelegate { func sessionControlsDidBecomeActive(_ session: AVCaptureSession) { if !uiIsHidden { runUIHider() } } @@ -53,6 +35,8 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A } } + var controls: controls? + /// The `AVCaptureSession` Malachite uses for everything. var cameraSession: AVCaptureSession? /// The currently selected `AVCaptureDevice` for input to ``cameraSession``. @@ -140,7 +124,6 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A /// A `Bool` that determines whether or not the user interface is currently hidden to the user. var uiIsHidden = false - /// The title for the focus slider. var focusTitle = UILabel() /// The title for the exposure slider. @@ -177,12 +160,14 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A super.viewDidLoad() self.view.backgroundColor = .clear + #warning("malachite init") utilities.debugNSLog("[Initialization] Starting up Malachite") + self.controls = CameraView.controls(delegate: self) if utilities.versionType == "INTERNAL" { - utilities.internalNSLog("[Initialization] Running an INTERNAL build, logging will be force enabled") + utilities.internalNSLog("[Initialization] Running an INTERNAL build") } else if utilities.versionType == "DEBUG" { - utilities.debugNSLog("[Initialization] Running a DEBUG build, logging will be force enabled") + utilities.debugNSLog("[Initialization] Running a DEBUG build") } else if utilities.versionType == "RELEASE" { utilities.NSLog("[Initialization] Running a RELEASE build") } @@ -208,6 +193,7 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A utilities.preferences.capture.format.jpeg = true } + #warning("malachite camera init") cameraPreview?.frame.size = self.view.frame.size utilities.debugNSLog("[Initialization] Bringing up AVCaptureSession") cameraSession = AVCaptureSession() @@ -272,6 +258,7 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A utilities.debugNSLog("[Initialization] No cameras detected, skipping to user interface bringup") } + #warning("malachite notif init") utilities.debugNSLog("[Initialization] Setting up notification observer for orientation changes") #if MAIN_APP NotificationCenter.default.addObserver(self, selector: #selector(orientationChanged), name: UIDevice.orientationDidChangeNotification, object: nil) @@ -289,12 +276,14 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A UIDevice.current.beginGeneratingDeviceOrientationNotifications() + #warning("malachite init") if utilities.versionType == "INTERNAL" || utilities.versionType == "DEBUG" { if utilities.preferences.debug.logging.preferences { MalachitePreferencesUtils().printPreferences() } } + #warning("malachite camera init") if #available (iOS 17.2, *) { let interaction = AVCaptureEventInteraction { event in if event.phase == .ended { @@ -306,6 +295,7 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A } + #warning("malachite photo init") let cameraAuthStatus = PHPhotoLibrary.authorizationStatus(for: .addOnly) if cameraAuthStatus == .notDetermined { @@ -396,168 +386,13 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A setupLmaoView() #endif - cameraButton = utilities.views.returnProperButton(symbolName: "camera", cornerRadius: 30, viewForBounds: self.view, hapticClass: utilities.haptics) + #warning("can be made into a for loop") + self.controls!.bringUpControlLayer() - flashlightButton = utilities.views.returnProperButton(symbolName: "flashlight.off.fill", cornerRadius: 30, viewForBounds: self.view, hapticClass: utilities.haptics) - captureButton = utilities.views.returnProperButton(symbolName: "camera.aperture", cornerRadius: 45, viewForBounds: view, hapticClass: utilities.haptics) - focusButton = utilities.views.returnProperButton(symbolName: "scope", cornerRadius: 30, viewForBounds: view, hapticClass: utilities.haptics) - focusSliderButton = utilities.views.returnProperButton(symbolName: "", cornerRadius: 30, viewForBounds: self.view, hapticClass: utilities.haptics) - focusLockButton = utilities.views.returnProperButton(symbolName: "lock.open", cornerRadius: 30, viewForBounds: self.view, hapticClass: utilities.haptics) - exposureButton = utilities.views.returnProperButton(symbolName: "plusminus", cornerRadius: 30, viewForBounds: view, hapticClass: utilities.haptics) - exposureSliderButton = utilities.views.returnProperButton(symbolName: "", cornerRadius: 30, viewForBounds: view, hapticClass: utilities.haptics) - exposureLockButton = utilities.views.returnProperButton(symbolName: "lock.open", cornerRadius: 30, viewForBounds: view, hapticClass: utilities.haptics) - settingsButton = utilities.views.returnProperButton(symbolName: "gear", cornerRadius: 30, viewForBounds: self.view, hapticClass: utilities.haptics) - aeafFeedback = utilities.views.returnProperButton(symbolName: "", cornerRadius: 60, viewForBounds: self.view, hapticClass: utilities.haptics) - currentCamera = utilities.views.returnProperButton(symbolName: "", cornerRadius: 30, viewForBounds: self.view, hapticClass: nil) - focusSlider.translatesAutoresizingMaskIntoConstraints = false - exposureSlider.translatesAutoresizingMaskIntoConstraints = false focusLockButton.alpha = 0.0 exposureLockButton.alpha = 0.0 aeafFeedback.alpha = 0.0 - self.view.addSubview(cameraButton) - self.view.addSubview(flashlightButton) - self.view.addSubview(captureButton) - self.view.addSubview(focusButton) - self.view.addSubview(focusSliderButton) - self.view.addSubview(focusLockButton) - self.view.addSubview(exposureButton) - self.view.addSubview(exposureSliderButton) - self.view.addSubview(exposureLockButton) - self.view.addSubview(settingsButton) - self.view.addSubview(aeafFeedback) - self.view.addSubview(currentCamera) - focusSliderButton.addSubview(focusSlider) - exposureSliderButton.addSubview(exposureSlider) - - if self.availableRearCameras.count > 0 { - cameraButton.addTarget(self, action: #selector(self.runInputSwitch), for: .touchUpInside) - flashlightButton.addTarget(self, action: #selector(self.runFlashlightToggle), for: .touchUpInside) - captureButton.addTarget(self, action: #selector(self.runImageCapture), for: .touchUpInside) - focusSlider.addTarget(self, action: #selector(self.runManualFocusController), for: .valueChanged) - focusSlider.addTarget(utilities.haptics, action: #selector(utilities.haptics.buttonMediumHaptics(_:)), for: .touchUpInside) - focusLockButton.addTarget(self, action: #selector(runManualFocusLockController), for: .touchUpInside) - exposureSlider.addTarget(self, action: #selector(runManualExposureController), for: .valueChanged) - exposureSlider.addTarget(utilities.haptics, action: #selector(utilities.haptics.buttonMediumHaptics(_:)), for: .touchUpInside) - exposureLockButton.addTarget(self, action: #selector(self.runManualExposureLockController), for: .touchUpInside) - } - - focusButton.addTarget(self, action: #selector(self.runManualFocusUIHider), for: .touchUpInside) - exposureButton.addTarget(self, action: #selector(self.runManualExposureUIHider), for: .touchUpInside) - settingsButton.addTarget(self, action: #selector(self.presentSettingsView), for: .touchUpInside) - - zoomRecognizer = UIPinchGestureRecognizer(target: self, action:#selector(runZoomController)) - aeafRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(runaeafController)) - uiHiderRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(runUIHider)) - uiHiderRecognizer.numberOfTouchesRequired = 2 - - - focusTitle = utilities.tooltips.returnLabelForTooltipFlows(viewForBounds: view, textForFlow: NSLocalizedString("uibutton.focus.title", comment: ""), anchorConstant: 10) - exposureTitle = utilities.tooltips.returnLabelForTooltipFlows(viewForBounds: view, textForFlow: NSLocalizedString("uibutton.exposure.title", comment: ""), anchorConstant: 80) - - self.view.addGestureRecognizer(zoomRecognizer) - let tapGestureElements = utilities.preferences.userInterface.tapAndHold - if !tapGestureElements.contains("off") { self.view.addGestureRecognizer(aeafRecognizer) } - self.view.addGestureRecognizer(uiHiderRecognizer) - - var lockButtonsX = -80.0 - var lockButtonsY = 0.0 - - if self.view.frame.size.width >= 370 { - utilities.debugNSLog("[Initialization] Device screen is capable of displaying lock button inline") - lockButtonsX = -300.0 - } else { - // TODO: Make lock buttons not clip into other bars! - NSLog("[Initialization] Device screen is too small for inline lock button") - lockButtonsY = 70.0 - } - - utilities.tooltips.fadeOutTooltipFlow(labelsToFade: [ focusTitle, exposureTitle]) - utilities.tooltips.zoomTooltipFlow(button: currentCamera, viewForBounds: self.view, camera: selectedDevice) - - NSLayoutConstraint.activate([ - cameraButton.widthAnchor.constraint(equalToConstant: 60), - cameraButton.heightAnchor.constraint(equalToConstant: 60), - cameraButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -10), - cameraButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10), - - settingsButton.widthAnchor.constraint(equalToConstant: 60), - settingsButton.heightAnchor.constraint(equalToConstant: 60), - settingsButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -80), - settingsButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10), - - flashlightButton.widthAnchor.constraint(equalToConstant: 60), - flashlightButton.heightAnchor.constraint(equalToConstant: 60), - flashlightButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -10), - flashlightButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10), - - captureButton.widthAnchor.constraint(equalToConstant: 90), - captureButton.heightAnchor.constraint(equalToConstant: 90), - captureButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -10), - captureButton.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor, constant: 0), - - focusButton.widthAnchor.constraint(equalToConstant: 60), - focusButton.heightAnchor.constraint(equalToConstant: 60), - focusButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10), - focusButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10), - - focusSliderButton.widthAnchor.constraint(equalToConstant: 210), - focusSliderButton.heightAnchor.constraint(equalToConstant: 60), - focusSliderButton.topAnchor.constraint(equalTo: focusButton.topAnchor), - focusSliderButton.leadingAnchor.constraint(equalTo: focusButton.trailingAnchor, constant: 10), - - focusSlider.widthAnchor.constraint(equalToConstant: 180), - focusSlider.heightAnchor.constraint(equalToConstant: 80), - focusSlider.centerYAnchor.constraint(equalTo: focusSliderButton.centerYAnchor), - focusSlider.centerXAnchor.constraint(equalTo: focusSliderButton.trailingAnchor, constant: -105), - - focusLockButton.widthAnchor.constraint(equalToConstant: 60), - focusLockButton.heightAnchor.constraint(equalToConstant: 60), - focusLockButton.topAnchor.constraint(equalTo: focusButton.topAnchor, constant: lockButtonsY), - focusLockButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: lockButtonsX), - - exposureButton.widthAnchor.constraint(equalToConstant: 60), - exposureButton.heightAnchor.constraint(equalToConstant: 60), - exposureButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 80), - exposureButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10), - - exposureSliderButton.widthAnchor.constraint(equalToConstant: 210), - exposureSliderButton.heightAnchor.constraint(equalToConstant: 60), - exposureSliderButton.topAnchor.constraint(equalTo: exposureButton.topAnchor), - exposureSliderButton.leadingAnchor.constraint(equalTo: exposureButton.trailingAnchor, constant: 10), - - exposureSlider.widthAnchor.constraint(equalToConstant: 180), - exposureSlider.heightAnchor.constraint(equalToConstant: 80), - exposureSlider.centerYAnchor.constraint(equalTo: exposureSliderButton.centerYAnchor), - exposureSlider.centerXAnchor.constraint(equalTo: exposureSliderButton.trailingAnchor, constant: -105), - - exposureLockButton.widthAnchor.constraint(equalToConstant: 60), - exposureLockButton.heightAnchor.constraint(equalToConstant: 60), - exposureLockButton.topAnchor.constraint(equalTo: exposureButton.topAnchor, constant: lockButtonsY), - exposureLockButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: lockButtonsX), - - currentCamera.widthAnchor.constraint(equalToConstant: 60), - currentCamera.heightAnchor.constraint(equalToConstant: 60), - currentCamera.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10), - currentCamera.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10), - ]) - - if utilities.preferences.userInterface.appLaunch { - cameraButton.alpha = 0.0 - flashlightButton.alpha = 0.0 - captureButton.alpha = 0.0 - focusButton.alpha = 0.0 - focusSliderButton.alpha = 0.0 - exposureButton.alpha = 0.0 - exposureSliderButton.alpha = 0.0 - settingsButton.alpha = 0.0 - currentCamera.alpha = 0.0 - focusTitle.alpha = 0.0 - exposureTitle.alpha = 0.0 - - uiIsHidden = true - } - setupGameKitAlert() changeIdleTimerState() } @@ -584,6 +419,7 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A } func setupLmaoView() { + #warning("remove simulator support") let lmaoView = UIImageView(image: utilities.views.returnImageForSimulator()) self.view.addSubview(lmaoView) @@ -595,6 +431,9 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A ]) } + /// Stub function. It literally does nothing. + @objc func stub() { } + /// Function to enable or disable the idle timer. @objc func changeIdleTimerState() { #if MAIN_APP @@ -711,7 +550,6 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A /// Function to switch cameras and attach new inputs to ``cameraSession``, and set settings based on the `activeFormat` of ``selectedDevice``. @objc func runInputSwitch() { - #warning("fix this breaking after one camera switch") cameraSession?.beginConfiguration() cameraButton.isUserInteractionEnabled = false if (self.availableRearCameras.count < 2 || utilities.preferences.debug.breakApp) && !self.initRun { @@ -760,7 +598,7 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A cameraSession?.commitConfiguration() DispatchQueue.main.async() { [self] in - utilities.tooltips.zoomTooltipFlow(button: currentCamera, viewForBounds: view, camera: selectedDevice) + self.controls!.initTooltips(showLabels: false, showCamera: true) } cameraButton.isUserInteractionEnabled = true @@ -882,7 +720,7 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A let getterForOrientation = UIImage(data: imageData) let previewImage = UIImage(ciImage: CIImage(data: imageData, options: [.applyOrientationProperty: true, .properties: [kCGImagePropertyOrientation: CGImagePropertyOrientation(getterForOrientation!.imageOrientation).rawValue]])!) - let photoPreview = MalachitePhotoPreview() + let photoPreview = PhotoPreviewView() photoPreview.photoImageData = imageData photoPreview.photoImageView.frame = view.frame photoPreview.photoImage = previewImage @@ -1120,7 +958,7 @@ class MalachiteView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, A func showUI() { UIView.animate(withDuration: 0.25) { [self] in for subview in self.view.subviews { - if subview != cameraView { + if subview != cameraView && subview != aeafFeedback { if subview == focusLockButton { if manualFocusSliderIsActive { subview.alpha = 1.0 } } else if subview == exposureLockButton { diff --git a/Malachite/Views/DeveloperView/DeveloperView.swift b/Malachite/Views/DeveloperView/DeveloperView.swift index e0e6e74..e854221 100644 --- a/Malachite/Views/DeveloperView/DeveloperView.swift +++ b/Malachite/Views/DeveloperView/DeveloperView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import UIKit struct DeveloperView: View { /// A State variable used for determining whether or not this view is being presented as a modal. @@ -22,21 +23,27 @@ struct DeveloperView: View { } .navigationTitle("view.title.developer") .toolbar(content: { + #warning("log export implementation eta") ToolbarItemGroup(placement: .topBarLeading) { - NavigationLink(destination: QuickHelpViewDeveloper(utilities: utilities, dismissAction: dismissAction)) { - if #available(iOS 26.0, *) { - Image(systemName: "questionmark.circle") - } else { - Image(systemName: "questionmark.circle").tint(.primary) - } - } + if #available(iOS 26.0, *) { help } } ToolbarItemGroup(placement: .topBarTrailing) { + if #unavailable(iOS 26.0) { help; } MalachiteToolbarUtils(action: self.dismissAction, image: "checkmark", primary: true) } }) } + var help: some View { + NavigationLink(destination: QuickHelpViewDeveloper(utilities: utilities, dismissAction: dismissAction)) { + if #available(iOS 26.0, *) { + Image(systemName: "questionmark.circle") + } else { + Image(systemName: "questionmark.circle").tint(.primary) + } + } + } + struct createBuildInformation: View { var label: LocalizedStringKey var value: String diff --git a/Malachite/Views/PhotoPreviewView/PhotoPreviewView+Controls.swift b/Malachite/Views/PhotoPreviewView/PhotoPreviewView+Controls.swift new file mode 100644 index 0000000..36938a4 --- /dev/null +++ b/Malachite/Views/PhotoPreviewView/PhotoPreviewView+Controls.swift @@ -0,0 +1,66 @@ +// +// PhotoPreviewView+Controls.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/27/25. +// + +import Foundation +import UIKit + +extension PhotoPreviewView { + class controls: NSObject { + /// The existing instance of ``PhotoPreviewView`` to act on. + var delegate = PhotoPreviewView() + + init(delegate: PhotoPreviewView) { self.delegate = delegate } + + func initButtons() { + let buttonConstraints: [MalachiteViewUtils.buttonBuilder.constraints] = [ + MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: -10.0, LXP: false, LYA: delegate.view.safeAreaLayoutGuide.topAnchor, LYC: 10.0, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), + MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: -10.0, LXP: false, LYA: delegate.view.safeAreaLayoutGuide.topAnchor, LYC: 80.0, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), + MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: -10.0, LXP: false, LYA: delegate.view.safeAreaLayoutGuide.topAnchor, LYC: 150.0, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), + ] + + let buttonConfigs: [MalachiteViewUtils.buttonBuilder] = [ + MalachiteViewUtils.buttonBuilder(symbolName: "xmark", action: #selector(delegate.dismissView), dimensions: [ 30.0, 60.0 ], constraints: buttonConstraints[0], assign: { [self] button in delegate.dismissButton = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "photo.on.rectangle", action: #selector(delegate.savePhotoWrapped), dimensions: [ 30.0, 60.0 ], constraints: buttonConstraints[1], assign: { [self] button in delegate.savePhotoButton = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "square.and.arrow.up", action: #selector(delegate.sharePhoto), dimensions: [ 30.0, 60.0 ], constraints: buttonConstraints[2], assign: { [self] button in delegate.sharePhotoButton = button }), + ] + + for config in buttonConfigs { + let button = delegate.utilities.views.createAndAddButtonToView(symbolName: config.symbolName, delegate: delegate, view: delegate.view, utilities: delegate.utilities, action: config.action, dimensions: config.dimensions, constraints: config.constraints) + config.assign(button) + } + } + + func initRecognizers() { + let doubleTapRecognizer = UITapGestureRecognizer(target: delegate, action: #selector(handleDoubleTap(_:))) + doubleTapRecognizer.numberOfTapsRequired = 2 + delegate.photoScrollView.addGestureRecognizer(doubleTapRecognizer) + } + + func initTooltips(showLabels: Bool) { + if showLabels { + let tooltipConfigs: [ MalachiteViewUtils.tooltipBuilder ] = [ + MalachiteViewUtils.tooltipBuilder(text: "uibutton.close.title", anchor: 10, assign: { [self] label in delegate.dismissTitle = label } ), + MalachiteViewUtils.tooltipBuilder(text: "uibutton.save.title", anchor: 80, assign: { [self] label in delegate.savePhotoTitle = label } ), + MalachiteViewUtils.tooltipBuilder(text: "uibutton.share.title", anchor: 150, assign: { [self] label in delegate.sharePhotoTitle = label } ), + ] + + var labels: [ UILabel ] = [] + for config in tooltipConfigs { + labels.append(delegate.utilities.tooltips.returnLabelForTooltipFlows(viewForBounds: delegate.view, textForFlow: NSLocalizedString(config.text, comment: ""), anchorConstant: config.anchor)) + } + + delegate.utilities.tooltips.fadeOutTooltipFlow(labelsToFade: labels) + } + } + + func bringUpControlLayer() { + initButtons() + initRecognizers() + initTooltips(showLabels: true) + } + } +} diff --git a/Malachite/Views/MalachitePhotoPreview.swift b/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift similarity index 84% rename from Malachite/Views/MalachitePhotoPreview.swift rename to Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift index dcda68d..5514f9c 100755 --- a/Malachite/Views/MalachitePhotoPreview.swift +++ b/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift @@ -10,13 +10,15 @@ import UIKit import Photos import LinkPresentation -class MalachitePhotoPreview : UIViewController, UIScrollViewDelegate { +class PhotoPreviewView : UIViewController, UIScrollViewDelegate { /// A variable to hold the existing instance of ``MalachiteClassesObject``. var utilities = MalachiteClassesObject() /// The scroll view that holds the image view for zooming and panning. var photoScrollView = UIScrollView() + var controls: controls? + /** The image view that holds the captuerd image for user review. @@ -75,6 +77,8 @@ class MalachitePhotoPreview : UIViewController, UIScrollViewDelegate { // TODO: dev/malachitekit refactor this file self.finalizedImage = self.finalizeImageForExport(imageData: self.photoImageData) + self.controls = PhotoPreviewView.controls(delegate: self) + super.viewDidLoad() self.view.backgroundColor = .red @@ -133,48 +137,11 @@ class MalachitePhotoPreview : UIViewController, UIScrollViewDelegate { photoScrollView.contentInset = UIEdgeInsets(top: yOffset, left: xOffset, bottom: yOffset, right: xOffset) - dismissTitle = utilities.tooltips.returnLabelForTooltipFlows(viewForBounds: view, textForFlow: NSLocalizedString("uibutton.close.title", comment: ""), anchorConstant: 10) - savePhotoTitle = utilities.tooltips.returnLabelForTooltipFlows(viewForBounds: view, textForFlow: NSLocalizedString( "uibutton.save.title", comment: ""), anchorConstant: 80) - sharePhotoTitle = utilities.tooltips.returnLabelForTooltipFlows(viewForBounds: view, textForFlow: NSLocalizedString( "uibutton.share.title", comment: ""), anchorConstant: 150) - self.view.addSubview(blurredBackgroundView) photoScrollView.addSubview(photoImageView) self.view.addSubview(photoScrollView) - let doubleTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap(_:))) - doubleTapRecognizer.numberOfTapsRequired = 2 - photoScrollView.addGestureRecognizer(doubleTapRecognizer) - - - dismissButton = utilities.views.returnProperButton(symbolName: "xmark", cornerRadius: 30, viewForBounds: self.view, hapticClass: utilities.haptics) - self.view.addSubview(dismissButton) - NSLayoutConstraint.activate([ - dismissButton.widthAnchor.constraint(equalToConstant: 60), - dismissButton.heightAnchor.constraint(equalToConstant: 60), - dismissButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10), - dismissButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10), - ]) - dismissButton.addTarget(self, action: #selector(self.dismissView), for: .touchUpInside) - - savePhotoButton = utilities.views.returnProperButton(symbolName: "photo.on.rectangle", cornerRadius: 30, viewForBounds: self.view, hapticClass: utilities.haptics) - self.view.addSubview(savePhotoButton) - NSLayoutConstraint.activate([ - savePhotoButton.widthAnchor.constraint(equalToConstant: 60), - savePhotoButton.heightAnchor.constraint(equalToConstant: 60), - savePhotoButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 80), - savePhotoButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10), - ]) - savePhotoButton.addTarget(self, action: #selector(self.savePhotoWrapped), for: .touchUpInside) - - sharePhotoButton = utilities.views.returnProperButton(symbolName: "square.and.arrow.up", cornerRadius: 30, viewForBounds: self.view, hapticClass: utilities.haptics) - self.view.addSubview(sharePhotoButton) - NSLayoutConstraint.activate([ - sharePhotoButton.widthAnchor.constraint(equalToConstant: 60), - sharePhotoButton.heightAnchor.constraint(equalToConstant: 60), - sharePhotoButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 150), - sharePhotoButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10), - ]) - sharePhotoButton.addTarget(self, action: #selector(self.sharePhoto), for: .touchUpInside) + self.controls!.bringUpControlLayer() orientationChanged() } @@ -183,7 +150,7 @@ class MalachitePhotoPreview : UIViewController, UIScrollViewDelegate { return photoImageView } - @objc private func handleDoubleTap(_ sender: UITapGestureRecognizer) { + @objc func handleDoubleTap(_ sender: UITapGestureRecognizer) { if photoScrollView.zoomScale == 1 { photoScrollView.setZoomScale(2, animated: true) } else { @@ -192,7 +159,7 @@ class MalachitePhotoPreview : UIViewController, UIScrollViewDelegate { } /// Function to allow the user to close the model view. - @objc private func dismissView() { + @objc func dismissView() { DispatchQueue.main.async { self.navigationController?.dismiss(animated: true) } @@ -224,8 +191,8 @@ class MalachitePhotoPreview : UIViewController, UIScrollViewDelegate { } /// Function to share the image to other apps or people without saving to the Photos library. - @objc private func sharePhoto() { - let shareableData = try! dataToShareable(data: finalizedImage, title: "sharable.title") + @objc func sharePhoto() { + let shareableData = try! dataToShareable(data: finalizedImage, title: "sharable.title".localized) let shareSheet = UIActivityViewController(activityItems: [shareableData], applicationActivities: nil) shareSheet.popoverPresentationController?.sourceView = sharePhotoButton if #available(iOS 26.0, *) { diff --git a/Malachite/Views/SettingsView/SettingsView.swift b/Malachite/Views/SettingsView/SettingsView.swift index f550739..ff3b11c 100755 --- a/Malachite/Views/SettingsView/SettingsView.swift +++ b/Malachite/Views/SettingsView/SettingsView.swift @@ -28,34 +28,40 @@ struct SettingsView: View { - Toolbar item for dismissing the view. */ var body: some View { - MalachiteNagivationViewUtils() { - Form { - About(utilities: utilities, dismissAction: dismissAction) - Preview(utilities: utilities, dismissAction: dismissAction) - Resolution(utilities: utilities, dismissAction: dismissAction) - Photo(utilities: utilities, dismissAction: dismissAction) - Watermarking(utilities: utilities, dismissAction: dismissAction) - UserInterface(utilities: utilities, dismissAction: dismissAction) - if utilities.versionType == "DEBUG" { DeveloperView.Settings(utilities: utilities) } - } - .onAppear { onAppear() } - .onDisappear { onDisappear() } - .navigationTitle("view.title.settings") - .toolbar(content: { - ToolbarItemGroup(placement: .topBarLeading) { - NavigationLink(destination: QuickHelpView(utilities: utilities, dismissAction: dismissAction)) { - if #available(iOS 26.0, *) { - Image(systemName: "questionmark.circle") - } else { - Image(systemName: "questionmark.circle").tint(.primary) - } + if #available(iOS 16.0, *) { + NavigationStack { guts } + } else { + NavigationView { guts }.navigationViewStyle(.stack) + } + } + + var guts: some View { + Form { + About(utilities: utilities, dismissAction: dismissAction) + Preview(utilities: utilities, dismissAction: dismissAction) + Resolution(utilities: utilities, dismissAction: dismissAction) + Photo(utilities: utilities, dismissAction: dismissAction) + Watermarking(utilities: utilities, dismissAction: dismissAction) + UserInterface(utilities: utilities, dismissAction: dismissAction) + if utilities.versionType == "DEBUG" { DeveloperView.Settings(utilities: utilities) } + } + .onAppear { onAppear() } + .onDisappear { onDisappear() } + .navigationTitle("view.title.settings") + .toolbar(content: { + ToolbarItemGroup(placement: .topBarLeading) { + NavigationLink(destination: QuickHelpView(utilities: utilities, dismissAction: dismissAction)) { + if #available(iOS 26.0, *) { + Image(systemName: "questionmark.circle") + } else { + Image(systemName: "questionmark.circle").tint(.primary) } } - ToolbarItemGroup(placement: .topBarTrailing) { - MalachiteToolbarUtils(action: self.dismissAction, image: "checkmark", primary: true) - } - }) - } + } + ToolbarItemGroup(placement: .topBarTrailing) { + MalachiteToolbarUtils(action: self.dismissAction, image: "checkmark", primary: true) + } + }) } func onAppear() { From fb96e7dcd74d39c90c689141b4f3744147eb84d8 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Fri, 29 Aug 2025 20:29:25 -0600 Subject: [PATCH 13/66] Switch localizations for tooltips to manual These aren't stale, Xcode just can't find them anymore. --- Malachite/Localizable.xcstrings | 10 +++++----- Malachite/Views/CameraView/CameraView+Controls.swift | 2 +- .../PhotoPreviewView/PhotoPreviewView+Controls.swift | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Malachite/Localizable.xcstrings b/Malachite/Localizable.xcstrings index 056e3f0..3f2d439 100755 --- a/Malachite/Localizable.xcstrings +++ b/Malachite/Localizable.xcstrings @@ -1628,7 +1628,7 @@ } }, "uibutton.close.title" : { - "extractionState" : "stale", + "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { @@ -1639,7 +1639,7 @@ } }, "uibutton.exposure.title" : { - "extractionState" : "stale", + "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { @@ -1650,7 +1650,7 @@ } }, "uibutton.focus.title" : { - "extractionState" : "stale", + "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { @@ -1661,7 +1661,7 @@ } }, "uibutton.save.title" : { - "extractionState" : "stale", + "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { @@ -1672,7 +1672,7 @@ } }, "uibutton.share.title" : { - "extractionState" : "stale", + "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { diff --git a/Malachite/Views/CameraView/CameraView+Controls.swift b/Malachite/Views/CameraView/CameraView+Controls.swift index af940c1..84839b2 100644 --- a/Malachite/Views/CameraView/CameraView+Controls.swift +++ b/Malachite/Views/CameraView/CameraView+Controls.swift @@ -99,7 +99,7 @@ extension CameraView { var labels: [ UILabel ] = [] for config in tooltipConfigs { - labels.append(delegate.utilities.tooltips.returnLabelForTooltipFlows(viewForBounds: delegate.view, textForFlow: NSLocalizedString(config.text, comment: ""), anchorConstant: config.anchor)) + labels.append(delegate.utilities.tooltips.returnLabelForTooltipFlows(viewForBounds: delegate.view, textForFlow: NSLocalizedString(config.text.localized, comment: ""), anchorConstant: config.anchor)) } delegate.utilities.tooltips.fadeOutTooltipFlow(labelsToFade: labels) diff --git a/Malachite/Views/PhotoPreviewView/PhotoPreviewView+Controls.swift b/Malachite/Views/PhotoPreviewView/PhotoPreviewView+Controls.swift index 36938a4..7e17700 100644 --- a/Malachite/Views/PhotoPreviewView/PhotoPreviewView+Controls.swift +++ b/Malachite/Views/PhotoPreviewView/PhotoPreviewView+Controls.swift @@ -50,7 +50,7 @@ extension PhotoPreviewView { var labels: [ UILabel ] = [] for config in tooltipConfigs { - labels.append(delegate.utilities.tooltips.returnLabelForTooltipFlows(viewForBounds: delegate.view, textForFlow: NSLocalizedString(config.text, comment: ""), anchorConstant: config.anchor)) + labels.append(delegate.utilities.tooltips.returnLabelForTooltipFlows(viewForBounds: delegate.view, textForFlow: NSLocalizedString(config.text.localized, comment: ""), anchorConstant: config.anchor)) } delegate.utilities.tooltips.fadeOutTooltipFlow(labelsToFade: labels) From 35f9aa578449a8d873d7b6161fa0fb85af673d7a Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Thu, 4 Sep 2025 02:08:54 -0600 Subject: [PATCH 14/66] Refactor MalachiteView -> CameraView, pt. 2 - Move initialization to its own class and out of viewDidLoad() - Move preview layer building into CameraView+Preview.swift - Move bringup to Camera+Bringup.swift - Move UI hide/show to MalachiteViewUtils.swift - Created a UIGestureRecognizer array for rewritten UI hide/show funcs - Move various functions out of CameraView into classes that they fit into - Move Camera Control init + delegate to CameraView+Controls - Move Darwin notification init to CameraView+Notifications - Moved overriden funcs to CameraView+Overrides Other changes included in this commit: - Create a .name property on UIGestureRecognizer to more easily manage them - Rewrite dimensions argument for button building to generate corner radius - Add option to block accidental gestures (swiping from the bottom), currently requires a restart - Add option to disable Camera Control, currently requires a restart - Add preference for Camera Control options + order, currently not customizable - Rename localizations for internal options - Fixed the game kit button logic in Preferences+Extension (intentionally broken elsewhere) - Fixed various force unwrapping in bringup code to fix iOS simulator support - Fixed crash on launch on (at least) iOS 17, probably fixes other versions too --- Malachite.xcodeproj/project.pbxproj | 98 +++- Malachite/Localizable.xcstrings | 104 ++-- Malachite/SceneDelegate.swift | 9 +- .../CameraUtils/Camera+Bringup.swift | 30 ++ Malachite/Utilities/CameraUtils/Camera.swift | 12 + Malachite/Utilities/GameUtils/Game+View.swift | 27 + .../{ => GameUtils}/MalachiteGameUtils.swift | 9 + .../Utilities/InitUtils/Init+Debug.swift | 24 + .../Utilities/InitUtils/Init+Internal.swift | 29 ++ Malachite/Utilities/InitUtils/Init.swift | 49 ++ .../Utilities/MalachiteFunctionUtils.swift | 46 +- Malachite/Utilities/MalachiteUtils.swift | 6 +- Malachite/Utilities/MalachiteViewUtils.swift | 83 ++- .../MalachitePreferences.swift | 3 + .../MalachitePreferencesUtils.swift | 14 +- .../Preferences+Extension.swift | 22 +- Malachite/Utilities/temputils.swift | 19 + .../Views/AboutView/AboutView+Info.swift | 4 +- .../CameraView/CameraView+Controls.swift | 192 ++++++- .../CameraView/CameraView+Notifications.swift | 45 ++ .../CameraView/CameraView+Overrides.swift | 46 ++ .../Views/CameraView/CameraView+Preview.swift | 22 + Malachite/Views/CameraView/CameraView.swift | 472 +++--------------- .../DeveloperView+Internal.swift | 39 +- .../PhotoPreviewView+Controls.swift | 6 +- 25 files changed, 899 insertions(+), 511 deletions(-) create mode 100644 Malachite/Utilities/CameraUtils/Camera+Bringup.swift create mode 100644 Malachite/Utilities/CameraUtils/Camera.swift create mode 100644 Malachite/Utilities/GameUtils/Game+View.swift rename Malachite/Utilities/{ => GameUtils}/MalachiteGameUtils.swift (94%) create mode 100644 Malachite/Utilities/InitUtils/Init+Debug.swift create mode 100644 Malachite/Utilities/InitUtils/Init+Internal.swift create mode 100644 Malachite/Utilities/InitUtils/Init.swift create mode 100644 Malachite/Utilities/temputils.swift create mode 100644 Malachite/Views/CameraView/CameraView+Notifications.swift create mode 100644 Malachite/Views/CameraView/CameraView+Overrides.swift diff --git a/Malachite.xcodeproj/project.pbxproj b/Malachite.xcodeproj/project.pbxproj index b68e4b0..020b7a2 100755 --- a/Malachite.xcodeproj/project.pbxproj +++ b/Malachite.xcodeproj/project.pbxproj @@ -7,6 +7,15 @@ objects = { /* Begin PBXBuildFile section */ + 78145F782E65135C003AEE51 /* Init.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78145F772E651357003AEE51 /* Init.swift */; }; + 78145F792E65135C003AEE51 /* Init.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78145F772E651357003AEE51 /* Init.swift */; }; + 78145F7A2E65135C003AEE51 /* Init.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78145F772E651357003AEE51 /* Init.swift */; }; + 7824CAED2E62CDDE0072870F /* CameraView+Overrides.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7824CAEC2E62CDD90072870F /* CameraView+Overrides.swift */; }; + 7824CAEE2E62CDDE0072870F /* CameraView+Overrides.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7824CAEC2E62CDD90072870F /* CameraView+Overrides.swift */; }; + 7824CAEF2E62CDDE0072870F /* CameraView+Overrides.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7824CAEC2E62CDD90072870F /* CameraView+Overrides.swift */; }; + 7824CAF12E62E4970072870F /* Camera+Bringup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7824CAF02E62E4920072870F /* Camera+Bringup.swift */; }; + 7824CAF22E62E4970072870F /* Camera+Bringup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7824CAF02E62E4920072870F /* Camera+Bringup.swift */; }; + 7824CAF32E62E4970072870F /* Camera+Bringup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7824CAF02E62E4920072870F /* Camera+Bringup.swift */; }; 7837C10B2E34C45B009396B0 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78917F5D2B99AE72005E10FA /* SwiftUI.framework */; }; 7837C10C2E34C45B009396B0 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78917F5B2B99AE72005E10FA /* WidgetKit.framework */; }; 7837C10E2E34C45B009396B0 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */; }; @@ -17,6 +26,18 @@ 785F084D2B12D41100244EB4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 785F084C2B12D41100244EB4 /* AppDelegate.swift */; }; 785F084F2B12D41100244EB4 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 785F084E2B12D41100244EB4 /* SceneDelegate.swift */; }; 785F08592B12D41300244EB4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 785F08572B12D41300244EB4 /* LaunchScreen.storyboard */; }; + 7866F5CE2E6299A4009AC9BF /* Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7866F5CD2E6299A0009AC9BF /* Camera.swift */; }; + 7866F5CF2E6299A4009AC9BF /* Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7866F5CD2E6299A0009AC9BF /* Camera.swift */; }; + 7866F5D02E6299A4009AC9BF /* Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7866F5CD2E6299A0009AC9BF /* Camera.swift */; }; + 7866F5D22E62A596009AC9BF /* CameraView+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7866F5D12E62A591009AC9BF /* CameraView+Notifications.swift */; }; + 7866F5D32E62A596009AC9BF /* CameraView+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7866F5D12E62A591009AC9BF /* CameraView+Notifications.swift */; }; + 7866F5D42E62A596009AC9BF /* CameraView+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7866F5D12E62A591009AC9BF /* CameraView+Notifications.swift */; }; + 7866F5D62E62A5FA009AC9BF /* temputils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7866F5D52E62A5F7009AC9BF /* temputils.swift */; }; + 7866F5D72E62A5FA009AC9BF /* temputils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7866F5D52E62A5F7009AC9BF /* temputils.swift */; }; + 7866F5D82E62A5FA009AC9BF /* temputils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7866F5D52E62A5F7009AC9BF /* temputils.swift */; }; + 7866F5DB2E62ADB1009AC9BF /* Game+View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7866F5DA2E62ADAB009AC9BF /* Game+View.swift */; }; + 7866F5DC2E62ADB1009AC9BF /* Game+View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7866F5DA2E62ADAB009AC9BF /* Game+View.swift */; }; + 7866F5DD2E62ADB1009AC9BF /* Game+View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7866F5DA2E62ADAB009AC9BF /* Game+View.swift */; }; 786DAE232E4F28A600BE3137 /* DeveloperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE172E4F28A600BE3137 /* DeveloperView.swift */; }; 786DAE242E4F28A600BE3137 /* DeveloperView+BuildInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE182E4F28A600BE3137 /* DeveloperView+BuildInfo.swift */; }; 786DAE252E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE192E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift */; }; @@ -152,6 +173,12 @@ 78AF687E2E5D99EF00BB9D5C /* Preferences+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AF687C2E5D99E300BB9D5C /* Preferences+Extension.swift */; }; 78AF687F2E5D99EF00BB9D5C /* Preferences+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AF687C2E5D99E300BB9D5C /* Preferences+Extension.swift */; }; 78B72BB12E332830002E2D4E /* MalachiteWatch.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 78B72BA72E33282E002E2D4E /* MalachiteWatch.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 78C2EFC02E6971E600C6DD79 /* Init+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78C2EFBF2E6971DE00C6DD79 /* Init+Debug.swift */; }; + 78C2EFC12E6971E600C6DD79 /* Init+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78C2EFBF2E6971DE00C6DD79 /* Init+Debug.swift */; }; + 78C2EFC22E6971E600C6DD79 /* Init+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78C2EFBF2E6971DE00C6DD79 /* Init+Debug.swift */; }; + 78C2EFC42E6972D600C6DD79 /* Init+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78C2EFC32E6972D300C6DD79 /* Init+Internal.swift */; }; + 78C2EFC52E6972D600C6DD79 /* Init+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78C2EFC32E6972D300C6DD79 /* Init+Internal.swift */; }; + 78C2EFC62E6972D600C6DD79 /* Init+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78C2EFC32E6972D300C6DD79 /* Init+Internal.swift */; }; 78CAC36A2CCD99B600A35AE8 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */; }; 78E170772E51C97B009BEF2F /* CompatibilityView+Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E170762E51C971009BEF2F /* CompatibilityView+Camera.swift */; }; 78E170782E51C97B009BEF2F /* CompatibilityView+Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E170762E51C971009BEF2F /* CompatibilityView+Camera.swift */; }; @@ -251,7 +278,10 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 78145F772E651357003AEE51 /* Init.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Init.swift; sourceTree = ""; }; 78163D8D2CCB7BAE00146126 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; + 7824CAEC2E62CDD90072870F /* CameraView+Overrides.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraView+Overrides.swift"; sourceTree = ""; }; + 7824CAF02E62E4920072870F /* Camera+Bringup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Camera+Bringup.swift"; sourceTree = ""; }; 782FC7752B2DAFAB007709C1 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; 7837C1152E34C45B009396B0 /* WidgetBundleWatch.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetBundleWatch.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 78474D2B2D7AC879006FBB96 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = ""; }; @@ -262,6 +292,10 @@ 785F084E2B12D41100244EB4 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 785F08582B12D41300244EB4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 785F085A2B12D41300244EB4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 7866F5CD2E6299A0009AC9BF /* Camera.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Camera.swift; sourceTree = ""; }; + 7866F5D12E62A591009AC9BF /* CameraView+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraView+Notifications.swift"; sourceTree = ""; }; + 7866F5D52E62A5F7009AC9BF /* temputils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = temputils.swift; sourceTree = ""; }; + 7866F5DA2E62ADAB009AC9BF /* Game+View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Game+View.swift"; sourceTree = ""; }; 786DAE172E4F28A600BE3137 /* DeveloperView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperView.swift; sourceTree = ""; }; 786DAE182E4F28A600BE3137 /* DeveloperView+BuildInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DeveloperView+BuildInfo.swift"; sourceTree = ""; }; 786DAE192E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DeveloperView+DeviceInfo.swift"; sourceTree = ""; }; @@ -319,6 +353,8 @@ 78AF686B2E5D97E800BB9D5C /* CameraView+Controls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraView+Controls.swift"; sourceTree = ""; }; 78AF687C2E5D99E300BB9D5C /* Preferences+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Preferences+Extension.swift"; sourceTree = ""; }; 78B72BA72E33282E002E2D4E /* MalachiteWatch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MalachiteWatch.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 78C2EFBF2E6971DE00C6DD79 /* Init+Debug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Init+Debug.swift"; sourceTree = ""; }; + 78C2EFC32E6972D300C6DD79 /* Init+Internal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Init+Internal.swift"; sourceTree = ""; }; 78E142C92DD426260016B3DB /* Codesigning.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Codesigning.xcconfig; sourceTree = ""; }; 78E170762E51C971009BEF2F /* CompatibilityView+Camera.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CompatibilityView+Camera.swift"; sourceTree = ""; }; 78E1707A2E51CC5C009BEF2F /* CompatibilityView+Resolutions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CompatibilityView+Resolutions.swift"; sourceTree = ""; }; @@ -428,6 +464,24 @@ path = Malachite; sourceTree = ""; }; + 7866F5CC2E62999B009AC9BF /* CameraUtils */ = { + isa = PBXGroup; + children = ( + 7866F5CD2E6299A0009AC9BF /* Camera.swift */, + 7824CAF02E62E4920072870F /* Camera+Bringup.swift */, + ); + path = CameraUtils; + sourceTree = ""; + }; + 7866F5D92E62ADA0009AC9BF /* GameUtils */ = { + isa = PBXGroup; + children = ( + 7866F5DA2E62ADAB009AC9BF /* Game+View.swift */, + 786DAE452E4F28B100BE3137 /* MalachiteGameUtils.swift */, + ); + path = GameUtils; + sourceTree = ""; + }; 786DAE1B2E4F28A600BE3137 /* DeveloperView */ = { isa = PBXGroup; children = ( @@ -467,9 +521,12 @@ 786DAE4B2E4F28B100BE3137 /* Utilities */ = { isa = PBXGroup; children = ( + 78C2EFBE2E6971CB00C6DD79 /* InitUtils */, + 7866F5D92E62ADA0009AC9BF /* GameUtils */, + 7866F5D52E62A5F7009AC9BF /* temputils.swift */, + 7866F5CC2E62999B009AC9BF /* CameraUtils */, 786DAE432E4F28B100BE3137 /* PreferenceUtils */, 786DAE442E4F28B100BE3137 /* MalachiteFunctionUtils.swift */, - 786DAE452E4F28B100BE3137 /* MalachiteGameUtils.swift */, 786DAE462E4F28B100BE3137 /* MalachiteHapticUtils.swift */, 786DAE472E4F28B100BE3137 /* MalachiteIntentUtils.swift */, 786DAE482E4F28B100BE3137 /* MalachiteTooltipUtils.swift */, @@ -565,6 +622,8 @@ 786DAE212E4F28A600BE3137 /* CameraView.swift */, 78AF686B2E5D97E800BB9D5C /* CameraView+Controls.swift */, 78AF68672E5D97E000BB9D5C /* CameraView+Preview.swift */, + 7866F5D12E62A591009AC9BF /* CameraView+Notifications.swift */, + 7824CAEC2E62CDD90072870F /* CameraView+Overrides.swift */, ); path = CameraView; sourceTree = ""; @@ -578,6 +637,16 @@ path = PhotoPreviewView; sourceTree = ""; }; + 78C2EFBE2E6971CB00C6DD79 /* InitUtils */ = { + isa = PBXGroup; + children = ( + 78145F772E651357003AEE51 /* Init.swift */, + 78C2EFBF2E6971DE00C6DD79 /* Init+Debug.swift */, + 78C2EFC32E6972D300C6DD79 /* Init+Internal.swift */, + ); + path = InitUtils; + sourceTree = ""; + }; 78E170752E51C312009BEF2F /* CompatibilityView */ = { isa = PBXGroup; children = ( @@ -856,6 +925,7 @@ files = ( 788262BB2E5170F4000085AC /* AboutView+Info.swift in Sources */, 78E1707D2E51CC63009BEF2F /* CompatibilityView+Resolutions.swift in Sources */, + 7866F5DC2E62ADB1009AC9BF /* Game+View.swift in Sources */, 788262DA2E5171F0000085AC /* AboutView+Story.swift in Sources */, 78AF686C2E5D97EC00BB9D5C /* CameraView+Controls.swift in Sources */, 786DAE552E4F28B100BE3137 /* MalachitePreferences.swift in Sources */, @@ -872,16 +942,22 @@ 788262622E513080000085AC /* SettingsView+About.swift in Sources */, 786DAE5B2E4F28B100BE3137 /* MalachiteTooltipUtils.swift in Sources */, 786DAE5C2E4F28B100BE3137 /* MalachiteUtils.swift in Sources */, + 7866F5D22E62A596009AC9BF /* CameraView+Notifications.swift in Sources */, 786DAE5D2E4F28B100BE3137 /* MalachiteViewUtils.swift in Sources */, + 78C2EFC62E6972D600C6DD79 /* Init+Internal.swift in Sources */, 788262722E5130CA000085AC /* SettingsView+Photo.swift in Sources */, 786DAE2D2E4F28A600BE3137 /* DeveloperView.swift in Sources */, + 78145F7A2E65135C003AEE51 /* Init.swift in Sources */, 786DAE2E2E4F28A600BE3137 /* DeveloperView+BuildInfo.swift in Sources */, + 7866F5D72E62A5FA009AC9BF /* temputils.swift in Sources */, 7882627A2E5130DB000085AC /* SettingsView+UI.swift in Sources */, 786DAE2F2E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift in Sources */, 786DAE302E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */, + 7824CAED2E62CDDE0072870F /* CameraView+Overrides.swift in Sources */, 78AF687E2E5D99EF00BB9D5C /* Preferences+Extension.swift in Sources */, 786DAE312E4F28A600BE3137 /* AboutView.swift in Sources */, 786DAE322E4F28A600BE3137 /* CompatibilityView.swift in Sources */, + 78C2EFC22E6971E600C6DD79 /* Init+Debug.swift in Sources */, 7882627E2E514750000085AC /* DeveloperView+Internal.swift in Sources */, 786DAE332E4F28A600BE3137 /* PhotoPreviewView.swift in Sources */, 786DAE342E4F28A600BE3137 /* QuickHelpView.swift in Sources */, @@ -896,8 +972,10 @@ 785F084D2B12D41100244EB4 /* AppDelegate.swift in Sources */, 788262962E514F38000085AC /* QuickHelpView+Watermarking.swift in Sources */, 787B1CAA2B8B095E000AFECC /* Malachite.docc in Sources */, + 7824CAF22E62E4970072870F /* Camera+Bringup.swift in Sources */, 788262662E51309C000085AC /* SettingsView+Preview.swift in Sources */, 788262E02E517252000085AC /* AboutView+Eggs.swift in Sources */, + 7866F5CE2E6299A4009AC9BF /* Camera.swift in Sources */, 78E1707F2E51CE45009BEF2F /* CompatibilityView+Format.swift in Sources */, 785F084F2B12D41100244EB4 /* SceneDelegate.swift in Sources */, ); @@ -909,6 +987,7 @@ files = ( 788262BC2E5170F4000085AC /* AboutView+Info.swift in Sources */, 78E1707B2E51CC63009BEF2F /* CompatibilityView+Resolutions.swift in Sources */, + 7866F5DD2E62ADB1009AC9BF /* Game+View.swift in Sources */, 788262DB2E5171F0000085AC /* AboutView+Story.swift in Sources */, 78AF686E2E5D97EC00BB9D5C /* CameraView+Controls.swift in Sources */, 786DAE5E2E4F28B100BE3137 /* MalachitePreferences.swift in Sources */, @@ -925,16 +1004,22 @@ 788262612E513080000085AC /* SettingsView+About.swift in Sources */, 786DAE642E4F28B100BE3137 /* MalachiteTooltipUtils.swift in Sources */, 786DAE652E4F28B100BE3137 /* MalachiteUtils.swift in Sources */, + 7866F5D32E62A596009AC9BF /* CameraView+Notifications.swift in Sources */, 786DAE662E4F28B100BE3137 /* MalachiteViewUtils.swift in Sources */, + 78C2EFC52E6972D600C6DD79 /* Init+Internal.swift in Sources */, 788262702E5130CA000085AC /* SettingsView+Photo.swift in Sources */, 786DAE372E4F28A600BE3137 /* DeveloperView.swift in Sources */, + 78145F792E65135C003AEE51 /* Init.swift in Sources */, 786DAE382E4F28A600BE3137 /* DeveloperView+BuildInfo.swift in Sources */, + 7866F5D82E62A5FA009AC9BF /* temputils.swift in Sources */, 788262792E5130DB000085AC /* SettingsView+UI.swift in Sources */, 786DAE392E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift in Sources */, 786DAE6D2E4F28B700BE3137 /* ControlCenterWidget.swift in Sources */, + 7824CAEE2E62CDDE0072870F /* CameraView+Overrides.swift in Sources */, 78AF687D2E5D99EF00BB9D5C /* Preferences+Extension.swift in Sources */, 786DAE6E2E4F28B700BE3137 /* LockScreenWidget.swift in Sources */, 786DAE6F2E4F28B700BE3137 /* MalachiteWidgetBundle.swift in Sources */, + 78C2EFC02E6971E600C6DD79 /* Init+Debug.swift in Sources */, 7882627D2E514750000085AC /* DeveloperView+Internal.swift in Sources */, 786DAE3A2E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */, 786DAE3B2E4F28A600BE3137 /* AboutView.swift in Sources */, @@ -949,8 +1034,10 @@ 786DAE3E2E4F28A600BE3137 /* QuickHelpView.swift in Sources */, 788262952E514F38000085AC /* QuickHelpView+Watermarking.swift in Sources */, 786DAE3F2E4F28A600BE3137 /* SettingsView.swift in Sources */, + 7824CAF32E62E4970072870F /* Camera+Bringup.swift in Sources */, 788262652E51309C000085AC /* SettingsView+Preview.swift in Sources */, 788262DE2E517252000085AC /* AboutView+Eggs.swift in Sources */, + 7866F5CF2E6299A4009AC9BF /* Camera.swift in Sources */, 78E170812E51CE45009BEF2F /* CompatibilityView+Format.swift in Sources */, 786DAE402E4F28A600BE3137 /* CameraView.swift in Sources */, ); @@ -962,23 +1049,28 @@ files = ( 786DAE232E4F28A600BE3137 /* DeveloperView.swift in Sources */, 788262782E5130DB000085AC /* SettingsView+UI.swift in Sources */, + 7866F5D02E6299A4009AC9BF /* Camera.swift in Sources */, 786DAE242E4F28A600BE3137 /* DeveloperView+BuildInfo.swift in Sources */, 786DAE252E4F28A600BE3137 /* DeveloperView+DeviceInfo.swift in Sources */, 78E170782E51C97B009BEF2F /* CompatibilityView+Camera.swift in Sources */, 788262602E513080000085AC /* SettingsView+About.swift in Sources */, 786DAE4C2E4F28B100BE3137 /* MalachitePreferences.swift in Sources */, + 78C2EFC42E6972D600C6DD79 /* Init+Internal.swift in Sources */, 788262DF2E517252000085AC /* AboutView+Eggs.swift in Sources */, 788262972E514F38000085AC /* QuickHelpView+Watermarking.swift in Sources */, 788262752E5130D4000085AC /* SettingsView+Watermarking.swift in Sources */, 7882627C2E514750000085AC /* DeveloperView+Internal.swift in Sources */, + 78145F782E65135C003AEE51 /* Init.swift in Sources */, 786DAE4D2E4F28B100BE3137 /* MalachitePreferencesUtils.swift in Sources */, 788262DC2E5171F0000085AC /* AboutView+Story.swift in Sources */, 78AF686D2E5D97EC00BB9D5C /* CameraView+Controls.swift in Sources */, 788262642E51309C000085AC /* SettingsView+Preview.swift in Sources */, 786DAE782E4F28B900BE3137 /* MalachiteCaptureBundle.swift in Sources */, 786DAE4E2E4F28B100BE3137 /* MalachiteFunctionUtils.swift in Sources */, + 7824CAF12E62E4970072870F /* Camera+Bringup.swift in Sources */, 7882629B2E514F3F000085AC /* QuickHelpView+UI.swift in Sources */, 786DAE4F2E4F28B100BE3137 /* MalachiteGameUtils.swift in Sources */, + 7824CAEF2E62CDDE0072870F /* CameraView+Overrides.swift in Sources */, 7882626C2E5130C1000085AC /* SettingsView+Resolution.swift in Sources */, 78AF686A2E5D97E500BB9D5C /* CameraView+Preview.swift in Sources */, 786DAE502E4F28B100BE3137 /* MalachiteHapticUtils.swift in Sources */, @@ -987,7 +1079,9 @@ 786DAE522E4F28B100BE3137 /* MalachiteTooltipUtils.swift in Sources */, 786DAE532E4F28B100BE3137 /* MalachiteUtils.swift in Sources */, 788262BA2E5170F4000085AC /* AboutView+Info.swift in Sources */, + 7866F5D42E62A596009AC9BF /* CameraView+Notifications.swift in Sources */, 786DAE542E4F28B100BE3137 /* MalachiteViewUtils.swift in Sources */, + 78C2EFC12E6971E600C6DD79 /* Init+Debug.swift in Sources */, 788262872E514F12000085AC /* QuickHelpView+About.swift in Sources */, 786DAE262E4F28A600BE3137 /* DeveloperView+Settings.swift in Sources */, 784EDDD52E5EE52600DFF370 /* PhotoPreviewView+Controls.swift in Sources */, @@ -995,10 +1089,12 @@ 786DAE272E4F28A600BE3137 /* AboutView.swift in Sources */, 78E1707C2E51CC63009BEF2F /* CompatibilityView+Resolutions.swift in Sources */, 786DAE282E4F28A600BE3137 /* CompatibilityView.swift in Sources */, + 7866F5D62E62A5FA009AC9BF /* temputils.swift in Sources */, 78AF687F2E5D99EF00BB9D5C /* Preferences+Extension.swift in Sources */, 786DAE292E4F28A600BE3137 /* PhotoPreviewView.swift in Sources */, 788262712E5130CA000085AC /* SettingsView+Photo.swift in Sources */, 78E170802E51CE45009BEF2F /* CompatibilityView+Format.swift in Sources */, + 7866F5DB2E62ADB1009AC9BF /* Game+View.swift in Sources */, 7882628D2E514F25000085AC /* QuickHelpView+Resolution.swift in Sources */, 786DAE2A2E4F28A600BE3137 /* QuickHelpView.swift in Sources */, 7882628B2E514F18000085AC /* QuickHelpView+Preview.swift in Sources */, diff --git a/Malachite/Localizable.xcstrings b/Malachite/Localizable.xcstrings index 3f2d439..6fa6534 100755 --- a/Malachite/Localizable.xcstrings +++ b/Malachite/Localizable.xcstrings @@ -164,7 +164,7 @@ } }, "alert.button.ok" : { - "comment" : "Default action", + "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { @@ -186,7 +186,7 @@ } }, "alert.button.report" : { - "comment" : "Default action", + "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { @@ -974,6 +974,66 @@ } } }, + "internal.option.blockaccidentalgestures" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Block accidental gestures" + } + } + } + }, + "internal.option.cameracontrol" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enable Camera Control overlay" + } + } + } + }, + "internal.option.settingsgesture" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Swipe up for Settings" + } + } + } + }, + "internal.option.settingsgesture.1" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "1 finger" + } + } + } + }, + "internal.option.settingsgesture.2" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "2 fingers" + } + } + } + }, + "internal.option.settingsgesture.3" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "3 fingers" + } + } + } + }, "settings.detail.photo.continuous" : { "localizations" : { "en" : { @@ -1547,46 +1607,6 @@ } } }, - "settings.option.ui.settingsgesture" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Swipe up for Settings" - } - } - } - }, - "settings.option.ui.settingsgesture.1" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "1 finger" - } - } - } - }, - "settings.option.ui.settingsgesture.2" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "2 fingers" - } - } - } - }, - "settings.option.ui.settingsgesture.3" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "3 fingers" - } - } - } - }, "settings.option.ui.tapgesture" : { "localizations" : { "en" : { diff --git a/Malachite/SceneDelegate.swift b/Malachite/SceneDelegate.swift index 2a37978..7fb3e68 100755 --- a/Malachite/SceneDelegate.swift +++ b/Malachite/SceneDelegate.swift @@ -11,8 +11,15 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } + let utilities = MalachiteClassesObject() + let initialization = Init(utilities: utilities) + let rootVC = CameraView() + + initialization.initMalachite() + rootVC.utilities = utilities + window = UIWindow(windowScene: windowScene) - window?.rootViewController = CameraView() + window?.rootViewController = rootVC window?.makeKeyAndVisible() } } diff --git a/Malachite/Utilities/CameraUtils/Camera+Bringup.swift b/Malachite/Utilities/CameraUtils/Camera+Bringup.swift new file mode 100644 index 0000000..39e3db0 --- /dev/null +++ b/Malachite/Utilities/CameraUtils/Camera+Bringup.swift @@ -0,0 +1,30 @@ +// +// Camera+Bringup.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/30/25. +// + +import AVFoundation +import Foundation + +extension Camera { + class Bringup { + private var utilities: MalachiteClassesObject + + init( utilities: MalachiteClassesObject ) { self.utilities = utilities } + + func checkForHEICCompatibility() { + if !utilities.function.supportsHEIC() { + utilities.debugNSLog("[Initialization] HEIF enabled on a device that doesn't support it, disabling") + utilities.preferences.capture.format.heic = false + utilities.preferences.capture.format.jpeg = true + } + } + + func createAVCaptureSession(session: AVCaptureSession?) -> AVCaptureSession { + guard let session = session else { return AVCaptureSession() } + return session + } + } +} diff --git a/Malachite/Utilities/CameraUtils/Camera.swift b/Malachite/Utilities/CameraUtils/Camera.swift new file mode 100644 index 0000000..d35b462 --- /dev/null +++ b/Malachite/Utilities/CameraUtils/Camera.swift @@ -0,0 +1,12 @@ +// +// Camera.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/29/25. +// + +import Foundation + +class Camera { + +} diff --git a/Malachite/Utilities/GameUtils/Game+View.swift b/Malachite/Utilities/GameUtils/Game+View.swift new file mode 100644 index 0000000..89f096b --- /dev/null +++ b/Malachite/Utilities/GameUtils/Game+View.swift @@ -0,0 +1,27 @@ +// +// Game+View.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/29/25. +// + +import Foundation +import UIKit + +extension MalachiteGameUtils { + func setupGameKitAlert() -> UIAlertController { + let alert = UIAlertController(title: "alert.title.gamekit".localized, message: "alert.detail.gamekit".localized, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "alert.button.reopen".localized, style: .default, handler: { _ in exit(11) })) + alert.addAction(UIAlertAction(title: "alert.button.report".localized, style: .default, handler: { _ in + guard let url = URL(string: "https://www.youtube.com/watch?v=At8v_Yc044Y") else { return } +#if MAIN_APP + UIApplication.shared.open(url, options: [:], completionHandler: nil) +#endif + })) + alert.addAction(UIAlertAction(title: "alert.button.ignore".localized, style: .default, handler: { _ in + MalachitePreferencesUtils.shared.preferences.general.gamekit.alerted = false + })) + + return alert + } +} diff --git a/Malachite/Utilities/MalachiteGameUtils.swift b/Malachite/Utilities/GameUtils/MalachiteGameUtils.swift similarity index 94% rename from Malachite/Utilities/MalachiteGameUtils.swift rename to Malachite/Utilities/GameUtils/MalachiteGameUtils.swift index f29f573..6b181bc 100755 --- a/Malachite/Utilities/MalachiteGameUtils.swift +++ b/Malachite/Utilities/GameUtils/MalachiteGameUtils.swift @@ -41,6 +41,15 @@ public class MalachiteGameUtils : NSObject, GKGameCenterControllerDelegate { leaderboards.loadLeaderboards() } } + + /// Function to change the GameKit enabled state. + @objc func changeGameCenterEnabled() { + DispatchQueue.global(qos: .background).async { [self] in + if MalachitePreferencesUtils.shared.preferences.general.gamekit.enabled { + setupGameCenter() + } + } + } } public class MalachiteGameAchievementUtils : NSObject { diff --git a/Malachite/Utilities/InitUtils/Init+Debug.swift b/Malachite/Utilities/InitUtils/Init+Debug.swift new file mode 100644 index 0000000..e36f1c3 --- /dev/null +++ b/Malachite/Utilities/InitUtils/Init+Debug.swift @@ -0,0 +1,24 @@ +// +// Init+Debug.swift +// Malachite +// +// Created by Eva Isabella Luna on 9/4/25. +// + +extension Init { + class Debug { + private var utilities: MalachiteClassesObject + + init( utilities: MalachiteClassesObject ) { self.utilities = utilities } + + /// Prints the contents of preferences.plist at app launch. + public func printPreferences() { + if utilities.preferences.debug.logging.preferences { MalachitePreferencesUtils().printPreferences() } + } + + /// Runs all of the initialization functions defined in this class. + public func initMalachite() { + printPreferences() + } + } +} diff --git a/Malachite/Utilities/InitUtils/Init+Internal.swift b/Malachite/Utilities/InitUtils/Init+Internal.swift new file mode 100644 index 0000000..367a289 --- /dev/null +++ b/Malachite/Utilities/InitUtils/Init+Internal.swift @@ -0,0 +1,29 @@ +// +// Init+Internal.swift +// Malachite +// +// Created by Eva Isabella Luna on 9/4/25. +// + +extension Init { + class Internal { + private var utilities: MalachiteClassesObject + + init( utilities: MalachiteClassesObject ) { self.utilities = utilities } + + /// Checks whether or not the current device is the same device as previously recorded in preferences. + public func deviceCheck() { + if !utilities.preferences.ext.deviceModel.isSameDevice(in: utilities.preferences) { + utilities.internalNSLog("[Initialization] This is a new device, rechecking compatibility.") + utilities.preferences.general.deviceModel = utilities.preferences.ext.deviceModel.get() + } else { + utilities.internalNSLog("[Initialization] This is the same device, can skip compatibility checks.") + } + } + + /// Runs all of the initialization functions defined in this class. + public func initMalachite() { + deviceCheck() + } + } +} diff --git a/Malachite/Utilities/InitUtils/Init.swift b/Malachite/Utilities/InitUtils/Init.swift new file mode 100644 index 0000000..a26436b --- /dev/null +++ b/Malachite/Utilities/InitUtils/Init.swift @@ -0,0 +1,49 @@ +// +// Init.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/31/25. +// + +import Foundation + +class Init { + private var utilities: MalachiteClassesObject + private var debug: Debug + private var intrnl: Internal + + init( utilities: MalachiteClassesObject ) { self.utilities = utilities; self.debug = Debug(utilities: self.utilities); self.intrnl = Internal(utilities: self.utilities) } + + /// Prints a message about Malachite starting. + public func startupLog() { utilities.debugNSLog("[Initialization] Starting up Malachite") } + + /// Prints a message about what build type the current installation of Malachite was compiled with. + public func versionTypeCheck() { + if utilities.versionType == "INTERNAL" { + utilities.internalNSLog("[Initialization] Running an INTERNAL build") + } else if utilities.versionType == "DEBUG" { + utilities.debugNSLog("[Initialization] Running a DEBUG build") + } else if utilities.versionType == "RELEASE" { + utilities.NSLog("[Initialization] Running a RELEASE build") + } + } + + /// Checks whether or not Malachite is initializing from the main app or an App Extension. + public func appExtensionCheck() { +#if APP_EXTENSION + utilities.debugNSLog("[Initialization] Running out of an app extension.") +#elseif MAIN_APP + utilities.debugNSLog("[Initialization] Running out of the main app.") +#endif + } + + /// Runs all of the initialization functions defined in this class. + public func initMalachite() { + startupLog() + versionTypeCheck() + appExtensionCheck() + + if utilities.versionType == "DEBUG" || utilities.versionType == "INTERNAL" { debug.initMalachite() } + if utilities.versionType == "INTERNAL" { intrnl.initMalachite() } + } +} diff --git a/Malachite/Utilities/MalachiteFunctionUtils.swift b/Malachite/Utilities/MalachiteFunctionUtils.swift index 31d83b2..11422d3 100755 --- a/Malachite/Utilities/MalachiteFunctionUtils.swift +++ b/Malachite/Utilities/MalachiteFunctionUtils.swift @@ -50,6 +50,13 @@ public class MalachiteFunctionUtils : NSObject { return false } + /// Function to enable or disable the idle timer. + @objc func changeIdleTimerState() { + #if MAIN_APP + UIApplication.shared.isIdleTimerDisabled = MalachitePreferencesUtils.shared.preferences.userInterface.idleTimerDisabled + #endif + } + /// Function that handles pinch to zoom. public func zoom(sender pinch: UIPinchGestureRecognizer, floater float: inout CGFloat, captureDevice device: inout AVCaptureDevice, lastZoomFactor zoomFactor: inout CGFloat, hapticClass haptic: MalachiteHapticUtils) { func minMaxZoom(_ factor: CGFloat) -> CGFloat { @@ -240,9 +247,11 @@ public class MalachiteFunctionUtils : NSObject { MalachiteClassesObject().debugNSLog("[Camera Input] Removing currently active camera input") session.removeInput(input!) } else { - if !cameras.isEmpty { device = cameras.first } + if !cameras.isEmpty { device = cameras.first! } } + guard let device = device else { return } + if firstRun { for camera in cameras { var tmpDictionary = Dictionary() @@ -273,28 +282,28 @@ public class MalachiteFunctionUtils : NSObject { firstRun = false - deviceFormatSupportsHDR(device: device!) + deviceFormatSupportsHDR(device: device) do { - try device?.lockForConfiguration() - defer { device?.unlockForConfiguration() } - MalachiteClassesObject().debugNSLog("[Camera Input] Selected input: \(String(describing: device?.formats[(device?.formats.count)! - 1]))") - device?.activeFormat = (device?.formats[(device?.formats.count)! - 1])! - continuousAEAF(device: device!) + try device.lockForConfiguration() + defer { device.unlockForConfiguration() } + MalachiteClassesObject().debugNSLog("[Camera Input] Selected input: \(String(describing: device.formats[(device.formats.count) - 1]))") + device.activeFormat = (device.formats[(device.formats.count) - 1]) + continuousAEAF(device: device) - guard let focus = device?.isLockingFocusWithCustomLensPositionSupported else { return } + let focus = device.isLockingFocusWithCustomLensPositionSupported if !focus { NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.unsupportedLensPositionNotification.name, object: nil) } - guard let exposure = device?.isExposureModeSupported(.custom) else { return } + let exposure = device.isExposureModeSupported(.custom) if !exposure { NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.unsupportedISOValueNotification.name, object: nil) } - device?.automaticallyAdjustsVideoHDREnabled = false + device.automaticallyAdjustsVideoHDREnabled = false if MalachiteClassesObject().preferences.capture.hdr { if self.supportsHDR { MalachiteClassesObject().debugNSLog("[Camera Input] Force enabled HDR on camera") - if device?.activeFormat.isVideoHDRSupported == true { - device?.isVideoHDREnabled = true + if device.activeFormat.isVideoHDRSupported == true { + device.isVideoHDREnabled = true } else { MalachiteClassesObject().debugNSLog("[Camera Input] Current capture mode doesn't support HDR, it needs to be disabled") MalachiteClassesObject().preferences.capture.hdr = false @@ -305,11 +314,11 @@ public class MalachiteFunctionUtils : NSObject { } } else { MalachiteClassesObject().debugNSLog("[Camera Input] Force disabled HDR on camera") - if device?.activeFormat.isGlobalToneMappingSupported == true { - device?.isGlobalToneMappingEnabled = false + if device.activeFormat.isGlobalToneMappingSupported == true { + device.isGlobalToneMappingEnabled = false } - if device?.activeFormat.isVideoHDRSupported == true { - device?.isVideoHDREnabled = false + if device.activeFormat.isVideoHDRSupported == true { + device.isVideoHDREnabled = false } } } catch { @@ -318,14 +327,14 @@ public class MalachiteFunctionUtils : NSObject { MalachiteClassesObject().debugNSLog("[Camera Input] Attempting to attach device input to session") - do { input = try AVCaptureDeviceInput(device: device!) } + do { input = try AVCaptureDeviceInput(device: device) } catch { print(error) } MalachiteClassesObject().debugNSLog("[Camera Input] Attached input, finishing configuration") if session.canAddInput(input!) { session.addInput(input!) } - switchInputMegapixels(device: device!, photoOutput: output) + switchInputMegapixels(device: device, photoOutput: output) } @available(iOS 18.0, *) @@ -382,6 +391,7 @@ public class MalachiteFunctionUtils : NSObject { /// Function that handles taking images on `AVCapturePhotoOutput`. public func captureImage(output photoOutput: AVCapturePhotoOutput, viewForBounds view: UIView, captureDelegate delegate: AVCapturePhotoCaptureDelegate) -> AVCapturePhotoOutput { + if photoOutput.connections.count < 1 { return photoOutput } var format = [String: Any]() if MalachiteClassesObject().preferences.compatibility.heic && supportsHEIC() { format = [AVVideoCodecKey : AVVideoCodecType.hevc] diff --git a/Malachite/Utilities/MalachiteUtils.swift b/Malachite/Utilities/MalachiteUtils.swift index 172eafe..534ead0 100755 --- a/Malachite/Utilities/MalachiteUtils.swift +++ b/Malachite/Utilities/MalachiteUtils.swift @@ -29,7 +29,7 @@ public class MalachiteClassesObject : NSObject { public let games = MalachiteGameUtils() /// Private static storage for the session queue. - @available(iOS 17.0, *) + @available(iOS 18.0, *) private static var _sessionQueue: DispatchSerialQueue = DispatchSerialQueue(label: "dev.crystll1ne.Malachite.controlsSessionQueue") /// A session queue used to absorb Camera Control states. @available(iOS 18.0, *) @@ -61,9 +61,7 @@ public class MalachiteClassesObject : NSObject { /// A function to only log in INTERNAL builds public func internalNSLog(_ format: String, file: String = #file, line: Int = #line, function: String = #function) { - if self.versionType == "INTERNAL" { - Foundation.NSLog("[\(file):\(line)] [\(function)] \(format)") - } + if self.versionType == "INTERNAL" { Foundation.NSLog("[\(file):\(line)] [\(function)] \(format)") } } /// A function that calls ``debugNSLog`` on DEBUG and ``internalNSLog`` on INTERNAL. diff --git a/Malachite/Utilities/MalachiteViewUtils.swift b/Malachite/Utilities/MalachiteViewUtils.swift index b1a105e..2f1bf4e 100755 --- a/Malachite/Utilities/MalachiteViewUtils.swift +++ b/Malachite/Utilities/MalachiteViewUtils.swift @@ -9,6 +9,7 @@ import Foundation import Photos import UIKit import SwiftUI +import ObjectiveC.runtime public class MalachiteViewUtils : NSObject { /// Function that returns a buttons for the user interface. @@ -18,7 +19,7 @@ public class MalachiteViewUtils : NSObject { button.setImage(buttonImage, for: .normal) button.translatesAutoresizingMaskIntoConstraints = false button.layer.masksToBounds = true - button.layer.cornerRadius = dimensions[0] + button.layer.cornerRadius = (dimensions.count > 2) ? dimensions[2] : (dimensions.count > 1) ? dimensions[1] / 2 : dimensions[0] / 2 button.bringSubviewToFront(button.imageView!) button.imageView?.clipsToBounds = false button.imageView?.contentMode = .center @@ -44,8 +45,8 @@ public class MalachiteViewUtils : NSObject { view.addSubview(button) var constraintsToAdd: [NSLayoutConstraint] = [ - button.widthAnchor.constraint(equalToConstant: dimensions[1]), - button.heightAnchor.constraint(equalToConstant: (dimensions.count > 2) ? dimensions[2] : dimensions[1]) + button.widthAnchor.constraint(equalToConstant: dimensions[0]), + button.heightAnchor.constraint(equalToConstant: (dimensions.count > 1) ? dimensions[1] : dimensions[0]) ] if let LXA = constraints.LXA, let LXC = constraints.LXC, let LXP = constraints.LXP { @@ -84,6 +85,16 @@ public class MalachiteViewUtils : NSObject { return slider } + /// Function that returns an alert view for the user interface, with only an option to dismiss. + public func createAlertController(title: String, message: String, button: UIButton, defaultSet: Bool, action: ((UIAlertAction) -> Void)?) -> UIAlertController { + let alert = UIAlertController(title: title.localized, message: message.localized, preferredStyle: .actionSheet) + alert.popoverPresentationController?.sourceView = button + if #available(iOS 26.0, *) { alert.preferredTransition = .zoom { _ in button } } + if defaultSet { if let action = action { alert.addAction(UIAlertAction(title: "alert.button.ok".localized, style: .default, handler: action)) } } + + return alert + } + /// Function that returns blurs for the user interface. public func returnProperEffectView(viewForBounds view: UIView, effect: UIVisualEffect) -> UIVisualEffectView { var effectView = UIVisualEffectView() @@ -191,11 +202,65 @@ public class MalachiteViewUtils : NSObject { return UIImage(data: data!)! } + func setupLmaoView(view: UIView) { + let lmaoView = UIImageView(image: returnImageForSimulator()) + view.addSubview(lmaoView) + + NSLayoutConstraint.activate([ + lmaoView.widthAnchor.constraint(equalToConstant: 60), + lmaoView.heightAnchor.constraint(equalToConstant: 60), + lmaoView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -80), + lmaoView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10), + ]) + } + + func hideUI(view: UIView, blacklisted: [ AnyObject ], conditionals: [ UIView : Bool ], gestureRecognizers: [ UIGestureRecognizer ]) { + DispatchQueue.main.async { + UIView.animate(withDuration: 0.25) { + for subview in view.subviews { + if !blacklisted.contains(where: { $0 === subview }) { + if let subviewConditional = conditionals[subview] { if subviewConditional { subview.alpha = 0.0 } } + else { subview.alpha = 0.0 } + } + } + + for recognizer in gestureRecognizers { + if !blacklisted.contains(where: { $0 === recognizer }) { + if !MalachitePreferencesUtils.shared.preferences.userInterface.hiddenControls.contains(recognizer.name) { + //recognizer.isEnabled = false + } + } + } + } + } + } + + func showUI(view: UIView, blacklisted: [ AnyObject ], conditionals: [ UIView : Bool ], gestureRecognizers: [ UIGestureRecognizer ]) { + DispatchQueue.main.async { + UIView.animate(withDuration: 0.25) { + for subview in view.subviews { + if !blacklisted.contains(where: { $0 === subview }) { + if let subviewConditional = conditionals[subview] { if subviewConditional { subview.alpha = 1.0 } } + else { subview.alpha = 1.0 } + } + } + + for recognizer in gestureRecognizers { + if !blacklisted.contains(where: { $0 === recognizer }) { + //recognizer.isEnabled = true + } + } + } + } + } + public struct buttonBuilder { let symbolName: String + #warning("allow passing nil here, cba to fix at the moment") let action: Selector let dimensions: [ CGFloat ] let constraints: constraints + let hidden: Bool let assign: (UIButton) -> Void public struct constraints { @@ -226,6 +291,18 @@ public class MalachiteViewUtils : NSObject { } } +private var UIGestureRecognizerNameKey: UInt8 = 0 + +extension UIGestureRecognizer { + var name: String { + get { + if let value = objc_getAssociatedObject(self, &UIGestureRecognizerNameKey) as? String { return value } + return String(describing: type(of: self)) + } + set { objc_setAssociatedObject(self, &UIGestureRecognizerNameKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC) } + } +} + /// An extension for `UIDeviceOrientation` that properly orients the `cameraSession` extension UIDeviceOrientation { var videoOrientation: AVCaptureVideoOrientation { diff --git a/Malachite/Utilities/PreferenceUtils/MalachitePreferences.swift b/Malachite/Utilities/PreferenceUtils/MalachitePreferences.swift index 527ca95..ff92ba3 100755 --- a/Malachite/Utilities/PreferenceUtils/MalachitePreferences.swift +++ b/Malachite/Utilities/PreferenceUtils/MalachitePreferences.swift @@ -110,6 +110,9 @@ struct MalachitePreferences: Codable { var evaintrnl: evaintrnlPreferences struct evaintrnlPreferences: Codable { + var blockAccidentalGestures: Bool var settingsGesture: Int + var cameraControlEnabled: Bool + var cameraControlOptions: [ String ] } } diff --git a/Malachite/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift b/Malachite/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift index 1aaffff..c6e04df 100755 --- a/Malachite/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift +++ b/Malachite/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift @@ -113,7 +113,7 @@ class MalachitePreferencesUtils { if let previewPreferences = oldPreferences["preview"] as? [ String: AnyObject ] { currentPreferences.preview.aspect = previewPreferences["aspect"] as? Bool ?? false currentPreferences.preview.stablize = previewPreferences["stabilize"] as? Bool ?? false - currentPreferences.preview.fastPath = true + currentPreferences.preview.fastPath = previewPreferences["fastPath"] as? Bool ?? true } if let capturePreferences = oldPreferences["capture"] as? [ String: AnyObject ] { @@ -146,12 +146,15 @@ class MalachitePreferencesUtils { if let debugPreferences = oldPreferences["debug"] as? [ String: AnyObject ] { currentPreferences.debug.logging.preferences = debugPreferences["logging"]?["preferences"] as? Bool ?? false - currentPreferences.debug.breakApp = false - currentPreferences.debug.logging.unified = true + currentPreferences.debug.logging.unified = debugPreferences["logging"]?["unified"] as? Bool ?? true + currentPreferences.debug.breakApp = debugPreferences["breakApp"] as? Bool ?? false } if let evaintrnlPreferences = oldPreferences["evaintrnl"] as? [ String: AnyObject ] { + currentPreferences.evaintrnl.blockAccidentalGestures = evaintrnlPreferences["blockAccidentalGestures"] as? Bool ?? true currentPreferences.evaintrnl.settingsGesture = evaintrnlPreferences["settingsGesture"] as? Int ?? 2 + currentPreferences.evaintrnl.cameraControlEnabled = evaintrnlPreferences["cameraControlEnabled"] as? Bool ?? true + currentPreferences.evaintrnl.cameraControlOptions = evaintrnlPreferences["cameraControlOptions"] as? [ String ] ?? [ "zoom", "focus", "cameras", "flash", "flashLevel", "exposureBias" ] } return currentPreferences @@ -222,7 +225,10 @@ class MalachitePreferencesUtils { breakApp: false ), evaintrnl: MalachitePreferences.evaintrnlPreferences( - settingsGesture: 2 + blockAccidentalGestures: true, + settingsGesture: 2, + cameraControlEnabled: true, + cameraControlOptions: [ "zoom", "focus", "cameras", "flash", "flashLevel", "exposureBias" ] ) ) } diff --git a/Malachite/Utilities/PreferenceUtils/Preferences+Extension.swift b/Malachite/Utilities/PreferenceUtils/Preferences+Extension.swift index cb3f0c4..3a7f9a0 100644 --- a/Malachite/Utilities/PreferenceUtils/Preferences+Extension.swift +++ b/Malachite/Utilities/PreferenceUtils/Preferences+Extension.swift @@ -8,20 +8,26 @@ import Foundation extension MalachitePreferences { - var ext: Utils { return Utils() } class Utils { var gameKitButton = 0 /// Shows the GameKit enable switch in About settings. - public func showGameKitOptionInAbout(in preferences: inout MalachitePreferences) -> Void { + public func showGameKitOptionInAbout(in preferences: inout MalachitePreferences, clicks: inout Int) -> Void { MalachiteClassesObject().debugNSLog("04F807A163D50211A2456C3460EACFACCBC5BF436AFC268F0DBAA529") - if gameKitButton < 7 { - gameKitButton += 1 - } else { + if clicks < 7 { + clicks += 1 + } else if clicks == 7 { preferences.general.gamekit.alerted = true - exit(11) + DispatchQueue.global(qos: .background).async { + for i in (1...10).reversed() { + MalachiteClassesObject().debugNSLog("Bomb planted, exploding in \(i) seconds...") + sleep(1) + } + exit(SIGSEGV) + } } } + var dictionary: ELDictionary { return ELDictionary() } class ELDictionary { public func isValid(dictionary: Dictionary) -> Bool { @@ -32,6 +38,7 @@ extension MalachitePreferences { return dictionary.count } } + var deviceModel: DeviceModel { return DeviceModel() } class DeviceModel { public func get() -> String { @@ -46,7 +53,7 @@ extension MalachitePreferences { return identifier } - public func isSameDevice(in preferences: inout MalachitePreferences) -> Bool { + public func isSameDevice(in preferences: MalachitePreferences) -> Bool { if get() == preferences.general.deviceModel { return true } return false } @@ -67,4 +74,3 @@ extension MalachitePreferences { } } } - diff --git a/Malachite/Utilities/temputils.swift b/Malachite/Utilities/temputils.swift new file mode 100644 index 0000000..af210ab --- /dev/null +++ b/Malachite/Utilities/temputils.swift @@ -0,0 +1,19 @@ +// +// temputils.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/29/25. +// + +#warning("This entire file is to be refactored with MalachiteKit") + +import Foundation +import UIKit + +class temputils { + struct notificationBuilder { + let delegate: Any + let name: NSNotification.Name + let action: Selector + } +} diff --git a/Malachite/Views/AboutView/AboutView+Info.swift b/Malachite/Views/AboutView/AboutView+Info.swift index 179c914..dc6f0f4 100644 --- a/Malachite/Views/AboutView/AboutView+Info.swift +++ b/Malachite/Views/AboutView/AboutView+Info.swift @@ -12,6 +12,8 @@ extension AboutView { /// A variable to hold the existing instance of ``MalachiteClassesObject``. var utilities = MalachiteClassesObject() + @State private var clicks = 0 + init( utilities: MalachiteClassesObject, ) { @@ -38,7 +40,7 @@ extension AboutView { Spacer() Button { if utilities.versionType == "INTERNAL" { - utilities.preferences.ext.showGameKitOptionInAbout(in: &utilities.preferences) + if clicks < 8 { utilities.preferences.ext.showGameKitOptionInAbout(in: &utilities.preferences, clicks: &clicks) } } } label: { if #available(iOS 26.0, *) { diff --git a/Malachite/Views/CameraView/CameraView+Controls.swift b/Malachite/Views/CameraView/CameraView+Controls.swift index 84839b2..71c2af3 100644 --- a/Malachite/Views/CameraView/CameraView+Controls.swift +++ b/Malachite/Views/CameraView/CameraView+Controls.swift @@ -6,15 +6,20 @@ // import Foundation +import AVFoundation import UIKit +// MARK: ControlLayer - Main extension CameraView { - class controls: NSObject { + class ControlLayer: NSObject, AVCaptureSessionControlsDelegate { /// The existing instance of ``CameraView`` to act on. var delegate = CameraView() init(delegate: CameraView) { self.delegate = delegate } + /// An array of the recognizers initalized by this control layer. + var recognizers = [ UIGestureRecognizer ]() + func initButtons() { var lockButtonsX = -80.0 var lockButtonsY = 0.0 @@ -24,7 +29,7 @@ extension CameraView { lockButtonsX = -300.0 } else { // TODO: Make lock buttons not clip into other bars! - NSLog("[Initialization] Device screen is too small for inline lock button") + delegate.utilities.debugNSLog("[Initialization] Device screen is too small for inline lock button") lockButtonsY = 70.0 } @@ -34,33 +39,34 @@ extension CameraView { MalachiteViewUtils.buttonBuilder.constraints(LXA: nil, LXC: nil, LXP: nil, LYA: delegate.view.safeAreaLayoutGuide.bottomAnchor, LYC: -10.0, LYP: true, CXA: delegate.view.safeAreaLayoutGuide.centerXAnchor, CXC: 0.0, CYA: nil, CYC: nil), MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: -10.0, LXP: false, LYA: delegate.view.safeAreaLayoutGuide.topAnchor, LYC: 10.0, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: 0.0, LXP: true, LYA: delegate.view.safeAreaLayoutGuide.topAnchor, LYC: 10.0, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), - MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: lockButtonsX, LXP: false, LYA: delegate.focusButton.topAnchor, LYC: lockButtonsY, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), + MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: lockButtonsX, LXP: false, LYA: delegate.view.safeAreaLayoutGuide.topAnchor, LYC: 10.0 + lockButtonsY, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: -10.0, LXP: false, LYA: delegate.view.safeAreaLayoutGuide.topAnchor, LYC: 80.0, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: 0.0, LXP: true, LYA: delegate.view.safeAreaLayoutGuide.topAnchor, LYC: 80.0, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), - MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: lockButtonsX, LXP: false, LYA: delegate.exposureButton.topAnchor, LYC: lockButtonsY, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), + MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: lockButtonsX, LXP: false, LYA: delegate.view.safeAreaLayoutGuide.topAnchor, LYC: 80.0 + lockButtonsY, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.leadingAnchor, LXC: 10.0, LXP: true, LYA: delegate.view.safeAreaLayoutGuide.bottomAnchor, LYC: -80.0, LYP: true, CXA: nil, CXC: nil, CYA: nil, CYC: nil), MalachiteViewUtils.buttonBuilder.constraints(LXA: nil, LXC: nil, LXP: nil, LYA: nil, LYC: nil, LYP: nil, CXA: nil, CXC: nil, CYA: nil, CYC: nil), MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.leadingAnchor, LXC: 10.0, LXP: true, LYA: delegate.view.safeAreaLayoutGuide.topAnchor, LYC: 10.0, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), ] let buttonConfigs: [MalachiteViewUtils.buttonBuilder] = [ - MalachiteViewUtils.buttonBuilder(symbolName: "camera", action: #selector(delegate.runInputSwitch), dimensions: [ 30.0, 60.0 ], constraints: buttonConstraints[0], assign: { [self] button in delegate.cameraButton = button }), - MalachiteViewUtils.buttonBuilder(symbolName: "flashlight.off.fill", action: #selector(delegate.runFlashlightToggle), dimensions: [ 30.0, 60.0 ], constraints: buttonConstraints[1], assign: { [self] button in delegate.flashlightButton = button }), - MalachiteViewUtils.buttonBuilder(symbolName: "camera.aperture", action: #selector(delegate.runImageCapture), dimensions: [ 45.0, 90.0 ], constraints: buttonConstraints[2], assign: { [self] button in delegate.captureButton = button }), - MalachiteViewUtils.buttonBuilder(symbolName: "scope", action: #selector(delegate.runManualFocusUIHider), dimensions: [ 30.0, 60.0 ], constraints: buttonConstraints[3], assign: { [self] button in delegate.focusButton = button }), - MalachiteViewUtils.buttonBuilder(symbolName: "", action: #selector(delegate.stub), dimensions: [ 30.0, 210.0, 60.0 ], constraints: buttonConstraints[4], assign: { [self] button in delegate.focusSliderButton = button }), - MalachiteViewUtils.buttonBuilder(symbolName: "lock.open", action: #selector(delegate.runManualFocusLockController), dimensions: [ 30.0, 60.0 ], constraints: buttonConstraints[5], assign: { [self] button in delegate.focusLockButton = button }), - MalachiteViewUtils.buttonBuilder(symbolName: "plusminus", action: #selector(delegate.runManualExposureUIHider), dimensions: [ 30.0, 60.0 ], constraints: buttonConstraints[6], assign: { [self] button in delegate.exposureButton = button }), - MalachiteViewUtils.buttonBuilder(symbolName: "", action: #selector(delegate.stub), dimensions: [ 30.0, 210.0, 60.0 ], constraints: buttonConstraints[7], assign: { [self] button in delegate.exposureSliderButton = button }), - MalachiteViewUtils.buttonBuilder(symbolName: "lock.open", action: #selector(delegate.runManualExposureLockController), dimensions: [ 30.0, 60.0 ], constraints: buttonConstraints[8], assign: { [self] button in delegate.exposureLockButton = button }), - MalachiteViewUtils.buttonBuilder(symbolName: "gear", action: #selector(delegate.presentSettingsView), dimensions: [ 30.0, 60.0 ], constraints: buttonConstraints[9], assign: { [self] button in delegate.settingsButton = button }), - MalachiteViewUtils.buttonBuilder(symbolName: "", action: #selector(delegate.stub), dimensions: [ 30.0, 120.0 ], constraints: buttonConstraints[10], assign: { [self] button in delegate.aeafFeedback = button }), - MalachiteViewUtils.buttonBuilder(symbolName: "", action: #selector(delegate.stub), dimensions: [ 30.0, 60.0 ], constraints: buttonConstraints[11], assign: { [self] button in delegate.currentCamera = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "camera", action: #selector(delegate.runInputSwitch), dimensions: [ 60.0 ], constraints: buttonConstraints[0], hidden: false, assign: { [self] button in delegate.cameraButton = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "flashlight.off.fill", action: #selector(delegate.runFlashlightToggle), dimensions: [ 60.0 ], constraints: buttonConstraints[1], hidden: false, assign: { [self] button in delegate.flashlightButton = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "camera.aperture", action: #selector(delegate.runImageCapture), dimensions: [ 90.0 ], constraints: buttonConstraints[2], hidden: false, assign: { [self] button in delegate.captureButton = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "scope", action: #selector(delegate.runManualFocusUIHider), dimensions: [ 60.0 ], constraints: buttonConstraints[3], hidden: false, assign: { [self] button in delegate.focusButton = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "", action: #selector(delegate.stub), dimensions: [ 210.0, 60.0 ], constraints: buttonConstraints[4], hidden: false, assign: { [self] button in delegate.focusSliderButton = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "lock.open", action: #selector(delegate.runManualFocusLockController), dimensions: [ 60.0 ], constraints: buttonConstraints[5], hidden: true, assign: { [self] button in delegate.focusLockButton = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "plusminus", action: #selector(delegate.runManualExposureUIHider), dimensions: [ 60.0 ], constraints: buttonConstraints[6], hidden: false, assign: { [self] button in delegate.exposureButton = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "", action: #selector(delegate.stub), dimensions: [ 210.0, 60.0 ], constraints: buttonConstraints[7], hidden: false, assign: { [self] button in delegate.exposureSliderButton = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "lock.open", action: #selector(delegate.runManualExposureLockController), dimensions: [ 60.0 ], constraints: buttonConstraints[8], hidden: true, assign: { [self] button in delegate.exposureLockButton = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "gear", action: #selector(delegate.presentSettingsView), dimensions: [ 60.0 ], constraints: buttonConstraints[9], hidden: false, assign: { [self] button in delegate.settingsButton = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "", action: #selector(delegate.stub), dimensions: [ 120.0 ], constraints: buttonConstraints[10], hidden: true, assign: { [self] button in delegate.aeafFeedback = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "", action: #selector(delegate.stub), dimensions: [ 60.0 ], constraints: buttonConstraints[11], hidden: false, assign: { [self] button in delegate.currentCamera = button }), ] for config in buttonConfigs { let button = delegate.utilities.views.createAndAddButtonToView(symbolName: config.symbolName, delegate: delegate, view: delegate.view, utilities: delegate.utilities, action: config.action, dimensions: config.dimensions, constraints: config.constraints) if delegate.utilities.preferences.userInterface.appLaunch { button.alpha = 0.0; delegate.uiIsHidden = true } + if config.hidden { button.alpha = 0.0 } config.assign(button) } } @@ -78,16 +84,23 @@ extension CameraView { func initRecognizers() { delegate.zoomRecognizer = UIPinchGestureRecognizer(target: delegate, action:#selector(runZoomController)) - delegate.view.addGestureRecognizer(delegate.zoomRecognizer) + delegate.zoomRecognizer.name = "zoom" + self.recognizers.append(delegate.zoomRecognizer) if !delegate.utilities.preferences.userInterface.tapAndHold.contains("off") { delegate.aeafRecognizer = UILongPressGestureRecognizer(target: delegate, action: #selector(runaeafController)) - delegate.view.addGestureRecognizer(delegate.aeafRecognizer) + delegate.aeafRecognizer.name = "tah" + self.recognizers.append(delegate.aeafRecognizer) } - delegate.uiHiderRecognizer = UILongPressGestureRecognizer(target: delegate, action: #selector(runUIHider)) + delegate.uiHiderRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(runUIHider)) delegate.uiHiderRecognizer.numberOfTouchesRequired = 2 - delegate.view.addGestureRecognizer(delegate.uiHiderRecognizer) + self.recognizers.append(delegate.uiHiderRecognizer) + + for recognizer in recognizers { + recognizer.cancelsTouchesInView = false + delegate.view.addGestureRecognizer(recognizer) + } } func initTooltips(showLabels: Bool, showCamera: Bool) { @@ -113,7 +126,146 @@ extension CameraView { initSliders() initRecognizers() if !delegate.utilities.preferences.userInterface.appLaunch { initTooltips(showLabels: true, showCamera: true) } + if #available(iOS 18.0, *) { + if delegate.utilities.versionType == "INTERNAL" && delegate.utilities.preferences.evaintrnl.cameraControlEnabled { + initCameraControl() + } + } + } + } +} + +// MARK: ControlLayer - Camera Control +@available(iOS 18.0, *) +extension CameraView.ControlLayer { + func sessionControlsDidBecomeActive(_ session: AVCaptureSession) { + if !delegate.uiIsHidden { runUIHider() } + } + + func sessionControlsWillEnterFullscreenAppearance(_ session: AVCaptureSession) { + if !delegate.uiIsHidden { runUIHider() } + } + + func sessionControlsWillExitFullscreenAppearance(_ session: AVCaptureSession) { + if delegate.uiIsHidden { runUIHider() } + } + + func sessionControlsDidBecomeInactive(_ session: AVCaptureSession) { + if delegate.uiIsHidden { runUIHider() } + if delegate.cameraIndex != nil { + delegate.runInputSwitch() + delegate.cameraIndex = nil + } + } + + func initCameraControl() { + guard delegate.cameraSession!.supportsControls else { return } + var controls: [ AVCaptureControl ] = [] + +#warning("malachitekit should properly sync this with the zoom slider") + if delegate.utilities.preferences.evaintrnl.cameraControlOptions.contains("zoom") { + let zoomSlider = AVCaptureSlider("Zoom", symbolName: "plus.viewfinder", in: 1.0...Float(MalachiteClassesObject().preferences.capture.maximumZoom)) + zoomSlider.prominentValues = [ 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0 ] + zoomSlider.setActionQueue(delegate.utilities.sessionQueue) { [self] position in + delegate.zoomFloater = CGFloat(position) + delegate.runZoomController() + } + controls.append(zoomSlider) + } + +#warning("same as above") + if delegate.utilities.preferences.evaintrnl.cameraControlOptions.contains("focus") { + let focusSlider = AVCaptureSlider("Focus", symbolName: "scope", in: 0.0...1.0) + focusSlider.setActionQueue(delegate.utilities.sessionQueue) { [self] position in + delegate.focusFloater = position + delegate.runManualFocusController() + delegate.focusFloater = nil + } + controls.append(focusSlider) + } + + if delegate.utilities.preferences.evaintrnl.cameraControlOptions.contains("cameras") { + let cameraSwitcher = AVCaptureIndexPicker("Cameras", symbolName: "camera.fill", localizedIndexTitles: delegate.availableRearCameras.map { $0.localizedName } ) + cameraSwitcher.selectedIndex = delegate.availableRearCameras.firstIndex(of: delegate.selectedDevice!)! + cameraSwitcher.setActionQueue(delegate.utilities.sessionQueue) { [self] index in + delegate.cameraIndex = index + } + controls.append(cameraSwitcher) + } + + var flashSwitcher: AVCaptureIndexPicker? + if delegate.utilities.preferences.evaintrnl.cameraControlOptions.contains("flash") { + flashSwitcher = AVCaptureIndexPicker("Flash", symbolName: "bolt.fill", numberOfIndexes: 2, localizedTitleTransform: { index in + switch index { + case 0: return NSLocalizedString("flash.off", comment: "") + case 1: return NSLocalizedString("flash.on", comment: "") + default: return "" + } + }) + flashSwitcher!.setActionQueue(delegate.utilities.sessionQueue) { [self] index in + flashSwitcher!.selectedIndex = delegate.flashStatus ? 1 : 0 + delegate.runFlashlightToggle() + flashSwitcher!.selectedIndex = delegate.flashStatus ? 1 : 0 + } + controls.append(flashSwitcher!) + } + + if delegate.utilities.preferences.evaintrnl.cameraControlOptions.contains("flashLevel") { + let flashSlider = AVCaptureSlider("Flash Level", symbolName: "lightbulb.fill", in: 0.0...1.0) + flashSlider.setActionQueue(delegate.utilities.sessionQueue) { [self] position in + if (position == 0.0 && delegate.flashStatus) || (position != 0.0 && !delegate.flashStatus) { + delegate.flashFloater = position + delegate.runFlashlightToggle() + delegate.flashFloater = nil + if let flashSwitcher = flashSwitcher { flashSwitcher.selectedIndex = delegate.flashStatus ? 1 : 0 } + } else { + delegate.utilities.function.flashLevelTest(captureDevice: delegate.selectedDevice!, floater: position) + } + } + controls.append(flashSlider) + } + + if delegate.utilities.preferences.evaintrnl.cameraControlOptions.contains("exposureBias") { + let systemBiasSlider = AVCaptureSystemExposureBiasSlider(device: delegate.selectedDevice!) + controls.append(systemBiasSlider) + } + + if delegate.utilities.versionType == "INTERNAL" { + delegate.cameraSession?.setControlsDelegate(self, queue: delegate.utilities.sessionQueue) + delegate.utilities.function.addControlsToSession(session: &delegate.cameraSession!, controls: controls) } } } +// MARK: ControlLayer - Misc +extension CameraView.ControlLayer { + @objc func updateSettingsGestureFingerCount() { + delegate.settingsRecognizer.numberOfTouchesRequired = delegate.utilities.preferences.evaintrnl.settingsGesture + } + + @objc func runSettingsGesture() { + if delegate.settingsRecognizer.state == UIGestureRecognizer.State.ended { + delegate.presentSettingsView() + } + } + + /// Function to show and hide the user interface that was drawn with ``setupView()``. + @objc func runUIHider() { + #warning("update for Liquid Glass, using UIView.animate is not recommended") + if delegate.uiHiderRecognizer.state == UITapGestureRecognizer.State.ended || delegate.uiHiderRecognizer.state == UITapGestureRecognizer.State.changed { return } + + DispatchQueue.main.async { [self] in + if !delegate.uiIsHidden { + delegate.utilities.views.hideUI(view: delegate.view, blacklisted: [ delegate.aeafFeedback, delegate.uiHiderRecognizer ], conditionals: [ delegate.focusLockButton : delegate.manualFocusSliderIsActive, delegate.exposureLockButton : delegate.manualExposureSliderIsActive], gestureRecognizers: self.recognizers) + } else { + delegate.utilities.views.showUI(view: delegate.view, blacklisted: [ delegate.aeafFeedback, delegate.uiHiderRecognizer ], conditionals: [ delegate.focusLockButton : delegate.manualFocusSliderIsActive, delegate.exposureLockButton : delegate.manualExposureSliderIsActive], gestureRecognizers: self.recognizers) + delegate.utilities.tooltips.zoomTooltipFlow(button: delegate.currentCamera, viewForBounds: delegate.view, camera: delegate.selectedDevice) + } + + delegate.uiIsHidden = !delegate.uiIsHidden + delegate.utilities.haptics.triggerNotificationHaptic(type: .success) + } + } +} + + diff --git a/Malachite/Views/CameraView/CameraView+Notifications.swift b/Malachite/Views/CameraView/CameraView+Notifications.swift new file mode 100644 index 0000000..a2d231f --- /dev/null +++ b/Malachite/Views/CameraView/CameraView+Notifications.swift @@ -0,0 +1,45 @@ +// +// CameraView+Notifications.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/29/25. +// + +import Foundation +import UIKit + +extension CameraView { + class notifications: NSObject { + /// The existing instance of ``CameraView`` to act on. + var delegate = CameraView() + + init(delegate: CameraView) { self.delegate = delegate } + + func initNotifications() { + let notificationConfigs: [temputils.notificationBuilder] = [ + temputils.notificationBuilder(delegate: delegate, name: UIDevice.orientationDidChangeNotification, action: #selector(orientationChanged)), + temputils.notificationBuilder(delegate: delegate, name: MalachiteFunctionUtils.Notifications.aspectFillNotification.name, action: #selector(changeAspectFill)), + temputils.notificationBuilder(delegate: delegate, name: MalachiteFunctionUtils.Notifications.exposureLimitNotification.name, action: #selector(changeExposureLimit)), + temputils.notificationBuilder(delegate: delegate, name: MalachiteFunctionUtils.Notifications.stabilizerNotification.name, action: #selector(changeStabilizerMode)), + temputils.notificationBuilder(delegate: delegate.utilities.games, name: MalachiteFunctionUtils.Notifications.gameCenterEnabledNotification.name, action: #selector(delegate.utilities.games.changeGameCenterEnabled)), + temputils.notificationBuilder(delegate: delegate, name: MalachiteFunctionUtils.Notifications.unsupportedISOValueNotification.name, action: #selector(runManualExposureUIHiderWhenUnsupported)), + temputils.notificationBuilder(delegate: delegate, name: MalachiteFunctionUtils.Notifications.unsupportedLensPositionNotification.name, action: #selector(runManualFocusUIHiderWhenUnsupported)), + temputils.notificationBuilder(delegate: delegate, name: MalachiteFunctionUtils.Notifications.continousAEAFNotification.name, action: #selector(changeContinuousAEAF)), + temputils.notificationBuilder(delegate: delegate, name: MalachiteFunctionUtils.Notifications.aeafTapGestureNotification.name, action: #selector(changeAEAFRecognizer)), + temputils.notificationBuilder(delegate: delegate.utilities.function, name: MalachiteFunctionUtils.Notifications.idleTimerNotification.name, action: #selector(delegate.utilities.function.changeIdleTimerState)), + temputils.notificationBuilder(delegate: delegate, name: MalachiteFunctionUtils.Notifications.megaPixelSwitchNotification.name, action: #selector(runInputMegapixelSwitch)), + ] + + for config in notificationConfigs { + delegate.utilities.debugNSLog("[Initialization] Setting up notification observer for \(config.name.rawValue) changes") + NotificationCenter.default.addObserver(config.delegate, selector: config.action, name: config.name, object: nil) + } + + UIDevice.current.beginGeneratingDeviceOrientationNotifications() + } + + func bringUpNotifications() { + initNotifications() + } + } +} diff --git a/Malachite/Views/CameraView/CameraView+Overrides.swift b/Malachite/Views/CameraView/CameraView+Overrides.swift new file mode 100644 index 0000000..772fa83 --- /dev/null +++ b/Malachite/Views/CameraView/CameraView+Overrides.swift @@ -0,0 +1,46 @@ +// +// CameraView+Overrides.swift +// Malachite +// +// Created by Eva Isabella Luna on 8/30/25. +// + +import UIKit + +extension CameraView { + /// Override function to force the status bar to never be shown. + override var prefersStatusBarHidden: Bool { return true } + + /// Override function to force the app to be in portrait mode on iPhone. + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + if utilities.idiom == .phone { return .portrait } + return .all + } + + /// Override function to force the system to reject gestures from the bottom of the screen. + override var preferredScreenEdgesDeferringSystemGestures: UIRectEdge { + return utilities.preferences.evaintrnl.blockAccidentalGestures ? [.bottom] : [] + } + + /// Override function for layoutSubviews. + override func viewWillLayoutSubviews() { + super.viewWillLayoutSubviews() + self.view.center = CGPoint(x: self.view.bounds.midX, y: self.view.bounds.midY) + self.view.frame = self.view.bounds + } + + /// Override function to trigger actions when the screen rotates. + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + super.viewWillTransition(to: size, with: coordinator) + + coordinator.animate(alongsideTransition: { [self] context in + #if MAIN_APP + if let windowScene = UIApplication.shared.connectedScenes.first(where: { $0 is UIWindowScene }) as? UIWindowScene { + let orientation = windowScene.interfaceOrientation + self.cameraPreview.connection!.videoOrientation = self.transformOrientation(orientation: orientation) + } + #endif + self.cameraPreview.frame.size = self.view.frame.size + }) + } +} diff --git a/Malachite/Views/CameraView/CameraView+Preview.swift b/Malachite/Views/CameraView/CameraView+Preview.swift index 3665ef9..19b084f 100644 --- a/Malachite/Views/CameraView/CameraView+Preview.swift +++ b/Malachite/Views/CameraView/CameraView+Preview.swift @@ -5,3 +5,25 @@ // Created by Eva Isabella Luna on 8/26/25. // +import AVFoundation +import Foundation + +extension CameraView { + class Preview { + /// The existing instance of ``CameraView`` to act on. + var delegate = CameraView() + + init(delegate: CameraView) { self.delegate = delegate } + + func createPreviewLayer(previewLayer: AVCaptureVideoPreviewLayer?) -> AVCaptureVideoPreviewLayer { + guard let previewLayer = previewLayer else { + let layer = AVCaptureVideoPreviewLayer() + layer.frame.size = delegate.view.frame.size + return layer + } + + previewLayer.frame.size = delegate.view.frame.size + return previewLayer + } + } +} diff --git a/Malachite/Views/CameraView/CameraView.swift b/Malachite/Views/CameraView/CameraView.swift index 6ef4dad..dbffcd2 100755 --- a/Malachite/Views/CameraView/CameraView.swift +++ b/Malachite/Views/CameraView/CameraView.swift @@ -10,32 +10,13 @@ import UIKit import Foundation import AVFoundation import AVKit -import LockedCameraCapture import Photos import GameKit -class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCapturePhotoCaptureDelegate, AVCaptureSessionControlsDelegate { - func sessionControlsDidBecomeActive(_ session: AVCaptureSession) { - if !uiIsHidden { runUIHider() } - } - - func sessionControlsWillEnterFullscreenAppearance(_ session: AVCaptureSession) { - if !uiIsHidden { runUIHider() } - } - - func sessionControlsWillExitFullscreenAppearance(_ session: AVCaptureSession) { - if uiIsHidden { runUIHider() } - } - - func sessionControlsDidBecomeInactive(_ session: AVCaptureSession) { - if uiIsHidden { runUIHider() } - if cameraIndex != nil { - runInputSwitch() - self.cameraIndex = nil - } - } - - var controls: controls? +class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCapturePhotoCaptureDelegate { + var controlLayer: ControlLayer? + var notifications: notifications? + var preview: Preview? /// The `AVCaptureSession` Malachite uses for everything. var cameraSession: AVCaptureSession? @@ -52,7 +33,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// The `AVCapturePhotoOutput` used to capture photos with ``selectedDevice`` and ``cameraSession``. var photoOutput = AVCapturePhotoOutput() /// The `AVCaptureVideoPreviewLayer` used to allow users to see a preview of their camera before taking a shot with ``photoOutput``. - var cameraPreview: AVCaptureVideoPreviewLayer? + var cameraPreview = AVCaptureVideoPreviewLayer() /// A `Bool` that determines whether or not the wide angle lens is in use. var wideAngleInUse = true /// A `Bool` that determines whether or not the app is still initializing. Uses for tasks that should only be run once at the start of Malachite. @@ -146,8 +127,6 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// An observer for the device's rotation. private var rotationObserver: NSObjectProtocol? - var cameraView = UIView() - /** viewDidLoad override for the main user interface. @@ -158,50 +137,26 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa */ override func viewDidLoad() { super.viewDidLoad() - self.view.backgroundColor = .clear - - #warning("malachite init") - utilities.debugNSLog("[Initialization] Starting up Malachite") - self.controls = CameraView.controls(delegate: self) - - if utilities.versionType == "INTERNAL" { - utilities.internalNSLog("[Initialization] Running an INTERNAL build") - } else if utilities.versionType == "DEBUG" { - utilities.debugNSLog("[Initialization] Running a DEBUG build") - } else if utilities.versionType == "RELEASE" { - utilities.NSLog("[Initialization] Running a RELEASE build") - } + self.view.backgroundColor = .black - #if APP_EXTENSION - utilities.debugNSLog("[Initialization] Running out of an app extension.") - #elseif MAIN_APP - utilities.debugNSLog("[Initialization] Running out of the main app.") - #endif + self.controlLayer = CameraView.ControlLayer(delegate: self) + self.notifications = CameraView.notifications(delegate: self) + self.preview = CameraView.Preview(delegate: self) - if utilities.versionType == "INTERNAL" { - if !utilities.preferences.ext.deviceModel.isSameDevice(in: &utilities.preferences) { - utilities.internalNSLog("[Initialization] This is a new device, rechecking compatibility.") - utilities.preferences.general.deviceModel = utilities.preferences.ext.deviceModel.get() - } else { - utilities.internalNSLog("[Initialization] This is the same device, can skip compatibility checks.") - } - } - - if !utilities.function.supportsHEIC() { - utilities.debugNSLog("[Initialization] HEIF enabled on a device that doesn't support it, disabling") - utilities.preferences.capture.format.heic = false - utilities.preferences.capture.format.jpeg = true - } + #warning("this is temporary for testing") + let bringup = Camera.Bringup(utilities: utilities) + bringup.checkForHEICCompatibility() #warning("malachite camera init") - cameraPreview?.frame.size = self.view.frame.size utilities.debugNSLog("[Initialization] Bringing up AVCaptureSession") - cameraSession = AVCaptureSession() + cameraSession = bringup.createAVCaptureSession(session: cameraSession) + cameraPreview = preview!.createPreviewLayer(previewLayer: cameraPreview) utilities.debugNSLog("[Initialization] Bringing up AVCaptureDeviceInput") utilities.debugNSLog("[Camera Input] Getting current camera system capabilities") + #warning("malachite camera init") var camerasToDiscover: [AVCaptureDevice.DeviceType] = [] if #available(iOS 17.0, *) { camerasToDiscover = [.builtInUltraWideCamera, .builtInWideAngleCamera, .builtInTelephotoCamera ] } else { camerasToDiscover = [.builtInUltraWideCamera, .builtInWideAngleCamera, .builtInTelephotoCamera] } @@ -214,12 +169,9 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa utilities.debugNSLog("[Camera Input] \(device.deviceType.rawValue) available") } + #warning("malachite camera init") runInputSwitch() - if #available(iOS 18.0, *) { - cameraSession?.setControlsDelegate(self, queue: utilities.sessionQueue) - } - if self.availableRearCameras.first != nil { photoOutput = AVCapturePhotoOutput() if #unavailable(iOS 16.0) { photoOutput.isHighResolutionCaptureEnabled = true } @@ -236,19 +188,17 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa statusBarOrientation = windowScene.interfaceOrientation } #endif - cameraPreview?.frame = view.layer.bounds + cameraPreview.frame = view.layer.bounds let videoOrientation: AVCaptureVideoOrientation = (statusBarOrientation.videoOrientation) - cameraPreview?.connection?.videoOrientation = videoOrientation + cameraPreview.connection?.videoOrientation = videoOrientation if utilities.preferences.preview.aspect { - cameraPreview?.videoGravity = AVLayerVideoGravity.resizeAspectFill + cameraPreview.videoGravity = AVLayerVideoGravity.resizeAspectFill } else { - cameraPreview?.videoGravity = AVLayerVideoGravity.resizeAspect + cameraPreview.videoGravity = AVLayerVideoGravity.resizeAspect } - cameraView = UIView(frame: self.view.bounds) - cameraView.layer.addSublayer(cameraPreview!) - self.view.insertSubview(cameraView, at: 0) + self.view.layer.addSublayer(cameraPreview) utilities.debugNSLog("[Initialization] Starting session stream") DispatchQueue.global(qos: .background).async { @@ -258,30 +208,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa utilities.debugNSLog("[Initialization] No cameras detected, skipping to user interface bringup") } - #warning("malachite notif init") - utilities.debugNSLog("[Initialization] Setting up notification observer for orientation changes") - #if MAIN_APP - NotificationCenter.default.addObserver(self, selector: #selector(orientationChanged), name: UIDevice.orientationDidChangeNotification, object: nil) - #endif - NotificationCenter.default.addObserver(self, selector: #selector(changeAspectFill), name: MalachiteFunctionUtils.Notifications.aspectFillNotification.name, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(changeExposureLimit), name: MalachiteFunctionUtils.Notifications.exposureLimitNotification.name, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(changeStabilizerMode), name: MalachiteFunctionUtils.Notifications.stabilizerNotification.name, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(changeGameCenterEnabled), name: MalachiteFunctionUtils.Notifications.gameCenterEnabledNotification.name, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(runManualExposureUIHiderWhenUnsupported), name: MalachiteFunctionUtils.Notifications.unsupportedISOValueNotification.name, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(runManualFocusUIHiderWhenUnsupported), name: MalachiteFunctionUtils.Notifications.unsupportedLensPositionNotification.name, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(changeContinuousAEAF), name: MalachiteFunctionUtils.Notifications.continousAEAFNotification.name, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(changeAEAFRecognizer), name: MalachiteFunctionUtils.Notifications.aeafTapGestureNotification.name, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(changeIdleTimerState), name: MalachiteFunctionUtils.Notifications.idleTimerNotification.name, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(runInputMegapixelSwitch), name: MalachiteFunctionUtils.Notifications.megaPixelSwitchNotification.name, object: nil) - - UIDevice.current.beginGeneratingDeviceOrientationNotifications() - #warning("malachite init") - if utilities.versionType == "INTERNAL" || utilities.versionType == "DEBUG" { - if utilities.preferences.debug.logging.preferences { - MalachitePreferencesUtils().printPreferences() - } - } #warning("malachite camera init") if #available (iOS 17.2, *) { @@ -320,12 +247,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa super.viewDidAppear(animated) utilities.debugNSLog("[Initialization] Presenting user interface") - setupView() - if utilities.versionType == "INTERNAL" { - setupView_INTERNAL() - } - - self.changeGameCenterEnabled() + (utilities.versionType == "INTERNAL") ? setupView_INTERNAL() : setupView() } /** @@ -348,13 +270,18 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa } func setupView_INTERNAL() { - settingsRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(runSettingsGesture)) - updateSettingsGestureFingerCount() + utilities.games.changeGameCenterEnabled() + if utilities.preferences.general.gamekit.alerted { self.present(utilities.games.setupGameKitAlert(), animated: true, completion: nil) } + + settingsRecognizer = UISwipeGestureRecognizer(target: self.controlLayer!, action: #selector(self.controlLayer!.runSettingsGesture)) + self.controlLayer!.updateSettingsGestureFingerCount() settingsRecognizer.direction = .up self.view.addGestureRecognizer(settingsRecognizer) - NotificationCenter.default.addObserver(self, selector: #selector(updateSettingsGestureFingerCount), name: MalachiteFunctionUtils.Notifications.settingsGestureNotification.name, object: nil) + NotificationCenter.default.addObserver(self.controlLayer!, selector: #selector(self.controlLayer!.updateSettingsGestureFingerCount), name: MalachiteFunctionUtils.Notifications.settingsGestureNotification.name, object: nil) + + setupView() } /** @@ -380,74 +307,27 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa - ``uiHiderRecognizer`` - Tap and hold with two fingers */ func setupView(){ - self.view.backgroundColor = .black - +#warning("remove simulator support") #if targetEnvironment(simulator) - setupLmaoView() + utilities.views.setupLmaoView(view: self.view) #endif - #warning("can be made into a for loop") - self.controls!.bringUpControlLayer() - - focusLockButton.alpha = 0.0 - exposureLockButton.alpha = 0.0 - aeafFeedback.alpha = 0.0 - - setupGameKitAlert() - changeIdleTimerState() - } - - func setupGameKitAlert() { - if utilities.preferences.general.gamekit.alerted { - let alert = UIAlertController(title: "alert.title.gamekit".localized, message: "alert.detail.gamekit".localized, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: NSLocalizedString("alert.button.reopen", comment: "Default action"), style: .default, handler: { _ in - exit(11) - })) - alert.addAction(UIAlertAction(title: NSLocalizedString("alert.button.report", comment: "Default action"), style: .default, handler: { _ in - guard let url = URL(string: "https://www.youtube.com/watch?v=At8v_Yc044Y") else { - return - } - #if MAIN_APP - UIApplication.shared.open(url, options: [:], completionHandler: nil) - #endif - })) - alert.addAction(UIAlertAction(title: NSLocalizedString("alert.button.ignore", comment: "Default action"), style: .default, handler: { _ in - self.utilities.preferences.general.gamekit.alerted = false - })) - self.present(alert, animated: true, completion: nil) - } - } - - func setupLmaoView() { - #warning("remove simulator support") - let lmaoView = UIImageView(image: utilities.views.returnImageForSimulator()) - self.view.addSubview(lmaoView) + self.controlLayer!.bringUpControlLayer() + self.notifications!.bringUpNotifications() - NSLayoutConstraint.activate([ - lmaoView.widthAnchor.constraint(equalToConstant: 60), - lmaoView.heightAnchor.constraint(equalToConstant: 60), - lmaoView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: -80), - lmaoView.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor, constant: -10), - ]) + utilities.function.changeIdleTimerState() } /// Stub function. It literally does nothing. @objc func stub() { } - /// Function to enable or disable the idle timer. - @objc func changeIdleTimerState() { - #if MAIN_APP - UIApplication.shared.isIdleTimerDisabled = utilities.preferences.userInterface.idleTimerDisabled - #endif - } - /// Function to dynamically update the aspect ratio for ``cameraPreview`` through ``MalachiteSettingsView``. @objc func changeAspectFill() { UIView.animate(withDuration: 20) { [self] in if utilities.preferences.preview.aspect { - cameraPreview?.videoGravity = AVLayerVideoGravity.resizeAspectFill + cameraPreview.videoGravity = AVLayerVideoGravity.resizeAspectFill } else { - cameraPreview?.videoGravity = AVLayerVideoGravity.resizeAspect + cameraPreview.videoGravity = AVLayerVideoGravity.resizeAspect } } } @@ -469,7 +349,8 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa } @objc func changeContinuousAEAF() { - utilities.function.continuousAEAF(device: selectedDevice!) + guard let selectedDevice = self.selectedDevice else { return } + utilities.function.continuousAEAF(device: selectedDevice) } @objc func changeAEAFRecognizer() { @@ -495,26 +376,17 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa if #available(iOS 17.0, *) { if ((selectedDevice?.activeFormat.isVideoStabilizationModeSupported(.previewOptimized)) != nil) { utilities.debugNSLog("[Preview Stabilization] Enabling enhanced stabilization mode") - cameraPreview?.connection!.preferredVideoStabilizationMode = .previewOptimized + cameraPreview.connection!.preferredVideoStabilizationMode = .previewOptimized return } } if ((selectedDevice?.activeFormat.isVideoStabilizationModeSupported(.standard)) != nil) { utilities.debugNSLog("[Preview Stabilization] Enabling standard stabilization mode") - cameraPreview?.connection!.preferredVideoStabilizationMode = .standard + cameraPreview.connection!.preferredVideoStabilizationMode = .standard } } else { - cameraPreview?.connection!.preferredVideoStabilizationMode = .off - } - } - - /// Function to change the GameKit enabled state. - @objc func changeGameCenterEnabled() { - DispatchQueue.global(qos: .background).async { [self] in - if utilities.preferences.general.gamekit.enabled { - utilities.games.setupGameCenter() - } + cameraPreview.connection!.preferredVideoStabilizationMode = .off } } @@ -522,14 +394,9 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa @objc func presentSettingsView() { #if APP_EXTENSION utilities.debugNSLog("[Settings] Attempt to access Settings UI from app extension") - let alert = UIAlertController(title: "alert.title.app_extensions.settings".localized, message: "alert.detail.app_extensions.settings".localized, preferredStyle: .actionSheet) - alert.popoverPresentationController?.sourceView = settingsButton - if #available(iOS 26.0, *) { - alert.preferredTransition = .zoom { [self] _ in settingsButton } - } - alert.addAction(UIAlertAction(title: "alert.button.ok".localized, style: .default, handler: { _ in + let alert = utilities.views.createAlertController(title: "alert.title.app_extensions.settings", message: "alert.detail.app_extensions.settings", button: settingsButton, defaultSet: true, action: { _ in self.utilities.debugNSLog("[Settings] Dialog has been dismissed") - })) + }) self.present(alert, animated: true, completion: nil) return #elseif MAIN_APP @@ -554,14 +421,9 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa cameraButton.isUserInteractionEnabled = false if (self.availableRearCameras.count < 2 || utilities.preferences.debug.breakApp) && !self.initRun { utilities.debugNSLog("[Camera Input] Only one AVCaptureDevice is available to use, showing error") - let alert = UIAlertController(title: "alert.title.camera_switch".localized, message: "alert.detail.camera_switch".localized, preferredStyle: .actionSheet) - alert.popoverPresentationController?.sourceView = cameraButton - if #available(iOS 26.0, *) { - alert.preferredTransition = .zoom { [self] _ in cameraButton } - } - alert.addAction(UIAlertAction(title: "alert.button.ok".localized, style: .default, handler: { _ in + let alert = utilities.views.createAlertController(title: "alert.title.camera_switch", message: "alert.detail.camera_switch", button: cameraButton, defaultSet: true, action: { _ in self.utilities.debugNSLog("[Camera Input] Dialog has been dismissed") - })) + }) self.present(alert, animated: true, completion: nil) cameraButton.isUserInteractionEnabled = true return @@ -593,77 +455,24 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa firstRun: &initRun) - if #available(iOS 18.0, *) { addControls() } + if #available(iOS 18.0, *) { + if utilities.versionType == "INTERNAL" && utilities.preferences.evaintrnl.cameraControlEnabled { + self.controlLayer!.initCameraControl() + } + } cameraSession?.commitConfiguration() DispatchQueue.main.async() { [self] in - self.controls!.initTooltips(showLabels: false, showCamera: true) + self.controlLayer!.initTooltips(showLabels: false, showCamera: true) } cameraButton.isUserInteractionEnabled = true } - @available(iOS 18.0, *) - func addControls() { - guard cameraSession!.supportsControls else { return } - - let systemBiasSlider = AVCaptureSystemExposureBiasSlider(device: selectedDevice!) - - #warning("malachitekit should properly sync this with the zoom slider") - let zoomSlider = AVCaptureSlider("Zoom", symbolName: "plus.viewfinder", in: 1.0...Float(MalachiteClassesObject().preferences.capture.maximumZoom)) - zoomSlider.prominentValues = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0 ] - zoomSlider.setActionQueue(utilities.sessionQueue) { position in - self.zoomFloater = CGFloat(position) - self.runZoomController() - } - - #warning("same as above") - let focusSlider = AVCaptureSlider("Focus", symbolName: "scope", in: 0.0...1.0) - focusSlider.setActionQueue(utilities.sessionQueue) { position in - self.focusFloater = position - self.runManualFocusController() - self.focusFloater = nil - } - - let cameraSwitcher = AVCaptureIndexPicker("Cameras", symbolName: "camera.fill", localizedIndexTitles: self.availableRearCameras.map { $0.localizedName } ) - cameraSwitcher.selectedIndex = self.availableRearCameras.firstIndex(of: self.selectedDevice!)! - cameraSwitcher.setActionQueue(utilities.sessionQueue) { index in - self.cameraIndex = index - } - - let flashSwitcher = AVCaptureIndexPicker("Flash", symbolName: "bolt.fill", numberOfIndexes: 2, localizedTitleTransform: { index in - switch index { - case 0: return NSLocalizedString("flash.off", comment: "") - case 1: return NSLocalizedString("flash.on", comment: "") - default: return "" - } - }) - flashSwitcher.setActionQueue(utilities.sessionQueue) { index in - flashSwitcher.selectedIndex = self.flashStatus ? 1 : 0 - self.runFlashlightToggle() - flashSwitcher.selectedIndex = self.flashStatus ? 1 : 0 - } - - let flashSlider = AVCaptureSlider("Flash Level", symbolName: "lightbulb.fill", in: 0.0...1.0) - flashSlider.setActionQueue(utilities.sessionQueue) { position in - if (position == 0.0 && self.flashStatus) || (position != 0.0 && !self.flashStatus) { - self.flashFloater = position - self.runFlashlightToggle() - self.flashFloater = nil - flashSwitcher.selectedIndex = self.flashStatus ? 1 : 0 - } else { - self.utilities.function.flashLevelTest(captureDevice: self.selectedDevice!, floater: position) - } - } - - if utilities.versionType == "INTERNAL" { - utilities.function.addControlsToSession(session: &cameraSession!, controls: [ zoomSlider, focusSlider, cameraSwitcher, flashSwitcher, flashSlider, systemBiasSlider]) - } - } - @objc func runInputMegapixelSwitch() { - utilities.function.switchInputMegapixels(device: selectedDevice!, photoOutput: self.photoOutput) + guard let selectedDevice = self.selectedDevice else { return } + utilities.function.switchInputMegapixels(device: selectedDevice, photoOutput: self.photoOutput) } /// Function to toggle the flashlight's on state. @@ -676,14 +485,9 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa isFlashOn: &flashStatus) } else { utilities.debugNSLog("[Flashlight] No flashlight available") - let alert = UIAlertController(title: "alert.title.flashlight".localized, message: "alert.detail.flashlight".localized, preferredStyle: .actionSheet) - alert.popoverPresentationController?.sourceView = flashlightButton - if #available(iOS 26.0, *) { - alert.preferredTransition = .zoom { [self] _ in flashlightButton } - } - alert.addAction(UIAlertAction(title: NSLocalizedString("alert.button.ok", comment: "Default action"), style: .default, handler: { _ in + let alert = utilities.views.createAlertController(title: "alert.title.flashlight", message: "alert.detail.flashlight", button: flashlightButton, defaultSet: true, action: { _ in self.utilities.debugNSLog("[Flashlight] Dialog has been dismissed") - })) + }) self.present(alert, animated: true, completion: nil) } } @@ -702,14 +506,9 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa self.photoOutput = utilities.function.captureImage(output: self.photoOutput, viewForBounds: self.view, captureDelegate: self) } else { utilities.debugNSLog("[Capture Photo] PHPhotoLibrary not authorized, showing error") - let alert = UIAlertController(title: "alert.title.phphotolibrary".localized, message: "alert.detail.phphotolibrary".localized, preferredStyle: .actionSheet) - alert.popoverPresentationController?.sourceView = captureButton - if #available(iOS 26.0, *) { - alert.preferredTransition = .zoom { [self] _ in captureButton } - } - alert.addAction(UIAlertAction(title: NSLocalizedString("alert.button.ok", comment: "Default action"), style: .default, handler: { _ in + let alert = utilities.views.createAlertController(title: "alert.title.phphotolibrary", message: "alert.detail.phphotolibrary", button: captureButton, defaultSet: true, action: { _ in self.utilities.debugNSLog("[Capture Photo] Dialog has been dismissed") - })) + }) self.present(alert, animated: true, completion: nil) } } @@ -768,8 +567,9 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to autofocus + autoexposure with ``aeafRecognizer``. @objc func runaeafController() { + guard var selectedDevice = self.selectedDevice else { return } utilities.function.pointOfInterestAEAF(sender: aeafRecognizer, - captureDevice: &selectedDevice!, + captureDevice: &selectedDevice, button: aeafFeedback, viewForScale: self.view, hapticClass: utilities.haptics) @@ -783,14 +583,9 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa sender: exposureSlider) } else { utilities.debugNSLog("[Manual Exposure] Current camera is not capable of adjusting exposure") - let alert = UIAlertController(title: "alert.title.exposure".localized, message: "alert.detail.exposure".localized, preferredStyle: .actionSheet) - alert.popoverPresentationController?.sourceView = exposureButton - if #available(iOS 26.0, *) { - alert.preferredTransition = .zoom { [self] _ in exposureButton } - } - alert.addAction(UIAlertAction(title: NSLocalizedString("alert.button.ok", comment: "Default action"), style: .default, handler: { _ in + let alert = utilities.views.createAlertController(title: "alert.title.exposure", message: "alert.detail.exposure", button: exposureButton, defaultSet: true, action: { _ in self.utilities.debugNSLog("[Manual Exposure] Dialog has been dismissed") - })) + }) self.present(alert, animated: true, completion: nil) } } @@ -805,14 +600,9 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa associatedSliderButton: exposureSliderButton) } else { utilities.debugNSLog("[Manual Focus] Current camera is not capable of adjusting exposure") - let alert = UIAlertController(title: "alert.title.exposure".localized, message: "alert.detail.exposure".localized, preferredStyle: .actionSheet) - alert.popoverPresentationController?.sourceView = exposureButton - if #available(iOS 26.0, *) { - alert.preferredTransition = .zoom { [self] _ in exposureButton } - } - alert.addAction(UIAlertAction(title: NSLocalizedString("alert.button.ok", comment: "Default action"), style: .default, handler: { _ in + let alert = utilities.views.createAlertController(title: "alert.title.exposure", message: "alert.detail.exposure", button: exposureButton, defaultSet: true, action: { _ in self.utilities.debugNSLog("[Manual Exposure] Dialog has been dismissed") - })) + }) self.present(alert, animated: true, completion: nil) } } @@ -848,15 +638,11 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa sender: focusSlider, floater: focusFloater ?? focusSlider.value) } else { + #warning("refactor to call unsupported codepath") utilities.debugNSLog("[Manual Focus] Current camera is not capable of adjusting focus") - let alert = UIAlertController(title: "alert.title.focus".localized, message: "alert.detail.focus".localized, preferredStyle: .actionSheet) - alert.popoverPresentationController?.sourceView = focusButton - if #available(iOS 26.0, *) { - alert.preferredTransition = .zoom { [self] _ in focusButton } - } - alert.addAction(UIAlertAction(title: NSLocalizedString("alert.button.ok", comment: "Default action"), style: .default, handler: { _ in + let alert = utilities.views.createAlertController(title: "alert.title.focus", message: "alert.detail.focus", button: focusButton, defaultSet: true, action: { _ in self.utilities.debugNSLog("[Manual Focus] Dialog has been dismissed") - })) + }) self.present(alert, animated: true, completion: nil) } } @@ -871,14 +657,9 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa associatedSliderButton: focusSliderButton) } else { utilities.debugNSLog("[Manual Focus] Current camera is not capable of adjusting focus") - let alert = UIAlertController(title: "alert.title.focus".localized, message: "alert.detail.focus".localized, preferredStyle: .actionSheet) - alert.popoverPresentationController?.sourceView = focusButton - if #available(iOS 26.0, *) { - alert.preferredTransition = .zoom { [self] _ in focusButton } - } - alert.addAction(UIAlertAction(title: NSLocalizedString("alert.button.ok", comment: "Default action"), style: .default, handler: { _ in + let alert = utilities.views.createAlertController(title: "alert.title.focus", message: "alert.detail.focus", button: focusButton, defaultSet: true, action: { _ in self.utilities.debugNSLog("[Manual Focus] Dialog has been dismissed") - })) + }) self.present(alert, animated: true, completion: nil) } } @@ -906,80 +687,8 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa viewForRecognizers: self.view) } - @objc func updateSettingsGestureFingerCount() { - settingsRecognizer.numberOfTouchesRequired = utilities.preferences.evaintrnl.settingsGesture - } - - @objc func runSettingsGesture() { - if settingsRecognizer.state == UIGestureRecognizer.State.ended { - self.presentSettingsView() - } - } - - /// Function to show and hide the user interface that was drawn with ``setupView()``. - @objc func runUIHider() { - if uiHiderRecognizer.state == UITapGestureRecognizer.State.ended || uiHiderRecognizer.state == UITapGestureRecognizer.State.changed { return } - - let gestureRecognizers = [ zoomRecognizer, aeafRecognizer ] - - DispatchQueue.main.async { [self] in - if !uiIsHidden { - hideUI() - } else { - showUI() - utilities.tooltips.zoomTooltipFlow(button: currentCamera, viewForBounds: self.view, camera: selectedDevice) - } - - uiIsHidden = !uiIsHidden - utilities.haptics.triggerNotificationHaptic(type: .success) - } - - func hideUI() { - UIView.animate(withDuration: 0.25) { [self] in - for subview in self.view.subviews { - if subview != cameraView { - if subview == focusLockButton { - if manualFocusSliderIsActive { subview.alpha = 0.0 } - } else if subview == exposureLockButton { - if manualExposureSliderIsActive { subview.alpha = 0.0 } - } else { - subview.alpha = 0.0 - } - } - } - } - let hiddenRecognizers = utilities.preferences.userInterface.hiddenControls - for gestureRecognizer in gestureRecognizers { - if gestureRecognizer == zoomRecognizer && !hiddenRecognizers.contains("zoom") { self.view.removeGestureRecognizer(gestureRecognizer) } - if gestureRecognizer == aeafRecognizer && !hiddenRecognizers.contains("tah") { self.view.removeGestureRecognizer(gestureRecognizer) } - } - } - - func showUI() { - UIView.animate(withDuration: 0.25) { [self] in - for subview in self.view.subviews { - if subview != cameraView && subview != aeafFeedback { - if subview == focusLockButton { - if manualFocusSliderIsActive { subview.alpha = 1.0 } - } else if subview == exposureLockButton { - if manualExposureSliderIsActive { subview.alpha = 1.0 } - } else { - subview.alpha = 1.0 - } - } - } - } - - for gestureRecognizer in gestureRecognizers { - guard let currentRecognizers = self.view.gestureRecognizers else { return } - if !currentRecognizers.contains(gestureRecognizer) { - self.view.addGestureRecognizer(gestureRecognizer) - } - } - } - } - /// Function to handle device rotation. + #warning("refactor to view utils") @objc func orientationChanged() { utilities.views.rotateButtonsWithOrientation(buttonsToRotate: [ cameraButton, flashlightButton, @@ -990,46 +699,5 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa exposureButton, exposureLockButton ]) } - - /// Override function to force the status bar to never be shown. - override var prefersStatusBarHidden: Bool { - return true - } - - /// Override function to force the app to be in portrait mode on iPhone. - override var supportedInterfaceOrientations: UIInterfaceOrientationMask { - if utilities.idiom == .phone { - return .portrait - } - - return .all - } - - /// Override function to force the system to reject gestures from the bottom of the screen. - override var preferredScreenEdgesDeferringSystemGestures: UIRectEdge { - return [.bottom] - } - - /// Override function for layoutSubviews. - override func viewWillLayoutSubviews() { - super.viewWillLayoutSubviews() - cameraView.center = CGPoint(x: cameraView.bounds.midX, y: cameraView.bounds.midY) - cameraView.frame = self.view.bounds - } - - /// Override function to trigger actions when the screen rotates. - override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { - super.viewWillTransition(to: size, with: coordinator) - - coordinator.animate(alongsideTransition: { [self] context in - #if MAIN_APP - if let windowScene = UIApplication.shared.connectedScenes.first(where: { $0 is UIWindowScene }) as? UIWindowScene { - let orientation = windowScene.interfaceOrientation - self.cameraPreview?.connection!.videoOrientation = self.transformOrientation(orientation: orientation) - } - #endif - self.cameraPreview?.frame.size = self.view.frame.size - }) - } } diff --git a/Malachite/Views/DeveloperView/DeveloperView+Internal.swift b/Malachite/Views/DeveloperView/DeveloperView+Internal.swift index 5404c9f..21feab2 100644 --- a/Malachite/Views/DeveloperView/DeveloperView+Internal.swift +++ b/Malachite/Views/DeveloperView/DeveloperView+Internal.swift @@ -11,6 +11,10 @@ extension DeveloperView { struct InternalSettings: View { /// A State variable used for determining how many fingers are used for the settings gesture. @State private var settingsGestureFingers = Int() + /// A State variable used for determining whether or not to block accidental gestures. + @State private var blockAccidentalGestures = Bool() + /// A State variable used for determining whether or not to enable the Camera Control. + @State private var cameraControlEnabled = Bool() var utilities: MalachiteClassesObject @@ -27,15 +31,32 @@ extension DeveloperView { disabled: nil, dangerous: false) { - Picker("settings.option.ui.settingsgesture", selection: $settingsGestureFingers) { - Text("settings.option.ui.settingsgesture.1") + Picker("internal.option.settingsgesture", selection: $settingsGestureFingers) { + Text("internal.option.settingsgesture.1") .tag(1) - Text("settings.option.ui.settingsgesture.2") + Text("internal.option.settingsgesture.2") .tag(2) - Text("settings.option.ui.settingsgesture.3") + Text("internal.option.settingsgesture.3") .tag(3) } } + MalachiteCellViewUtils( + icon: "", + disabled: nil, + dangerous: false) + { + Toggle("internal.option.blockaccidentalgestures", isOn: $blockAccidentalGestures) + } + if #available(iOS 18.0, *) { + MalachiteCellViewUtils( + icon: "", + disabled: nil, + dangerous: false) + { + Toggle("internal.option.cameracontrol", isOn: $cameraControlEnabled) + } + } + #warning("do camera control options") } .onAppear(perform: onAppear) .onDisappear(perform: onDisappear) @@ -44,16 +65,26 @@ extension DeveloperView { utilities.preferences.evaintrnl.settingsGesture = settingsGestureFingers } + .onChange(of: blockAccidentalGestures) {_ in + utilities.preferences.evaintrnl.blockAccidentalGestures = blockAccidentalGestures + } + .onChange(of: cameraControlEnabled) {_ in + utilities.preferences.evaintrnl.cameraControlEnabled = cameraControlEnabled + } } func onAppear() { settingsGestureFingers = utilities.preferences.evaintrnl.settingsGesture + blockAccidentalGestures = utilities.preferences.evaintrnl.blockAccidentalGestures + cameraControlEnabled = utilities.preferences.evaintrnl.cameraControlEnabled } func onDisappear() { NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.settingsGestureNotification.name, object: nil) utilities.preferences.evaintrnl.settingsGesture = settingsGestureFingers + utilities.preferences.evaintrnl.blockAccidentalGestures = blockAccidentalGestures + utilities.preferences.evaintrnl.cameraControlEnabled = cameraControlEnabled } } } diff --git a/Malachite/Views/PhotoPreviewView/PhotoPreviewView+Controls.swift b/Malachite/Views/PhotoPreviewView/PhotoPreviewView+Controls.swift index 7e17700..4642cdb 100644 --- a/Malachite/Views/PhotoPreviewView/PhotoPreviewView+Controls.swift +++ b/Malachite/Views/PhotoPreviewView/PhotoPreviewView+Controls.swift @@ -23,9 +23,9 @@ extension PhotoPreviewView { ] let buttonConfigs: [MalachiteViewUtils.buttonBuilder] = [ - MalachiteViewUtils.buttonBuilder(symbolName: "xmark", action: #selector(delegate.dismissView), dimensions: [ 30.0, 60.0 ], constraints: buttonConstraints[0], assign: { [self] button in delegate.dismissButton = button }), - MalachiteViewUtils.buttonBuilder(symbolName: "photo.on.rectangle", action: #selector(delegate.savePhotoWrapped), dimensions: [ 30.0, 60.0 ], constraints: buttonConstraints[1], assign: { [self] button in delegate.savePhotoButton = button }), - MalachiteViewUtils.buttonBuilder(symbolName: "square.and.arrow.up", action: #selector(delegate.sharePhoto), dimensions: [ 30.0, 60.0 ], constraints: buttonConstraints[2], assign: { [self] button in delegate.sharePhotoButton = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "xmark", action: #selector(delegate.dismissView), dimensions: [ 60.0 ], constraints: buttonConstraints[0], hidden: false, assign: { [self] button in delegate.dismissButton = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "photo.on.rectangle", action: #selector(delegate.savePhotoWrapped), dimensions: [ 60.0 ], constraints: buttonConstraints[1], hidden: false, assign: { [self] button in delegate.savePhotoButton = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "square.and.arrow.up", action: #selector(delegate.sharePhoto), dimensions: [ 60.0 ], constraints: buttonConstraints[2], hidden: false, assign: { [self] button in delegate.sharePhotoButton = button }), ] for config in buttonConfigs { From ee30c1958dc2600cc242ec89610e62de9a5ad6f7 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Thu, 4 Sep 2025 02:24:40 -0600 Subject: [PATCH 15/66] Return to a stub struct to fix build issues --- Malachite/Utilities/MalachiteIntentUtils.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Malachite/Utilities/MalachiteIntentUtils.swift b/Malachite/Utilities/MalachiteIntentUtils.swift index 124e6c4..1c59c62 100755 --- a/Malachite/Utilities/MalachiteIntentUtils.swift +++ b/Malachite/Utilities/MalachiteIntentUtils.swift @@ -21,7 +21,7 @@ struct MalachiteLaunchIntent: AppIntent { #if os(iOS) @available(iOS 18.0, *) struct MalachiteCaptureIntent: CameraCaptureIntent { - typealias AppContext = MalachitePreferences + typealias AppContext = MalachiteContext static let title: LocalizedStringResource = "appname.open" static let description = IntentDescription("appname.open.description") @@ -31,4 +31,9 @@ struct MalachiteCaptureIntent: CameraCaptureIntent { return .result() } } + +@available(iOS 18.0, *) +struct MalachiteContext: Codable { + // TODO +} #endif From 81a1067c78e89b2cd543b431419f007990e36fd6 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Thu, 4 Sep 2025 03:11:13 -0600 Subject: [PATCH 16/66] WidgetBundleWatch base SDK should be watchOS --- Malachite.xcodeproj/project.pbxproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Malachite.xcodeproj/project.pbxproj b/Malachite.xcodeproj/project.pbxproj index 020b7a2..071347f 100755 --- a/Malachite.xcodeproj/project.pbxproj +++ b/Malachite.xcodeproj/project.pbxproj @@ -1183,6 +1183,7 @@ OTHER_SWIFT_FLAGS = "-DAPP_EXTENSION"; PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).Malachite.WatchRemote.WidgetBundle"; PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "watchos watchsimulator"; SUPPORTS_MACCATALYST = NO; @@ -1226,6 +1227,7 @@ OTHER_SWIFT_FLAGS = "-DAPP_EXTENSION"; PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).Malachite.WatchRemote.WidgetBundle"; PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "watchos watchsimulator"; SUPPORTS_MACCATALYST = NO; @@ -1269,6 +1271,7 @@ OTHER_SWIFT_FLAGS = "-DAPP_EXTENSION"; PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).Malachite.WatchRemote.WidgetBundle"; PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "watchos watchsimulator"; SUPPORTS_MACCATALYST = NO; From da174b8dffaea0d23f01e2fe07f795ef6032ebce Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Wed, 10 Sep 2025 23:41:08 -0600 Subject: [PATCH 17/66] Update Xcode project to 16.3 Also some fixes in the Embed build information scripts --- Malachite.xcodeproj/project.pbxproj | 324 ++++++++++++++++++---------- 1 file changed, 208 insertions(+), 116 deletions(-) diff --git a/Malachite.xcodeproj/project.pbxproj b/Malachite.xcodeproj/project.pbxproj index 071347f..a5cdb11 100755 --- a/Malachite.xcodeproj/project.pbxproj +++ b/Malachite.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 77; + objectVersion = 90; objects = { /* Begin PBXBuildFile section */ @@ -233,47 +233,39 @@ /* Begin PBXCopyFilesBuildPhase section */ 7837C11A2E34C47D009396B0 /* Embed Foundation Extensions */ = { isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; dstPath = ""; - dstSubfolderSpec = 13; + dstSubfolder = PlugIns; files = ( 7837C1172E34C47D009396B0 /* WidgetBundleWatch.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; - runOnlyForDeploymentPostprocessing = 0; }; 78917F6A2B99AE73005E10FA /* Embed Foundation Extensions */ = { isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; dstPath = ""; - dstSubfolderSpec = 13; + dstSubfolder = PlugIns; files = ( 78917F692B99AE73005E10FA /* WidgetBundle.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; - runOnlyForDeploymentPostprocessing = 0; }; 78A9DD702CD55551002C131D /* Embed ExtensionKit Extensions */ = { isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; dstPath = "$(EXTENSIONS_FOLDER_PATH)"; - dstSubfolderSpec = 16; + dstSubfolder = Product; files = ( 78A9DD6A2CD55551002C131D /* CaptureBundle.appex in Embed ExtensionKit Extensions */, ); name = "Embed ExtensionKit Extensions"; - runOnlyForDeploymentPostprocessing = 0; }; 78B72BB62E332830002E2D4E /* Embed Watch Content */ = { isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; dstPath = "$(CONTENTS_FOLDER_PATH)/Watch"; - dstSubfolderSpec = 16; + dstSubfolder = Product; files = ( 78B72BB12E332830002E2D4E /* MalachiteWatch.app in Embed Watch Content */, ); name = "Embed Watch Content"; - runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ @@ -382,36 +374,28 @@ /* Begin PBXFrameworksBuildPhase section */ 7837C10A2E34C45B009396B0 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; files = ( 7837C10B2E34C45B009396B0 /* SwiftUI.framework in Frameworks */, 7837C10C2E34C45B009396B0 /* WidgetKit.framework in Frameworks */, ); - runOnlyForDeploymentPostprocessing = 0; }; 78917F562B99AE72005E10FA /* Frameworks */ = { isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; files = ( 78917F5E2B99AE72005E10FA /* SwiftUI.framework in Frameworks */, 78917F5C2B99AE72005E10FA /* WidgetKit.framework in Frameworks */, ); - runOnlyForDeploymentPostprocessing = 0; }; 78A9DD5E2CD55551002C131D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; files = ( 78A9DE312CD581F1002C131D /* LockedCameraCapture.framework in Frameworks */, ); - runOnlyForDeploymentPostprocessing = 0; }; 78B72BA42E33282E002E2D4E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ @@ -671,8 +655,6 @@ ); buildRules = ( ); - dependencies = ( - ); fileSystemSynchronizedGroups = ( 786DAD982E4F283500BE3137 /* Assets */, ); @@ -717,8 +699,6 @@ ); buildRules = ( ); - dependencies = ( - ); fileSystemSynchronizedGroups = ( 786DAD982E4F283500BE3137 /* Assets */, ); @@ -737,14 +717,10 @@ ); buildRules = ( ); - dependencies = ( - ); fileSystemSynchronizedGroups = ( 786DAD982E4F283500BE3137 /* Assets */, ); name = CaptureBundle; - packageProductDependencies = ( - ); productName = MalachiteCaptureBundle; productReference = 78A9DD612CD55551002C131D /* CaptureBundle.appex */; productType = "com.apple.product-type.extensionkit-extension"; @@ -769,8 +745,6 @@ 786DAD982E4F283500BE3137 /* Assets */, ); name = MalachiteWatch; - packageProductDependencies = ( - ); productName = "MalachiteWatch Watch App"; productReference = 78B72BA72E33282E002E2D4E /* MalachiteWatch.app */; productType = "com.apple.product-type.application"; @@ -807,7 +781,7 @@ Base, ); mainGroup = 785F08402B12D41100244EB4; - preferredProjectObjectVersion = 77; + preferredProjectObjectVersion = 90; productRefGroup = 785F084A2B12D41100244EB4 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -824,43 +798,33 @@ /* Begin PBXResourcesBuildPhase section */ 7837C10D2E34C45B009396B0 /* Resources */ = { isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; files = ( 7837C10E2E34C45B009396B0 /* Localizable.xcstrings in Resources */, ); - runOnlyForDeploymentPostprocessing = 0; }; 785F08472B12D41100244EB4 /* Resources */ = { isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; files = ( 78EC5D5F2E4EC110005044E2 /* Localizable.xcstrings in Resources */, 785F08592B12D41300244EB4 /* LaunchScreen.storyboard in Resources */, ); - runOnlyForDeploymentPostprocessing = 0; }; 78917F572B99AE72005E10FA /* Resources */ = { isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; files = ( 78CAC36A2CCD99B600A35AE8 /* Localizable.xcstrings in Resources */, ); - runOnlyForDeploymentPostprocessing = 0; }; 78A9DD5F2CD55551002C131D /* Resources */ = { isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; files = ( 78A9DE752CD5AE5F002C131D /* Localizable.xcstrings in Resources */, ); - runOnlyForDeploymentPostprocessing = 0; }; 78B72BA52E33282E002E2D4E /* Resources */ = { isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ @@ -868,60 +832,201 @@ 78329B0F2C22699A006D326F /* Embed build information */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); inputPaths = ( "${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}", ); name = "Embed build information "; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/bash; - shellScript = "eval \"${GCC_PREPROCESSOR_DEFINITIONS}\"\n\nPATH=/opt/homebrew/bin:/usr/local/bin:$PATH\nPLISTBUDDY=\"/usr/libexec/PlistBuddy\"\nINFOPLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n\n# If there is no git repo to pull this info from, this is set to undefined later.\nCFBUILDHASH=$(git --git-dir=\"${PROJECT_DIR}/.git\" --work-tree=\"${PROJECT_DIR}\" rev-parse --short HEAD)\nCFBUILDBRAN=$(git branch --show-current)\nCFBUILDDATE=$(date +'%Y-%m-%d-%H.%M.%S')\nif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && -z $(system_profiler SPHardwareDataType | grep 'MacVM1,1') ]]; then\n # If the build is not INTERNAL, these are set to redacted later.\n CFBUILDUSER=$(whoami)\n CFBUILDHOST=$(hostname)\nelif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && !(-z $(system_profiler SPHardwareDataType | grep 'MacVM1,1')) ]]; then\n # Xcode Cloud built this, but the hostname string is atrocious. Replace with these.\n CFBUILDUSER=\"evaluna (App Store Connect)\"\n CFBUILDHOST=\"Xcode Cloud\"\nfi\n\nif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && -z $(gpg --list-secret-keys | grep 4A5B8C2A065654E24DE50FF03716ACDC524F1879) ]]; then\n echo \"Error: INTERNAL build type detected but Eva's GPG key was not found.\"\n echo \"Error: See 'Embed build information' in Build Phases for a solution\"\n # Be aware that I use the INTERNAL target to gate off any unstable/unready features. By removing this line, \n # you are acknowledging that you will not receive support for any issues encountered with Malachite until you \n # are running DEBUG or RELEASE. If you just want a DEBUG build, change the Build Configuration to Debug in \n # Product > Scheme > Edit Scheme...\n if [[ -z $(system_profiler SPHardwareDataType | grep 'MacVM1,1') ]]; then\n exit\n else\n # This line of code should keep internal TestFlight builds as INTERNAL, and external ones as DEBUG\n if [[ $(git --git-dir=\"${PROJECT_DIR}/.git\" rev-parse --abbrev-ref HEAD) != *\"dev\"* ]]; then\n CFBUILDTYPE=\"DEBUG\"\n fi\n fi\nfi\n\n\n\n# Print all of the variables in the build log. Useful for debugging\necho \"\"\necho \"CFBUILDHASH = ${CFBUILDHASH}\"\necho \"CFBUILDBRAN = ${CFBUILDBRAN}\"\necho \"CFBUILDDATE = ${CFBUILDDATE}\"\necho \"CFBUILDTYPE = ${CFBUILDTYPE}\"\nif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" ]]; then\n echo \"CFBUILDUSER = ${CFBUILDUSER}\"\n echo \"CFBUILDHOST = ${CFBUILDHOST}\"\nfi\necho \"INFOPLIST = ${INFOPLIST}\"\n\n# Make sure the key is present in the plist before trying to set it.\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildHash' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildHash string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildBran' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildBran string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildDate' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildDate string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildType' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildType string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildUser' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildUser string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildHost' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildHost string\" \"${INFOPLIST}\"; fi\n\n# Fallback values, either to fill in the gaps or prevent app crashes\nif [ -z \"$CFBUILDHASH\" ]; then CFBUILDHASH=undefined; fi\nif [ -z \"$CFBUILDBRAN\" ]; then CFBUILDBRAN=undefined; fi\nif [ -z \"$CFBUILDTYPE\" ]; then CFBUILDTYPE=debug; fi\nif [ -z \"$CFBUILDUSER\" ]; then CFBUILDUSER=redacted; fi\nif [ -z \"$CFBUILDHOST\" ]; then CFBUILDHOST=redacted; fi\n\n# Set the values in the Info.plist\n$PLISTBUDDY -c \"Set :CFBuildHash $CFBUILDHASH\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildBran $CFBUILDBRAN\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildDate $CFBUILDDATE\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildType $CFBUILDTYPE\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildUser $CFBUILDUSER\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildHost $CFBUILDHOST\" \"${INFOPLIST}\"\n"; + shellScript = ( + "eval \"${GCC_PREPROCESSOR_DEFINITIONS}\"", + "", + "PATH=/opt/homebrew/bin:/usr/local/bin:$PATH", + "PLISTBUDDY=\"/usr/libexec/PlistBuddy\"", + "INFOPLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"", + "", + "# If there is no git repo to pull this info from, this is set to undefined later.", + "CFBUILDHASH=$(git --git-dir=\"${PROJECT_DIR}/.git\" --work-tree=\"${PROJECT_DIR}\" rev-parse --short HEAD)", + "CFBUILDBRAN=$(git branch --show-current)", + "CFBUILDDATE=$(date +'%Y-%m-%d-%H.%M.%S')", + "if [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && -z $(system_profiler SPHardwareDataType | grep 'MacVM1,1') ]]; then", + " # If the build is not INTERNAL, these are set to redacted later.", + " CFBUILDUSER=$(whoami)", + " CFBUILDHOST=$(hostname)", + "elif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && !(-z $(system_profiler SPHardwareDataType | grep 'MacVM1,1')) ]]; then", + " # Xcode Cloud built this, but the hostname string is atrocious. Replace with these.", + " CFBUILDUSER=\"evaluna (App Store Connect)\"", + " CFBUILDHOST=\"Xcode Cloud\"", + "fi", + "", + "if [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && -z $(gpg --list-secret-keys | grep 4A5B8C2A065654E24DE50FF03716ACDC524F1879) ]]; then", + " echo \"Error: INTERNAL build type detected but Eva's GPG key was not found.\"", + " echo \"Error: See 'Embed build information' in Build Phases for a solution\"", + " # Be aware that I use the INTERNAL target to gate off any unstable/unready features. By removing this line, ", + " # you are acknowledging that you will not receive support for any issues encountered with Malachite until you ", + " # are running DEBUG or RELEASE. If you just want a DEBUG build, change the Build Configuration to Debug in ", + " # Product > Scheme > Edit Scheme...", + " if [[ -z $(system_profiler SPHardwareDataType | grep 'MacVM1,1') ]]; then", + " exit", + " else", + " # This line of code should keep internal TestFlight builds as INTERNAL, and external ones as DEBUG", + " if [[ $(git --git-dir=\"${PROJECT_DIR}/.git\" rev-parse --abbrev-ref HEAD) != *\"dev\"* ]]; then", + " CFBUILDTYPE=\"DEBUG\"", + " fi", + " fi", + "fi", + "", + "", + "", + "# Print all of the variables in the build log. Useful for debugging", + "echo \"\"", + "echo \"CFBUILDHASH = ${CFBUILDHASH}\"", + "echo \"CFBUILDBRAN = ${CFBUILDBRAN}\"", + "echo \"CFBUILDDATE = ${CFBUILDDATE}\"", + "echo \"CFBUILDTYPE = ${CFBUILDTYPE}\"", + "if [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" ]]; then", + " echo \"CFBUILDUSER = ${CFBUILDUSER}\"", + " echo \"CFBUILDHOST = ${CFBUILDHOST}\"", + "fi", + "echo \"INFOPLIST = ${INFOPLIST}\"", + "", + "# Make sure the key is present in the plist before trying to set it.", + "TMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildHash' \"${INFOPLIST}\" 2>/dev/null)", + "if [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildHash string\" \"${INFOPLIST}\"; fi", + "TMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildBran' \"${INFOPLIST}\" 2>/dev/null)", + "if [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildBran string\" \"${INFOPLIST}\"; fi", + "TMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildDate' \"${INFOPLIST}\" 2>/dev/null)", + "if [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildDate string\" \"${INFOPLIST}\"; fi", + "TMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildType' \"${INFOPLIST}\" 2>/dev/null)", + "if [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildType string\" \"${INFOPLIST}\"; fi", + "TMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildUser' \"${INFOPLIST}\" 2>/dev/null)", + "if [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildUser string\" \"${INFOPLIST}\"; fi", + "TMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildHost' \"${INFOPLIST}\" 2>/dev/null)", + "if [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildHost string\" \"${INFOPLIST}\"; fi", + "", + "# Fallback values, either to fill in the gaps or prevent app crashes", + "if [ -z \"$CFBUILDHASH\" ]; then CFBUILDHASH=undefined; fi", + "if [ -z \"$CFBUILDBRAN\" ]; then CFBUILDBRAN=undefined; fi", + "if [ -z \"$CFBUILDDATE\" ]; then CFBUILDDATE=undefined; fi", + "if [ -z \"$CFBUILDTYPE\" ]; then CFBUILDTYPE=debug; fi", + "if [ -z \"$CFBUILDUSER\" ]; then CFBUILDUSER=redacted; fi", + "if [ -z \"$CFBUILDHOST\" ]; then CFBUILDHOST=redacted; fi", + "", + "# Set the values in the Info.plist", + "$PLISTBUDDY -c \"Set :CFBuildHash $CFBUILDHASH\" \"${INFOPLIST}\"", + "$PLISTBUDDY -c \"Set :CFBuildBran $CFBUILDBRAN\" \"${INFOPLIST}\"", + "$PLISTBUDDY -c \"Set :CFBuildDate $CFBUILDDATE\" \"${INFOPLIST}\"", + "$PLISTBUDDY -c \"Set :CFBuildType $CFBUILDTYPE\" \"${INFOPLIST}\"", + "$PLISTBUDDY -c \"Set :CFBuildUser $CFBUILDUSER\" \"${INFOPLIST}\"", + "$PLISTBUDDY -c \"Set :CFBuildHost $CFBUILDHOST\" \"${INFOPLIST}\"", + "", + ); }; 78EC5D602E4EC1AD005044E2 /* Embed build information */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); inputPaths = ( "${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}", ); name = "Embed build information"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/bash; - shellScript = "eval \"${GCC_PREPROCESSOR_DEFINITIONS}\"\n\nPATH=/opt/homebrew/bin:/usr/local/bin:$PATH\nPLISTBUDDY=\"/usr/libexec/PlistBuddy\"\nINFOPLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n\n# If there is no git repo to pull this info from, this is set to undefined later.\nCFBUILDHASH=$(git --git-dir=\"${PROJECT_DIR}/.git\" --work-tree=\"${PROJECT_DIR}\" rev-parse --short HEAD)\nCFBUILDBRAN=$(git branch --show-current)\nCFBUILDDATE=$(date +'%Y-%m-%d-%H.%M.%S')\nif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && -z $(system_profiler SPHardwareDataType | grep 'MacVM1,1') ]]; then\n # If the build is not INTERNAL, these are set to redacted later.\n CFBUILDUSER=$(whoami)\n CFBUILDHOST=$(hostname)\nelif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && !(-z $(system_profiler SPHardwareDataType | grep 'MacVM1,1')) ]]; then\n # Xcode Cloud built this, but the hostname string is atrocious. Replace with these.\n CFBUILDUSER=\"evaluna (App Store Connect)\"\n CFBUILDHOST=\"Xcode Cloud\"\nfi\n\nif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && -z $(gpg --list-secret-keys | grep 4A5B8C2A065654E24DE50FF03716ACDC524F1879) ]]; then\n echo \"Error: INTERNAL build type detected but Eva's GPG key was not found.\"\n echo \"Error: See 'Embed build information' in Build Phases for a solution\"\n # Be aware that I use the INTERNAL target to gate off any unstable/unready features. By removing this line, \n # you are acknowledging that you will not receive support for any issues encountered with Malachite until you \n # are running DEBUG or RELEASE. If you just want a DEBUG build, change the Build Configuration to Debug in \n # Product > Scheme > Edit Scheme...\n if [[ -z $(system_profiler SPHardwareDataType | grep 'MacVM1,1') ]]; then\n exit\n else\n # This line of code should keep internal TestFlight builds as INTERNAL, and external ones as DEBUG\n if [[ $(git --git-dir=\"${PROJECT_DIR}/.git\" rev-parse --abbrev-ref HEAD) != *\"dev\"* ]]; then\n CFBUILDTYPE=\"DEBUG\"\n fi\n fi\nfi\n\n\n\n# Print all of the variables in the build log. Useful for debugging\necho \"\"\necho \"CFBUILDHASH = ${CFBUILDHASH}\"\necho \"CFBUILDBRAN = ${CFBUILDBRAN}\"\necho \"CFBUILDDATE = ${CFBUILDDATE}\"\necho \"CFBUILDTYPE = ${CFBUILDTYPE}\"\nif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" ]]; then\n echo \"CFBUILDUSER = ${CFBUILDUSER}\"\n echo \"CFBUILDHOST = ${CFBUILDHOST}\"\nfi\necho \"INFOPLIST = ${INFOPLIST}\"\n\n# Make sure the key is present in the plist before trying to set it.\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildHash' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildHash string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildBran' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildBran string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildDate' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildDate string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildType' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildType string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildUser' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildUser string\" \"${INFOPLIST}\"; fi\nTMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildHost' \"${INFOPLIST}\" 2>/dev/null)\nif [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildHost string\" \"${INFOPLIST}\"; fi\n\n# Fallback values, either to fill in the gaps or prevent app crashes\nif [ -z \"$CFBUILDHASH\" ]; then CFBUILDHASH=undefined; fi\nif [ -z \"$CFBUILDBRAN\" ]; then CFBUILDBRAN=undefined; fi\nif [ -z \"$CFBUILDTYPE\" ]; then CFBUILDTYPE=debug; fi\nif [ -z \"$CFBUILDUSER\" ]; then CFBUILDUSER=redacted; fi\nif [ -z \"$CFBUILDHOST\" ]; then CFBUILDHOST=redacted; fi\n\n# Set the values in the Info.plist\n$PLISTBUDDY -c \"Set :CFBuildHash $CFBUILDHASH\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildBran $CFBUILDBRAN\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildDate $CFBUILDDATE\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildType $CFBUILDTYPE\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildUser $CFBUILDUSER\" \"${INFOPLIST}\"\n$PLISTBUDDY -c \"Set :CFBuildHost $CFBUILDHOST\" \"${INFOPLIST}\"\n"; + shellScript = ( + "eval \"${GCC_PREPROCESSOR_DEFINITIONS}\"", + "", + "PATH=/opt/homebrew/bin:/usr/local/bin:$PATH", + "PLISTBUDDY=\"/usr/libexec/PlistBuddy\"", + "INFOPLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"", + "", + "# If there is no git repo to pull this info from, this is set to undefined later.", + "CFBUILDHASH=$(git --git-dir=\"${PROJECT_DIR}/.git\" --work-tree=\"${PROJECT_DIR}\" rev-parse --short HEAD)", + "CFBUILDBRAN=$(git branch --show-current)", + "CFBUILDDATE=$(date +'%Y-%m-%d-%H.%M.%S')", + "if [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && -z $(system_profiler SPHardwareDataType | grep 'MacVM1,1') ]]; then", + " # If the build is not INTERNAL, these are set to redacted later.", + " CFBUILDUSER=$(whoami)", + " CFBUILDHOST=$(hostname)", + "elif [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && !(-z $(system_profiler SPHardwareDataType | grep 'MacVM1,1')) ]]; then", + " # Xcode Cloud built this, but the hostname string is atrocious. Replace with these.", + " CFBUILDUSER=\"evaluna (App Store Connect)\"", + " CFBUILDHOST=\"Xcode Cloud\"", + "fi", + "", + "if [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" && -z $(gpg --list-secret-keys | grep 4A5B8C2A065654E24DE50FF03716ACDC524F1879) ]]; then", + " echo \"Error: INTERNAL build type detected but Eva's GPG key was not found.\"", + " echo \"Error: See 'Embed build information' in Build Phases for a solution\"", + " # Be aware that I use the INTERNAL target to gate off any unstable/unready features. By removing this line, ", + " # you are acknowledging that you will not receive support for any issues encountered with Malachite until you ", + " # are running DEBUG or RELEASE. If you just want a DEBUG build, change the Build Configuration to Debug in ", + " # Product > Scheme > Edit Scheme...", + " if [[ -z $(system_profiler SPHardwareDataType | grep 'MacVM1,1') ]]; then", + " exit", + " else", + " # This line of code should keep internal TestFlight builds as INTERNAL, and external ones as DEBUG", + " if [[ $(git --git-dir=\"${PROJECT_DIR}/.git\" rev-parse --abbrev-ref HEAD) != *\"dev\"* ]]; then", + " CFBUILDTYPE=\"DEBUG\"", + " fi", + " fi", + "fi", + "", + "", + "", + "# Print all of the variables in the build log. Useful for debugging", + "echo \"\"", + "echo \"CFBUILDHASH = ${CFBUILDHASH}\"", + "echo \"CFBUILDBRAN = ${CFBUILDBRAN}\"", + "echo \"CFBUILDDATE = ${CFBUILDDATE}\"", + "echo \"CFBUILDTYPE = ${CFBUILDTYPE}\"", + "if [[ \"${CFBUILDTYPE}\" == \"INTERNAL\" ]]; then", + " echo \"CFBUILDUSER = ${CFBUILDUSER}\"", + " echo \"CFBUILDHOST = ${CFBUILDHOST}\"", + "fi", + "echo \"INFOPLIST = ${INFOPLIST}\"", + "", + "# Make sure the key is present in the plist before trying to set it.", + "TMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildHash' \"${INFOPLIST}\" 2>/dev/null)", + "if [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildHash string\" \"${INFOPLIST}\"; fi", + "TMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildBran' \"${INFOPLIST}\" 2>/dev/null)", + "if [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildBran string\" \"${INFOPLIST}\"; fi", + "TMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildDate' \"${INFOPLIST}\" 2>/dev/null)", + "if [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildDate string\" \"${INFOPLIST}\"; fi", + "TMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildType' \"${INFOPLIST}\" 2>/dev/null)", + "if [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildType string\" \"${INFOPLIST}\"; fi", + "TMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildUser' \"${INFOPLIST}\" 2>/dev/null)", + "if [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildUser string\" \"${INFOPLIST}\"; fi", + "TMP=$(/usr/libexec/PlistBuddy -c 'print :CFBuildHost' \"${INFOPLIST}\" 2>/dev/null)", + "if [ -z \"$TMP\" ]; then $PLISTBUDDY -c \"Add :CFBuildHost string\" \"${INFOPLIST}\"; fi", + "", + "# Fallback values, either to fill in the gaps or prevent app crashes", + "if [ -z \"$CFBUILDHASH\" ]; then CFBUILDHASH=undefined; fi", + "if [ -z \"$CFBUILDBRAN\" ]; then CFBUILDBRAN=undefined; fi", + "if [ -z \"$CFBUILDDATE\" ]; then CFBUILDDATE=undefined; fi", + "if [ -z \"$CFBUILDTYPE\" ]; then CFBUILDTYPE=debug; fi", + "if [ -z \"$CFBUILDUSER\" ]; then CFBUILDUSER=redacted; fi", + "if [ -z \"$CFBUILDHOST\" ]; then CFBUILDHOST=redacted; fi", + "", + "# Set the values in the Info.plist", + "$PLISTBUDDY -c \"Set :CFBuildHash $CFBUILDHASH\" \"${INFOPLIST}\"", + "$PLISTBUDDY -c \"Set :CFBuildBran $CFBUILDBRAN\" \"${INFOPLIST}\"", + "$PLISTBUDDY -c \"Set :CFBuildDate $CFBUILDDATE\" \"${INFOPLIST}\"", + "$PLISTBUDDY -c \"Set :CFBuildType $CFBUILDTYPE\" \"${INFOPLIST}\"", + "$PLISTBUDDY -c \"Set :CFBuildUser $CFBUILDUSER\" \"${INFOPLIST}\"", + "$PLISTBUDDY -c \"Set :CFBuildHost $CFBUILDHOST\" \"${INFOPLIST}\"", + "", + ); }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 7837C1052E34C45B009396B0 /* Sources */ = { isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; files = ( 786DAE712E4F28B700BE3137 /* ControlCenterWidget.swift in Sources */, 786DAE722E4F28B700BE3137 /* LockScreenWidget.swift in Sources */, 786DAE732E4F28B700BE3137 /* MalachiteWidgetBundle.swift in Sources */, 786DAE672E4F28B100BE3137 /* MalachiteIntentUtils.swift in Sources */, ); - runOnlyForDeploymentPostprocessing = 0; }; 785F08452B12D41100244EB4 /* Sources */ = { isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; files = ( 788262BB2E5170F4000085AC /* AboutView+Info.swift in Sources */, 78E1707D2E51CC63009BEF2F /* CompatibilityView+Resolutions.swift in Sources */, @@ -979,11 +1084,9 @@ 78E1707F2E51CE45009BEF2F /* CompatibilityView+Format.swift in Sources */, 785F084F2B12D41100244EB4 /* SceneDelegate.swift in Sources */, ); - runOnlyForDeploymentPostprocessing = 0; }; 78917F552B99AE72005E10FA /* Sources */ = { isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; files = ( 788262BC2E5170F4000085AC /* AboutView+Info.swift in Sources */, 78E1707B2E51CC63009BEF2F /* CompatibilityView+Resolutions.swift in Sources */, @@ -1041,11 +1144,9 @@ 78E170812E51CE45009BEF2F /* CompatibilityView+Format.swift in Sources */, 786DAE402E4F28A600BE3137 /* CameraView.swift in Sources */, ); - runOnlyForDeploymentPostprocessing = 0; }; 78A9DD5D2CD55551002C131D /* Sources */ = { isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; files = ( 786DAE232E4F28A600BE3137 /* DeveloperView.swift in Sources */, 788262782E5130DB000085AC /* SettingsView+UI.swift in Sources */, @@ -1101,16 +1202,13 @@ 786DAE2B2E4F28A600BE3137 /* SettingsView.swift in Sources */, 786DAE2C2E4F28A600BE3137 /* CameraView.swift in Sources */, ); - runOnlyForDeploymentPostprocessing = 0; }; 78B72BA32E33282E002E2D4E /* Sources */ = { isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; files = ( 786DAE7D2E4F28BF00BE3137 /* ContentView.swift in Sources */, 786DAE7E2E4F28BF00BE3137 /* MalachiteWatchApp.swift in Sources */, ); - runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ @@ -1154,7 +1252,7 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ - 7837C1122E34C45B009396B0 /* Debug */ = { + 7837C1122E34C45B009396B0 /* Debug configuration for PBXNativeTarget "WidgetBundleWatch" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1198,7 +1296,7 @@ }; name = Debug; }; - 7837C1132E34C45B009396B0 /* Internal */ = { + 7837C1132E34C45B009396B0 /* Internal configuration for PBXNativeTarget "WidgetBundleWatch" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1242,7 +1340,7 @@ }; name = Internal; }; - 7837C1142E34C45B009396B0 /* Release */ = { + 7837C1142E34C45B009396B0 /* Release configuration for PBXNativeTarget "WidgetBundleWatch" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1286,7 +1384,7 @@ }; name = Release; }; - 785F085B2B12D41300244EB4 /* Debug */ = { + 785F085B2B12D41300244EB4 /* Debug configuration for PBXProject "Malachite" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1352,7 +1450,7 @@ }; name = Debug; }; - 785F085C2B12D41300244EB4 /* Release */ = { + 785F085C2B12D41300244EB4 /* Release configuration for PBXProject "Malachite" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1411,7 +1509,7 @@ }; name = Release; }; - 785F085E2B12D41300244EB4 /* Debug */ = { + 785F085E2B12D41300244EB4 /* Debug configuration for PBXNativeTarget "Malachite" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1471,7 +1569,7 @@ }; name = Debug; }; - 785F085F2B12D41300244EB4 /* Release */ = { + 785F085F2B12D41300244EB4 /* Release configuration for PBXNativeTarget "Malachite" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1528,7 +1626,7 @@ }; name = Release; }; - 78917F6B2B99AE73005E10FA /* Debug */ = { + 78917F6B2B99AE73005E10FA /* Debug configuration for PBXNativeTarget "WidgetBundle" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1570,7 +1668,7 @@ }; name = Debug; }; - 78917F6C2B99AE73005E10FA /* Release */ = { + 78917F6C2B99AE73005E10FA /* Release configuration for PBXNativeTarget "WidgetBundle" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1612,7 +1710,7 @@ }; name = Release; }; - 78A9DD6D2CD55551002C131D /* Debug */ = { + 78A9DD6D2CD55551002C131D /* Debug configuration for PBXNativeTarget "CaptureBundle" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1652,7 +1750,7 @@ }; name = Debug; }; - 78A9DD6E2CD55551002C131D /* Internal */ = { + 78A9DD6E2CD55551002C131D /* Internal configuration for PBXNativeTarget "CaptureBundle" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1692,7 +1790,7 @@ }; name = Internal; }; - 78A9DD6F2CD55551002C131D /* Release */ = { + 78A9DD6F2CD55551002C131D /* Release configuration for PBXNativeTarget "CaptureBundle" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1732,7 +1830,7 @@ }; name = Release; }; - 78B72BB32E332830002E2D4E /* Debug */ = { + 78B72BB32E332830002E2D4E /* Debug configuration for PBXNativeTarget "MalachiteWatch" */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -1773,7 +1871,7 @@ }; name = Debug; }; - 78B72BB42E332830002E2D4E /* Internal */ = { + 78B72BB42E332830002E2D4E /* Internal configuration for PBXNativeTarget "MalachiteWatch" */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -1814,7 +1912,7 @@ }; name = Internal; }; - 78B72BB52E332830002E2D4E /* Release */ = { + 78B72BB52E332830002E2D4E /* Release configuration for PBXNativeTarget "MalachiteWatch" */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -1855,7 +1953,7 @@ }; name = Release; }; - 78F9DA642CBCDDB900A99638 /* Internal */ = { + 78F9DA642CBCDDB900A99638 /* Internal configuration for PBXProject "Malachite" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1922,7 +2020,7 @@ }; name = Internal; }; - 78F9DA652CBCDDB900A99638 /* Internal */ = { + 78F9DA652CBCDDB900A99638 /* Internal configuration for PBXNativeTarget "Malachite" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1982,7 +2080,7 @@ }; name = Internal; }; - 78F9DA662CBCDDB900A99638 /* Internal */ = { + 78F9DA662CBCDDB900A99638 /* Internal configuration for PBXNativeTarget "WidgetBundle" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -2030,61 +2128,55 @@ 7837C1112E34C45B009396B0 /* Build configuration list for PBXNativeTarget "WidgetBundleWatch" */ = { isa = XCConfigurationList; buildConfigurations = ( - 7837C1122E34C45B009396B0 /* Debug */, - 7837C1132E34C45B009396B0 /* Internal */, - 7837C1142E34C45B009396B0 /* Release */, + 7837C1122E34C45B009396B0 /* Debug configuration for PBXNativeTarget "WidgetBundleWatch" */, + 7837C1132E34C45B009396B0 /* Internal configuration for PBXNativeTarget "WidgetBundleWatch" */, + 7837C1142E34C45B009396B0 /* Release configuration for PBXNativeTarget "WidgetBundleWatch" */, ); - defaultConfigurationIsVisible = 0; defaultConfigurationName = Internal; }; 785F08442B12D41100244EB4 /* Build configuration list for PBXProject "Malachite" */ = { isa = XCConfigurationList; buildConfigurations = ( - 785F085B2B12D41300244EB4 /* Debug */, - 78F9DA642CBCDDB900A99638 /* Internal */, - 785F085C2B12D41300244EB4 /* Release */, + 785F085B2B12D41300244EB4 /* Debug configuration for PBXProject "Malachite" */, + 78F9DA642CBCDDB900A99638 /* Internal configuration for PBXProject "Malachite" */, + 785F085C2B12D41300244EB4 /* Release configuration for PBXProject "Malachite" */, ); - defaultConfigurationIsVisible = 0; defaultConfigurationName = Internal; }; 785F085D2B12D41300244EB4 /* Build configuration list for PBXNativeTarget "Malachite" */ = { isa = XCConfigurationList; buildConfigurations = ( - 785F085E2B12D41300244EB4 /* Debug */, - 78F9DA652CBCDDB900A99638 /* Internal */, - 785F085F2B12D41300244EB4 /* Release */, + 785F085E2B12D41300244EB4 /* Debug configuration for PBXNativeTarget "Malachite" */, + 78F9DA652CBCDDB900A99638 /* Internal configuration for PBXNativeTarget "Malachite" */, + 785F085F2B12D41300244EB4 /* Release configuration for PBXNativeTarget "Malachite" */, ); - defaultConfigurationIsVisible = 0; defaultConfigurationName = Internal; }; 78917F6D2B99AE73005E10FA /* Build configuration list for PBXNativeTarget "WidgetBundle" */ = { isa = XCConfigurationList; buildConfigurations = ( - 78917F6B2B99AE73005E10FA /* Debug */, - 78F9DA662CBCDDB900A99638 /* Internal */, - 78917F6C2B99AE73005E10FA /* Release */, + 78917F6B2B99AE73005E10FA /* Debug configuration for PBXNativeTarget "WidgetBundle" */, + 78F9DA662CBCDDB900A99638 /* Internal configuration for PBXNativeTarget "WidgetBundle" */, + 78917F6C2B99AE73005E10FA /* Release configuration for PBXNativeTarget "WidgetBundle" */, ); - defaultConfigurationIsVisible = 0; defaultConfigurationName = Internal; }; 78A9DD6C2CD55551002C131D /* Build configuration list for PBXNativeTarget "CaptureBundle" */ = { isa = XCConfigurationList; buildConfigurations = ( - 78A9DD6D2CD55551002C131D /* Debug */, - 78A9DD6E2CD55551002C131D /* Internal */, - 78A9DD6F2CD55551002C131D /* Release */, + 78A9DD6D2CD55551002C131D /* Debug configuration for PBXNativeTarget "CaptureBundle" */, + 78A9DD6E2CD55551002C131D /* Internal configuration for PBXNativeTarget "CaptureBundle" */, + 78A9DD6F2CD55551002C131D /* Release configuration for PBXNativeTarget "CaptureBundle" */, ); - defaultConfigurationIsVisible = 0; defaultConfigurationName = Internal; }; 78B72BB22E332830002E2D4E /* Build configuration list for PBXNativeTarget "MalachiteWatch" */ = { isa = XCConfigurationList; buildConfigurations = ( - 78B72BB32E332830002E2D4E /* Debug */, - 78B72BB42E332830002E2D4E /* Internal */, - 78B72BB52E332830002E2D4E /* Release */, + 78B72BB32E332830002E2D4E /* Debug configuration for PBXNativeTarget "MalachiteWatch" */, + 78B72BB42E332830002E2D4E /* Internal configuration for PBXNativeTarget "MalachiteWatch" */, + 78B72BB52E332830002E2D4E /* Release configuration for PBXNativeTarget "MalachiteWatch" */, ); - defaultConfigurationIsVisible = 0; defaultConfigurationName = Internal; }; /* End XCConfigurationList section */ From 5707cd1835711bc10247a761479b6007186091bc Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Sun, 21 Sep 2025 16:56:17 -0600 Subject: [PATCH 18/66] Massive CameraUtils refactor I'm committing this before what I am about to do breaks it. Currently, everything is still working fine. If you are installing Malachite for the first time, install the previous build first and then update. This build does not have working requests for camera access or photo library additions, so the app will die. --- Malachite.xcodeproj/project.pbxproj | 34 ++- .../CameraUtils/Camera+Bringup.swift | 82 +++++- .../Utilities/CameraUtils/Camera+Input.swift | 129 ++++++++++ Malachite/Utilities/CameraUtils/Camera.swift | 42 ++- .../CompatibilityUtils/Compatibility.swift | 74 ++++++ .../Utilities/InitUtils/Init+Internal.swift | 22 +- Malachite/Utilities/InitUtils/Init.swift | 11 +- .../Utilities/MalachiteFunctionUtils.swift | 173 ++----------- .../MalachitePreferences.swift | 13 +- .../MalachitePreferencesUtils.swift | 2 + .../Preferences+Extension.swift | 25 +- .../CameraView/CameraView+Controls.swift | 71 ++++-- .../CameraView/CameraView+Notifications.swift | 11 +- .../CameraView/CameraView+Overrides.swift | 4 +- .../Views/CameraView/CameraView+Preview.swift | 39 ++- Malachite/Views/CameraView/CameraView.swift | 240 +++++------------- .../SettingsView/SettingsView+Photo.swift | 17 +- 17 files changed, 586 insertions(+), 403 deletions(-) create mode 100644 Malachite/Utilities/CameraUtils/Camera+Input.swift create mode 100644 Malachite/Utilities/CompatibilityUtils/Compatibility.swift diff --git a/Malachite.xcodeproj/project.pbxproj b/Malachite.xcodeproj/project.pbxproj index a5cdb11..f13861b 100755 --- a/Malachite.xcodeproj/project.pbxproj +++ b/Malachite.xcodeproj/project.pbxproj @@ -10,6 +10,9 @@ 78145F782E65135C003AEE51 /* Init.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78145F772E651357003AEE51 /* Init.swift */; }; 78145F792E65135C003AEE51 /* Init.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78145F772E651357003AEE51 /* Init.swift */; }; 78145F7A2E65135C003AEE51 /* Init.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78145F772E651357003AEE51 /* Init.swift */; }; + 7818E23C2E80971C0046F621 /* Compatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7818E23B2E8097180046F621 /* Compatibility.swift */; }; + 7818E23D2E80971C0046F621 /* Compatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7818E23B2E8097180046F621 /* Compatibility.swift */; }; + 7818E23E2E80971C0046F621 /* Compatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7818E23B2E8097180046F621 /* Compatibility.swift */; }; 7824CAED2E62CDDE0072870F /* CameraView+Overrides.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7824CAEC2E62CDD90072870F /* CameraView+Overrides.swift */; }; 7824CAEE2E62CDDE0072870F /* CameraView+Overrides.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7824CAEC2E62CDD90072870F /* CameraView+Overrides.swift */; }; 7824CAEF2E62CDDE0072870F /* CameraView+Overrides.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7824CAEC2E62CDD90072870F /* CameraView+Overrides.swift */; }; @@ -105,6 +108,9 @@ 786DAE782E4F28B900BE3137 /* MalachiteCaptureBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE762E4F28B900BE3137 /* MalachiteCaptureBundle.swift */; }; 786DAE7D2E4F28BF00BE3137 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE792E4F28BF00BE3137 /* ContentView.swift */; }; 786DAE7E2E4F28BF00BE3137 /* MalachiteWatchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE7B2E4F28BF00BE3137 /* MalachiteWatchApp.swift */; }; + 78729C562E7368F3001027E9 /* Camera+Input.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78729C552E7368F0001027E9 /* Camera+Input.swift */; }; + 78729C572E7368F3001027E9 /* Camera+Input.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78729C552E7368F0001027E9 /* Camera+Input.swift */; }; + 78729C582E7368F3001027E9 /* Camera+Input.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78729C552E7368F0001027E9 /* Camera+Input.swift */; }; 787B1CAA2B8B095E000AFECC /* Malachite.docc in Sources */ = {isa = PBXBuildFile; fileRef = 787B1CA92B8B095E000AFECC /* Malachite.docc */; }; 788262602E513080000085AC /* SettingsView+About.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882625F2E51307B000085AC /* SettingsView+About.swift */; }; 788262612E513080000085AC /* SettingsView+About.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882625F2E51307B000085AC /* SettingsView+About.swift */; }; @@ -272,6 +278,7 @@ /* Begin PBXFileReference section */ 78145F772E651357003AEE51 /* Init.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Init.swift; sourceTree = ""; }; 78163D8D2CCB7BAE00146126 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; + 7818E23B2E8097180046F621 /* Compatibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Compatibility.swift; sourceTree = ""; }; 7824CAEC2E62CDD90072870F /* CameraView+Overrides.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraView+Overrides.swift"; sourceTree = ""; }; 7824CAF02E62E4920072870F /* Camera+Bringup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Camera+Bringup.swift"; sourceTree = ""; }; 782FC7752B2DAFAB007709C1 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; @@ -316,6 +323,7 @@ 786DAE792E4F28BF00BE3137 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 786DAE7A2E4F28BF00BE3137 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 786DAE7B2E4F28BF00BE3137 /* MalachiteWatchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteWatchApp.swift; sourceTree = ""; }; + 78729C552E7368F0001027E9 /* Camera+Input.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Camera+Input.swift"; sourceTree = ""; }; 787B1CA92B8B095E000AFECC /* Malachite.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = Malachite.docc; sourceTree = ""; }; 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = Malachite/Localizable.xcstrings; sourceTree = SOURCE_ROOT; }; 7882625F2E51307B000085AC /* SettingsView+About.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsView+About.swift"; sourceTree = ""; }; @@ -400,6 +408,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 7818E23A2E8097110046F621 /* CompatibilityUtils */ = { + isa = PBXGroup; + children = ( + 7818E23B2E8097180046F621 /* Compatibility.swift */, + ); + path = CompatibilityUtils; + sourceTree = ""; + }; 785F08402B12D41100244EB4 = { isa = PBXGroup; children = ( @@ -453,6 +469,7 @@ children = ( 7866F5CD2E6299A0009AC9BF /* Camera.swift */, 7824CAF02E62E4920072870F /* Camera+Bringup.swift */, + 78729C552E7368F0001027E9 /* Camera+Input.swift */, ); path = CameraUtils; sourceTree = ""; @@ -505,8 +522,8 @@ 786DAE4B2E4F28B100BE3137 /* Utilities */ = { isa = PBXGroup; children = ( + 7818E23A2E8097110046F621 /* CompatibilityUtils */, 78C2EFBE2E6971CB00C6DD79 /* InitUtils */, - 7866F5D92E62ADA0009AC9BF /* GameUtils */, 7866F5D52E62A5F7009AC9BF /* temputils.swift */, 7866F5CC2E62999B009AC9BF /* CameraUtils */, 786DAE432E4F28B100BE3137 /* PreferenceUtils */, @@ -516,6 +533,7 @@ 786DAE482E4F28B100BE3137 /* MalachiteTooltipUtils.swift */, 786DAE492E4F28B100BE3137 /* MalachiteUtils.swift */, 786DAE4A2E4F28B100BE3137 /* MalachiteViewUtils.swift */, + 7866F5D92E62ADA0009AC9BF /* GameUtils */, ); path = Utilities; sourceTree = ""; @@ -919,6 +937,10 @@ "$PLISTBUDDY -c \"Set :CFBuildUser $CFBUILDUSER\" \"${INFOPLIST}\"", "$PLISTBUDDY -c \"Set :CFBuildHost $CFBUILDHOST\" \"${INFOPLIST}\"", "", + "", + "", + "", + "", ); }; 78EC5D602E4EC1AD005044E2 /* Embed build information */ = { @@ -1011,6 +1033,10 @@ "$PLISTBUDDY -c \"Set :CFBuildUser $CFBUILDUSER\" \"${INFOPLIST}\"", "$PLISTBUDDY -c \"Set :CFBuildHost $CFBUILDHOST\" \"${INFOPLIST}\"", "", + "", + "", + "", + "", ); }; /* End PBXShellScriptBuildPhase section */ @@ -1037,6 +1063,7 @@ 786DAE562E4F28B100BE3137 /* MalachitePreferencesUtils.swift in Sources */, 78AF68682E5D97E500BB9D5C /* CameraView+Preview.swift in Sources */, 7882628A2E514F18000085AC /* QuickHelpView+Preview.swift in Sources */, + 78729C582E7368F3001027E9 /* Camera+Input.swift in Sources */, 788262E32E5172E8000085AC /* AboutView+Credits.swift in Sources */, 786DAE572E4F28B100BE3137 /* MalachiteFunctionUtils.swift in Sources */, 788262932E514F2D000085AC /* QuickHelpView+Photo.swift in Sources */, @@ -1044,6 +1071,7 @@ 786DAE592E4F28B100BE3137 /* MalachiteHapticUtils.swift in Sources */, 78E170772E51C97B009BEF2F /* CompatibilityView+Camera.swift in Sources */, 786DAE5A2E4F28B100BE3137 /* MalachiteIntentUtils.swift in Sources */, + 7818E23D2E80971C0046F621 /* Compatibility.swift in Sources */, 788262622E513080000085AC /* SettingsView+About.swift in Sources */, 786DAE5B2E4F28B100BE3137 /* MalachiteTooltipUtils.swift in Sources */, 786DAE5C2E4F28B100BE3137 /* MalachiteUtils.swift in Sources */, @@ -1097,6 +1125,7 @@ 786DAE5F2E4F28B100BE3137 /* MalachitePreferencesUtils.swift in Sources */, 78AF68692E5D97E500BB9D5C /* CameraView+Preview.swift in Sources */, 788262892E514F18000085AC /* QuickHelpView+Preview.swift in Sources */, + 78729C562E7368F3001027E9 /* Camera+Input.swift in Sources */, 788262E22E5172E8000085AC /* AboutView+Credits.swift in Sources */, 786DAE602E4F28B100BE3137 /* MalachiteFunctionUtils.swift in Sources */, 788262922E514F2D000085AC /* QuickHelpView+Photo.swift in Sources */, @@ -1104,6 +1133,7 @@ 786DAE622E4F28B100BE3137 /* MalachiteHapticUtils.swift in Sources */, 78E170792E51C97B009BEF2F /* CompatibilityView+Camera.swift in Sources */, 786DAE632E4F28B100BE3137 /* MalachiteIntentUtils.swift in Sources */, + 7818E23C2E80971C0046F621 /* Compatibility.swift in Sources */, 788262612E513080000085AC /* SettingsView+About.swift in Sources */, 786DAE642E4F28B100BE3137 /* MalachiteTooltipUtils.swift in Sources */, 786DAE652E4F28B100BE3137 /* MalachiteUtils.swift in Sources */, @@ -1161,6 +1191,7 @@ 788262972E514F38000085AC /* QuickHelpView+Watermarking.swift in Sources */, 788262752E5130D4000085AC /* SettingsView+Watermarking.swift in Sources */, 7882627C2E514750000085AC /* DeveloperView+Internal.swift in Sources */, + 78729C572E7368F3001027E9 /* Camera+Input.swift in Sources */, 78145F782E65135C003AEE51 /* Init.swift in Sources */, 786DAE4D2E4F28B100BE3137 /* MalachitePreferencesUtils.swift in Sources */, 788262DC2E5171F0000085AC /* AboutView+Story.swift in Sources */, @@ -1196,6 +1227,7 @@ 788262712E5130CA000085AC /* SettingsView+Photo.swift in Sources */, 78E170802E51CE45009BEF2F /* CompatibilityView+Format.swift in Sources */, 7866F5DB2E62ADB1009AC9BF /* Game+View.swift in Sources */, + 7818E23E2E80971C0046F621 /* Compatibility.swift in Sources */, 7882628D2E514F25000085AC /* QuickHelpView+Resolution.swift in Sources */, 786DAE2A2E4F28A600BE3137 /* QuickHelpView.swift in Sources */, 7882628B2E514F18000085AC /* QuickHelpView+Preview.swift in Sources */, diff --git a/Malachite/Utilities/CameraUtils/Camera+Bringup.swift b/Malachite/Utilities/CameraUtils/Camera+Bringup.swift index 39e3db0..9e2dda6 100644 --- a/Malachite/Utilities/CameraUtils/Camera+Bringup.swift +++ b/Malachite/Utilities/CameraUtils/Camera+Bringup.swift @@ -7,24 +7,88 @@ import AVFoundation import Foundation +import Photos extension Camera { class Bringup { - private var utilities: MalachiteClassesObject + private var parent: Camera - init( utilities: MalachiteClassesObject ) { self.utilities = utilities } + init( parent: Camera ) { self.parent = parent } - func checkForHEICCompatibility() { - if !utilities.function.supportsHEIC() { - utilities.debugNSLog("[Initialization] HEIF enabled on a device that doesn't support it, disabling") - utilities.preferences.capture.format.heic = false - utilities.preferences.capture.format.jpeg = true + /** + Returns an array of ``AVCaptureDevice`` objects to use when attaching cameras to Malachite's ``AVCaptureSession``. + */ + func createCameraArray() -> [AVCaptureDevice] { + parent.utilities.debugNSLog("[Camera Initialization] Discovering available cameras") + var camerasToDiscover: [AVCaptureDevice.DeviceType] = [] + var camerasFound: [AVCaptureDevice] = [] + if #available(iOS 17.0, *) { camerasToDiscover = [.builtInUltraWideCamera, .builtInWideAngleCamera, .builtInTelephotoCamera ] } + else { camerasToDiscover = [.builtInUltraWideCamera, .builtInWideAngleCamera, .builtInTelephotoCamera] } + + let currentProcess = ProcessInfo() + AVCaptureDevice.DiscoverySession.init(deviceTypes: camerasToDiscover, mediaType: .video, position: (currentProcess.isiOSAppOnMac || currentProcess.isMacCatalystApp) ? .unspecified : .back).devices.forEach { device in + if parent.utilities.preferences.general.deviceModelHasChanged { parent.compatibility.checkCameraCapabilities(device: device) } + camerasFound.append(device) + parent.utilities.debugNSLog("[Camera Initialization] \(device.deviceType.rawValue) available") } + + return camerasFound } + /** + Creates and returns a blank ``AVCaptureSession`` for use in Malachite. + */ func createAVCaptureSession(session: AVCaptureSession?) -> AVCaptureSession { - guard let session = session else { return AVCaptureSession() } - return session + if let session = session { + parent.utilities.debugNSLog("[Camera Initialization] Reusing existing AVCaptureSession") + return session + } + parent.utilities.debugNSLog("[Camera Initialization] Creating new AVCaptureSession") + return AVCaptureSession() + } + + /** + Returns an ``AVCapturePhotoOutput`` object to use when taking a photo with Malachite's ``AVCaptureSession``. + */ + func createAndAddPhotoOutput(photoOutput: AVCapturePhotoOutput?, session: AVCaptureSession) -> AVCapturePhotoOutput { + if photoOutput != nil { parent.utilities.debugNSLog("[Camera Initialization] Reusing existing AVCapturePhotoOutput") + } else { parent.utilities.debugNSLog("[Camera Initialization] Creating new AVCapturePhotoOutput") } + let output = photoOutput ?? AVCapturePhotoOutput() + if !session.outputs.contains(output) { + parent.utilities.debugNSLog("[Camera Initialization] Running AVCapturePhotoOutput initialization steps") + if #unavailable(iOS 16.0) { output.isHighResolutionCaptureEnabled = true } + output.maxPhotoQualityPrioritization = .quality + session.sessionPreset = AVCaptureSession.Preset.photo + session.addOutput(output) + } else { + parent.utilities.debugNSLog("[Camera Initialization] AVCapturePhotoOutput already initialized, continuing") + } + + return output + } + + func createRequestToUseCamera() async -> Bool { + let status = AVCaptureDevice.authorizationStatus(for: .video) + + if status == .notDetermined { + return await AVCaptureDevice.requestAccess(for: .video) + } + + return false + } + + /** + Requests the ability to add photos to the user's library. + */ + func createRequestToAddPhotos() async -> Bool { + let status = PHPhotoLibrary.authorizationStatus(for: .addOnly) + + if status == .notDetermined { + let newStatus = await PHPhotoLibrary.requestAuthorization(for: .addOnly) + return newStatus == .authorized + } + + return false } } } diff --git a/Malachite/Utilities/CameraUtils/Camera+Input.swift b/Malachite/Utilities/CameraUtils/Camera+Input.swift new file mode 100644 index 0000000..1cba8d5 --- /dev/null +++ b/Malachite/Utilities/CameraUtils/Camera+Input.swift @@ -0,0 +1,129 @@ +// +// Camera+Input.swift +// Malachite +// +// Created by Eva Isabella Luna on 9/11/25. +// + +import AVFoundation +import Foundation + +extension Camera { + class Input { + var parent: Camera + + init( parent: Camera ) { self.parent = parent } + + /// Function to switch cameras and attach new inputs to ``cameraSession``, and set settings based on the `activeFormat` of ``selectedDevice``. + @objc func runInputSwitch() { + parent.currentDevice = selectDeviceForSwitch() + guard let device = parent.currentDevice else { return } + + parent.session.beginConfiguration() + let sessionIsEmpty = parent.session.inputs.isEmpty + parent.utilities.debugNSLog("[Camera Input] Getting ready to configure session") + + if !sessionIsEmpty { + parent.utilities.debugNSLog("[Camera Input] Removing currently active camera input") + parent.session.removeInput(parent.session.inputs[0]) + } + + do { + try device.lockForConfiguration() + defer { device.unlockForConfiguration() } + parent.utilities.debugNSLog("[Camera Input] Selected input: \(String(describing: device.formats[(device.formats.count) - 1]))") + device.activeFormat = (device.formats[(device.formats.count) - 1]) + parent.utilities.function.continuousAEAF(device: device) + + handleUnsupportedFeaturesOnSwitch(supported: device.isLockingFocusWithCustomLensPositionSupported, + notification: MalachiteFunctionUtils.Notifications.unsupportedLensPositionNotification.name) + + handleUnsupportedFeaturesOnSwitch(supported: device.isExposureModeSupported(.custom), + notification: MalachiteFunctionUtils.Notifications.unsupportedISOValueNotification.name) + + self.setHDREnabledOnDevice(device: device) + } catch { + parent.utilities.debugNSLog("[Camera Input] Error adjusting device properties: \(error.localizedDescription)") + } + + + parent.utilities.debugNSLog("[Camera Input] Attempting to attach device input to session") + var input: AVCaptureDeviceInput? + do { input = try AVCaptureDeviceInput(device: device) } + catch { print(error) } + guard let input = input else { return } + + parent.utilities.debugNSLog("[Camera Input] Attached input, finishing configuration") + if parent.session.canAddInput(input) && !parent.session.inputs.contains(input) { parent.session.addInput(input) } + parent.setupPhotoOutput() + if #available(iOS 16.0, *) { switchInputMegapixels(device: device, photoOutput: parent.output) } + parent.session.commitConfiguration() + + if #available(iOS 16.0, *) { + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.megaPixelSwitchNotification.name, object: nil) + } + } + + public func handleUnsupportedFeaturesOnSwitch(supported: Bool, notification: NSNotification.Name) { + if !supported { NotificationCenter.default.post(name: notification, object: nil) } + } + + public func selectDeviceForSwitch() -> AVCaptureDevice? { + if parent.cameras.count > 0 { + if parent.currentIndex != nil { return parent.cameras[parent.currentIndex!] } else { + if parent.session.inputs.isEmpty { + return parent.cameras.first + } else { + if let devicePosition = parent.cameras.firstIndex(of: parent.currentDevice!) { + return parent.cameras[(devicePosition == (parent.cameras.count - 1)) ? 0 : devicePosition + 1] + } + } + } + } + return nil + } + + public func setHDREnabledOnDevice(device: AVCaptureDevice) { + parent.compatibility.checkDeviceForHDRCompatibility(device: device) + + parent.utilities.debugNSLog("[Camera Input] Checking if we should enable HDR: supportedByDevice: \(parent.utilities.preferences.compatibility.hdr), enabledInPreferences: \(parent.utilities.preferences.capture.hdr)") + + device.automaticallyAdjustsVideoHDREnabled = false + device.isVideoHDREnabled = (parent.utilities.preferences.compatibility.hdr && parent.utilities.preferences.capture.hdr) + + parent.utilities.debugNSLog("[Camera Input]" + (device.isVideoHDREnabled ? "Disabling HDR" : " Enabling HDR")) + + if device.activeFormat.isGlobalToneMappingSupported { device.isGlobalToneMappingEnabled = false } + } + + @available(iOS 16.0, *) + @objc public func switchInputMegapixels(device: AVCaptureDevice, photoOutput: AVCapturePhotoOutput) { + let maxDimensions = device.activeFormat.supportedMaxPhotoDimensions[device.activeFormat.supportedMaxPhotoDimensions.count - 1] + + var mpSetting = Int() + + switch device.deviceType { + case .builtInUltraWideCamera: + mpSetting = MalachitePreferencesUtils.shared.preferences.capture.mp.ultrawide + case .builtInWideAngleCamera: + mpSetting = MalachitePreferencesUtils.shared.preferences.capture.mp.wideangle + case .builtInTelephotoCamera: + mpSetting = MalachitePreferencesUtils.shared.preferences.capture.mp.telephoto + default: + mpSetting = MalachitePreferencesUtils.shared.preferences.capture.mp.wideangle + } + + switch mpSetting { + case 48: + parent.utilities.debugNSLog("[Camera Input] Switching \(device.deviceType.rawValue) to 48MP mode") + if maxDimensions.width == 8064 && maxDimensions.height == 6048 { photoOutput.maxPhotoDimensions = CMVideoDimensions(width: 8064, height: 6048) } + case 12: + parent.utilities.debugNSLog("[Camera Input] Switching \(device.deviceType.rawValue) to 12MP mode") + if maxDimensions.width == 4032 && maxDimensions.height == 3024 { photoOutput.maxPhotoDimensions = CMVideoDimensions(width: 4032, height: 3024) } + default: + parent.utilities.debugNSLog("[Camera Input] Switching \(device.deviceType.rawValue) to 8MP mode") + if maxDimensions.width == 3264 && maxDimensions.height == 2448 { photoOutput.maxPhotoDimensions = CMVideoDimensions(width: 3264, height: 2448) } + } + } + } +} diff --git a/Malachite/Utilities/CameraUtils/Camera.swift b/Malachite/Utilities/CameraUtils/Camera.swift index d35b462..d390c6b 100644 --- a/Malachite/Utilities/CameraUtils/Camera.swift +++ b/Malachite/Utilities/CameraUtils/Camera.swift @@ -5,8 +5,48 @@ // Created by Eva Isabella Luna on 8/29/25. // +import AVFoundation import Foundation -class Camera { +class Camera: NSObject { + var utilities: MalachiteClassesObject + var bringup: Camera.Bringup! + var input: Camera.Input! + var compatibility: Compatibility! + + var session: AVCaptureSession! + var output: AVCapturePhotoOutput! + var cameras: [ AVCaptureDevice ]! + + var currentDevice: AVCaptureDevice? + var currentIndex: Int? + + var cameraGranted = false + var photosGranted = false + + init( + utilities: MalachiteClassesObject + ) { + self.utilities = utilities + super.init() + setupChildClasses() + setupSession() + setupPhotoOutput() + } + + public func setupChildClasses() { + self.bringup = Bringup(parent: self) + self.setupCameraArray() + self.input = Input(parent: self) + self.compatibility = Compatibility(utilities: utilities) + } + + public func setupCameraArray() { self.cameras = self.bringup.createCameraArray() } + + public func setupSession() { self.session = self.bringup.createAVCaptureSession(session: self.session) } + + public func setupPhotoOutput() { + self.output = self.bringup.createAndAddPhotoOutput(photoOutput: output, session: self.session) + } } diff --git a/Malachite/Utilities/CompatibilityUtils/Compatibility.swift b/Malachite/Utilities/CompatibilityUtils/Compatibility.swift new file mode 100644 index 0000000..0dffeb1 --- /dev/null +++ b/Malachite/Utilities/CompatibilityUtils/Compatibility.swift @@ -0,0 +1,74 @@ +// +// Compatibility.swift +// Malachite +// +// Created by Eva Isabella Luna on 9/21/25. +// + +import AVFoundation +import Foundation + +class Compatibility { + private var utilities: MalachiteClassesObject + + init( utilities: MalachiteClassesObject ) { + self.utilities = utilities + } + + /// Determines if the passed device's activeFormat supports HDR. + public func checkDeviceForHDRCompatibility(device: AVCaptureDevice) { + if utilities.preferences.compatibility.hdr != device.activeFormat.isVideoHDRSupported { + utilities.debugNSLog("[Compatibility] HDR compatibility has changed on the current camera's active format.") + utilities.preferences.compatibility.hdr = device.activeFormat.isVideoHDRSupported + } + } + + /** + Checks whether or not the current device is capable of encoding High Efficiency Image Format. + + If the device doesn't support HEIF, the option is disabled in preferences to prevent crashes. + + HEIF is supported on Apple devices with the A10 Fusion chip or later. + */ + func checkDeviceForHEICCompatibility() { + if !utilities.preferences.general.deviceModelHasChanged { return } + + let supportedTypeIdentifiers = CGImageDestinationCopyTypeIdentifiers() as NSArray + if utilities.preferences.compatibility.jpeg != supportedTypeIdentifiers.contains("public.jpeg") { + utilities.debugNSLog("[Compatibility] JPEG compatibility has changed on this device.") + utilities.preferences.compatibility.jpeg = supportedTypeIdentifiers.contains("public.jpeg") + } + if utilities.preferences.compatibility.heic != supportedTypeIdentifiers.contains("public.heic") { + utilities.debugNSLog("[Compatibility] HEIC compatibility has changed on this device.") + utilities.preferences.compatibility.heic = supportedTypeIdentifiers.contains("public.heic") + } + } + + /// Determines what resolutions that the passed ``AVCaptureDevice`` is capable of shooting. + func checkCameraCapabilities(device: AVCaptureDevice) { + var tmpDictionary = Dictionary() + for format in device.formats { + var maxDimensions: CMVideoDimensions + if #available (iOS 16.0, *) { + maxDimensions = format.supportedMaxPhotoDimensions[format.supportedMaxPhotoDimensions.count - 1] + } else { + maxDimensions = format.highResolutionStillImageDimensions + } + if format == device.formats[0] { utilities.debugNSLog("[Compatibility] Querying supported modes of \(device.deviceType.rawValue)") } + if maxDimensions.width == 3264 && maxDimensions.height == 2448 { tmpDictionary["8"] = true } + if maxDimensions.width == 4032 && maxDimensions.height == 3024 { tmpDictionary["12"] = true } + if maxDimensions.width == 8064 && maxDimensions.height == 6048 { tmpDictionary["48"] = true } + switch device.deviceType { + case .builtInUltraWideCamera: + utilities.preferences.compatibility.ultrawide = tmpDictionary + case .builtInWideAngleCamera: + utilities.preferences.compatibility.wideangle = tmpDictionary + case .builtInTelephotoCamera: + utilities.preferences.compatibility.telephoto = tmpDictionary + default: + break + } + } + utilities.debugNSLog("[Compatibility] \(device.deviceType.rawValue): \(tmpDictionary)") + } +} diff --git a/Malachite/Utilities/InitUtils/Init+Internal.swift b/Malachite/Utilities/InitUtils/Init+Internal.swift index 367a289..8281285 100644 --- a/Malachite/Utilities/InitUtils/Init+Internal.swift +++ b/Malachite/Utilities/InitUtils/Init+Internal.swift @@ -11,19 +11,29 @@ extension Init { init( utilities: MalachiteClassesObject ) { self.utilities = utilities } + /// Checks whether or not to print a small message about subscribing to Eva's Patreon in the logs. + public func isEvaBuild() { + if utilities.versionUser == "evaluna" || utilities.versionHost == "Xcode Cloud" { return } + utilities.internalNSLog("[Initialization] plz subscribe to patreon: https://patreon.com/crystall1nedev") + } + /// Checks whether or not the current device is the same device as previously recorded in preferences. - public func deviceCheck() { - if !utilities.preferences.ext.deviceModel.isSameDevice(in: utilities.preferences) { - utilities.internalNSLog("[Initialization] This is a new device, rechecking compatibility.") - utilities.preferences.general.deviceModel = utilities.preferences.ext.deviceModel.get() - } else { + public func isSameDevice() { + if utilities.preferences.general.deviceModelHasChanged { utilities.preferences.general.deviceModelHasChanged = false } + if utilities.preferences.general.deviceModel == utilities.preferences.ext.deviceModel() { utilities.internalNSLog("[Initialization] This is the same device, can skip compatibility checks.") + return } + + utilities.internalNSLog("[Initialization] This is a new device, rechecking compatibility.") + utilities.preferences.general.deviceModel = utilities.preferences.ext.deviceModel() + utilities.preferences.general.deviceModelHasChanged = true } /// Runs all of the initialization functions defined in this class. public func initMalachite() { - deviceCheck() + isEvaBuild() + isSameDevice() } } } diff --git a/Malachite/Utilities/InitUtils/Init.swift b/Malachite/Utilities/InitUtils/Init.swift index a26436b..513c205 100644 --- a/Malachite/Utilities/InitUtils/Init.swift +++ b/Malachite/Utilities/InitUtils/Init.swift @@ -5,14 +5,19 @@ // Created by Eva Isabella Luna on 8/31/25. // +import AVFoundation import Foundation class Init { private var utilities: MalachiteClassesObject - private var debug: Debug - private var intrnl: Internal + private var debug: Init.Debug + private var intrnl: Init.Internal - init( utilities: MalachiteClassesObject ) { self.utilities = utilities; self.debug = Debug(utilities: self.utilities); self.intrnl = Internal(utilities: self.utilities) } + init( utilities: MalachiteClassesObject ) { + self.utilities = utilities + self.debug = Debug(utilities: utilities) + self.intrnl = Internal(utilities: utilities) + } /// Prints a message about Malachite starting. public func startupLog() { utilities.debugNSLog("[Initialization] Starting up Malachite") } diff --git a/Malachite/Utilities/MalachiteFunctionUtils.swift b/Malachite/Utilities/MalachiteFunctionUtils.swift index 11422d3..bf5cae9 100755 --- a/Malachite/Utilities/MalachiteFunctionUtils.swift +++ b/Malachite/Utilities/MalachiteFunctionUtils.swift @@ -13,8 +13,6 @@ import UIKit public class MalachiteFunctionUtils : NSObject { /// An array that returns the available image capture types supported by the camera. private let supportedImageCaptureTypes = CGImageDestinationCopyTypeIdentifiers() as NSArray - /// A `Bool` that determines whether or not the device supports HDR. - public var supportsHDR = false /// An `enum` that contains Notification names. public enum Notifications: String, NotificationName { @@ -31,25 +29,6 @@ public class MalachiteFunctionUtils : NSObject { case settingsGestureNotification } - /// Function that determines if the device supports HDR. - public func deviceFormatSupportsHDR(device hdrDevice: AVCaptureDevice) { - if hdrDevice.activeFormat.isVideoHDRSupported == true { - MalachitePreferencesUtils.shared.preferences.compatibility.hdr = true - self.supportsHDR = true - } - } - - /// Function that determines if the device supports HEIC. - public func supportsHEIC() -> Bool { - MalachitePreferencesUtils.shared.preferences.compatibility.jpeg = true - if supportedImageCaptureTypes.contains("public.heic") { - MalachitePreferencesUtils.shared.preferences.compatibility.heic = true - return true - } - - return false - } - /// Function to enable or disable the idle timer. @objc func changeIdleTimerState() { #if MAIN_APP @@ -239,104 +218,6 @@ public class MalachiteFunctionUtils : NSObject { } } - /// Function that handles connecting and disconnecting cameras, and changing format properties. - public func switchInput(session: inout AVCaptureSession, cameras: [AVCaptureDevice], device: inout AVCaptureDevice?, output: inout AVCapturePhotoOutput, input: inout AVCaptureDeviceInput?, button: UIButton, firstRun: inout Bool){ - MalachiteClassesObject().debugNSLog("[Camera Input] Getting ready to configure session") - - if !firstRun { - MalachiteClassesObject().debugNSLog("[Camera Input] Removing currently active camera input") - session.removeInput(input!) - } else { - if !cameras.isEmpty { device = cameras.first! } - } - - guard let device = device else { return } - - if firstRun { - for camera in cameras { - var tmpDictionary = Dictionary() - for format in camera.formats { - var maxDimensions: CMVideoDimensions - if #available (iOS 16.0, *) { - maxDimensions = format.supportedMaxPhotoDimensions[format.supportedMaxPhotoDimensions.count - 1] - } else { - maxDimensions = format.highResolutionStillImageDimensions - } - if format == camera.formats[0] { MalachiteClassesObject().debugNSLog("[Camera Input] Querying supported modes of \(camera.deviceType.rawValue)") } - if maxDimensions.width == 3264 && maxDimensions.height == 2448 { tmpDictionary["8"] = true } - if maxDimensions.width == 4032 && maxDimensions.height == 3024 { tmpDictionary["12"] = true } - if maxDimensions.width == 8064 && maxDimensions.height == 6048 { tmpDictionary["48"] = true } - switch camera.deviceType { - case .builtInUltraWideCamera: - MalachitePreferencesUtils.shared.preferences.compatibility.ultrawide = tmpDictionary - case .builtInWideAngleCamera: - MalachitePreferencesUtils.shared.preferences.compatibility.wideangle = tmpDictionary - case .builtInTelephotoCamera: - MalachitePreferencesUtils.shared.preferences.compatibility.telephoto = tmpDictionary - default: - break - } - } - } - } - - firstRun = false - - deviceFormatSupportsHDR(device: device) - - do { - try device.lockForConfiguration() - defer { device.unlockForConfiguration() } - MalachiteClassesObject().debugNSLog("[Camera Input] Selected input: \(String(describing: device.formats[(device.formats.count) - 1]))") - device.activeFormat = (device.formats[(device.formats.count) - 1]) - continuousAEAF(device: device) - - let focus = device.isLockingFocusWithCustomLensPositionSupported - if !focus { NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.unsupportedLensPositionNotification.name, object: nil) } - - let exposure = device.isExposureModeSupported(.custom) - if !exposure { NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.unsupportedISOValueNotification.name, object: nil) } - - device.automaticallyAdjustsVideoHDREnabled = false - - if MalachiteClassesObject().preferences.capture.hdr { - if self.supportsHDR { - MalachiteClassesObject().debugNSLog("[Camera Input] Force enabled HDR on camera") - if device.activeFormat.isVideoHDRSupported == true { - device.isVideoHDREnabled = true - } else { - MalachiteClassesObject().debugNSLog("[Camera Input] Current capture mode doesn't support HDR, it needs to be disabled") - MalachiteClassesObject().preferences.capture.hdr = false - } - } else { - MalachiteClassesObject().debugNSLog("[Camera Input] HDR enabled on a device that doesn't support it") - MalachiteClassesObject().preferences.capture.hdr = false - } - } else { - MalachiteClassesObject().debugNSLog("[Camera Input] Force disabled HDR on camera") - if device.activeFormat.isGlobalToneMappingSupported == true { - device.isGlobalToneMappingEnabled = false - } - if device.activeFormat.isVideoHDRSupported == true { - device.isVideoHDREnabled = false - } - } - } catch { - MalachiteClassesObject().debugNSLog("[Camera Input] Error adjusting device properties: \(error.localizedDescription)") - } - - - MalachiteClassesObject().debugNSLog("[Camera Input] Attempting to attach device input to session") - do { input = try AVCaptureDeviceInput(device: device) } - catch { - print(error) - } - - MalachiteClassesObject().debugNSLog("[Camera Input] Attached input, finishing configuration") - if session.canAddInput(input!) { session.addInput(input!) } - switchInputMegapixels(device: device, photoOutput: output) - } - @available(iOS 18.0, *) public func addControlsToSession(session: inout AVCaptureSession, controls: [AVCaptureControl]) { guard session.supportsControls else { return } @@ -357,43 +238,41 @@ public class MalachiteFunctionUtils : NSObject { session.commitConfiguration() } + @available(iOS 16.0, *) @objc public func switchInputMegapixels(device: AVCaptureDevice, photoOutput: AVCapturePhotoOutput) { - if #available(iOS 16.0, *) { - let maxDimensions = device.activeFormat.supportedMaxPhotoDimensions[device.activeFormat.supportedMaxPhotoDimensions.count - 1] - - var mpSetting = Int() - - switch device.deviceType { - case .builtInUltraWideCamera: - mpSetting = MalachitePreferencesUtils.shared.preferences.capture.mp.ultrawide - case .builtInWideAngleCamera: - mpSetting = MalachitePreferencesUtils.shared.preferences.capture.mp.wideangle - case .builtInTelephotoCamera: - mpSetting = MalachitePreferencesUtils.shared.preferences.capture.mp.telephoto - default: - mpSetting = MalachitePreferencesUtils.shared.preferences.capture.mp.wideangle - } - - switch mpSetting { - case 48: - MalachiteClassesObject().debugNSLog("[INTERNAL] Switching \(device.deviceType.rawValue) to 48MP mode") - if maxDimensions.width == 8064 && maxDimensions.height == 6048 { photoOutput.maxPhotoDimensions = CMVideoDimensions(width: 8064, height: 6048) } - case 12: - MalachiteClassesObject().debugNSLog("[INTERNAL] Switching \(device.deviceType.rawValue) to 12MP mode") - if maxDimensions.width == 4032 && maxDimensions.height == 3024 { photoOutput.maxPhotoDimensions = CMVideoDimensions(width: 4032, height: 3024) } - default: - MalachiteClassesObject().debugNSLog("[INTERNAL] Switching \(device.deviceType.rawValue) to 8MP mode") - if maxDimensions.width == 3264 && maxDimensions.height == 2448 { photoOutput.maxPhotoDimensions = CMVideoDimensions(width: 3264, height: 2448) } - } + let maxDimensions = device.activeFormat.supportedMaxPhotoDimensions[device.activeFormat.supportedMaxPhotoDimensions.count - 1] + + var mpSetting = Int() + + switch device.deviceType { + case .builtInUltraWideCamera: + mpSetting = MalachitePreferencesUtils.shared.preferences.capture.mp.ultrawide + case .builtInWideAngleCamera: + mpSetting = MalachitePreferencesUtils.shared.preferences.capture.mp.wideangle + case .builtInTelephotoCamera: + mpSetting = MalachitePreferencesUtils.shared.preferences.capture.mp.telephoto + default: + mpSetting = MalachitePreferencesUtils.shared.preferences.capture.mp.wideangle } + switch mpSetting { + case 48: + MalachiteClassesObject().debugNSLog("[INTERNAL] Switching \(device.deviceType.rawValue) to 48MP mode") + if maxDimensions.width == 8064 && maxDimensions.height == 6048 { photoOutput.maxPhotoDimensions = CMVideoDimensions(width: 8064, height: 6048) } + case 12: + MalachiteClassesObject().debugNSLog("[INTERNAL] Switching \(device.deviceType.rawValue) to 12MP mode") + if maxDimensions.width == 4032 && maxDimensions.height == 3024 { photoOutput.maxPhotoDimensions = CMVideoDimensions(width: 4032, height: 3024) } + default: + MalachiteClassesObject().debugNSLog("[INTERNAL] Switching \(device.deviceType.rawValue) to 8MP mode") + if maxDimensions.width == 3264 && maxDimensions.height == 2448 { photoOutput.maxPhotoDimensions = CMVideoDimensions(width: 3264, height: 2448) } + } } /// Function that handles taking images on `AVCapturePhotoOutput`. public func captureImage(output photoOutput: AVCapturePhotoOutput, viewForBounds view: UIView, captureDelegate delegate: AVCapturePhotoCaptureDelegate) -> AVCapturePhotoOutput { if photoOutput.connections.count < 1 { return photoOutput } var format = [String: Any]() - if MalachiteClassesObject().preferences.compatibility.heic && supportsHEIC() { + if MalachiteClassesObject().preferences.compatibility.heic { format = [AVVideoCodecKey : AVVideoCodecType.hevc] } else { format = [AVVideoCodecKey : AVVideoCodecType.jpeg] diff --git a/Malachite/Utilities/PreferenceUtils/MalachitePreferences.swift b/Malachite/Utilities/PreferenceUtils/MalachitePreferences.swift index ff92ba3..76ee330 100755 --- a/Malachite/Utilities/PreferenceUtils/MalachitePreferences.swift +++ b/Malachite/Utilities/PreferenceUtils/MalachitePreferences.swift @@ -32,12 +32,13 @@ struct MalachitePreferences: Codable { var general: generalPreferences struct generalPreferences: Codable { - var version: String - var prefsVersion: Int - var firstLaunch: Bool - var deviceModel: String - var photoCount: Int - var gamekit: gamekitPreferences + var version: String + var prefsVersion: Int + var firstLaunch: Bool + var deviceModel: String + var deviceModelHasChanged: Bool + var photoCount: Int + var gamekit: gamekitPreferences struct gamekitPreferences: Codable { var alerted: Bool diff --git a/Malachite/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift b/Malachite/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift index c6e04df..9af0729 100755 --- a/Malachite/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift +++ b/Malachite/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift @@ -104,6 +104,7 @@ class MalachitePreferencesUtils { if let generalPreferences = oldPreferences["general"] as? [ String: AnyObject ] { currentPreferences.general.firstLaunch = generalPreferences["firstLaunch"] as? Bool ?? false currentPreferences.general.deviceModel = generalPreferences["deviceModel"] as? String ?? "Eva1,1" + currentPreferences.general.deviceModelHasChanged = generalPreferences["deviceModelHasChanged"] as? Bool ?? false currentPreferences.general.photoCount = generalPreferences["photoCount"] as? Int ?? 0 currentPreferences.general.gamekit.alerted = generalPreferences["gamekit"]?["alerted"] as? Bool ?? false currentPreferences.general.gamekit.found = generalPreferences["gamekit"]?["found"] as? Bool ?? false @@ -177,6 +178,7 @@ class MalachitePreferencesUtils { prefsVersion: 6, firstLaunch: false, deviceModel: "Eva1,1", + deviceModelHasChanged: false, photoCount: 0, gamekit: MalachitePreferences.generalPreferences.gamekitPreferences( alerted: false, diff --git a/Malachite/Utilities/PreferenceUtils/Preferences+Extension.swift b/Malachite/Utilities/PreferenceUtils/Preferences+Extension.swift index 3a7f9a0..386a55e 100644 --- a/Malachite/Utilities/PreferenceUtils/Preferences+Extension.swift +++ b/Malachite/Utilities/PreferenceUtils/Preferences+Extension.swift @@ -39,25 +39,18 @@ extension MalachitePreferences { } } - var deviceModel: DeviceModel { return DeviceModel() } - class DeviceModel { - public func get() -> String { - var systemInfo = utsname() - uname(&systemInfo) - let machineMirror = Mirror(reflecting: systemInfo.machine) - let identifier = machineMirror.children.reduce("") { identifier, element in - guard let value = element.value as? Int8, value != 0 else { return identifier } - return identifier + String(UnicodeScalar(UInt8(value))) - } - - return identifier + public func deviceModel() -> String { + var systemInfo = utsname() + uname(&systemInfo) + let machineMirror = Mirror(reflecting: systemInfo.machine) + let identifier = machineMirror.children.reduce("") { identifier, element in + guard let value = element.value as? Int8, value != 0 else { return identifier } + return identifier + String(UnicodeScalar(UInt8(value))) } - public func isSameDevice(in preferences: MalachitePreferences) -> Bool { - if get() == preferences.general.deviceModel { return true } - return false - } + return identifier } + public func runPhotoCounter() { let value = MalachiteClassesObject().preferences.general.photoCount diff --git a/Malachite/Views/CameraView/CameraView+Controls.swift b/Malachite/Views/CameraView/CameraView+Controls.swift index 71c2af3..2dc62c9 100644 --- a/Malachite/Views/CameraView/CameraView+Controls.swift +++ b/Malachite/Views/CameraView/CameraView+Controls.swift @@ -7,19 +7,31 @@ import Foundation import AVFoundation +import AVKit import UIKit // MARK: ControlLayer - Main extension CameraView { class ControlLayer: NSObject, AVCaptureSessionControlsDelegate { - /// The existing instance of ``CameraView`` to act on. + /** + The existing instance of ``CameraView`` to act on. + */ var delegate = CameraView() init(delegate: CameraView) { self.delegate = delegate } - /// An array of the recognizers initalized by this control layer. + /** + An array of ``UIGestureRecognizer`` objects that are managed by this control layer. + */ var recognizers = [ UIGestureRecognizer ]() + /** + The ``AVCaptureEventInteraction`` that catches volume button and Camera Control events for taking photos. + */ + var eventInteraction: Any? = { if #available(iOS 17.2, *) { return AVCaptureEventInteraction?.self } else { return nil } }() + /** + Creates, adds, and constrains the ``UIButton`` objects that are managed by this control layer. + */ func initButtons() { var lockButtonsX = -80.0 var lockButtonsY = 0.0 @@ -71,6 +83,9 @@ extension CameraView { } } + /** + Creates, adds, and constrains the ``UISlider`` objects that are managed by this control layer. + */ func initSliders() { let sliderConfigs: [MalachiteViewUtils.sliderBuilder] = [ MalachiteViewUtils.sliderBuilder(action: #selector(delegate.runManualFocusController), dimensions: [ 180.0, 80.0 ], view: delegate.focusSliderButton, assign: { [self] slider in delegate.focusSlider = slider } ), @@ -82,6 +97,9 @@ extension CameraView { } } + /** + Creates and adds the ``UIGestureRecognizer`` objects that are managed by this control layer. + */ func initRecognizers() { delegate.zoomRecognizer = UIPinchGestureRecognizer(target: delegate, action:#selector(runZoomController)) delegate.zoomRecognizer.name = "zoom" @@ -103,6 +121,9 @@ extension CameraView { } } + /** + Creates, adds, and fades the tooltip flows that are managed by this control layer. + */ func initTooltips(showLabels: Bool, showCamera: Bool) { if showLabels { let tooltipConfigs: [ MalachiteViewUtils.tooltipBuilder ] = [ @@ -118,14 +139,20 @@ extension CameraView { delegate.utilities.tooltips.fadeOutTooltipFlow(labelsToFade: labels) } - if showCamera { delegate.utilities.tooltips.zoomTooltipFlow(button: delegate.currentCamera, viewForBounds: delegate.view, camera: delegate.selectedDevice) } + if delegate.camera.currentDevice != nil { + if showCamera { delegate.utilities.tooltips.zoomTooltipFlow(button: delegate.currentCamera, viewForBounds: delegate.view, camera: delegate.camera.currentDevice) } + } } + /** + Runs all other initialization functions defined in this control layer's class. + */ func bringUpControlLayer() { initButtons() initSliders() initRecognizers() - if !delegate.utilities.preferences.userInterface.appLaunch { initTooltips(showLabels: true, showCamera: true) } + //if !delegate.utilities.preferences.userInterface.appLaunch { initTooltips(showLabels: true, showCamera: true) } + if #available(iOS 17.2, *) { initEventInteraction() } if #available(iOS 18.0, *) { if delegate.utilities.versionType == "INTERNAL" && delegate.utilities.preferences.evaintrnl.cameraControlEnabled { initCameraControl() @@ -152,14 +179,15 @@ extension CameraView.ControlLayer { func sessionControlsDidBecomeInactive(_ session: AVCaptureSession) { if delegate.uiIsHidden { runUIHider() } - if delegate.cameraIndex != nil { + if delegate.camera.currentIndex != nil { delegate.runInputSwitch() - delegate.cameraIndex = nil + delegate.camera.currentIndex = nil } } func initCameraControl() { - guard delegate.cameraSession!.supportsControls else { return } + guard delegate.camera.session != nil else { return } + guard delegate.camera.session.supportsControls else { return } var controls: [ AVCaptureControl ] = [] #warning("malachitekit should properly sync this with the zoom slider") @@ -185,10 +213,12 @@ extension CameraView.ControlLayer { } if delegate.utilities.preferences.evaintrnl.cameraControlOptions.contains("cameras") { - let cameraSwitcher = AVCaptureIndexPicker("Cameras", symbolName: "camera.fill", localizedIndexTitles: delegate.availableRearCameras.map { $0.localizedName } ) - cameraSwitcher.selectedIndex = delegate.availableRearCameras.firstIndex(of: delegate.selectedDevice!)! + let cameraSwitcher = AVCaptureIndexPicker("Cameras", symbolName: "camera.fill", localizedIndexTitles: delegate.camera.cameras.map { $0.localizedName } ) + if let device = delegate.camera.currentDevice { + cameraSwitcher.selectedIndex = delegate.camera.cameras.firstIndex(of: device)! + } cameraSwitcher.setActionQueue(delegate.utilities.sessionQueue) { [self] index in - delegate.cameraIndex = index + delegate.camera.currentIndex = index } controls.append(cameraSwitcher) } @@ -219,20 +249,22 @@ extension CameraView.ControlLayer { delegate.flashFloater = nil if let flashSwitcher = flashSwitcher { flashSwitcher.selectedIndex = delegate.flashStatus ? 1 : 0 } } else { - delegate.utilities.function.flashLevelTest(captureDevice: delegate.selectedDevice!, floater: position) + delegate.utilities.function.flashLevelTest(captureDevice: delegate.camera.currentDevice!, floater: position) } } controls.append(flashSlider) } if delegate.utilities.preferences.evaintrnl.cameraControlOptions.contains("exposureBias") { - let systemBiasSlider = AVCaptureSystemExposureBiasSlider(device: delegate.selectedDevice!) + if let device = delegate.camera.currentDevice { + let systemBiasSlider = AVCaptureSystemExposureBiasSlider(device: device) controls.append(systemBiasSlider) + } } if delegate.utilities.versionType == "INTERNAL" { - delegate.cameraSession?.setControlsDelegate(self, queue: delegate.utilities.sessionQueue) - delegate.utilities.function.addControlsToSession(session: &delegate.cameraSession!, controls: controls) + delegate.camera.session.setControlsDelegate(self, queue: delegate.utilities.sessionQueue) + delegate.utilities.function.addControlsToSession(session: &delegate.camera.session, controls: controls) } } } @@ -259,13 +291,22 @@ extension CameraView.ControlLayer { delegate.utilities.views.hideUI(view: delegate.view, blacklisted: [ delegate.aeafFeedback, delegate.uiHiderRecognizer ], conditionals: [ delegate.focusLockButton : delegate.manualFocusSliderIsActive, delegate.exposureLockButton : delegate.manualExposureSliderIsActive], gestureRecognizers: self.recognizers) } else { delegate.utilities.views.showUI(view: delegate.view, blacklisted: [ delegate.aeafFeedback, delegate.uiHiderRecognizer ], conditionals: [ delegate.focusLockButton : delegate.manualFocusSliderIsActive, delegate.exposureLockButton : delegate.manualExposureSliderIsActive], gestureRecognizers: self.recognizers) - delegate.utilities.tooltips.zoomTooltipFlow(button: delegate.currentCamera, viewForBounds: delegate.view, camera: delegate.selectedDevice) + delegate.utilities.tooltips.zoomTooltipFlow(button: delegate.currentCamera, viewForBounds: delegate.view, camera: delegate.camera.currentDevice) } delegate.uiIsHidden = !delegate.uiIsHidden delegate.utilities.haptics.triggerNotificationHaptic(type: .success) } } + + @available(iOS 17.2, *) + func initEventInteraction() { + let interaction = AVCaptureEventInteraction { event in + if event.phase == .ended { self.delegate.runImageCapture() } + } + delegate.view.addInteraction(interaction) + eventInteraction = interaction + } } diff --git a/Malachite/Views/CameraView/CameraView+Notifications.swift b/Malachite/Views/CameraView/CameraView+Notifications.swift index a2d231f..08a91cb 100644 --- a/Malachite/Views/CameraView/CameraView+Notifications.swift +++ b/Malachite/Views/CameraView/CameraView+Notifications.swift @@ -9,14 +9,14 @@ import Foundation import UIKit extension CameraView { - class notifications: NSObject { + class Notifications: NSObject { /// The existing instance of ``CameraView`` to act on. var delegate = CameraView() init(delegate: CameraView) { self.delegate = delegate } func initNotifications() { - let notificationConfigs: [temputils.notificationBuilder] = [ + var notificationConfigs: [temputils.notificationBuilder] = [ temputils.notificationBuilder(delegate: delegate, name: UIDevice.orientationDidChangeNotification, action: #selector(orientationChanged)), temputils.notificationBuilder(delegate: delegate, name: MalachiteFunctionUtils.Notifications.aspectFillNotification.name, action: #selector(changeAspectFill)), temputils.notificationBuilder(delegate: delegate, name: MalachiteFunctionUtils.Notifications.exposureLimitNotification.name, action: #selector(changeExposureLimit)), @@ -27,9 +27,14 @@ extension CameraView { temputils.notificationBuilder(delegate: delegate, name: MalachiteFunctionUtils.Notifications.continousAEAFNotification.name, action: #selector(changeContinuousAEAF)), temputils.notificationBuilder(delegate: delegate, name: MalachiteFunctionUtils.Notifications.aeafTapGestureNotification.name, action: #selector(changeAEAFRecognizer)), temputils.notificationBuilder(delegate: delegate.utilities.function, name: MalachiteFunctionUtils.Notifications.idleTimerNotification.name, action: #selector(delegate.utilities.function.changeIdleTimerState)), - temputils.notificationBuilder(delegate: delegate, name: MalachiteFunctionUtils.Notifications.megaPixelSwitchNotification.name, action: #selector(runInputMegapixelSwitch)), ] + if #available(iOS 16.0, *) { + notificationConfigs.append(contentsOf: [ + temputils.notificationBuilder(delegate: delegate, name: MalachiteFunctionUtils.Notifications.megaPixelSwitchNotification.name, action: #selector(runInputMegapixelSwitch)), + ]) + } + for config in notificationConfigs { delegate.utilities.debugNSLog("[Initialization] Setting up notification observer for \(config.name.rawValue) changes") NotificationCenter.default.addObserver(config.delegate, selector: config.action, name: config.name, object: nil) diff --git a/Malachite/Views/CameraView/CameraView+Overrides.swift b/Malachite/Views/CameraView/CameraView+Overrides.swift index 772fa83..c76b941 100644 --- a/Malachite/Views/CameraView/CameraView+Overrides.swift +++ b/Malachite/Views/CameraView/CameraView+Overrides.swift @@ -37,10 +37,10 @@ extension CameraView { #if MAIN_APP if let windowScene = UIApplication.shared.connectedScenes.first(where: { $0 is UIWindowScene }) as? UIWindowScene { let orientation = windowScene.interfaceOrientation - self.cameraPreview.connection!.videoOrientation = self.transformOrientation(orientation: orientation) + self.preview.previewLayer.connection!.videoOrientation = self.transformOrientation(orientation: orientation) } #endif - self.cameraPreview.frame.size = self.view.frame.size + self.preview.previewLayer.frame.size = self.view.frame.size }) } } diff --git a/Malachite/Views/CameraView/CameraView+Preview.swift b/Malachite/Views/CameraView/CameraView+Preview.swift index 19b084f..b021977 100644 --- a/Malachite/Views/CameraView/CameraView+Preview.swift +++ b/Malachite/Views/CameraView/CameraView+Preview.swift @@ -7,23 +7,48 @@ import AVFoundation import Foundation +import UIKit extension CameraView { class Preview { /// The existing instance of ``CameraView`` to act on. var delegate = CameraView() + /// The `AVCaptureVideoPreviewLayer` used to allow users to see a preview of their camera before taking a shot with ``photoOutput``. + var previewLayer = AVCaptureVideoPreviewLayer() init(delegate: CameraView) { self.delegate = delegate } - func createPreviewLayer(previewLayer: AVCaptureVideoPreviewLayer?) -> AVCaptureVideoPreviewLayer { - guard let previewLayer = previewLayer else { - let layer = AVCaptureVideoPreviewLayer() - layer.frame.size = delegate.view.frame.size - return layer + func createPreviewLayer() { + previewLayer = AVCaptureVideoPreviewLayer(session: delegate.camera.session) + previewLayer.frame.size = delegate.view.frame.size + } + + func configurePreviewLayer() { + var statusBarOrientation = UIInterfaceOrientation.portrait + #if MAIN_APP + if let windowScene = UIApplication.shared.connectedScenes.first(where: { $0 is UIWindowScene }) as? UIWindowScene { + statusBarOrientation = windowScene.interfaceOrientation } + #endif + previewLayer.frame = delegate.view.layer.bounds + let videoOrientation: AVCaptureVideoOrientation = (statusBarOrientation.videoOrientation) + if let connection = previewLayer.connection { connection.videoOrientation = videoOrientation } - previewLayer.frame.size = delegate.view.frame.size - return previewLayer + if delegate.utilities.preferences.preview.aspect { + previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill + } else { + previewLayer.videoGravity = AVLayerVideoGravity.resizeAspect + } + } + + func addPreviewLayer() { + delegate.view.layer.addSublayer(previewLayer) + } + + func initPreviewLayer() { + createPreviewLayer() + configurePreviewLayer() + addPreviewLayer() } } } diff --git a/Malachite/Views/CameraView/CameraView.swift b/Malachite/Views/CameraView/CameraView.swift index dbffcd2..9b60aea 100755 --- a/Malachite/Views/CameraView/CameraView.swift +++ b/Malachite/Views/CameraView/CameraView.swift @@ -14,30 +14,21 @@ import Photos import GameKit class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCapturePhotoCaptureDelegate { - var controlLayer: ControlLayer? - var notifications: notifications? - var preview: Preview? - - /// The `AVCaptureSession` Malachite uses for everything. - var cameraSession: AVCaptureSession? - /// The currently selected `AVCaptureDevice` for input to ``cameraSession``. - var selectedDevice: AVCaptureDevice? - /// An array respresenting the device's available rear cameras. This variable will be `nil` if no cameras are present (as is the case of the iOS simulator) - var availableRearCameras = [AVCaptureDevice]() + /// An instance of ``MalachiteClassesObject`` for reuse across the app. + public var utilities = MalachiteClassesObject() + + var camera: Camera! + + var controlLayer: CameraView.ControlLayer! + var notifications: CameraView.Notifications! + var preview: CameraView.Preview! + /// The device's currently available rear ultra-wide angle `AVCaptureDevice`, if available. This variable is `nil` if no ultra-wide angle camera is present (i.e. single-camera, Simulator). var ultraWideDevice: AVCaptureDevice? /// The device's currently available wide angle `AVCaptureDevice`, if available. This variable is `nil` if no wide angle camera is present (currently only in the Simulator). var wideAngleDevice: AVCaptureDevice? - /// The currently selected `AVCaptureDeviceInput` for input to ``cameraSession``. + /// The currently selected `AVCaptureDeviceInput` for camera.input to ``cameraSession``. var selectedInput: AVCaptureDeviceInput? - /// The `AVCapturePhotoOutput` used to capture photos with ``selectedDevice`` and ``cameraSession``. - var photoOutput = AVCapturePhotoOutput() - /// The `AVCaptureVideoPreviewLayer` used to allow users to see a preview of their camera before taking a shot with ``photoOutput``. - var cameraPreview = AVCaptureVideoPreviewLayer() - /// A `Bool` that determines whether or not the wide angle lens is in use. - var wideAngleInUse = true - /// A `Bool` that determines whether or not the app is still initializing. Uses for tasks that should only be run once at the start of Malachite. - var initRun = true /// A `CGFloat` that temporarily holds the zoom factor. var zoomFloater = CGFloat() /// A `Float` that temporarily holds the focus factor. @@ -46,8 +37,6 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa var flashFloater: Float? /// A `Bool` that temporarily holds the current status of the flashlight. var flashStatus = Bool() - /// An `Int` that temporarily holds the index of the camera to switch to. - var cameraIndex: Int? /// A `UIButton` that enables the user to switch between the ultra-wide and wide angle cameras. var cameraButton = UIButton() @@ -94,14 +83,6 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa var settingsRecognizer = UISwipeGestureRecognizer() /// A `UILongPressGestureRecognizer` that handles hiding all elements of the user interface, and disabling the ``zoomRecognizer`` and ``aeafRecognizer`` gestures. var uiHiderRecognizer = UILongPressGestureRecognizer() - /// - var eventInteraction: Any? = { - if #available(iOS 17.2, *) { - return AVCaptureEventInteraction?.self - } else { - return nil - } - }() /// A `Bool` that determines whether or not the user interface is currently hidden to the user. var uiIsHidden = false @@ -122,8 +103,6 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// A `UIActivityIndicatorView` used to let the user know that Malachite is processing the image. var progressIndicator = UIActivityIndicatorView() - /// An instance of ``MalachiteClassesObject`` for reuse across the app. - public var utilities = MalachiteClassesObject() /// An observer for the device's rotation. private var rotationObserver: NSObjectProtocol? @@ -138,98 +117,28 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = .black + self.camera = Camera(utilities: utilities) self.controlLayer = CameraView.ControlLayer(delegate: self) - self.notifications = CameraView.notifications(delegate: self) + self.notifications = CameraView.Notifications(delegate: self) self.preview = CameraView.Preview(delegate: self) - #warning("this is temporary for testing") - let bringup = Camera.Bringup(utilities: utilities) - bringup.checkForHEICCompatibility() - - #warning("malachite camera init") - utilities.debugNSLog("[Initialization] Bringing up AVCaptureSession") - cameraSession = bringup.createAVCaptureSession(session: cameraSession) - cameraPreview = preview!.createPreviewLayer(previewLayer: cameraPreview) - - utilities.debugNSLog("[Initialization] Bringing up AVCaptureDeviceInput") - - utilities.debugNSLog("[Camera Input] Getting current camera system capabilities") - - #warning("malachite camera init") - var camerasToDiscover: [AVCaptureDevice.DeviceType] = [] - if #available(iOS 17.0, *) { camerasToDiscover = [.builtInUltraWideCamera, .builtInWideAngleCamera, .builtInTelephotoCamera ] } - else { camerasToDiscover = [.builtInUltraWideCamera, .builtInWideAngleCamera, .builtInTelephotoCamera] } - - utilities.debugNSLog("[Camera Input] Discovering available cameras") - let currentProcess = ProcessInfo() - AVCaptureDevice.DiscoverySession.init(deviceTypes: camerasToDiscover, mediaType: .video, position: (currentProcess.isiOSAppOnMac || currentProcess.isMacCatalystApp) ? .unspecified : .back).devices.forEach { device in - self.availableRearCameras.append(device) - utilities.debugNSLog("[Camera Input] \(device.localizedName)") - utilities.debugNSLog("[Camera Input] \(device.deviceType.rawValue) available") - } - - #warning("malachite camera init") - runInputSwitch() - - if self.availableRearCameras.first != nil { - photoOutput = AVCapturePhotoOutput() - if #unavailable(iOS 16.0) { photoOutput.isHighResolutionCaptureEnabled = true } - photoOutput.maxPhotoQualityPrioritization = .quality - cameraSession?.sessionPreset = AVCaptureSession.Preset.photo - cameraSession?.addOutput(photoOutput) - + if camera.cameras.first != nil { utilities.debugNSLog("[Initialization] Bringing up AVCaptureVideoPreviewLayer") - cameraPreview = AVCaptureVideoPreviewLayer(session: cameraSession!) - - var statusBarOrientation = UIInterfaceOrientation.portrait - #if MAIN_APP - if let windowScene = UIApplication.shared.connectedScenes.first(where: { $0 is UIWindowScene }) as? UIWindowScene { - statusBarOrientation = windowScene.interfaceOrientation - } - #endif - cameraPreview.frame = view.layer.bounds - let videoOrientation: AVCaptureVideoOrientation = (statusBarOrientation.videoOrientation) - cameraPreview.connection?.videoOrientation = videoOrientation - - if utilities.preferences.preview.aspect { - cameraPreview.videoGravity = AVLayerVideoGravity.resizeAspectFill - } else { - cameraPreview.videoGravity = AVLayerVideoGravity.resizeAspect - } - - self.view.layer.addSublayer(cameraPreview) + preview.initPreviewLayer() + runInputSwitch() utilities.debugNSLog("[Initialization] Starting session stream") DispatchQueue.global(qos: .background).async { - self.cameraSession?.startRunning() + self.camera.session.startRunning() } } else { utilities.debugNSLog("[Initialization] No cameras detected, skipping to user interface bringup") } - #warning("malachite init") - - #warning("malachite camera init") - if #available (iOS 17.2, *) { - let interaction = AVCaptureEventInteraction { event in - if event.phase == .ended { - self.runImageCapture() - } - } - self.view.addInteraction(interaction) - eventInteraction = interaction - } - +#warning("malachite camera init") - #warning("malachite photo init") - let cameraAuthStatus = PHPhotoLibrary.authorizationStatus(for: .addOnly) - if cameraAuthStatus == .notDetermined { - PHPhotoLibrary.requestAuthorization(for: .addOnly) { [self] status in - utilities.debugNSLog("[Permissions] Camera authorization status: \(status)") - } - } } /** @@ -273,13 +182,13 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa utilities.games.changeGameCenterEnabled() if utilities.preferences.general.gamekit.alerted { self.present(utilities.games.setupGameKitAlert(), animated: true, completion: nil) } - settingsRecognizer = UISwipeGestureRecognizer(target: self.controlLayer!, action: #selector(self.controlLayer!.runSettingsGesture)) - self.controlLayer!.updateSettingsGestureFingerCount() + settingsRecognizer = UISwipeGestureRecognizer(target: self.controlLayer, action: #selector(self.controlLayer.runSettingsGesture)) + self.controlLayer.updateSettingsGestureFingerCount() settingsRecognizer.direction = .up self.view.addGestureRecognizer(settingsRecognizer) - NotificationCenter.default.addObserver(self.controlLayer!, selector: #selector(self.controlLayer!.updateSettingsGestureFingerCount), name: MalachiteFunctionUtils.Notifications.settingsGestureNotification.name, object: nil) + NotificationCenter.default.addObserver(self.controlLayer, selector: #selector(self.controlLayer.updateSettingsGestureFingerCount), name: MalachiteFunctionUtils.Notifications.settingsGestureNotification.name, object: nil) setupView() } @@ -307,13 +216,12 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa - ``uiHiderRecognizer`` - Tap and hold with two fingers */ func setupView(){ -#warning("remove simulator support") #if targetEnvironment(simulator) utilities.views.setupLmaoView(view: self.view) #endif - self.controlLayer!.bringUpControlLayer() - self.notifications!.bringUpNotifications() + self.controlLayer.bringUpControlLayer() + self.notifications.bringUpNotifications() utilities.function.changeIdleTimerState() } @@ -325,20 +233,20 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa @objc func changeAspectFill() { UIView.animate(withDuration: 20) { [self] in if utilities.preferences.preview.aspect { - cameraPreview.videoGravity = AVLayerVideoGravity.resizeAspectFill + self.preview.previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill } else { - cameraPreview.videoGravity = AVLayerVideoGravity.resizeAspect + self.preview.previewLayer.videoGravity = AVLayerVideoGravity.resizeAspect } } } /// Function to dynamically change the auto exposure and ``exposureSlider`` values when toggling in ``MalachiteSettingsView``. @objc func changeExposureLimit() { - guard let exposure = selectedDevice?.isExposureModeSupported(.continuousAutoExposure) else { return } + guard let exposure = camera.currentDevice?.isExposureModeSupported(.continuousAutoExposure) else { return } do { - try selectedDevice?.lockForConfiguration() - defer { selectedDevice?.unlockForConfiguration() } - if exposure { selectedDevice?.exposureMode = .continuousAutoExposure } + try camera.currentDevice?.lockForConfiguration() + defer { camera.currentDevice?.unlockForConfiguration() } + if exposure { camera.currentDevice?.exposureMode = .continuousAutoExposure } } catch { utilities.debugNSLog("[Change Exposure Limit] Couldn't lock device for configuration") } @@ -349,7 +257,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa } @objc func changeContinuousAEAF() { - guard let selectedDevice = self.selectedDevice else { return } + guard let selectedDevice = camera.currentDevice else { return } utilities.function.continuousAEAF(device: selectedDevice) } @@ -372,21 +280,22 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to change the video stabilization mode for the ``cameraPreview``. @objc func changeStabilizerMode() { + guard let connection = self.preview.previewLayer.connection else { print("bruh"); return } if utilities.preferences.preview.stablize { if #available(iOS 17.0, *) { - if ((selectedDevice?.activeFormat.isVideoStabilizationModeSupported(.previewOptimized)) != nil) { + if ((camera.currentDevice?.activeFormat.isVideoStabilizationModeSupported(.previewOptimized)) != nil) { utilities.debugNSLog("[Preview Stabilization] Enabling enhanced stabilization mode") - cameraPreview.connection!.preferredVideoStabilizationMode = .previewOptimized + connection.preferredVideoStabilizationMode = .previewOptimized return } } - if ((selectedDevice?.activeFormat.isVideoStabilizationModeSupported(.standard)) != nil) { + if ((camera.currentDevice?.activeFormat.isVideoStabilizationModeSupported(.standard)) != nil) { utilities.debugNSLog("[Preview Stabilization] Enabling standard stabilization mode") - cameraPreview.connection!.preferredVideoStabilizationMode = .standard + connection.preferredVideoStabilizationMode = .standard } } else { - cameraPreview.connection!.preferredVideoStabilizationMode = .off + connection.preferredVideoStabilizationMode = .off } } @@ -415,11 +324,10 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa #endif } - /// Function to switch cameras and attach new inputs to ``cameraSession``, and set settings based on the `activeFormat` of ``selectedDevice``. + /// Function to switch cameras and attach new camera.inputs to ``cameraSession``, and set settings based on the `activeFormat` of ``selectedDevice``. @objc func runInputSwitch() { - cameraSession?.beginConfiguration() - cameraButton.isUserInteractionEnabled = false - if (self.availableRearCameras.count < 2 || utilities.preferences.debug.breakApp) && !self.initRun { + DispatchQueue.main.async { self.cameraButton.isUserInteractionEnabled = false } + if (self.camera.cameras.count < 2 || utilities.preferences.debug.breakApp) && !self.camera.session.inputs.isEmpty { utilities.debugNSLog("[Camera Input] Only one AVCaptureDevice is available to use, showing error") let alert = utilities.views.createAlertController(title: "alert.title.camera_switch", message: "alert.detail.camera_switch", button: cameraButton, defaultSet: true, action: { _ in self.utilities.debugNSLog("[Camera Input] Dialog has been dismissed") @@ -429,57 +337,40 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa return } - UIView.animate(withDuration: 0.5) { - self.focusSlider.value = 0.0 - self.exposureSlider.value = 0.0 - } - - if cameraIndex != nil { selectedDevice = availableRearCameras[cameraIndex!] } else { - if availableRearCameras.count > 1 && !initRun { - if let devicePosition = availableRearCameras.firstIndex(of: selectedDevice!) { - if devicePosition == (availableRearCameras.count - 1) { - selectedDevice = availableRearCameras[0] - } else { - selectedDevice = availableRearCameras[devicePosition + 1] - } - } + DispatchQueue.main.async { + UIView.animate(withDuration: 0.5) { + self.focusSlider.value = 0.0 + self.exposureSlider.value = 0.0 } } - utilities.function.switchInput(session: &cameraSession!, - cameras: availableRearCameras, - device: &selectedDevice, - output: &photoOutput, - input: &selectedInput, - button: cameraButton, - firstRun: &initRun) + camera.input.runInputSwitch() if #available(iOS 18.0, *) { if utilities.versionType == "INTERNAL" && utilities.preferences.evaintrnl.cameraControlEnabled { - self.controlLayer!.initCameraControl() + DispatchQueue.main.async { self.controlLayer.initCameraControl() } } } - cameraSession?.commitConfiguration() - DispatchQueue.main.async() { [self] in - self.controlLayer!.initTooltips(showLabels: false, showCamera: true) + DispatchQueue.main.async { self.controlLayer.initTooltips(showLabels: false, showCamera: true) } } - cameraButton.isUserInteractionEnabled = true + DispatchQueue.main.async { self.cameraButton.isUserInteractionEnabled = true } } + @available(iOS 16.0, *) @objc func runInputMegapixelSwitch() { - guard let selectedDevice = self.selectedDevice else { return } - utilities.function.switchInputMegapixels(device: selectedDevice, photoOutput: self.photoOutput) + guard let selectedDevice = camera.currentDevice else { return } + utilities.function.switchInputMegapixels(device: selectedDevice, photoOutput: self.camera.output) } /// Function to toggle the flashlight's on state. @objc func runFlashlightToggle() { - guard let flashlight = selectedDevice?.isFlashAvailable else { return } - if flashlight && !utilities.preferences.debug.breakApp { - utilities.function.toggleFlash(captureDevice: &selectedDevice!, + guard var selectedDevice = camera.currentDevice else { return } + if selectedDevice.isFlashAvailable && !utilities.preferences.debug.breakApp { + utilities.function.toggleFlash(captureDevice: &selectedDevice, flashlightButton: flashlightButton, floater: flashFloater, isFlashOn: &flashStatus) @@ -503,7 +394,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa let status = PHPhotoLibrary.authorizationStatus(for: .addOnly) if (status == .authorized || status == .limited) && !utilities.preferences.debug.breakApp { - self.photoOutput = utilities.function.captureImage(output: self.photoOutput, viewForBounds: self.view, captureDelegate: self) + self.camera.output = utilities.function.captureImage(output: self.camera.output, viewForBounds: self.view, captureDelegate: self) } else { utilities.debugNSLog("[Capture Photo] PHPhotoLibrary not authorized, showing error") let alert = utilities.views.createAlertController(title: "alert.title.phphotolibrary", message: "alert.detail.phphotolibrary", button: captureButton, defaultSet: true, action: { _ in @@ -523,7 +414,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa photoPreview.photoImageData = imageData photoPreview.photoImageView.frame = view.frame photoPreview.photoImage = previewImage - if utilities.versionType == "INTERNAL" && utilities.preferences.preview.fastPath { + if utilities.preferences.preview.fastPath { photoPreview.savePhoto(finalImage: photoPreview.finalizeImageForExport(imageData: imageData)) } else { let navigationController = UINavigationController(rootViewController: photoPreview) @@ -558,16 +449,17 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to zoom in and out with ``zoomRecognizer``. @objc func runZoomController() { + guard var selectedDevice = camera.currentDevice else { return } utilities.function.zoom(sender: zoomRecognizer, floater: &zoomFloater, - captureDevice: &selectedDevice!, + captureDevice: &selectedDevice, lastZoomFactor: &lastZoomFactor, hapticClass: utilities.haptics) } /// Function to autofocus + autoexposure with ``aeafRecognizer``. @objc func runaeafController() { - guard var selectedDevice = self.selectedDevice else { return } + guard var selectedDevice = camera.currentDevice else { return } utilities.function.pointOfInterestAEAF(sender: aeafRecognizer, captureDevice: &selectedDevice, button: aeafFeedback, @@ -577,9 +469,9 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to handle ``exposureSlider`` interaction. @objc func runManualExposureController() { - guard let exposure = selectedDevice?.isExposureModeSupported(.custom) else { return } - if exposure && !utilities.preferences.debug.breakApp { - utilities.function.manualExposure(captureDevice: &selectedDevice!, + guard var selectedDevice = camera.currentDevice else { return } + if selectedDevice.isExposureModeSupported(.custom) && !utilities.preferences.debug.breakApp { + utilities.function.manualExposure(captureDevice: &selectedDevice, sender: exposureSlider) } else { utilities.debugNSLog("[Manual Exposure] Current camera is not capable of adjusting exposure") @@ -592,7 +484,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to show and hide the ``exposureSliderButton`` and ``exposureLockButton``. @objc func runManualExposureUIHider() { - guard let exposure = selectedDevice?.isExposureModeSupported(.custom) else { return } + guard let exposure = camera.currentDevice?.isExposureModeSupported(.custom) else { return } if exposure && !utilities.preferences.debug.breakApp { manualExposureSliderIsActive = utilities.views.runSliderControllers(sliderIsShown: manualExposureSliderIsActive, optionButton: exposureButton, @@ -632,9 +524,9 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to handle ``focusSlider`` interaction. @objc func runManualFocusController() { - guard let focus = selectedDevice?.isLockingFocusWithCustomLensPositionSupported else { return } - if focus && !utilities.preferences.debug.breakApp { - utilities.function.manualFocus(captureDevice: &selectedDevice!, + guard var selectedDevice = camera.currentDevice else { return } + if selectedDevice.isLockingFocusWithCustomLensPositionSupported && !utilities.preferences.debug.breakApp { + utilities.function.manualFocus(captureDevice: &selectedDevice, sender: focusSlider, floater: focusFloater ?? focusSlider.value) } else { @@ -649,8 +541,8 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to handle ``focusSlider`` interaction. @objc func runManualFocusUIHider() { - guard let focus = selectedDevice?.isLockingFocusWithCustomLensPositionSupported else { return } - if focus && !utilities.preferences.debug.breakApp { + guard var selectedDevice = camera.currentDevice else { return } + if selectedDevice.isLockingFocusWithCustomLensPositionSupported && !utilities.preferences.debug.breakApp { manualFocusSliderIsActive = utilities.views.runSliderControllers(sliderIsShown: manualFocusSliderIsActive, optionButton: focusButton, lockButton: focusLockButton, diff --git a/Malachite/Views/SettingsView/SettingsView+Photo.swift b/Malachite/Views/SettingsView/SettingsView+Photo.swift index bd52ad6..7893ca7 100644 --- a/Malachite/Views/SettingsView/SettingsView+Photo.swift +++ b/Malachite/Views/SettingsView/SettingsView+Photo.swift @@ -9,10 +9,6 @@ import SwiftUI extension SettingsView { struct Photo: View { - /// A State variable used for determining whether or not the device supports HDR capture in its current mode. - @State private var supportsHDR = Bool() - /// A State variable used for determining whether or not the device supports HEIC capture. - @State private var supportsHEIC = Bool() /// A State variable used for presenting the user with a footer based on capabilities. @State private var formatFooterText: String? /// A State variable used for determining the active photo format. @@ -38,7 +34,7 @@ extension SettingsView { Section(header: Text("settings.header.photo"), footer: (formatFooterText != nil) ? Text(formatFooterText!) : nil) { MalachiteCellViewUtils( icon: "square.and.arrow.down", - disabled: !supportsHEIC, + disabled: !utilities.preferences.compatibility.heic, dangerous: false) { Picker("settings.option.photo.fileformat", selection: $photoFormat) { @@ -51,7 +47,7 @@ extension SettingsView { MalachiteCellViewUtils( icon: "camera.filters", - disabled: !supportsHDR, + disabled: !utilities.preferences.compatibility.hdr, dangerous: false) { Toggle("settings.option.photo.hdr", isOn: $hdrSwitch) @@ -99,14 +95,9 @@ extension SettingsView { } func onAppear() { - supportsHDR = utilities.function.supportsHDR - supportsHEIC = utilities.function.supportsHEIC() + if !utilities.preferences.compatibility.heic { formatFooterText = "settings.footer.photo.heif".localized } - if !supportsHEIC { - formatFooterText = "settings.footer.photo.heif".localized - } - - if !supportsHDR { + if !utilities.preferences.compatibility.hdr { formatFooterText = (formatFooterText != nil) ? formatFooterText! + "settings.footer.photo.hdr".localized : "settings.footer.photo.hdr".localized } From f8e3eee4f2b9a19ef0511ebbbc20ea4a26f2ec20 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Sun, 21 Sep 2025 18:05:22 -0600 Subject: [PATCH 19/66] Fix permissions issue from the last commit This should also fix any other permissions issues that I forgot to account for in the past. --- Malachite.xcodeproj/project.pbxproj | 10 +++ .../CameraUtils/Camera+Bringup.swift | 27 +------ .../CameraUtils/Camera+Permissions.swift | 81 +++++++++++++++++++ Malachite/Utilities/CameraUtils/Camera.swift | 27 +++++-- .../Utilities/MalachiteFunctionUtils.swift | 1 + .../CameraView/CameraView+Notifications.swift | 1 + .../Views/CameraView/CameraView+Preview.swift | 2 +- Malachite/Views/CameraView/CameraView.swift | 8 +- 8 files changed, 120 insertions(+), 37 deletions(-) create mode 100644 Malachite/Utilities/CameraUtils/Camera+Permissions.swift diff --git a/Malachite.xcodeproj/project.pbxproj b/Malachite.xcodeproj/project.pbxproj index f13861b..474dd1c 100755 --- a/Malachite.xcodeproj/project.pbxproj +++ b/Malachite.xcodeproj/project.pbxproj @@ -19,6 +19,9 @@ 7824CAF12E62E4970072870F /* Camera+Bringup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7824CAF02E62E4920072870F /* Camera+Bringup.swift */; }; 7824CAF22E62E4970072870F /* Camera+Bringup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7824CAF02E62E4920072870F /* Camera+Bringup.swift */; }; 7824CAF32E62E4970072870F /* Camera+Bringup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7824CAF02E62E4920072870F /* Camera+Bringup.swift */; }; + 782850012E80C05100826FA7 /* Camera+Permissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782850002E80C04D00826FA7 /* Camera+Permissions.swift */; }; + 782850022E80C05100826FA7 /* Camera+Permissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782850002E80C04D00826FA7 /* Camera+Permissions.swift */; }; + 782850032E80C05100826FA7 /* Camera+Permissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782850002E80C04D00826FA7 /* Camera+Permissions.swift */; }; 7837C10B2E34C45B009396B0 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78917F5D2B99AE72005E10FA /* SwiftUI.framework */; }; 7837C10C2E34C45B009396B0 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78917F5B2B99AE72005E10FA /* WidgetKit.framework */; }; 7837C10E2E34C45B009396B0 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */; }; @@ -281,6 +284,7 @@ 7818E23B2E8097180046F621 /* Compatibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Compatibility.swift; sourceTree = ""; }; 7824CAEC2E62CDD90072870F /* CameraView+Overrides.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraView+Overrides.swift"; sourceTree = ""; }; 7824CAF02E62E4920072870F /* Camera+Bringup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Camera+Bringup.swift"; sourceTree = ""; }; + 782850002E80C04D00826FA7 /* Camera+Permissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Camera+Permissions.swift"; sourceTree = ""; }; 782FC7752B2DAFAB007709C1 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; 7837C1152E34C45B009396B0 /* WidgetBundleWatch.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetBundleWatch.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 78474D2B2D7AC879006FBB96 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = ""; }; @@ -468,6 +472,7 @@ isa = PBXGroup; children = ( 7866F5CD2E6299A0009AC9BF /* Camera.swift */, + 782850002E80C04D00826FA7 /* Camera+Permissions.swift */, 7824CAF02E62E4920072870F /* Camera+Bringup.swift */, 78729C552E7368F0001027E9 /* Camera+Input.swift */, ); @@ -941,6 +946,7 @@ "", "", "", + "", ); }; 78EC5D602E4EC1AD005044E2 /* Embed build information */ = { @@ -1037,6 +1043,7 @@ "", "", "", + "", ); }; /* End PBXShellScriptBuildPhase section */ @@ -1099,6 +1106,7 @@ 7882626E2E5130C1000085AC /* SettingsView+Resolution.swift in Sources */, 786DAE352E4F28A600BE3137 /* SettingsView.swift in Sources */, 7882628F2E514F25000085AC /* QuickHelpView+Resolution.swift in Sources */, + 782850012E80C05100826FA7 /* Camera+Permissions.swift in Sources */, 7882629A2E514F3F000085AC /* QuickHelpView+UI.swift in Sources */, 788262762E5130D4000085AC /* SettingsView+Watermarking.swift in Sources */, 786DAE362E4F28A600BE3137 /* CameraView.swift in Sources */, @@ -1161,6 +1169,7 @@ 7882626D2E5130C1000085AC /* SettingsView+Resolution.swift in Sources */, 786DAE3C2E4F28A600BE3137 /* CompatibilityView.swift in Sources */, 7882628E2E514F25000085AC /* QuickHelpView+Resolution.swift in Sources */, + 782850022E80C05100826FA7 /* Camera+Permissions.swift in Sources */, 788262992E514F3F000085AC /* QuickHelpView+UI.swift in Sources */, 788262742E5130D4000085AC /* SettingsView+Watermarking.swift in Sources */, 786DAE3D2E4F28A600BE3137 /* PhotoPreviewView.swift in Sources */, @@ -1197,6 +1206,7 @@ 788262DC2E5171F0000085AC /* AboutView+Story.swift in Sources */, 78AF686D2E5D97EC00BB9D5C /* CameraView+Controls.swift in Sources */, 788262642E51309C000085AC /* SettingsView+Preview.swift in Sources */, + 782850032E80C05100826FA7 /* Camera+Permissions.swift in Sources */, 786DAE782E4F28B900BE3137 /* MalachiteCaptureBundle.swift in Sources */, 786DAE4E2E4F28B100BE3137 /* MalachiteFunctionUtils.swift in Sources */, 7824CAF12E62E4970072870F /* Camera+Bringup.swift in Sources */, diff --git a/Malachite/Utilities/CameraUtils/Camera+Bringup.swift b/Malachite/Utilities/CameraUtils/Camera+Bringup.swift index 9e2dda6..a052c93 100644 --- a/Malachite/Utilities/CameraUtils/Camera+Bringup.swift +++ b/Malachite/Utilities/CameraUtils/Camera+Bringup.swift @@ -19,6 +19,7 @@ extension Camera { Returns an array of ``AVCaptureDevice`` objects to use when attaching cameras to Malachite's ``AVCaptureSession``. */ func createCameraArray() -> [AVCaptureDevice] { + if !parent.permissions.cameraGranted { return [] } parent.utilities.debugNSLog("[Camera Initialization] Discovering available cameras") var camerasToDiscover: [AVCaptureDevice.DeviceType] = [] var camerasFound: [AVCaptureDevice] = [] @@ -51,6 +52,7 @@ extension Camera { Returns an ``AVCapturePhotoOutput`` object to use when taking a photo with Malachite's ``AVCaptureSession``. */ func createAndAddPhotoOutput(photoOutput: AVCapturePhotoOutput?, session: AVCaptureSession) -> AVCapturePhotoOutput { + if !parent.permissions.photosGranted { return AVCapturePhotoOutput() } if photoOutput != nil { parent.utilities.debugNSLog("[Camera Initialization] Reusing existing AVCapturePhotoOutput") } else { parent.utilities.debugNSLog("[Camera Initialization] Creating new AVCapturePhotoOutput") } let output = photoOutput ?? AVCapturePhotoOutput() @@ -66,29 +68,6 @@ extension Camera { return output } - - func createRequestToUseCamera() async -> Bool { - let status = AVCaptureDevice.authorizationStatus(for: .video) - - if status == .notDetermined { - return await AVCaptureDevice.requestAccess(for: .video) - } - - return false - } - - /** - Requests the ability to add photos to the user's library. - */ - func createRequestToAddPhotos() async -> Bool { - let status = PHPhotoLibrary.authorizationStatus(for: .addOnly) - - if status == .notDetermined { - let newStatus = await PHPhotoLibrary.requestAuthorization(for: .addOnly) - return newStatus == .authorized - } - - return false - } } } + diff --git a/Malachite/Utilities/CameraUtils/Camera+Permissions.swift b/Malachite/Utilities/CameraUtils/Camera+Permissions.swift new file mode 100644 index 0000000..18c8c71 --- /dev/null +++ b/Malachite/Utilities/CameraUtils/Camera+Permissions.swift @@ -0,0 +1,81 @@ +// +// Camera+Permissions.swift +// Malachite +// +// Created by Eva Isabella Luna on 9/21/25. +// + +import AVFoundation +import Foundation +import Photos + +extension Camera { + class Permissions { + private var utilities: MalachiteClassesObject + + init( utilities: MalachiteClassesObject ) { self.utilities = utilities } + + var cameraGranted = false + var photosGranted = false + + /// Requests camera and photo library permissions and updates ``cameraGranted`` and ``photosGranted`` + @MainActor + func requestPermissions() async { + let cameraOK = await self.createRequestToUseCamera() + self.cameraGranted = cameraOK + + let photosOK = await self.createRequestToAddPhotos() + self.photosGranted = photosOK + + utilities.debugNSLog("[Camera Permissions] Results — camera=\(cameraOK), photos(add-only)=\(photosOK)") + } + + /** + Requests the ability to use the camera. + */ + func createRequestToUseCamera() async -> Bool { + let status = AVCaptureDevice.authorizationStatus(for: .video) + switch status { + case .notDetermined: + let granted = await AVCaptureDevice.requestAccess(for: .video) + utilities.debugNSLog("[Camera Permissions] Camera access requested: granted=\(granted)") + return granted + case .authorized: + utilities.debugNSLog("[Camera Permissions] Camera access already authorized") + return true + case .restricted, .denied: + utilities.debugNSLog("[Camera Permissions] Camera access restricted/denied") + return false + @unknown default: + utilities.debugNSLog("[Camera Permissions] Camera access unknown status: \(status.rawValue)") + return false + } + } + + /** + Requests the ability to add photos to the user's library. + */ + func createRequestToAddPhotos() async -> Bool { + let status = PHPhotoLibrary.authorizationStatus(for: .addOnly) + switch status { + case .notDetermined: + let newStatus = await PHPhotoLibrary.requestAuthorization(for: .addOnly) + let granted = (newStatus == .authorized) + utilities.debugNSLog("[Camera Permissions] Photo library (add-only) requested: granted=\(granted)") + return granted + case .authorized: + utilities.debugNSLog("[Camera Permissions] Photo library (add-only) already authorized") + return true + case .limited: + utilities.debugNSLog("[Camera Permissions] Photo library (add-only) limited — treating as not granted") + return false + case .denied, .restricted: + utilities.debugNSLog("[Camera Permissions] Photo library (add-only) restricted/denied") + return false + @unknown default: + utilities.debugNSLog("[Camera Permissions] Photo library (add-only) unknown status: \(status.rawValue)") + return false + } + } + } +} diff --git a/Malachite/Utilities/CameraUtils/Camera.swift b/Malachite/Utilities/CameraUtils/Camera.swift index d390c6b..2617082 100644 --- a/Malachite/Utilities/CameraUtils/Camera.swift +++ b/Malachite/Utilities/CameraUtils/Camera.swift @@ -7,12 +7,14 @@ import AVFoundation import Foundation +import Photos class Camera: NSObject { var utilities: MalachiteClassesObject var bringup: Camera.Bringup! var input: Camera.Input! + var permissions: Camera.Permissions! var compatibility: Compatibility! var session: AVCaptureSession! @@ -22,24 +24,35 @@ class Camera: NSObject { var currentDevice: AVCaptureDevice? var currentIndex: Int? - var cameraGranted = false - var photosGranted = false - init( utilities: MalachiteClassesObject ) { self.utilities = utilities + self.permissions = Permissions(utilities: utilities) + self.compatibility = Compatibility(utilities: utilities) super.init() - setupChildClasses() - setupSession() - setupPhotoOutput() + + Task { + @MainActor in await self.permissions.requestPermissions() + if self.permissions.cameraGranted { + setupChildClasses() + setupSession() + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.cameraClassNotification.name, object: nil) + } else { + utilities.debugNSLog("[Permissions] Setup skipped due to no camera access.") + } + if self.permissions.photosGranted { + setupPhotoOutput() + } else { + utilities.debugNSLog("[Permissions] Setup skipped due to no camera access.") + } + } } public func setupChildClasses() { self.bringup = Bringup(parent: self) self.setupCameraArray() self.input = Input(parent: self) - self.compatibility = Compatibility(utilities: utilities) } public func setupCameraArray() { self.cameras = self.bringup.createCameraArray() } diff --git a/Malachite/Utilities/MalachiteFunctionUtils.swift b/Malachite/Utilities/MalachiteFunctionUtils.swift index bf5cae9..9c4b093 100755 --- a/Malachite/Utilities/MalachiteFunctionUtils.swift +++ b/Malachite/Utilities/MalachiteFunctionUtils.swift @@ -16,6 +16,7 @@ public class MalachiteFunctionUtils : NSObject { /// An `enum` that contains Notification names. public enum Notifications: String, NotificationName { + case cameraClassNotification case aspectFillNotification case exposureLimitNotification case stabilizerNotification diff --git a/Malachite/Views/CameraView/CameraView+Notifications.swift b/Malachite/Views/CameraView/CameraView+Notifications.swift index 08a91cb..169c870 100644 --- a/Malachite/Views/CameraView/CameraView+Notifications.swift +++ b/Malachite/Views/CameraView/CameraView+Notifications.swift @@ -18,6 +18,7 @@ extension CameraView { func initNotifications() { var notificationConfigs: [temputils.notificationBuilder] = [ temputils.notificationBuilder(delegate: delegate, name: UIDevice.orientationDidChangeNotification, action: #selector(orientationChanged)), + temputils.notificationBuilder(delegate: delegate, name: MalachiteFunctionUtils.Notifications.cameraClassNotification.name, action: #selector(cameraClassDidLoad)), temputils.notificationBuilder(delegate: delegate, name: MalachiteFunctionUtils.Notifications.aspectFillNotification.name, action: #selector(changeAspectFill)), temputils.notificationBuilder(delegate: delegate, name: MalachiteFunctionUtils.Notifications.exposureLimitNotification.name, action: #selector(changeExposureLimit)), temputils.notificationBuilder(delegate: delegate, name: MalachiteFunctionUtils.Notifications.stabilizerNotification.name, action: #selector(changeStabilizerMode)), diff --git a/Malachite/Views/CameraView/CameraView+Preview.swift b/Malachite/Views/CameraView/CameraView+Preview.swift index b021977..f5b0bfa 100644 --- a/Malachite/Views/CameraView/CameraView+Preview.swift +++ b/Malachite/Views/CameraView/CameraView+Preview.swift @@ -42,7 +42,7 @@ extension CameraView { } func addPreviewLayer() { - delegate.view.layer.addSublayer(previewLayer) + delegate.view.layer.insertSublayer(previewLayer, at: 0) } func initPreviewLayer() { diff --git a/Malachite/Views/CameraView/CameraView.swift b/Malachite/Views/CameraView/CameraView.swift index 9b60aea..8bb6790 100755 --- a/Malachite/Views/CameraView/CameraView.swift +++ b/Malachite/Views/CameraView/CameraView.swift @@ -122,7 +122,9 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa self.controlLayer = CameraView.ControlLayer(delegate: self) self.notifications = CameraView.Notifications(delegate: self) self.preview = CameraView.Preview(delegate: self) - + } + + @objc func cameraClassDidLoad() { if camera.cameras.first != nil { utilities.debugNSLog("[Initialization] Bringing up AVCaptureVideoPreviewLayer") preview.initPreviewLayer() @@ -135,10 +137,6 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa } else { utilities.debugNSLog("[Initialization] No cameras detected, skipping to user interface bringup") } - -#warning("malachite camera init") - - } /** From 5d54c29a83e18e1886a2677626732d045fd9a680 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Mon, 22 Sep 2025 15:23:02 -0600 Subject: [PATCH 20/66] Fix race condition on devices with multiple cameras --- .../CameraUtils/Camera+Bringup.swift | 29 ++++--------------- Malachite/Utilities/CameraUtils/Camera.swift | 17 ++++------- .../CameraView/CameraView+Controls.swift | 8 +---- Malachite/Views/CameraView/CameraView.swift | 9 +++--- 4 files changed, 18 insertions(+), 45 deletions(-) diff --git a/Malachite/Utilities/CameraUtils/Camera+Bringup.swift b/Malachite/Utilities/CameraUtils/Camera+Bringup.swift index a052c93..219d0a0 100644 --- a/Malachite/Utilities/CameraUtils/Camera+Bringup.swift +++ b/Malachite/Utilities/CameraUtils/Camera+Bringup.swift @@ -36,37 +36,20 @@ extension Camera { return camerasFound } - /** - Creates and returns a blank ``AVCaptureSession`` for use in Malachite. - */ - func createAVCaptureSession(session: AVCaptureSession?) -> AVCaptureSession { - if let session = session { - parent.utilities.debugNSLog("[Camera Initialization] Reusing existing AVCaptureSession") - return session - } - parent.utilities.debugNSLog("[Camera Initialization] Creating new AVCaptureSession") - return AVCaptureSession() - } - /** Returns an ``AVCapturePhotoOutput`` object to use when taking a photo with Malachite's ``AVCaptureSession``. */ - func createAndAddPhotoOutput(photoOutput: AVCapturePhotoOutput?, session: AVCaptureSession) -> AVCapturePhotoOutput { - if !parent.permissions.photosGranted { return AVCapturePhotoOutput() } - if photoOutput != nil { parent.utilities.debugNSLog("[Camera Initialization] Reusing existing AVCapturePhotoOutput") - } else { parent.utilities.debugNSLog("[Camera Initialization] Creating new AVCapturePhotoOutput") } - let output = photoOutput ?? AVCapturePhotoOutput() - if !session.outputs.contains(output) { + func addPhotoOutput(session: AVCaptureSession) { + if !parent.permissions.photosGranted { return } + if !session.outputs.contains(parent.output) { parent.utilities.debugNSLog("[Camera Initialization] Running AVCapturePhotoOutput initialization steps") - if #unavailable(iOS 16.0) { output.isHighResolutionCaptureEnabled = true } - output.maxPhotoQualityPrioritization = .quality + if #unavailable(iOS 16.0) { parent.output.isHighResolutionCaptureEnabled = true } + parent.output.maxPhotoQualityPrioritization = .quality session.sessionPreset = AVCaptureSession.Preset.photo - session.addOutput(output) + session.addOutput(parent.output) } else { parent.utilities.debugNSLog("[Camera Initialization] AVCapturePhotoOutput already initialized, continuing") } - - return output } } } diff --git a/Malachite/Utilities/CameraUtils/Camera.swift b/Malachite/Utilities/CameraUtils/Camera.swift index 2617082..ef79b48 100644 --- a/Malachite/Utilities/CameraUtils/Camera.swift +++ b/Malachite/Utilities/CameraUtils/Camera.swift @@ -17,8 +17,8 @@ class Camera: NSObject { var permissions: Camera.Permissions! var compatibility: Compatibility! - var session: AVCaptureSession! - var output: AVCapturePhotoOutput! + var session = AVCaptureSession() + var output = AVCapturePhotoOutput() var cameras: [ AVCaptureDevice ]! var currentDevice: AVCaptureDevice? @@ -35,9 +35,7 @@ class Camera: NSObject { Task { @MainActor in await self.permissions.requestPermissions() if self.permissions.cameraGranted { - setupChildClasses() - setupSession() - NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.cameraClassNotification.name, object: nil) + setupChildren() } else { utilities.debugNSLog("[Permissions] Setup skipped due to no camera access.") } @@ -49,17 +47,14 @@ class Camera: NSObject { } } - public func setupChildClasses() { + public func setupChildren() { self.bringup = Bringup(parent: self) self.setupCameraArray() self.input = Input(parent: self) + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.cameraClassNotification.name, object: nil) } public func setupCameraArray() { self.cameras = self.bringup.createCameraArray() } - public func setupSession() { self.session = self.bringup.createAVCaptureSession(session: self.session) } - - public func setupPhotoOutput() { - self.output = self.bringup.createAndAddPhotoOutput(photoOutput: output, session: self.session) - } + public func setupPhotoOutput() { self.bringup.addPhotoOutput(session: self.session) } } diff --git a/Malachite/Views/CameraView/CameraView+Controls.swift b/Malachite/Views/CameraView/CameraView+Controls.swift index 2dc62c9..69c16ce 100644 --- a/Malachite/Views/CameraView/CameraView+Controls.swift +++ b/Malachite/Views/CameraView/CameraView+Controls.swift @@ -151,13 +151,8 @@ extension CameraView { initButtons() initSliders() initRecognizers() - //if !delegate.utilities.preferences.userInterface.appLaunch { initTooltips(showLabels: true, showCamera: true) } + if !delegate.utilities.preferences.userInterface.appLaunch { initTooltips(showLabels: true, showCamera: true) } if #available(iOS 17.2, *) { initEventInteraction() } - if #available(iOS 18.0, *) { - if delegate.utilities.versionType == "INTERNAL" && delegate.utilities.preferences.evaintrnl.cameraControlEnabled { - initCameraControl() - } - } } } } @@ -186,7 +181,6 @@ extension CameraView.ControlLayer { } func initCameraControl() { - guard delegate.camera.session != nil else { return } guard delegate.camera.session.supportsControls else { return } var controls: [ AVCaptureControl ] = [] diff --git a/Malachite/Views/CameraView/CameraView.swift b/Malachite/Views/CameraView/CameraView.swift index 8bb6790..cb262e4 100755 --- a/Malachite/Views/CameraView/CameraView.swift +++ b/Malachite/Views/CameraView/CameraView.swift @@ -117,18 +117,20 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = .black + self.notifications = CameraView.Notifications(delegate: self) + self.notifications.bringUpNotifications() + self.camera = Camera(utilities: utilities) - self.controlLayer = CameraView.ControlLayer(delegate: self) - self.notifications = CameraView.Notifications(delegate: self) self.preview = CameraView.Preview(delegate: self) + self.controlLayer = CameraView.ControlLayer(delegate: self) } @objc func cameraClassDidLoad() { if camera.cameras.first != nil { utilities.debugNSLog("[Initialization] Bringing up AVCaptureVideoPreviewLayer") + self.runInputSwitch() preview.initPreviewLayer() - runInputSwitch() utilities.debugNSLog("[Initialization] Starting session stream") DispatchQueue.global(qos: .background).async { @@ -219,7 +221,6 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa #endif self.controlLayer.bringUpControlLayer() - self.notifications.bringUpNotifications() utilities.function.changeIdleTimerState() } From 24d564ff76baff813acb24a02d4bafaf23d357b5 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Mon, 22 Sep 2025 15:44:13 -0600 Subject: [PATCH 21/66] Various compatibility check changes - Moved deviceModel and deviceModelHasChanged into compatibilityPreferences - Removed prefsVersion key, it was unused after the migratePreferences() rewrite - Call compatibility.checkDeviceForHEICCompatibility() from Init --- Malachite/Utilities/CameraUtils/Camera+Bringup.swift | 2 +- .../Utilities/CompatibilityUtils/Compatibility.swift | 2 +- Malachite/Utilities/InitUtils/Init+Internal.swift | 8 ++++---- Malachite/Utilities/InitUtils/Init.swift | 3 +++ .../PreferenceUtils/MalachitePreferences.swift | 9 ++++++--- .../PreferenceUtils/MalachitePreferencesUtils.swift | 10 +++++----- .../Views/DeveloperView/DeveloperView+DeviceInfo.swift | 2 +- 7 files changed, 21 insertions(+), 15 deletions(-) diff --git a/Malachite/Utilities/CameraUtils/Camera+Bringup.swift b/Malachite/Utilities/CameraUtils/Camera+Bringup.swift index 219d0a0..3829fb1 100644 --- a/Malachite/Utilities/CameraUtils/Camera+Bringup.swift +++ b/Malachite/Utilities/CameraUtils/Camera+Bringup.swift @@ -28,7 +28,7 @@ extension Camera { let currentProcess = ProcessInfo() AVCaptureDevice.DiscoverySession.init(deviceTypes: camerasToDiscover, mediaType: .video, position: (currentProcess.isiOSAppOnMac || currentProcess.isMacCatalystApp) ? .unspecified : .back).devices.forEach { device in - if parent.utilities.preferences.general.deviceModelHasChanged { parent.compatibility.checkCameraCapabilities(device: device) } + if parent.utilities.preferences.compatibility.device.changed { parent.compatibility.checkCameraCapabilities(device: device) } camerasFound.append(device) parent.utilities.debugNSLog("[Camera Initialization] \(device.deviceType.rawValue) available") } diff --git a/Malachite/Utilities/CompatibilityUtils/Compatibility.swift b/Malachite/Utilities/CompatibilityUtils/Compatibility.swift index 0dffeb1..f3e98a6 100644 --- a/Malachite/Utilities/CompatibilityUtils/Compatibility.swift +++ b/Malachite/Utilities/CompatibilityUtils/Compatibility.swift @@ -31,7 +31,7 @@ class Compatibility { HEIF is supported on Apple devices with the A10 Fusion chip or later. */ func checkDeviceForHEICCompatibility() { - if !utilities.preferences.general.deviceModelHasChanged { return } + if !utilities.preferences.compatibility.device.changed { return } let supportedTypeIdentifiers = CGImageDestinationCopyTypeIdentifiers() as NSArray if utilities.preferences.compatibility.jpeg != supportedTypeIdentifiers.contains("public.jpeg") { diff --git a/Malachite/Utilities/InitUtils/Init+Internal.swift b/Malachite/Utilities/InitUtils/Init+Internal.swift index 8281285..5fd0efa 100644 --- a/Malachite/Utilities/InitUtils/Init+Internal.swift +++ b/Malachite/Utilities/InitUtils/Init+Internal.swift @@ -19,15 +19,15 @@ extension Init { /// Checks whether or not the current device is the same device as previously recorded in preferences. public func isSameDevice() { - if utilities.preferences.general.deviceModelHasChanged { utilities.preferences.general.deviceModelHasChanged = false } - if utilities.preferences.general.deviceModel == utilities.preferences.ext.deviceModel() { + if utilities.preferences.compatibility.device.changed { utilities.preferences.compatibility.device.changed = false } + if utilities.preferences.compatibility.device.model == utilities.preferences.ext.deviceModel() { utilities.internalNSLog("[Initialization] This is the same device, can skip compatibility checks.") return } utilities.internalNSLog("[Initialization] This is a new device, rechecking compatibility.") - utilities.preferences.general.deviceModel = utilities.preferences.ext.deviceModel() - utilities.preferences.general.deviceModelHasChanged = true + utilities.preferences.compatibility.device.model = utilities.preferences.ext.deviceModel() + utilities.preferences.compatibility.device.changed = true } /// Runs all of the initialization functions defined in this class. diff --git a/Malachite/Utilities/InitUtils/Init.swift b/Malachite/Utilities/InitUtils/Init.swift index 513c205..1ab1b8d 100644 --- a/Malachite/Utilities/InitUtils/Init.swift +++ b/Malachite/Utilities/InitUtils/Init.swift @@ -12,11 +12,13 @@ class Init { private var utilities: MalachiteClassesObject private var debug: Init.Debug private var intrnl: Init.Internal + private var compatibility: Compatibility init( utilities: MalachiteClassesObject ) { self.utilities = utilities self.debug = Debug(utilities: utilities) self.intrnl = Internal(utilities: utilities) + self.compatibility = Compatibility(utilities: utilities) } /// Prints a message about Malachite starting. @@ -47,6 +49,7 @@ class Init { startupLog() versionTypeCheck() appExtensionCheck() + self.compatibility.checkDeviceForHEICCompatibility() if utilities.versionType == "DEBUG" || utilities.versionType == "INTERNAL" { debug.initMalachite() } if utilities.versionType == "INTERNAL" { intrnl.initMalachite() } diff --git a/Malachite/Utilities/PreferenceUtils/MalachitePreferences.swift b/Malachite/Utilities/PreferenceUtils/MalachitePreferences.swift index 76ee330..10d20d8 100755 --- a/Malachite/Utilities/PreferenceUtils/MalachitePreferences.swift +++ b/Malachite/Utilities/PreferenceUtils/MalachitePreferences.swift @@ -19,6 +19,7 @@ struct MalachitePreferences: Codable { var compatibility: compatibilityPreferences struct compatibilityPreferences: Codable { + var device: devicePreferences var ultrawide: [ String : Bool ] var wideangle: [ String : Bool ] var telephoto: [ String : Bool ] @@ -27,16 +28,18 @@ struct MalachitePreferences: Codable { var raw: Bool var proraw: Bool var hdr: Bool + + struct devicePreferences: Codable { + var model: String + var changed: Bool + } } var general: generalPreferences struct generalPreferences: Codable { var version: String - var prefsVersion: Int var firstLaunch: Bool - var deviceModel: String - var deviceModelHasChanged: Bool var photoCount: Int var gamekit: gamekitPreferences diff --git a/Malachite/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift b/Malachite/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift index 9af0729..5794a79 100755 --- a/Malachite/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift +++ b/Malachite/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift @@ -91,6 +91,8 @@ class MalachitePreferencesUtils { var currentPreferences = initPreferences() if let compatibilityPreferences = oldPreferences["compatibility"] as? [ String: AnyObject ] { + currentPreferences.compatibility.device.model = compatibilityPreferences["device"]?["model"] as? String ?? "Eva1,1" + currentPreferences.compatibility.device.changed = compatibilityPreferences["device"]?["changed"] as? Bool ?? false currentPreferences.compatibility.ultrawide = compatibilityPreferences["ultrawide"] as? [ String: Bool ] ?? [ "invalid" : false] currentPreferences.compatibility.wideangle = compatibilityPreferences["wideangle"] as? [ String: Bool ] ?? [ "invalid" : false] currentPreferences.compatibility.telephoto = compatibilityPreferences["telephoto"] as? [ String: Bool ] ?? [ "invalid" : false] @@ -103,8 +105,6 @@ class MalachitePreferencesUtils { if let generalPreferences = oldPreferences["general"] as? [ String: AnyObject ] { currentPreferences.general.firstLaunch = generalPreferences["firstLaunch"] as? Bool ?? false - currentPreferences.general.deviceModel = generalPreferences["deviceModel"] as? String ?? "Eva1,1" - currentPreferences.general.deviceModelHasChanged = generalPreferences["deviceModelHasChanged"] as? Bool ?? false currentPreferences.general.photoCount = generalPreferences["photoCount"] as? Int ?? 0 currentPreferences.general.gamekit.alerted = generalPreferences["gamekit"]?["alerted"] as? Bool ?? false currentPreferences.general.gamekit.found = generalPreferences["gamekit"]?["found"] as? Bool ?? false @@ -164,6 +164,9 @@ class MalachitePreferencesUtils { func initPreferences() -> MalachitePreferences { return MalachitePreferences( compatibility: MalachitePreferences.compatibilityPreferences( + device: MalachitePreferences.compatibilityPreferences.devicePreferences( + model: "Eva1,1", + changed: false), ultrawide: [ "invalid" : false ], wideangle: [ "invalid" : false ], telephoto: [ "invalid" : false ], @@ -175,10 +178,7 @@ class MalachitePreferencesUtils { ), general: MalachitePreferences.generalPreferences( version: Bundle.main.infoDictionary?["CFBundleVersion"] as! String, - prefsVersion: 6, firstLaunch: false, - deviceModel: "Eva1,1", - deviceModelHasChanged: false, photoCount: 0, gamekit: MalachitePreferences.generalPreferences.gamekitPreferences( alerted: false, diff --git a/Malachite/Views/DeveloperView/DeveloperView+DeviceInfo.swift b/Malachite/Views/DeveloperView/DeveloperView+DeviceInfo.swift index b696569..638b5a0 100644 --- a/Malachite/Views/DeveloperView/DeveloperView+DeviceInfo.swift +++ b/Malachite/Views/DeveloperView/DeveloperView+DeviceInfo.swift @@ -20,7 +20,7 @@ extension DeveloperView { var body: some View { Section(header: Text("developer.header.device"), footer: Text("developer.footer.device")) { - createBuildInformation(label: "developer.option.device_model", value: utilities.preferences.general.deviceModel) + createBuildInformation(label: "developer.option.device_model", value: utilities.preferences.compatibility.device.model) createBuildInformation(label: "developer.option.device_version", value: getCurrentOSVersion()) createBuildInformation(label: "developer.option.device_build", value: getCurrentOSBuild()) } From 0d1130957d01cadd2a5088d57e9213d40a83fe55 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Mon, 22 Sep 2025 22:56:13 -0600 Subject: [PATCH 22/66] "At this point, Malachite is a camera app." --- README.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c337c5f..fd99142 100755 --- a/README.md +++ b/README.md @@ -1,21 +1,22 @@ # Malachite *noun* 1. a crystal with the properties of revealing hidden parts of yourself -2. the name of my app to take control of the macro lens on iPhone +2. the name of my app to take back control of your iPhone or iPad's camera lenses. --- ## What is it? -Malachite is a **work-in-progress** app that gives you more control over the macro lens of (or connected to) your iPhone. +Malachite is a **work-in-progress** camera app, designed to put pro controls into the hands of even more users. ## What do I need?[^1] +Malachite will run on any iPhone or iPad with **iOS 15.0** or later. + I recommend one of the following configurations... -- An iPhone or iPad with one of the following: +- An iPhone or iPad with one of the following... - A built-in ultra-wide camera that supports Apple's macro mode: - iPhone 13 Pro or iPhone 13 Pro Max - iPhone 14 Pro or iPhone 14 Pro Max - iPhone 15 Pro or iPhone 15 Pro Max - iPhone 16 or later (excluding iPhone 16e) - - A third-party lens attachment -...and **iOS 15.0** or later. +- ...and/or a third-party lens attachment ## What can I do with this? - [x] Enjoy a fully-native, no-external library Swift app @@ -46,7 +47,13 @@ Malachite is on TestFlight, but only for **[my Patrons](https://patreon.com/crys 4. Build! ## What started this one, Eva? -So, I live with my love: @ThatStella7922. She and I are both big nerds, and I caught her using her macro lens on an Xbox 360 motherboard to let her work with traces and pads while she was RGH'ing it. The idea for a magnifier app came from how much time it took for her to get into the right camera setting, turn the flashlight on, and still not have much control beyond autofocus and zooming. With Malachite, I strove to solve this problem - and then I ended up making it even greater for the people who love macro photography on their own iPhones. +So, I live with my love: @ThatStella7922. She and I are both big nerds, and I caught her using her macro lens on an Xbox 360 motherboard to let her work with traces and pads while she was RGH'ing it. The idea for a magnifier app came from how much time it took for her to get into the right camera setting, turn the flashlight on, and still not have much control beyond autofocus and zooming. With Malachite, I strove to solve this problem - and thus, we had Malachite with its original purpose: a macro magnifier. + +As I was working on it, I'd drop builds into my Discord server. A few users came in and asked for various features - including image capture. I was originally opposed to it, since it *was* just for magnification... and yet, a few hours later, I'd hooked everything up to add image capture support - saving HEICs to the user's library or directly out of the share sheet. Malachite ended up morphing into a macro photography app that people used and enjoyed - and requested more out of. + +At this point, I've added plenty of extras. Camera switching, manual exposure, hardware button controls - those are just a few and I plan to implement so much more in the future. It took me a while to accept it, but this little side-project of mine was becoming something different, and the goal solidified itself as this: creating a powerful pro camera app that truly harnesses iPhone and iPad hardware, while staying simplistic in its design and accessible to anyone who wants to get into photography. + +To the people who helped get me here (and you know who you are), I thank you for helping me figure it out. [^1]: Malachite is validated against iPhone SE (1st generation) with no lens attachment, iPhone 8 Plus with no working main camera, iPhone 11, iPhone 16 Pro Max, and iPad Pro (11-inch). Not all features are available across all devices, due to hardware and software limitations. iOS version support may change depending on the difficulty of targeting older iOS versions and/or other factors. [^2]: Pinch-to-zoom will feature haptic feedback when reaching the minimum and maximum zoom levels in a future commit. From 2df0ca817f4dd4638dcdddf2683bfe1d8cb41ae5 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Wed, 24 Sep 2025 22:50:52 -0600 Subject: [PATCH 23/66] HEIF -> HEIC So this is awkward... I forgot that Apple's been using HEIC the entire time... --- Malachite/Localizable.xcstrings | 18 +++++++++--------- .../CompatibilityUtils/Compatibility.swift | 4 ++-- .../CompatibilityView+Format.swift | 2 +- .../PhotoPreviewView/PhotoPreviewView.swift | 16 ++++++++-------- .../SettingsView/SettingsView+Photo.swift | 4 ++-- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Malachite/Localizable.xcstrings b/Malachite/Localizable.xcstrings index 6fa6534..cad8059 100755 --- a/Malachite/Localizable.xcstrings +++ b/Malachite/Localizable.xcstrings @@ -594,24 +594,24 @@ } } }, - "compatibility.title.heif" : { + "compatibility.title.heic" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Device supports encoding in HEIF" + "value" : "Device supports encoding in HEIC" } } } }, - "compatibility.title.heif.unsupported" : { + "compatibility.title.heic.unsupported" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Device does not support HEIF encoding" + "value" : "Device does not support HEIC encoding" } } } @@ -1216,13 +1216,13 @@ } } }, - "settings.footer.photo.heif" : { + "settings.footer.photo.heic" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "The HEIF file format requires a device with the Apple A10 Fusion chip or later.\\n" + "value" : "The HEIC file format requires a device with the Apple A10 Fusion chip or later." } } } @@ -1337,12 +1337,12 @@ } } }, - "settings.option.photo.fileformat.heif" : { + "settings.option.photo.fileformat.heic" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "HEIF" + "value" : "HEIC" } } } @@ -1784,4 +1784,4 @@ } }, "version" : "1.1" -} \ No newline at end of file +} diff --git a/Malachite/Utilities/CompatibilityUtils/Compatibility.swift b/Malachite/Utilities/CompatibilityUtils/Compatibility.swift index f3e98a6..9835d06 100644 --- a/Malachite/Utilities/CompatibilityUtils/Compatibility.swift +++ b/Malachite/Utilities/CompatibilityUtils/Compatibility.swift @@ -26,9 +26,9 @@ class Compatibility { /** Checks whether or not the current device is capable of encoding High Efficiency Image Format. - If the device doesn't support HEIF, the option is disabled in preferences to prevent crashes. + If the device doesn't support HEIC, the option is disabled in preferences to prevent crashes. - HEIF is supported on Apple devices with the A10 Fusion chip or later. + HEIC is supported on Apple devices with the A10 Fusion chip or later. */ func checkDeviceForHEICCompatibility() { if !utilities.preferences.compatibility.device.changed { return } diff --git a/Malachite/Views/CompatibilityView/CompatibilityView+Format.swift b/Malachite/Views/CompatibilityView/CompatibilityView+Format.swift index 5409014..463e28c 100644 --- a/Malachite/Views/CompatibilityView/CompatibilityView+Format.swift +++ b/Malachite/Views/CompatibilityView/CompatibilityView+Format.swift @@ -21,7 +21,7 @@ extension CompatibilityView { var body: some View { Section(header: Text("compatibility.header.format")) { MalachiteCompatibilityViewUtils(title: "compatibility.title.jpeg", available: utilities.preferences.compatibility.jpeg) - MalachiteCompatibilityViewUtils(title: "compatibility.title.heif", available: utilities.preferences.compatibility.heic) + MalachiteCompatibilityViewUtils(title: "compatibility.title.heic", available: utilities.preferences.compatibility.heic) } } } diff --git a/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift b/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift index 5514f9c..afbcb22 100755 --- a/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift +++ b/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift @@ -61,8 +61,8 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { /// A variable to store whether or not HDR is enabled. let enableHDR = MalachitePreferencesUtils.shared.preferences.capture.hdr - /// A variable to store whether or not the HEIF file format is enabled. - let enableHEIF = MalachitePreferencesUtils.shared.preferences.capture.format.heic + /// A variable to store whether or not the HEIC file format is enabled. + let enableHEIC = MalachitePreferencesUtils.shared.preferences.capture.format.heic /** viewDidLoad override for the main user interface. @@ -208,7 +208,7 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { - Creates an image from the ``photoImageData`` that was passed on creation of the view controller. - If ``enableHDR`` is enabled, creates a gain map image with HDR data inside. - If the user has enabled watermarking, creates an image with the watermark and the original image's dimensions. - - If ``enableHEIF`` is enabled, create a HEIC representation of all above images combined. Otherwise, JPEG is used. + - If ``enableHEIC`` is enabled, create a HEIC representation of all above images combined. Otherwise, JPEG is used. */ public func finalizeImageForExport(imageData: Data) -> Data { var data = Data() @@ -238,7 +238,7 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { let outputImageWithProps = outputImage.settingProperties(imageProperties) - if enableHEIF { + if enableHEIC { data = returnHEIC(imageForRepresentation: outputImageWithProps, imageForGainMap: gainMapImage, imageColorspace: rawImage.colorSpace?.name) } else { data = returnJPEG(imageForRepresentation: outputImageWithProps, imageForGainMap: gainMapImage, imageColorspace: rawImage.colorSpace?.name) @@ -287,12 +287,12 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { let types = CGImageDestinationCopyTypeIdentifiers() as NSArray if types.contains("public.heic") { if enableHDR && (hdrImage != nil){ - return CIContext().heifRepresentation(of: image, format: .RGBA8, colorSpace: CGColorSpace(name: colorSpace!)!, options: [ .hdrGainMapImage : hdrImage! ])! + return CIContext().heicRepresentation(of: image, format: .RGBA8, colorSpace: CGColorSpace(name: colorSpace!)!, options: [ .hdrGainMapImage : hdrImage! ])! } else { - return CIContext().heifRepresentation(of: image, format: .RGBA8, colorSpace: CGColorSpace(name: colorSpace!)!)! + return CIContext().heicRepresentation(of: image, format: .RGBA8, colorSpace: CGColorSpace(name: colorSpace!)!)! } } else { - utilities.debugNSLog("[Capture Photo] Device does not support encoding HEIF, falling back to JPEG") + utilities.debugNSLog("[Capture Photo] Device does not support encoding HEIC, falling back to JPEG") utilities.preferences.capture.format.heic = false return returnJPEG(imageForRepresentation: image, imageForGainMap: hdrImage, imageColorspace: colorSpace) } @@ -300,7 +300,7 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { /// Function to return a JPEG representation of the passed image with its colorspace and an optional gain map image. func returnJPEG(imageForRepresentation image: CIImage, imageForGainMap hdrImage: CIImage?, imageColorspace colorSpace: CFString?) -> Data { - utilities.debugNSLog("[Capture Photo] HEIF is disabled, saving JPEG representation") + utilities.debugNSLog("[Capture Photo] HEIC is disabled, saving JPEG representation") if enableHDR && (hdrImage != nil) { return CIContext().jpegRepresentation(of: image, colorSpace: CGColorSpace(name: colorSpace!)!, options: [ .hdrGainMapImage : hdrImage! ])! } else { diff --git a/Malachite/Views/SettingsView/SettingsView+Photo.swift b/Malachite/Views/SettingsView/SettingsView+Photo.swift index 7893ca7..4de4405 100644 --- a/Malachite/Views/SettingsView/SettingsView+Photo.swift +++ b/Malachite/Views/SettingsView/SettingsView+Photo.swift @@ -40,7 +40,7 @@ extension SettingsView { Picker("settings.option.photo.fileformat", selection: $photoFormat) { Text("settings.option.photo.fileformat.jpeg") .tag(0) - Text("settings.option.photo.fileformat.heif") + Text("settings.option.photo.fileformat.heic") .tag(1) } } @@ -95,7 +95,7 @@ extension SettingsView { } func onAppear() { - if !utilities.preferences.compatibility.heic { formatFooterText = "settings.footer.photo.heif".localized } + if !utilities.preferences.compatibility.heic { formatFooterText = "settings.footer.photo.heic".localized } if !utilities.preferences.compatibility.hdr { formatFooterText = (formatFooterText != nil) ? formatFooterText! + "settings.footer.photo.hdr".localized : "settings.footer.photo.hdr".localized From cb0d96533f43f87c18492940ae860e25e83accc4 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Thu, 25 Sep 2025 00:17:13 -0600 Subject: [PATCH 24/66] Fix build error from last commit I was on autopilot --- Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift b/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift index afbcb22..ee3fcb8 100755 --- a/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift +++ b/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift @@ -287,9 +287,9 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { let types = CGImageDestinationCopyTypeIdentifiers() as NSArray if types.contains("public.heic") { if enableHDR && (hdrImage != nil){ - return CIContext().heicRepresentation(of: image, format: .RGBA8, colorSpace: CGColorSpace(name: colorSpace!)!, options: [ .hdrGainMapImage : hdrImage! ])! + return CIContext().heifRepresentation(of: image, format: .RGBA8, colorSpace: CGColorSpace(name: colorSpace!)!, options: [ .hdrGainMapImage : hdrImage! ])! } else { - return CIContext().heicRepresentation(of: image, format: .RGBA8, colorSpace: CGColorSpace(name: colorSpace!)!)! + return CIContext().heifRepresentation(of: image, format: .RGBA8, colorSpace: CGColorSpace(name: colorSpace!)!)! } } else { utilities.debugNSLog("[Capture Photo] Device does not support encoding HEIC, falling back to JPEG") From fcd2a1c334ddcfa1ce157de041c450e316305388 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Sun, 28 Sep 2025 16:23:28 -0600 Subject: [PATCH 25/66] Fix crashing on devices that do not support HDR I guess it makes sense that isVideoHDREnabled = false fails... --- Malachite.xcodeproj/project.pbxproj | 10 ---------- Malachite/Utilities/CameraUtils/Camera+Input.swift | 3 ++- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/Malachite.xcodeproj/project.pbxproj b/Malachite.xcodeproj/project.pbxproj index 474dd1c..fc1f1a6 100755 --- a/Malachite.xcodeproj/project.pbxproj +++ b/Malachite.xcodeproj/project.pbxproj @@ -942,11 +942,6 @@ "$PLISTBUDDY -c \"Set :CFBuildUser $CFBUILDUSER\" \"${INFOPLIST}\"", "$PLISTBUDDY -c \"Set :CFBuildHost $CFBUILDHOST\" \"${INFOPLIST}\"", "", - "", - "", - "", - "", - "", ); }; 78EC5D602E4EC1AD005044E2 /* Embed build information */ = { @@ -1039,11 +1034,6 @@ "$PLISTBUDDY -c \"Set :CFBuildUser $CFBUILDUSER\" \"${INFOPLIST}\"", "$PLISTBUDDY -c \"Set :CFBuildHost $CFBUILDHOST\" \"${INFOPLIST}\"", "", - "", - "", - "", - "", - "", ); }; /* End PBXShellScriptBuildPhase section */ diff --git a/Malachite/Utilities/CameraUtils/Camera+Input.swift b/Malachite/Utilities/CameraUtils/Camera+Input.swift index 1cba8d5..bf6461b 100644 --- a/Malachite/Utilities/CameraUtils/Camera+Input.swift +++ b/Malachite/Utilities/CameraUtils/Camera+Input.swift @@ -89,7 +89,8 @@ extension Camera { parent.utilities.debugNSLog("[Camera Input] Checking if we should enable HDR: supportedByDevice: \(parent.utilities.preferences.compatibility.hdr), enabledInPreferences: \(parent.utilities.preferences.capture.hdr)") device.automaticallyAdjustsVideoHDREnabled = false - device.isVideoHDREnabled = (parent.utilities.preferences.compatibility.hdr && parent.utilities.preferences.capture.hdr) + + if parent.utilities.preferences.compatibility.hdr { device.isVideoHDREnabled = parent.utilities.preferences.capture.hdr } parent.utilities.debugNSLog("[Camera Input]" + (device.isVideoHDREnabled ? "Disabling HDR" : " Enabling HDR")) From 89df66e39e79dac2121ee2f0bf0d7e4c661a5691 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Wed, 1 Oct 2025 00:56:38 -0600 Subject: [PATCH 26/66] Documentation for CameraUtils, some var renames --- .../CameraUtils/Camera+Bringup.swift | 14 +++- .../Utilities/CameraUtils/Camera+Input.swift | 44 ++++++++++-- .../CameraUtils/Camera+Permissions.swift | 12 ++-- Malachite/Utilities/CameraUtils/Camera.swift | 69 ++++++++++++++++++- .../CameraView/CameraView+Controls.swift | 18 ++--- Malachite/Views/CameraView/CameraView.swift | 34 +++++---- 6 files changed, 148 insertions(+), 43 deletions(-) diff --git a/Malachite/Utilities/CameraUtils/Camera+Bringup.swift b/Malachite/Utilities/CameraUtils/Camera+Bringup.swift index 3829fb1..df3ee3f 100644 --- a/Malachite/Utilities/CameraUtils/Camera+Bringup.swift +++ b/Malachite/Utilities/CameraUtils/Camera+Bringup.swift @@ -11,12 +11,17 @@ import Photos extension Camera { class Bringup { - private var parent: Camera + /// An existing instance of the ``Camera`` class. + var parent: Camera + /// Initailizer function for the ``Camera/Bringup`` class. init( parent: Camera ) { self.parent = parent } /** - Returns an array of ``AVCaptureDevice`` objects to use when attaching cameras to Malachite's ``AVCaptureSession``. + Returns an array of `AVCaptureDevice` objects to use when attaching cameras to Malachite's `AVCaptureSession`. + + If camera permissions aren't granted, or the current device doesn't have any cameras to add to this array, + this function returns `[]`. */ func createCameraArray() -> [AVCaptureDevice] { if !parent.permissions.cameraGranted { return [] } @@ -37,7 +42,10 @@ extension Camera { } /** - Returns an ``AVCapturePhotoOutput`` object to use when taking a photo with Malachite's ``AVCaptureSession``. + Returns an `AVCapturePhotoOutput` object to use when taking a photo with Malachite's `AVCaptureSession`. + + If photo library permissions aren't granted, this function returns before any `AVCapturePhotoOutput` + initialization occurs. */ func addPhotoOutput(session: AVCaptureSession) { if !parent.permissions.photosGranted { return } diff --git a/Malachite/Utilities/CameraUtils/Camera+Input.swift b/Malachite/Utilities/CameraUtils/Camera+Input.swift index bf6461b..dcdcff0 100644 --- a/Malachite/Utilities/CameraUtils/Camera+Input.swift +++ b/Malachite/Utilities/CameraUtils/Camera+Input.swift @@ -10,14 +10,25 @@ import Foundation extension Camera { class Input { + /// An existing instance of the ``Camera`` class. var parent: Camera + /// Initailizer function for the ``Camera/Input`` class. init( parent: Camera ) { self.parent = parent } - /// Function to switch cameras and attach new inputs to ``cameraSession``, and set settings based on the `activeFormat` of ``selectedDevice``. + /** + Changes the current device providing input to ``Camera/session``. + + A quick rundown of this function: + - Checks whether or not there is a camera available for switching using ``selectDeviceForSwitch()`` + - Checks whether or not there is a camera already providing an input to ``Camera.session``, and removing it + - Configures the currently active format on the new camera to the highest quality and resolution possible + - Sends notifications about unsupported features such as focus or exposure with ``handleUnsupportedFeaturesOnSwitch(supported:notification:)`` + - Creates and attaches an input with the ``Camera/device`` object to ``Camera/session`` + */ @objc func runInputSwitch() { - parent.currentDevice = selectDeviceForSwitch() - guard let device = parent.currentDevice else { return } + parent.device = selectDeviceForSwitch() + guard let device = parent.device else { return } parent.session.beginConfiguration() let sessionIsEmpty = parent.session.inputs.isEmpty @@ -68,13 +79,22 @@ extension Camera { if !supported { NotificationCenter.default.post(name: notification, object: nil) } } + /** + Returns a new `AVCaptureDevice` object to be used for camera switching. + + > Warning: Don't call this function manually if using ``runInputSwitch()`` - it's already called there. + + > Info: The camera that's chosen depends on how the user initiated this call. If they used the Camera Control on iPhone + 16 and later, this function switches to the specific camera they chose in the overlay. Otherwise, this function simply chooses + the next available camera in ``Camera/cameras``, or the first if the existing camera is at the end of the array. + */ public func selectDeviceForSwitch() -> AVCaptureDevice? { if parent.cameras.count > 0 { - if parent.currentIndex != nil { return parent.cameras[parent.currentIndex!] } else { + if parent.index != nil { return parent.cameras[parent.index!] } else { if parent.session.inputs.isEmpty { return parent.cameras.first } else { - if let devicePosition = parent.cameras.firstIndex(of: parent.currentDevice!) { + if let devicePosition = parent.cameras.firstIndex(of: parent.device!) { return parent.cameras[(devicePosition == (parent.cameras.count - 1)) ? 0 : devicePosition + 1] } } @@ -83,6 +103,14 @@ extension Camera { return nil } + /** + Enables or disables HDR on the passed `AVCaptureDevice` + + MalachiteKit's HDR implementation is built on Apple's APIs from iOS 14.1 on iPhone 12 and later. The user + is also provided with the facility to disable HDR should they need or want to. + + > Info: Supporting HDR on older iPhone models is being researched and will be available in a future release. + */ public func setHDREnabledOnDevice(device: AVCaptureDevice) { parent.compatibility.checkDeviceForHDRCompatibility(device: device) @@ -97,6 +125,12 @@ extension Camera { if device.activeFormat.isGlobalToneMappingSupported { device.isGlobalToneMappingEnabled = false } } + /** + Changes the maxPhotoDimensions property on the passed `AVCapturePhotoOutput`. + + MalachiteKit supports 8MP, 12MP, and 48MP cameras. See Apple's Tech Specs page for information on the capabilities + of specific devices. + */ @available(iOS 16.0, *) @objc public func switchInputMegapixels(device: AVCaptureDevice, photoOutput: AVCapturePhotoOutput) { let maxDimensions = device.activeFormat.supportedMaxPhotoDimensions[device.activeFormat.supportedMaxPhotoDimensions.count - 1] diff --git a/Malachite/Utilities/CameraUtils/Camera+Permissions.swift b/Malachite/Utilities/CameraUtils/Camera+Permissions.swift index 18c8c71..e6f780e 100644 --- a/Malachite/Utilities/CameraUtils/Camera+Permissions.swift +++ b/Malachite/Utilities/CameraUtils/Camera+Permissions.swift @@ -11,11 +11,15 @@ import Photos extension Camera { class Permissions { + /// An instance of ``MalachiteClassesObject`` for reuse across the app. private var utilities: MalachiteClassesObject + /// Initailizer function for the ``Camera/Permissions`` class. init( utilities: MalachiteClassesObject ) { self.utilities = utilities } + /// Whether or not the user has granted permission to use the camera system. var cameraGranted = false + /// Whether or not the user has granted permission to add to their photo library. var photosGranted = false /// Requests camera and photo library permissions and updates ``cameraGranted`` and ``photosGranted`` @@ -30,9 +34,7 @@ extension Camera { utilities.debugNSLog("[Camera Permissions] Results — camera=\(cameraOK), photos(add-only)=\(photosOK)") } - /** - Requests the ability to use the camera. - */ + /// Requests the ability to use the camera. func createRequestToUseCamera() async -> Bool { let status = AVCaptureDevice.authorizationStatus(for: .video) switch status { @@ -52,9 +54,7 @@ extension Camera { } } - /** - Requests the ability to add photos to the user's library. - */ + /// Requests the ability to add photos to the user's library. func createRequestToAddPhotos() async -> Bool { let status = PHPhotoLibrary.authorizationStatus(for: .addOnly) switch status { diff --git a/Malachite/Utilities/CameraUtils/Camera.swift b/Malachite/Utilities/CameraUtils/Camera.swift index ef79b48..d492848 100644 --- a/Malachite/Utilities/CameraUtils/Camera.swift +++ b/Malachite/Utilities/CameraUtils/Camera.swift @@ -10,20 +10,76 @@ import Foundation import Photos class Camera: NSObject { + /// An instance of ``MalachiteClassesObject`` for use in this class and its children. var utilities: MalachiteClassesObject + /// A DispatchQueue for use in this class, interacting with its objects, and its children. + var queue = DispatchQueue(label: "malachitekit.camera.sessionqueue") + + /// An instance of ``Bringup`` for use in this class. var bringup: Camera.Bringup! + /// An instance of ``Input`` for use in this class. var input: Camera.Input! + /// An instance of ``Permissions`` for use in this class. var permissions: Camera.Permissions! + /// An instance of ``Compatibility`` for use in this class and its children. var compatibility: Compatibility! + /** + MalachiteKit's main capture session. + + This object provides the client with an `AVCaptureSession` that serves as the main interface for the application. It is stored + in this variable for later referencing and usage. + */ var session = AVCaptureSession() + /** + MalachiteKit's main photo output object. + + During initalization of the ``Camera`` class, and after verifying that the application has permissions to use the camera and save + to the user's library, this object is added to ``session`` and stored for later referencing and usage. + */ var output = AVCapturePhotoOutput() + /** + An array of `AVCaptureDevice` objects used for camera selection and switching. + + During initalization of the ``Camera`` class, and after verifying that the application has permissions to use the camera, this object + will be populated by ``Bringup/createCameraArray()``. + */ var cameras: [ AVCaptureDevice ]! - var currentDevice: AVCaptureDevice? - var currentIndex: Int? + /** + The device currently selected for use, or in use, by ``session``. + + During camera switching in ``Input/runInputSwitch()``, this variable is set to the `AVCaptureDevice` that MalachiteKit + switches to. This variable enables the ability to read and write properties on the current camera device. + + > Warning: Don't replace the `AVCaptureDevice` object on this variable directly, and let ``Input/runInputSwitch()`` handle that action. + Modifying this variable directly can lead to changes being lost when the user switches between cameras. + */ + var device: AVCaptureDevice? + + /** + An integer variable used to store the location of ``device`` in ``cameras``. + + This variable helps to keep the Camera Control in sync with ``CameraView/cameraButton`` + > Warning: Don't modify this variable directly, as switching cameras with the Camera Control will overwrite its value. + */ + var index: Int? + + /** + Initailizer function for the ``Camera`` class. + + A quick rundown of the initialization happening in this class: + - ``utilities`` is set to the ``MalachiteClassesObject`` passed in the initalizer. + - An instance of the ``Permissions`` class is created to prepare for handling permission requests, and usage inside of children. + - An instance of the ``Compatibility`` class is created for usage inside of children. + + Additionally, the initalizer for the ``Camera`` class has an async task in it: + - Check and wait for the result of ``Permissions/requestPermissions()``. + - If camera permissions are granted, run ``Camera/setupChildren()`` to finish bringing up cameras. + - If photo library permissions are granted, run ``Camera/setupPhotoOutput()`` to finish bringing up the photo output. + */ init( utilities: MalachiteClassesObject ) { @@ -47,6 +103,13 @@ class Camera: NSObject { } } + /** + Sets up child classes required for MalachiteKit's core functionality. + + > Warning: Don't call this function manually, as it's already called when initalizing the ``Camera`` class. + > Info: To avoid race conditions, you can observe ``MalachiteFunctionUtils/Notifications/cameraClassNotification`` + and run code when a notification is posted to it. + */ public func setupChildren() { self.bringup = Bringup(parent: self) self.setupCameraArray() @@ -54,7 +117,9 @@ class Camera: NSObject { NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.cameraClassNotification.name, object: nil) } + /// Assigns ``cameras`` to the output value of ``Bringup/createCameraArray()`` public func setupCameraArray() { self.cameras = self.bringup.createCameraArray() } + /// Assigns ``output`` using ``Bringup/addPhotoOutput(session:)`` public func setupPhotoOutput() { self.bringup.addPhotoOutput(session: self.session) } } diff --git a/Malachite/Views/CameraView/CameraView+Controls.swift b/Malachite/Views/CameraView/CameraView+Controls.swift index 69c16ce..128a3d5 100644 --- a/Malachite/Views/CameraView/CameraView+Controls.swift +++ b/Malachite/Views/CameraView/CameraView+Controls.swift @@ -139,8 +139,8 @@ extension CameraView { delegate.utilities.tooltips.fadeOutTooltipFlow(labelsToFade: labels) } - if delegate.camera.currentDevice != nil { - if showCamera { delegate.utilities.tooltips.zoomTooltipFlow(button: delegate.currentCamera, viewForBounds: delegate.view, camera: delegate.camera.currentDevice) } + if delegate.camera.device != nil { + if showCamera { delegate.utilities.tooltips.zoomTooltipFlow(button: delegate.currentCamera, viewForBounds: delegate.view, camera: delegate.camera.device) } } } @@ -174,9 +174,9 @@ extension CameraView.ControlLayer { func sessionControlsDidBecomeInactive(_ session: AVCaptureSession) { if delegate.uiIsHidden { runUIHider() } - if delegate.camera.currentIndex != nil { + if delegate.camera.index != nil { delegate.runInputSwitch() - delegate.camera.currentIndex = nil + delegate.camera.index = nil } } @@ -208,11 +208,11 @@ extension CameraView.ControlLayer { if delegate.utilities.preferences.evaintrnl.cameraControlOptions.contains("cameras") { let cameraSwitcher = AVCaptureIndexPicker("Cameras", symbolName: "camera.fill", localizedIndexTitles: delegate.camera.cameras.map { $0.localizedName } ) - if let device = delegate.camera.currentDevice { + if let device = delegate.camera.device { cameraSwitcher.selectedIndex = delegate.camera.cameras.firstIndex(of: device)! } cameraSwitcher.setActionQueue(delegate.utilities.sessionQueue) { [self] index in - delegate.camera.currentIndex = index + delegate.camera.index = index } controls.append(cameraSwitcher) } @@ -243,14 +243,14 @@ extension CameraView.ControlLayer { delegate.flashFloater = nil if let flashSwitcher = flashSwitcher { flashSwitcher.selectedIndex = delegate.flashStatus ? 1 : 0 } } else { - delegate.utilities.function.flashLevelTest(captureDevice: delegate.camera.currentDevice!, floater: position) + delegate.utilities.function.flashLevelTest(captureDevice: delegate.camera.device!, floater: position) } } controls.append(flashSlider) } if delegate.utilities.preferences.evaintrnl.cameraControlOptions.contains("exposureBias") { - if let device = delegate.camera.currentDevice { + if let device = delegate.camera.device { let systemBiasSlider = AVCaptureSystemExposureBiasSlider(device: device) controls.append(systemBiasSlider) } @@ -285,7 +285,7 @@ extension CameraView.ControlLayer { delegate.utilities.views.hideUI(view: delegate.view, blacklisted: [ delegate.aeafFeedback, delegate.uiHiderRecognizer ], conditionals: [ delegate.focusLockButton : delegate.manualFocusSliderIsActive, delegate.exposureLockButton : delegate.manualExposureSliderIsActive], gestureRecognizers: self.recognizers) } else { delegate.utilities.views.showUI(view: delegate.view, blacklisted: [ delegate.aeafFeedback, delegate.uiHiderRecognizer ], conditionals: [ delegate.focusLockButton : delegate.manualFocusSliderIsActive, delegate.exposureLockButton : delegate.manualExposureSliderIsActive], gestureRecognizers: self.recognizers) - delegate.utilities.tooltips.zoomTooltipFlow(button: delegate.currentCamera, viewForBounds: delegate.view, camera: delegate.camera.currentDevice) + delegate.utilities.tooltips.zoomTooltipFlow(button: delegate.currentCamera, viewForBounds: delegate.view, camera: delegate.camera.device) } delegate.uiIsHidden = !delegate.uiIsHidden diff --git a/Malachite/Views/CameraView/CameraView.swift b/Malachite/Views/CameraView/CameraView.swift index cb262e4..464b27e 100755 --- a/Malachite/Views/CameraView/CameraView.swift +++ b/Malachite/Views/CameraView/CameraView.swift @@ -133,9 +133,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa preview.initPreviewLayer() utilities.debugNSLog("[Initialization] Starting session stream") - DispatchQueue.global(qos: .background).async { - self.camera.session.startRunning() - } + self.camera.queue.async { self.camera.session.startRunning() } } else { utilities.debugNSLog("[Initialization] No cameras detected, skipping to user interface bringup") } @@ -241,11 +239,11 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to dynamically change the auto exposure and ``exposureSlider`` values when toggling in ``MalachiteSettingsView``. @objc func changeExposureLimit() { - guard let exposure = camera.currentDevice?.isExposureModeSupported(.continuousAutoExposure) else { return } + guard let exposure = camera.device?.isExposureModeSupported(.continuousAutoExposure) else { return } do { - try camera.currentDevice?.lockForConfiguration() - defer { camera.currentDevice?.unlockForConfiguration() } - if exposure { camera.currentDevice?.exposureMode = .continuousAutoExposure } + try camera.device?.lockForConfiguration() + defer { camera.device?.unlockForConfiguration() } + if exposure { camera.device?.exposureMode = .continuousAutoExposure } } catch { utilities.debugNSLog("[Change Exposure Limit] Couldn't lock device for configuration") } @@ -256,7 +254,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa } @objc func changeContinuousAEAF() { - guard let selectedDevice = camera.currentDevice else { return } + guard let selectedDevice = camera.device else { return } utilities.function.continuousAEAF(device: selectedDevice) } @@ -282,14 +280,14 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa guard let connection = self.preview.previewLayer.connection else { print("bruh"); return } if utilities.preferences.preview.stablize { if #available(iOS 17.0, *) { - if ((camera.currentDevice?.activeFormat.isVideoStabilizationModeSupported(.previewOptimized)) != nil) { + if ((camera.device?.activeFormat.isVideoStabilizationModeSupported(.previewOptimized)) != nil) { utilities.debugNSLog("[Preview Stabilization] Enabling enhanced stabilization mode") connection.preferredVideoStabilizationMode = .previewOptimized return } } - if ((camera.currentDevice?.activeFormat.isVideoStabilizationModeSupported(.standard)) != nil) { + if ((camera.device?.activeFormat.isVideoStabilizationModeSupported(.standard)) != nil) { utilities.debugNSLog("[Preview Stabilization] Enabling standard stabilization mode") connection.preferredVideoStabilizationMode = .standard } @@ -361,13 +359,13 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa @available(iOS 16.0, *) @objc func runInputMegapixelSwitch() { - guard let selectedDevice = camera.currentDevice else { return } + guard let selectedDevice = camera.device else { return } utilities.function.switchInputMegapixels(device: selectedDevice, photoOutput: self.camera.output) } /// Function to toggle the flashlight's on state. @objc func runFlashlightToggle() { - guard var selectedDevice = camera.currentDevice else { return } + guard var selectedDevice = camera.device else { return } if selectedDevice.isFlashAvailable && !utilities.preferences.debug.breakApp { utilities.function.toggleFlash(captureDevice: &selectedDevice, flashlightButton: flashlightButton, @@ -448,7 +446,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to zoom in and out with ``zoomRecognizer``. @objc func runZoomController() { - guard var selectedDevice = camera.currentDevice else { return } + guard var selectedDevice = camera.device else { return } utilities.function.zoom(sender: zoomRecognizer, floater: &zoomFloater, captureDevice: &selectedDevice, @@ -458,7 +456,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to autofocus + autoexposure with ``aeafRecognizer``. @objc func runaeafController() { - guard var selectedDevice = camera.currentDevice else { return } + guard var selectedDevice = camera.device else { return } utilities.function.pointOfInterestAEAF(sender: aeafRecognizer, captureDevice: &selectedDevice, button: aeafFeedback, @@ -468,7 +466,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to handle ``exposureSlider`` interaction. @objc func runManualExposureController() { - guard var selectedDevice = camera.currentDevice else { return } + guard var selectedDevice = camera.device else { return } if selectedDevice.isExposureModeSupported(.custom) && !utilities.preferences.debug.breakApp { utilities.function.manualExposure(captureDevice: &selectedDevice, sender: exposureSlider) @@ -483,7 +481,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to show and hide the ``exposureSliderButton`` and ``exposureLockButton``. @objc func runManualExposureUIHider() { - guard let exposure = camera.currentDevice?.isExposureModeSupported(.custom) else { return } + guard let exposure = camera.device?.isExposureModeSupported(.custom) else { return } if exposure && !utilities.preferences.debug.breakApp { manualExposureSliderIsActive = utilities.views.runSliderControllers(sliderIsShown: manualExposureSliderIsActive, optionButton: exposureButton, @@ -523,7 +521,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to handle ``focusSlider`` interaction. @objc func runManualFocusController() { - guard var selectedDevice = camera.currentDevice else { return } + guard var selectedDevice = camera.device else { return } if selectedDevice.isLockingFocusWithCustomLensPositionSupported && !utilities.preferences.debug.breakApp { utilities.function.manualFocus(captureDevice: &selectedDevice, sender: focusSlider, @@ -540,7 +538,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to handle ``focusSlider`` interaction. @objc func runManualFocusUIHider() { - guard var selectedDevice = camera.currentDevice else { return } + guard var selectedDevice = camera.device else { return } if selectedDevice.isLockingFocusWithCustomLensPositionSupported && !utilities.preferences.debug.breakApp { manualFocusSliderIsActive = utilities.views.runSliderControllers(sliderIsShown: manualFocusSliderIsActive, optionButton: focusButton, From 274fa9d05e076a00ce105598484b09db2bb7c11e Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Thu, 2 Oct 2025 00:55:05 -0600 Subject: [PATCH 27/66] Various changes to the ControlLayer - Buttons, sliders, and recognizers are now in the ControlLayer - TODO: Update DocC for them, rethink buttonBuilder.constraints... - Flashlight level adjustment is now available outside of Camera Control - Major changes to manual slider adjustments: - Only one slider can be active on the screen at a time - The lock button has been relocated to above the flashlight on/off toggle - This implementation fixes smaller displays having clipping issues --- Malachite/Utilities/CameraUtils/Camera.swift | 2 +- Malachite/Utilities/MalachiteViewUtils.swift | 4 +- .../CameraView/CameraView+Controls.swift | 186 +++++++---- Malachite/Views/CameraView/CameraView.swift | 295 +++++++++--------- 4 files changed, 275 insertions(+), 212 deletions(-) diff --git a/Malachite/Utilities/CameraUtils/Camera.swift b/Malachite/Utilities/CameraUtils/Camera.swift index d492848..10ff4b3 100644 --- a/Malachite/Utilities/CameraUtils/Camera.swift +++ b/Malachite/Utilities/CameraUtils/Camera.swift @@ -61,7 +61,7 @@ class Camera: NSObject { /** An integer variable used to store the location of ``device`` in ``cameras``. - This variable helps to keep the Camera Control in sync with ``CameraView/cameraButton`` + This variable helps to keep the Camera Control in sync with ``CameraView/ControlLayer/camera`` > Warning: Don't modify this variable directly, as switching cameras with the Camera Control will overwrite its value. */ var index: Int? diff --git a/Malachite/Utilities/MalachiteViewUtils.swift b/Malachite/Utilities/MalachiteViewUtils.swift index 2f1bf4e..70d985a 100755 --- a/Malachite/Utilities/MalachiteViewUtils.swift +++ b/Malachite/Utilities/MalachiteViewUtils.swift @@ -14,7 +14,7 @@ import ObjectiveC.runtime public class MalachiteViewUtils : NSObject { /// Function that returns a buttons for the user interface. public func createAndAddButtonToView(symbolName: String, delegate: UIViewController, view: UIView, utilities: MalachiteClassesObject, action: Selector, dimensions: [ CGFloat ], constraints: MalachiteViewUtils.buttonBuilder.constraints) -> UIButton { - let button = UIButton() + let button = UIButton(type: .system) let buttonImage = UIImage(systemName: symbolName)?.withRenderingMode(.alwaysTemplate) button.setImage(buttonImage, for: .normal) button.translatesAutoresizingMaskIntoConstraints = false @@ -181,7 +181,7 @@ public class MalachiteViewUtils : NSObject { } /// Function that sets the lock and unlock state of the bassed slider lock buttons. - func runLockControllers(lockIsActive locked: Bool, lockButton button: inout UIButton, associatedSlider slider: inout UISlider, associatedGestureRecognizer gestureRecognizer: UIGestureRecognizer?, viewForRecognizers view: UIView) -> Bool { + func runLockControllers(lockIsActive locked: Bool, lockButton button: inout UIButton, associatedSlider slider: UISlider, associatedGestureRecognizer gestureRecognizer: UIGestureRecognizer?, viewForRecognizers view: UIView) -> Bool { if locked { button.setImage(UIImage(systemName: "lock.open")?.withRenderingMode(.alwaysTemplate), for: .normal) slider.isEnabled = true diff --git a/Malachite/Views/CameraView/CameraView+Controls.swift b/Malachite/Views/CameraView/CameraView+Controls.swift index 128a3d5..8b3f73e 100644 --- a/Malachite/Views/CameraView/CameraView+Controls.swift +++ b/Malachite/Views/CameraView/CameraView+Controls.swift @@ -20,64 +20,113 @@ extension CameraView { init(delegate: CameraView) { self.delegate = delegate } + struct buttonGroup { + /// A `UIButton` that enables the user to switch between the ultra-wide and wide angle cameras. + var camera = UIButton() + /// A `UIButton` that enables the user to toggle the flashlight's on state. + var flashlight = UIButton() + /// A `UIButton` that enables the user to take photos. + var capture = UIButton() + /// A `UIButton` that enables the user to change settings within the app. + var settings = UIButton() + /// A ``sliderGroup`` that enables the user to control manual focus adjustment. + var focus = sliderGroup() + /// A ``sliderGroup`` that enables the user to control manual exposure adjustment. + var exposure = sliderGroup() + /// A ``sliderGroup`` that enables the user to control the flashlight brightness level. + var flash = sliderGroup() + /// A `UIButton` that contains the blur for the on-screen feedback produced by the auto focus gesture. + var continuousFeedback = UIButton() + /// The button used to display what camera is in use. + var currentCamera = UIButton() + + struct sliderGroup { + var activator = UIButton() + var sliderShown = Bool() + var lockEnabled = Bool() + var lock = UIButton() + var slider = UISlider() + var container = UIButton() + } + } + + struct recognizerGroup { + /// A `UIPinchGestureRecognizer` that handles zooming in and out of the ``cameraSession``. + var zoom = UIPinchGestureRecognizer() + /// A `UILongPressGestureRecognizer` that handles enabling the AE+AF system at a specific point on the display for the ``cameraSession``. + var continuous = UILongPressGestureRecognizer() + /// A `UIPanGestureRecognizer` that handles opening settings with a gesture. + var settings = UISwipeGestureRecognizer() + /// A `UILongPressGestureRecognizer` that handles hiding all elements of the user interface, and disabling the ``zoomRecognizer`` and ``aeafRecognizer`` gestures. + var uiHider = UILongPressGestureRecognizer() + } + + struct titleGroup { + /// The title for the focus slider. + var focus = UILabel() + /// The title for the exposure slider. + var exposure = UILabel() + } + + var buttons = buttonGroup() + var recognizers = recognizerGroup() + var titles = titleGroup() + + /// A `Bool` that determines whether or not the user interface is currently hidden to the user. + var uiIsHidden = false + /** - An array of ``UIGestureRecognizer`` objects that are managed by this control layer. + An array of `UIGestureRecognizer` objects that are managed by this control layer. */ - var recognizers = [ UIGestureRecognizer ]() + var activeRecognizers = [ UIGestureRecognizer ]() /** - The ``AVCaptureEventInteraction`` that catches volume button and Camera Control events for taking photos. + The `AVCaptureEventInteraction` that catches volume button and Camera Control events for taking photos. */ var eventInteraction: Any? = { if #available(iOS 17.2, *) { return AVCaptureEventInteraction?.self } else { return nil } }() /** - Creates, adds, and constrains the ``UIButton`` objects that are managed by this control layer. + Creates, adds, and constrains the `UIButton` objects that are managed by this control layer. */ func initButtons() { - var lockButtonsX = -80.0 - var lockButtonsY = 0.0 - - if delegate.view.frame.size.width >= 370 { - delegate.utilities.debugNSLog("[Initialization] Device screen is capable of displaying lock button inline") - lockButtonsX = -300.0 - } else { - // TODO: Make lock buttons not clip into other bars! - delegate.utilities.debugNSLog("[Initialization] Device screen is too small for inline lock button") - lockButtonsY = 70.0 - } - let buttonConstraints: [MalachiteViewUtils.buttonBuilder.constraints] = [ MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.leadingAnchor, LXC: 10.0, LXP: true, LYA: delegate.view.safeAreaLayoutGuide.bottomAnchor, LYC: -10.0, LYP: true, CXA: nil, CXC: nil, CYA: nil, CYC: nil), MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: -10.0, LXP: false, LYA: delegate.view.safeAreaLayoutGuide.bottomAnchor, LYC: -10.0, LYP: true, CXA: nil, CXC: nil, CYA: nil, CYC: nil), MalachiteViewUtils.buttonBuilder.constraints(LXA: nil, LXC: nil, LXP: nil, LYA: delegate.view.safeAreaLayoutGuide.bottomAnchor, LYC: -10.0, LYP: true, CXA: delegate.view.safeAreaLayoutGuide.centerXAnchor, CXC: 0.0, CYA: nil, CYC: nil), MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: -10.0, LXP: false, LYA: delegate.view.safeAreaLayoutGuide.topAnchor, LYC: 10.0, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: 0.0, LXP: true, LYA: delegate.view.safeAreaLayoutGuide.topAnchor, LYC: 10.0, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), - MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: lockButtonsX, LXP: false, LYA: delegate.view.safeAreaLayoutGuide.topAnchor, LYC: 10.0 + lockButtonsY, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), + MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: -10.0, LXP: false, LYA: delegate.view.safeAreaLayoutGuide.bottomAnchor, LYC: -80.0, LYP: true, CXA: nil, CXC: nil, CYA: nil, CYC: nil), MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: -10.0, LXP: false, LYA: delegate.view.safeAreaLayoutGuide.topAnchor, LYC: 80.0, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: 0.0, LXP: true, LYA: delegate.view.safeAreaLayoutGuide.topAnchor, LYC: 80.0, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), - MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: lockButtonsX, LXP: false, LYA: delegate.view.safeAreaLayoutGuide.topAnchor, LYC: 80.0 + lockButtonsY, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), + MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: -10.0, LXP: false, LYA: delegate.view.safeAreaLayoutGuide.bottomAnchor, LYC: -80.0, LYP: true, CXA: nil, CXC: nil, CYA: nil, CYC: nil), + MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: -10.0, LXP: false, LYA: delegate.view.safeAreaLayoutGuide.topAnchor, LYC: 150.0, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), + MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: 0.0, LXP: true, LYA: delegate.view.safeAreaLayoutGuide.topAnchor, LYC: 150.0, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), + MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.trailingAnchor, LXC: -10.0, LXP: false, LYA: delegate.view.safeAreaLayoutGuide.bottomAnchor, LYC: -80.0, LYP: true, CXA: nil, CXC: nil, CYA: nil, CYC: nil), MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.leadingAnchor, LXC: 10.0, LXP: true, LYA: delegate.view.safeAreaLayoutGuide.bottomAnchor, LYC: -80.0, LYP: true, CXA: nil, CXC: nil, CYA: nil, CYC: nil), MalachiteViewUtils.buttonBuilder.constraints(LXA: nil, LXC: nil, LXP: nil, LYA: nil, LYC: nil, LYP: nil, CXA: nil, CXC: nil, CYA: nil, CYC: nil), MalachiteViewUtils.buttonBuilder.constraints(LXA: delegate.view.safeAreaLayoutGuide.leadingAnchor, LXC: 10.0, LXP: true, LYA: delegate.view.safeAreaLayoutGuide.topAnchor, LYC: 10.0, LYP: false, CXA: nil, CXC: nil, CYA: nil, CYC: nil), ] let buttonConfigs: [MalachiteViewUtils.buttonBuilder] = [ - MalachiteViewUtils.buttonBuilder(symbolName: "camera", action: #selector(delegate.runInputSwitch), dimensions: [ 60.0 ], constraints: buttonConstraints[0], hidden: false, assign: { [self] button in delegate.cameraButton = button }), - MalachiteViewUtils.buttonBuilder(symbolName: "flashlight.off.fill", action: #selector(delegate.runFlashlightToggle), dimensions: [ 60.0 ], constraints: buttonConstraints[1], hidden: false, assign: { [self] button in delegate.flashlightButton = button }), - MalachiteViewUtils.buttonBuilder(symbolName: "camera.aperture", action: #selector(delegate.runImageCapture), dimensions: [ 90.0 ], constraints: buttonConstraints[2], hidden: false, assign: { [self] button in delegate.captureButton = button }), - MalachiteViewUtils.buttonBuilder(symbolName: "scope", action: #selector(delegate.runManualFocusUIHider), dimensions: [ 60.0 ], constraints: buttonConstraints[3], hidden: false, assign: { [self] button in delegate.focusButton = button }), - MalachiteViewUtils.buttonBuilder(symbolName: "", action: #selector(delegate.stub), dimensions: [ 210.0, 60.0 ], constraints: buttonConstraints[4], hidden: false, assign: { [self] button in delegate.focusSliderButton = button }), - MalachiteViewUtils.buttonBuilder(symbolName: "lock.open", action: #selector(delegate.runManualFocusLockController), dimensions: [ 60.0 ], constraints: buttonConstraints[5], hidden: true, assign: { [self] button in delegate.focusLockButton = button }), - MalachiteViewUtils.buttonBuilder(symbolName: "plusminus", action: #selector(delegate.runManualExposureUIHider), dimensions: [ 60.0 ], constraints: buttonConstraints[6], hidden: false, assign: { [self] button in delegate.exposureButton = button }), - MalachiteViewUtils.buttonBuilder(symbolName: "", action: #selector(delegate.stub), dimensions: [ 210.0, 60.0 ], constraints: buttonConstraints[7], hidden: false, assign: { [self] button in delegate.exposureSliderButton = button }), - MalachiteViewUtils.buttonBuilder(symbolName: "lock.open", action: #selector(delegate.runManualExposureLockController), dimensions: [ 60.0 ], constraints: buttonConstraints[8], hidden: true, assign: { [self] button in delegate.exposureLockButton = button }), - MalachiteViewUtils.buttonBuilder(symbolName: "gear", action: #selector(delegate.presentSettingsView), dimensions: [ 60.0 ], constraints: buttonConstraints[9], hidden: false, assign: { [self] button in delegate.settingsButton = button }), - MalachiteViewUtils.buttonBuilder(symbolName: "", action: #selector(delegate.stub), dimensions: [ 120.0 ], constraints: buttonConstraints[10], hidden: true, assign: { [self] button in delegate.aeafFeedback = button }), - MalachiteViewUtils.buttonBuilder(symbolName: "", action: #selector(delegate.stub), dimensions: [ 60.0 ], constraints: buttonConstraints[11], hidden: false, assign: { [self] button in delegate.currentCamera = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "camera", action: #selector(delegate.runInputSwitch), dimensions: [ 60.0 ], constraints: buttonConstraints[0], hidden: false, assign: { [self] button in self.buttons.camera = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "flashlight.off.fill", action: #selector(delegate.runFlashlightToggle), dimensions: [ 60.0 ], constraints: buttonConstraints[1], hidden: false, assign: { [self] button in self.buttons.flashlight = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "camera.aperture", action: #selector(delegate.runImageCapture), dimensions: [ 90.0 ], constraints: buttonConstraints[2], hidden: false, assign: { [self] button in self.buttons.capture = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "scope", action: #selector(delegate.runManualFocusUIHider), dimensions: [ 60.0 ], constraints: buttonConstraints[3], hidden: false, assign: { [self] button in self.buttons.focus.activator = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "", action: #selector(delegate.stub), dimensions: [ 210.0, 60.0 ], constraints: buttonConstraints[4], hidden: false, assign: { [self] button in self.buttons.focus.container = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "lock.open", action: #selector(delegate.runManualFocusLockController), dimensions: [ 60.0 ], constraints: buttonConstraints[5], hidden: true, assign: { [self] button in self.buttons.focus.lock = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "plusminus", action: #selector(delegate.runManualExposureUIHider), dimensions: [ 60.0 ], constraints: buttonConstraints[6], hidden: false, assign: { [self] button in self.buttons.exposure.activator = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "", action: #selector(delegate.stub), dimensions: [ 210.0, 60.0 ], constraints: buttonConstraints[7], hidden: false, assign: { [self] button in self.buttons.exposure.container = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "lock.open", action: #selector(delegate.runManualExposureLockController), dimensions: [ 60.0 ], constraints: buttonConstraints[8], hidden: true, assign: { [self] button in self.buttons.exposure.lock = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "lightbulb", action: #selector(delegate.runManualFlashUIHider), dimensions: [ 60.0 ], constraints: buttonConstraints[9], hidden: false, assign: { [self] button in self.buttons.flash.activator = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "", action: #selector(delegate.stub), dimensions: [ 210.0, 60.0 ], constraints: buttonConstraints[10], hidden: false, assign: { [self] button in self.buttons.flash.container = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "lock.open", action: #selector(delegate.runManualFlashLockController), dimensions: [ 60.0 ], constraints: buttonConstraints[11], hidden: true, assign: { [self] button in self.buttons.flash.lock = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "gear", action: #selector(delegate.presentSettingsView), dimensions: [ 60.0 ], constraints: buttonConstraints[12], hidden: false, assign: { [self] button in self.buttons.settings = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "", action: #selector(delegate.stub), dimensions: [ 120.0 ], constraints: buttonConstraints[13], hidden: true, assign: { [self] button in self.buttons.continuousFeedback = button }), + MalachiteViewUtils.buttonBuilder(symbolName: "", action: #selector(delegate.stub), dimensions: [ 60.0 ], constraints: buttonConstraints[14], hidden: false, assign: { [self] button in self.buttons.currentCamera = button }), ] for config in buttonConfigs { let button = delegate.utilities.views.createAndAddButtonToView(symbolName: config.symbolName, delegate: delegate, view: delegate.view, utilities: delegate.utilities, action: config.action, dimensions: config.dimensions, constraints: config.constraints) - if delegate.utilities.preferences.userInterface.appLaunch { button.alpha = 0.0; delegate.uiIsHidden = true } + if delegate.utilities.preferences.userInterface.appLaunch { button.alpha = 0.0; self.uiIsHidden = true } if config.hidden { button.alpha = 0.0 } config.assign(button) } @@ -88,8 +137,9 @@ extension CameraView { */ func initSliders() { let sliderConfigs: [MalachiteViewUtils.sliderBuilder] = [ - MalachiteViewUtils.sliderBuilder(action: #selector(delegate.runManualFocusController), dimensions: [ 180.0, 80.0 ], view: delegate.focusSliderButton, assign: { [self] slider in delegate.focusSlider = slider } ), - MalachiteViewUtils.sliderBuilder(action: #selector(delegate.runManualExposureController), dimensions: [ 180.0, 80.0 ], view: delegate.exposureSliderButton, assign: { [self] slider in delegate.exposureSlider = slider } ), + MalachiteViewUtils.sliderBuilder(action: #selector(delegate.runManualFocusController), dimensions: [ 180.0, 80.0 ], view: self.buttons.focus.container, assign: { [self] slider in self.buttons.focus.slider = slider } ), + MalachiteViewUtils.sliderBuilder(action: #selector(delegate.runManualExposureController), dimensions: [ 180.0, 80.0 ], view: self.buttons.exposure.container, assign: { [self] slider in self.buttons.exposure.slider = slider } ), + MalachiteViewUtils.sliderBuilder(action: #selector(delegate.runManualFlashController), dimensions: [ 180.0, 80.0 ], view: self.buttons.flash.container, assign: { [self] slider in self.buttons.flash.slider = slider } ), ] for config in sliderConfigs { @@ -101,21 +151,30 @@ extension CameraView { Creates and adds the ``UIGestureRecognizer`` objects that are managed by this control layer. */ func initRecognizers() { - delegate.zoomRecognizer = UIPinchGestureRecognizer(target: delegate, action:#selector(runZoomController)) - delegate.zoomRecognizer.name = "zoom" - self.recognizers.append(delegate.zoomRecognizer) + self.recognizers.zoom = UIPinchGestureRecognizer(target: delegate, action:#selector(runZoomController)) + self.recognizers.zoom.name = "zoom" + self.activeRecognizers.append(self.recognizers.zoom) if !delegate.utilities.preferences.userInterface.tapAndHold.contains("off") { - delegate.aeafRecognizer = UILongPressGestureRecognizer(target: delegate, action: #selector(runaeafController)) - delegate.aeafRecognizer.name = "tah" - self.recognizers.append(delegate.aeafRecognizer) + self.recognizers.continuous = UILongPressGestureRecognizer(target: delegate, action: #selector(runaeafController)) + self.recognizers.continuous.name = "tah" + self.activeRecognizers.append(self.recognizers.continuous) } - delegate.uiHiderRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(runUIHider)) - delegate.uiHiderRecognizer.numberOfTouchesRequired = 2 - self.recognizers.append(delegate.uiHiderRecognizer) + self.recognizers.uiHider = UILongPressGestureRecognizer(target: self, action: #selector(runUIHider)) + self.recognizers.uiHider.numberOfTouchesRequired = 2 + self.activeRecognizers.append(self.recognizers.uiHider) + + if delegate.utilities.versionType == "INTERNAL" { + self.recognizers.settings = UISwipeGestureRecognizer(target: self, action: #selector(self.runSettingsGesture)) + self.updateSettingsGestureFingerCount() + NotificationCenter.default.addObserver(self, selector: #selector(self.updateSettingsGestureFingerCount), name: MalachiteFunctionUtils.Notifications.settingsGestureNotification.name, object: nil) + self.recognizers.settings.direction = .up + + self.activeRecognizers.append(self.recognizers.settings) + } - for recognizer in recognizers { + for recognizer in self.activeRecognizers { recognizer.cancelsTouchesInView = false delegate.view.addGestureRecognizer(recognizer) } @@ -127,8 +186,8 @@ extension CameraView { func initTooltips(showLabels: Bool, showCamera: Bool) { if showLabels { let tooltipConfigs: [ MalachiteViewUtils.tooltipBuilder ] = [ - MalachiteViewUtils.tooltipBuilder(text: "uibutton.focus.title", anchor: 10, assign: { [self] label in delegate.focusTitle = label } ), - MalachiteViewUtils.tooltipBuilder(text: "uibutton.exposure.title", anchor: 80, assign: { [self] label in delegate.exposureTitle = label } ), + MalachiteViewUtils.tooltipBuilder(text: "uibutton.focus.title", anchor: 10, assign: { [self] label in self.titles.focus = label } ), + MalachiteViewUtils.tooltipBuilder(text: "uibutton.exposure.title", anchor: 80, assign: { [self] label in self.titles.exposure = label } ), ] var labels: [ UILabel ] = [] @@ -140,7 +199,7 @@ extension CameraView { } if delegate.camera.device != nil { - if showCamera { delegate.utilities.tooltips.zoomTooltipFlow(button: delegate.currentCamera, viewForBounds: delegate.view, camera: delegate.camera.device) } + if showCamera { delegate.utilities.tooltips.zoomTooltipFlow(button: self.buttons.currentCamera, viewForBounds: delegate.view, camera: delegate.camera.device) } } } @@ -157,23 +216,32 @@ extension CameraView { } } +// MARK: ControlLayer - Slider Controls +extension CameraView.ControlLayer { + func hideOtherSliders(name: String) { + if name != "focus" && self.buttons.focus.sliderShown { delegate.runManualFocusUIHider() } + if name != "exposure" && self.buttons.exposure.sliderShown { delegate.runManualExposureUIHider() } + if name != "flash" && self.buttons.flash.sliderShown { delegate.runManualFlashUIHider() } + } +} + // MARK: ControlLayer - Camera Control @available(iOS 18.0, *) extension CameraView.ControlLayer { func sessionControlsDidBecomeActive(_ session: AVCaptureSession) { - if !delegate.uiIsHidden { runUIHider() } + if !self.uiIsHidden { runUIHider() } } func sessionControlsWillEnterFullscreenAppearance(_ session: AVCaptureSession) { - if !delegate.uiIsHidden { runUIHider() } + if !self.uiIsHidden { runUIHider() } } func sessionControlsWillExitFullscreenAppearance(_ session: AVCaptureSession) { - if delegate.uiIsHidden { runUIHider() } + if self.uiIsHidden { runUIHider() } } func sessionControlsDidBecomeInactive(_ session: AVCaptureSession) { - if delegate.uiIsHidden { runUIHider() } + if self.uiIsHidden { runUIHider() } if delegate.camera.index != nil { delegate.runInputSwitch() delegate.camera.index = nil @@ -265,12 +333,10 @@ extension CameraView.ControlLayer { // MARK: ControlLayer - Misc extension CameraView.ControlLayer { - @objc func updateSettingsGestureFingerCount() { - delegate.settingsRecognizer.numberOfTouchesRequired = delegate.utilities.preferences.evaintrnl.settingsGesture - } + @objc func updateSettingsGestureFingerCount() { self.recognizers.settings.numberOfTouchesRequired = delegate.utilities.preferences.evaintrnl.settingsGesture } @objc func runSettingsGesture() { - if delegate.settingsRecognizer.state == UIGestureRecognizer.State.ended { + if self.recognizers.settings.state == UIGestureRecognizer.State.ended { delegate.presentSettingsView() } } @@ -278,17 +344,17 @@ extension CameraView.ControlLayer { /// Function to show and hide the user interface that was drawn with ``setupView()``. @objc func runUIHider() { #warning("update for Liquid Glass, using UIView.animate is not recommended") - if delegate.uiHiderRecognizer.state == UITapGestureRecognizer.State.ended || delegate.uiHiderRecognizer.state == UITapGestureRecognizer.State.changed { return } + if self.recognizers.uiHider.state == UITapGestureRecognizer.State.ended || self.recognizers.uiHider.state == UITapGestureRecognizer.State.changed { return } DispatchQueue.main.async { [self] in - if !delegate.uiIsHidden { - delegate.utilities.views.hideUI(view: delegate.view, blacklisted: [ delegate.aeafFeedback, delegate.uiHiderRecognizer ], conditionals: [ delegate.focusLockButton : delegate.manualFocusSliderIsActive, delegate.exposureLockButton : delegate.manualExposureSliderIsActive], gestureRecognizers: self.recognizers) + if !self.uiIsHidden { + delegate.utilities.views.hideUI(view: delegate.view, blacklisted: [ self.buttons.continuousFeedback, self.recognizers.uiHider ], conditionals: [ self.buttons.focus.lock : self.buttons.focus.sliderShown, self.buttons.exposure.lock : self.buttons.exposure.sliderShown ], gestureRecognizers: self.activeRecognizers) } else { - delegate.utilities.views.showUI(view: delegate.view, blacklisted: [ delegate.aeafFeedback, delegate.uiHiderRecognizer ], conditionals: [ delegate.focusLockButton : delegate.manualFocusSliderIsActive, delegate.exposureLockButton : delegate.manualExposureSliderIsActive], gestureRecognizers: self.recognizers) - delegate.utilities.tooltips.zoomTooltipFlow(button: delegate.currentCamera, viewForBounds: delegate.view, camera: delegate.camera.device) + delegate.utilities.views.showUI(view: delegate.view, blacklisted: [ self.buttons.continuousFeedback, self.recognizers.uiHider ], conditionals: [ self.buttons.focus.lock : self.buttons.focus.sliderShown, self.buttons.exposure.lock : self.buttons.exposure.sliderShown ], gestureRecognizers: self.activeRecognizers) + delegate.utilities.tooltips.zoomTooltipFlow(button: self.buttons.currentCamera, viewForBounds: delegate.view, camera: delegate.camera.device) } - delegate.uiIsHidden = !delegate.uiIsHidden + self.uiIsHidden = !self.uiIsHidden delegate.utilities.haptics.triggerNotificationHaptic(type: .success) } } diff --git a/Malachite/Views/CameraView/CameraView.swift b/Malachite/Views/CameraView/CameraView.swift index 464b27e..560635c 100755 --- a/Malachite/Views/CameraView/CameraView.swift +++ b/Malachite/Views/CameraView/CameraView.swift @@ -17,18 +17,16 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// An instance of ``MalachiteClassesObject`` for reuse across the app. public var utilities = MalachiteClassesObject() + /// An instance of MalachiteKit's ``Camera`` class. var camera: Camera! + /// An instance of ``CameraView/ControlLayer`` for this view. var controlLayer: CameraView.ControlLayer! + /// An instance of ``CameraView/Notifications`` for this view. var notifications: CameraView.Notifications! + /// An instance of ``CameraView/Preview`` for this view. var preview: CameraView.Preview! - /// The device's currently available rear ultra-wide angle `AVCaptureDevice`, if available. This variable is `nil` if no ultra-wide angle camera is present (i.e. single-camera, Simulator). - var ultraWideDevice: AVCaptureDevice? - /// The device's currently available wide angle `AVCaptureDevice`, if available. This variable is `nil` if no wide angle camera is present (currently only in the Simulator). - var wideAngleDevice: AVCaptureDevice? - /// The currently selected `AVCaptureDeviceInput` for camera.input to ``cameraSession``. - var selectedInput: AVCaptureDeviceInput? /// A `CGFloat` that temporarily holds the zoom factor. var zoomFloater = CGFloat() /// A `Float` that temporarily holds the focus factor. @@ -38,61 +36,6 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// A `Bool` that temporarily holds the current status of the flashlight. var flashStatus = Bool() - /// A `UIButton` that enables the user to switch between the ultra-wide and wide angle cameras. - var cameraButton = UIButton() - /// A `UIButton` that enables the user to toggle the flashlight's on state. - var flashlightButton = UIButton() - /// A `UIButton` that enables the user to take photos. - var captureButton = UIButton() - /// A `UIButton` that enables the user to change settings within the app. - var settingsButton = UIButton() - - /// A `UIButton` that enables the user to reveal the ``focusSlider`` for manual focus adjustment. - var focusButton = UIButton() - /// A `UIButton` that holds the ``focusSlider`` for improved blur compatibility and shaping. - var focusSliderButton = UIButton() - /// A `UISlider` that enables the user to manually adjust the lens position. - var focusSlider = UISlider() - /// A `UIButton` that enables the user to toggle the lock states for the ``focusSlider`` and the ``aeafRecognizer``. - var focusLockButton = UIButton() - /// A `Bool` that determines whether or not the ``focusSlider`` is currently displayed on the user's screen. - var manualFocusSliderIsActive = false - /// A `Bool` that determines whether or not the ``focusLockButton`` is currently set to Locked. - var manualFocusLockIsActive = false - - /// A `UIButton` that enables the user to reveal the ``exposureSlider`` for manual exposure adjustment. - var exposureButton = UIButton() - /// A `UIButton` that holds the ``exposureSlider`` for improved blur compatibility and shaping. - var exposureSliderButton = UIButton() - /// A `UISlider` that enables the user to manually adjust the exposure level. - var exposureSlider = UISlider() - /// A `UIButton` that enables the user to toggle the lock state for the ``exposureSlider``. Auto exposure toggling will come at a later date. - var exposureLockButton = UIButton() - /// A `Bool` that determines whether or not the ``exposureSlider`` is currently displayed on the user's screen. - var manualExposureSliderIsActive = false - /// A `Bool` that determines whether or not the ``exposureLockButton`` is currently set to Locked. - var manualExposureLockIsActive = false - - /// A `UIPinchGestureRecognizer` that handles zooming in and out of the ``cameraSession``. - var zoomRecognizer = UIPinchGestureRecognizer() - /// A `UILongPressGestureRecognizer` that handles enabling the AE+AF system at a specific point on the display for the ``cameraSession``. - var aeafRecognizer = UILongPressGestureRecognizer() - /// A `UIButton` that contains the blur for the on-screen feedback produced by the auto focus gesture. - var aeafFeedback = UIButton() - /// A `UIPanGestureRecognizer` that handles opening settings with a gesture. - var settingsRecognizer = UISwipeGestureRecognizer() - /// A `UILongPressGestureRecognizer` that handles hiding all elements of the user interface, and disabling the ``zoomRecognizer`` and ``aeafRecognizer`` gestures. - var uiHiderRecognizer = UILongPressGestureRecognizer() - - /// A `Bool` that determines whether or not the user interface is currently hidden to the user. - var uiIsHidden = false - /// The title for the focus slider. - var focusTitle = UILabel() - /// The title for the exposure slider. - var exposureTitle = UILabel() - /// The button used to display what camera is in use. - var currentCamera = UIButton() - /// The minimum zoom value that the ``zoomRecognizer`` is allowed to reach. let minimumZoom: CGFloat = 1.0 /// The maximum zoom value that the ``zoomRecognizer`` is allowed to reach. @@ -103,9 +46,6 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// A `UIActivityIndicatorView` used to let the user know that Malachite is processing the image. var progressIndicator = UIActivityIndicatorView() - /// An observer for the device's rotation. - private var rotationObserver: NSObjectProtocol? - /** viewDidLoad override for the main user interface. @@ -179,15 +119,6 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa func setupView_INTERNAL() { utilities.games.changeGameCenterEnabled() if utilities.preferences.general.gamekit.alerted { self.present(utilities.games.setupGameKitAlert(), animated: true, completion: nil) } - - settingsRecognizer = UISwipeGestureRecognizer(target: self.controlLayer, action: #selector(self.controlLayer.runSettingsGesture)) - self.controlLayer.updateSettingsGestureFingerCount() - settingsRecognizer.direction = .up - - self.view.addGestureRecognizer(settingsRecognizer) - - NotificationCenter.default.addObserver(self.controlLayer, selector: #selector(self.controlLayer.updateSettingsGestureFingerCount), name: MalachiteFunctionUtils.Notifications.settingsGestureNotification.name, object: nil) - setupView() } @@ -249,7 +180,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa } UIView.animate(withDuration: 0.5) { - self.exposureSlider.value = 0.0 + self.controlLayer.buttons.exposure.slider.value = 0.0 } } @@ -263,14 +194,14 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa guard let currentGestureRecognizers = self.view.gestureRecognizers else { return } if tapGestureElements.contains("off") { - if currentGestureRecognizers.contains(aeafRecognizer) { + if currentGestureRecognizers.contains(self.controlLayer.recognizers.continuous) { utilities.debugNSLog("[AE+AF] Disabling tap and hold gesture") - self.view.removeGestureRecognizer(aeafRecognizer) + self.view.removeGestureRecognizer(self.controlLayer.recognizers.continuous) } } else { - if !currentGestureRecognizers.contains(aeafRecognizer) { + if !currentGestureRecognizers.contains(self.controlLayer.recognizers.continuous) { utilities.debugNSLog("[AE+AF] Enabling tap and hold gesture") - self.view.addGestureRecognizer(aeafRecognizer) + self.view.addGestureRecognizer(self.controlLayer.recognizers.continuous) } } } @@ -300,7 +231,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa @objc func presentSettingsView() { #if APP_EXTENSION utilities.debugNSLog("[Settings] Attempt to access Settings UI from app extension") - let alert = utilities.views.createAlertController(title: "alert.title.app_extensions.settings", message: "alert.detail.app_extensions.settings", button: settingsButton, defaultSet: true, action: { _ in + let alert = utilities.views.createAlertController(title: "alert.title.app_extensions.settings", message: "alert.detail.app_extensions.settings", button: self.controlLayer.buttons.settings, defaultSet: true, action: { _ in self.utilities.debugNSLog("[Settings] Dialog has been dismissed") }) self.present(alert, animated: true, completion: nil) @@ -310,11 +241,11 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa aboutView.utilities = self.utilities let hostingController = UIHostingController(rootView: aboutView) hostingController.modalPresentationStyle = UIModalPresentationStyle.popover - hostingController.popoverPresentationController?.sourceView = settingsButton + hostingController.popoverPresentationController?.sourceView = self.controlLayer.buttons.settings hostingController.isModalInPresentation = true if #available(iOS 26.0, *) { hostingController.preferredTransition = .zoom { [self] _ in - settingsButton + self.controlLayer.buttons.settings } } self.present(hostingController, animated: true, completion: nil) @@ -323,21 +254,21 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to switch cameras and attach new camera.inputs to ``cameraSession``, and set settings based on the `activeFormat` of ``selectedDevice``. @objc func runInputSwitch() { - DispatchQueue.main.async { self.cameraButton.isUserInteractionEnabled = false } + DispatchQueue.main.async { self.controlLayer.buttons.camera.isUserInteractionEnabled = false } if (self.camera.cameras.count < 2 || utilities.preferences.debug.breakApp) && !self.camera.session.inputs.isEmpty { utilities.debugNSLog("[Camera Input] Only one AVCaptureDevice is available to use, showing error") - let alert = utilities.views.createAlertController(title: "alert.title.camera_switch", message: "alert.detail.camera_switch", button: cameraButton, defaultSet: true, action: { _ in + let alert = utilities.views.createAlertController(title: "alert.title.camera_switch", message: "alert.detail.camera_switch", button: self.controlLayer.buttons.camera, defaultSet: true, action: { _ in self.utilities.debugNSLog("[Camera Input] Dialog has been dismissed") }) self.present(alert, animated: true, completion: nil) - cameraButton.isUserInteractionEnabled = true + self.controlLayer.buttons.camera.isUserInteractionEnabled = true return } DispatchQueue.main.async { UIView.animate(withDuration: 0.5) { - self.focusSlider.value = 0.0 - self.exposureSlider.value = 0.0 + self.controlLayer.buttons.focus.slider.value = 0.0 + self.controlLayer.buttons.exposure.slider.value = 0.0 } } @@ -354,7 +285,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa DispatchQueue.main.async { self.controlLayer.initTooltips(showLabels: false, showCamera: true) } } - DispatchQueue.main.async { self.cameraButton.isUserInteractionEnabled = true } + DispatchQueue.main.async { self.controlLayer.buttons.camera.isUserInteractionEnabled = true } } @available(iOS 16.0, *) @@ -368,12 +299,12 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa guard var selectedDevice = camera.device else { return } if selectedDevice.isFlashAvailable && !utilities.preferences.debug.breakApp { utilities.function.toggleFlash(captureDevice: &selectedDevice, - flashlightButton: flashlightButton, + flashlightButton: self.controlLayer.buttons.flashlight, floater: flashFloater, isFlashOn: &flashStatus) } else { utilities.debugNSLog("[Flashlight] No flashlight available") - let alert = utilities.views.createAlertController(title: "alert.title.flashlight", message: "alert.detail.flashlight", button: flashlightButton, defaultSet: true, action: { _ in + let alert = utilities.views.createAlertController(title: "alert.title.flashlight", message: "alert.detail.flashlight", button: self.controlLayer.buttons.flashlight, defaultSet: true, action: { _ in self.utilities.debugNSLog("[Flashlight] Dialog has been dismissed") }) self.present(alert, animated: true, completion: nil) @@ -382,10 +313,10 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to take an image. @objc func runImageCapture() { - self.captureButton.isEnabled = false - progressIndicator = UIActivityIndicatorView(frame: self.captureButton.frame) + self.controlLayer.buttons.capture.isEnabled = false + progressIndicator = UIActivityIndicatorView(frame: self.controlLayer.buttons.capture.frame) self.view.addSubview(progressIndicator) - self.captureButton.setImage(nil, for: .normal) + self.controlLayer.buttons.capture.setImage(nil, for: .normal) progressIndicator.startAnimating() let status = PHPhotoLibrary.authorizationStatus(for: .addOnly) @@ -394,7 +325,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa self.camera.output = utilities.function.captureImage(output: self.camera.output, viewForBounds: self.view, captureDelegate: self) } else { utilities.debugNSLog("[Capture Photo] PHPhotoLibrary not authorized, showing error") - let alert = utilities.views.createAlertController(title: "alert.title.phphotolibrary", message: "alert.detail.phphotolibrary", button: captureButton, defaultSet: true, action: { _ in + let alert = utilities.views.createAlertController(title: "alert.title.phphotolibrary", message: "alert.detail.phphotolibrary", button: self.controlLayer.buttons.capture, defaultSet: true, action: { _ in self.utilities.debugNSLog("[Capture Photo] Dialog has been dismissed") }) self.present(alert, animated: true, completion: nil) @@ -418,16 +349,16 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa navigationController.modalPresentationStyle = UIModalPresentationStyle.pageSheet navigationController.isModalInPresentation = true navigationController.isNavigationBarHidden = true - navigationController.popoverPresentationController?.sourceView = captureButton + navigationController.popoverPresentationController?.sourceView = self.controlLayer.buttons.capture if #available(iOS 26.0, *) { - navigationController.preferredTransition = .zoom { [self] _ in captureButton } + navigationController.preferredTransition = .zoom { [self] _ in self.controlLayer.buttons.capture } } self.present(navigationController, animated: true, completion: nil) NotificationCenter.default.addObserver(photoPreview, selector: #selector(orientationChanged), name: UIDevice.orientationDidChangeNotification, object: nil) } - self.captureButton.isEnabled = true - self.captureButton.setImage(UIImage(systemName: "camera.aperture"), for: .normal) + self.controlLayer.buttons.capture.isEnabled = true + self.controlLayer.buttons.capture.setImage(UIImage(systemName: "camera.aperture"), for: .normal) progressIndicator.stopAnimating() DispatchQueue.global(qos: .background).async { [self] in @@ -447,7 +378,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to zoom in and out with ``zoomRecognizer``. @objc func runZoomController() { guard var selectedDevice = camera.device else { return } - utilities.function.zoom(sender: zoomRecognizer, + utilities.function.zoom(sender: self.controlLayer.recognizers.zoom, floater: &zoomFloater, captureDevice: &selectedDevice, lastZoomFactor: &lastZoomFactor, @@ -457,9 +388,9 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to autofocus + autoexposure with ``aeafRecognizer``. @objc func runaeafController() { guard var selectedDevice = camera.device else { return } - utilities.function.pointOfInterestAEAF(sender: aeafRecognizer, + utilities.function.pointOfInterestAEAF(sender: self.controlLayer.recognizers.continuous, captureDevice: &selectedDevice, - button: aeafFeedback, + button: self.controlLayer.buttons.continuousFeedback, viewForScale: self.view, hapticClass: utilities.haptics) } @@ -469,10 +400,10 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa guard var selectedDevice = camera.device else { return } if selectedDevice.isExposureModeSupported(.custom) && !utilities.preferences.debug.breakApp { utilities.function.manualExposure(captureDevice: &selectedDevice, - sender: exposureSlider) + sender: self.controlLayer.buttons.exposure.slider) } else { utilities.debugNSLog("[Manual Exposure] Current camera is not capable of adjusting exposure") - let alert = utilities.views.createAlertController(title: "alert.title.exposure", message: "alert.detail.exposure", button: exposureButton, defaultSet: true, action: { _ in + let alert = utilities.views.createAlertController(title: "alert.title.exposure", message: "alert.detail.exposure", button: self.controlLayer.buttons.exposure.activator, defaultSet: true, action: { _ in self.utilities.debugNSLog("[Manual Exposure] Dialog has been dismissed") }) self.present(alert, animated: true, completion: nil) @@ -483,13 +414,14 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa @objc func runManualExposureUIHider() { guard let exposure = camera.device?.isExposureModeSupported(.custom) else { return } if exposure && !utilities.preferences.debug.breakApp { - manualExposureSliderIsActive = utilities.views.runSliderControllers(sliderIsShown: manualExposureSliderIsActive, - optionButton: exposureButton, - lockButton: exposureLockButton, - associatedSliderButton: exposureSliderButton) + self.controlLayer.hideOtherSliders(name: "exposure") + self.controlLayer.buttons.exposure.sliderShown = utilities.views.runSliderControllers(sliderIsShown: self.controlLayer.buttons.exposure.sliderShown, + optionButton: self.controlLayer.buttons.exposure.activator, + lockButton: self.controlLayer.buttons.exposure.lock, + associatedSliderButton: self.controlLayer.buttons.exposure.container) } else { utilities.debugNSLog("[Manual Focus] Current camera is not capable of adjusting exposure") - let alert = utilities.views.createAlertController(title: "alert.title.exposure", message: "alert.detail.exposure", button: exposureButton, defaultSet: true, action: { _ in + let alert = utilities.views.createAlertController(title: "alert.title.exposure", message: "alert.detail.exposure", button: self.controlLayer.buttons.exposure.activator, defaultSet: true, action: { _ in self.utilities.debugNSLog("[Manual Exposure] Dialog has been dismissed") }) self.present(alert, animated: true, completion: nil) @@ -497,24 +429,24 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa } @objc func runManualExposureUIHiderWhenUnsupported() { - if manualExposureSliderIsActive { - manualExposureSliderIsActive = utilities.views.runSliderControllers(sliderIsShown: manualExposureSliderIsActive, - optionButton: exposureButton, - lockButton: exposureLockButton, - associatedSliderButton: exposureSliderButton) + if self.controlLayer.buttons.exposure.sliderShown { + self.controlLayer.buttons.exposure.sliderShown = utilities.views.runSliderControllers(sliderIsShown: self.controlLayer.buttons.exposure.sliderShown, + optionButton: self.controlLayer.buttons.exposure.activator, + lockButton: self.controlLayer.buttons.exposure.lock, + associatedSliderButton: self.controlLayer.buttons.exposure.container) } else { - manualExposureSliderIsActive = utilities.views.runSliderControllers(sliderIsShown: true, - optionButton: exposureButton, - lockButton: exposureLockButton, - associatedSliderButton: exposureSliderButton) + self.controlLayer.buttons.exposure.sliderShown = utilities.views.runSliderControllers(sliderIsShown: true, + optionButton: self.controlLayer.buttons.exposure.activator, + lockButton: self.controlLayer.buttons.exposure.lock, + associatedSliderButton: self.controlLayer.buttons.exposure.container) } } /// Function to lock and unlock the ``exposureSlider``. @objc func runManualExposureLockController() { - manualExposureLockIsActive = utilities.views.runLockControllers(lockIsActive: manualExposureLockIsActive, - lockButton: &exposureLockButton, - associatedSlider: &exposureSlider, + self.controlLayer.buttons.exposure.lockEnabled = utilities.views.runLockControllers(lockIsActive: self.controlLayer.buttons.exposure.lockEnabled, + lockButton: &self.controlLayer.buttons.exposure.lock, + associatedSlider: self.controlLayer.buttons.exposure.slider, associatedGestureRecognizer: nil, viewForRecognizers: self.view) } @@ -524,12 +456,12 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa guard var selectedDevice = camera.device else { return } if selectedDevice.isLockingFocusWithCustomLensPositionSupported && !utilities.preferences.debug.breakApp { utilities.function.manualFocus(captureDevice: &selectedDevice, - sender: focusSlider, - floater: focusFloater ?? focusSlider.value) + sender: self.controlLayer.buttons.focus.slider, + floater: focusFloater ?? self.controlLayer.buttons.focus.slider.value) } else { #warning("refactor to call unsupported codepath") utilities.debugNSLog("[Manual Focus] Current camera is not capable of adjusting focus") - let alert = utilities.views.createAlertController(title: "alert.title.focus", message: "alert.detail.focus", button: focusButton, defaultSet: true, action: { _ in + let alert = utilities.views.createAlertController(title: "alert.title.focus", message: "alert.detail.focus", button: self.controlLayer.buttons.focus.activator, defaultSet: true, action: { _ in self.utilities.debugNSLog("[Manual Focus] Dialog has been dismissed") }) self.present(alert, animated: true, completion: nil) @@ -538,15 +470,16 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to handle ``focusSlider`` interaction. @objc func runManualFocusUIHider() { - guard var selectedDevice = camera.device else { return } + guard let selectedDevice = camera.device else { return } if selectedDevice.isLockingFocusWithCustomLensPositionSupported && !utilities.preferences.debug.breakApp { - manualFocusSliderIsActive = utilities.views.runSliderControllers(sliderIsShown: manualFocusSliderIsActive, - optionButton: focusButton, - lockButton: focusLockButton, - associatedSliderButton: focusSliderButton) + self.controlLayer.hideOtherSliders(name: "focus") + self.controlLayer.buttons.focus.sliderShown = utilities.views.runSliderControllers(sliderIsShown: self.controlLayer.buttons.focus.sliderShown, + optionButton: self.controlLayer.buttons.focus.activator, + lockButton: self.controlLayer.buttons.focus.lock, + associatedSliderButton: self.controlLayer.buttons.focus.container) } else { utilities.debugNSLog("[Manual Focus] Current camera is not capable of adjusting focus") - let alert = utilities.views.createAlertController(title: "alert.title.focus", message: "alert.detail.focus", button: focusButton, defaultSet: true, action: { _ in + let alert = utilities.views.createAlertController(title: "alert.title.focus", message: "alert.detail.focus", button: self.controlLayer.buttons.focus.activator, defaultSet: true, action: { _ in self.utilities.debugNSLog("[Manual Focus] Dialog has been dismissed") }) self.present(alert, animated: true, completion: nil) @@ -554,39 +487,103 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa } @objc func runManualFocusUIHiderWhenUnsupported() { - if manualFocusSliderIsActive { - manualFocusSliderIsActive = utilities.views.runSliderControllers(sliderIsShown: manualFocusSliderIsActive, - optionButton: focusButton, - lockButton: focusLockButton, - associatedSliderButton: focusSliderButton) + if self.controlLayer.buttons.focus.sliderShown { + self.controlLayer.buttons.focus.sliderShown = utilities.views.runSliderControllers(sliderIsShown: self.controlLayer.buttons.focus.sliderShown, + optionButton: self.controlLayer.buttons.focus.activator, + lockButton: self.controlLayer.buttons.focus.lock, + associatedSliderButton: self.controlLayer.buttons.focus.container) } else { - manualFocusSliderIsActive = utilities.views.runSliderControllers(sliderIsShown: true, - optionButton: focusButton, - lockButton: focusLockButton, - associatedSliderButton: focusSliderButton) + self.controlLayer.buttons.focus.sliderShown = utilities.views.runSliderControllers(sliderIsShown: true, + optionButton: self.controlLayer.buttons.focus.activator, + lockButton: self.controlLayer.buttons.focus.lock, + associatedSliderButton: self.controlLayer.buttons.focus.container) } } /// Function to show and hide the ``focusSliderButton`` and ``focusLockButton``. @objc func runManualFocusLockController() { - manualFocusLockIsActive = utilities.views.runLockControllers(lockIsActive: manualFocusLockIsActive, - lockButton: &focusLockButton, - associatedSlider: &focusSlider, - associatedGestureRecognizer: aeafRecognizer, + self.controlLayer.buttons.focus.lockEnabled = utilities.views.runLockControllers(lockIsActive: self.controlLayer.buttons.focus.lockEnabled, + lockButton: &self.controlLayer.buttons.focus.lock, + associatedSlider: self.controlLayer.buttons.focus.slider, + associatedGestureRecognizer: self.controlLayer.recognizers.continuous, + viewForRecognizers: self.view) + } + + /// Function to handle ``focusSlider`` interaction. + @objc func runManualFlashController() { + guard var selectedDevice = camera.device else { return } + if selectedDevice.hasTorch && !utilities.preferences.debug.breakApp { + if (self.controlLayer.buttons.flash.slider.value == 0.0 && flashStatus) || (self.controlLayer.buttons.flash.slider.value != 0.0 && !flashStatus) { + flashFloater = self.controlLayer.buttons.flash.slider.value + runFlashlightToggle() + flashFloater = nil + } else { + utilities.function.flashLevelTest(captureDevice: selectedDevice, + floater: flashFloater ?? self.controlLayer.buttons.flash.slider.value) + } + + } else { + #warning("refactor to call unsupported codepath") + utilities.debugNSLog("[Flashlight Level] Device does not have a flashlight") + let alert = utilities.views.createAlertController(title: "alert.title.flash", message: "alert.detail.flash", button: self.controlLayer.buttons.flash.activator, defaultSet: true, action: { _ in + self.utilities.debugNSLog("[Flashlight Level] Dialog has been dismissed") + }) + self.present(alert, animated: true, completion: nil) + } + } + + /// Function to handle ``focusSlider`` interaction. + @objc func runManualFlashUIHider() { + guard let selectedDevice = camera.device else { return } + if selectedDevice.hasTorch && !utilities.preferences.debug.breakApp { + self.controlLayer.hideOtherSliders(name: "flash") + self.controlLayer.buttons.flash.sliderShown = utilities.views.runSliderControllers(sliderIsShown: self.controlLayer.buttons.flash.sliderShown, + optionButton: self.controlLayer.buttons.flash.activator, + lockButton: self.controlLayer.buttons.flash.lock, + associatedSliderButton: self.controlLayer.buttons.flash.container) + } else { + utilities.debugNSLog("[Flashlight Level] Device does not have a flashlight") + let alert = utilities.views.createAlertController(title: "alert.title.flash", message: "alert.detail.flash", button: self.controlLayer.buttons.flash.activator, defaultSet: true, action: { _ in + self.utilities.debugNSLog("[Flashlight Level] Dialog has been dismissed") + }) + self.present(alert, animated: true, completion: nil) + } + } + + @objc func runManualFlashUIHiderWhenUnsupported() { + if self.controlLayer.buttons.flash.sliderShown { + self.controlLayer.buttons.flash.sliderShown = utilities.views.runSliderControllers(sliderIsShown: self.controlLayer.buttons.flash.sliderShown, + optionButton: self.controlLayer.buttons.flash.activator, + lockButton: self.controlLayer.buttons.flash.lock, + associatedSliderButton: self.controlLayer.buttons.flash.container) + } else { + self.controlLayer.buttons.flash.sliderShown = utilities.views.runSliderControllers(sliderIsShown: true, + optionButton: self.controlLayer.buttons.flash.activator, + lockButton: self.controlLayer.buttons.flash.lock, + associatedSliderButton: self.controlLayer.buttons.flash.container) + } + } + + /// Function to show and hide the ``focusSliderButton`` and ``focusLockButton``. + @objc func runManualFlashLockController() { + self.controlLayer.buttons.flash.lockEnabled = utilities.views.runLockControllers(lockIsActive: self.controlLayer.buttons.flash.lockEnabled, + lockButton: &self.controlLayer.buttons.flash.lock, + associatedSlider: self.controlLayer.buttons.flash.slider, + associatedGestureRecognizer: self.controlLayer.recognizers.continuous, viewForRecognizers: self.view) } /// Function to handle device rotation. #warning("refactor to view utils") @objc func orientationChanged() { - utilities.views.rotateButtonsWithOrientation(buttonsToRotate: [ cameraButton, - flashlightButton, - captureButton, - settingsButton, - focusButton, - focusLockButton, - exposureButton, - exposureLockButton ]) + utilities.views.rotateButtonsWithOrientation(buttonsToRotate: [ self.controlLayer.buttons.camera, + self.controlLayer.buttons.flashlight, + self.controlLayer.buttons.capture, + self.controlLayer.buttons.settings, + self.controlLayer.buttons.focus.activator, + self.controlLayer.buttons.focus.lock, + self.controlLayer.buttons.exposure.activator, + self.controlLayer.buttons.exposure.lock ]) } } From e64d0954290dd277dcc68d5b7c826481c8ca9cd0 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Thu, 2 Oct 2025 01:13:53 -0600 Subject: [PATCH 28/66] Developer option: enable image property logging --- Malachite/Localizable.xcstrings | 22 ++++++++++++++++++- .../MalachitePreferences.swift | 1 + .../MalachitePreferencesUtils.swift | 4 +++- .../DeveloperView+Settings.swift | 17 ++++++++++++-- .../PhotoPreviewView/PhotoPreviewView.swift | 2 +- .../Views/QuickHelpView/QuickHelpView.swift | 3 ++- 6 files changed, 43 insertions(+), 6 deletions(-) diff --git a/Malachite/Localizable.xcstrings b/Malachite/Localizable.xcstrings index cad8059..da47321 100755 --- a/Malachite/Localizable.xcstrings +++ b/Malachite/Localizable.xcstrings @@ -734,6 +734,16 @@ } } }, + "developer.detail.debug.logging.imageprops" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "DEBUG: Prints all metadata and properties taken with the image." + } + } + } + }, "developer.detail.debug.logging.preferences" : { "localizations" : { "en" : { @@ -844,6 +854,16 @@ } } }, + "developer.option.debug.logging.imageprops" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Log image properties" + } + } + } + }, "developer.option.debug.logging.preferences" : { "localizations" : { "en" : { @@ -1784,4 +1804,4 @@ } }, "version" : "1.1" -} +} \ No newline at end of file diff --git a/Malachite/Utilities/PreferenceUtils/MalachitePreferences.swift b/Malachite/Utilities/PreferenceUtils/MalachitePreferences.swift index 10d20d8..8beff6d 100755 --- a/Malachite/Utilities/PreferenceUtils/MalachitePreferences.swift +++ b/Malachite/Utilities/PreferenceUtils/MalachitePreferences.swift @@ -108,6 +108,7 @@ struct MalachitePreferences: Codable { struct debug_loggingPreferences: Codable { var preferences: Bool var unified: Bool + var imageProps: Bool } } diff --git a/Malachite/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift b/Malachite/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift index 5794a79..afca2b6 100755 --- a/Malachite/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift +++ b/Malachite/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift @@ -148,6 +148,7 @@ class MalachitePreferencesUtils { if let debugPreferences = oldPreferences["debug"] as? [ String: AnyObject ] { currentPreferences.debug.logging.preferences = debugPreferences["logging"]?["preferences"] as? Bool ?? false currentPreferences.debug.logging.unified = debugPreferences["logging"]?["unified"] as? Bool ?? true + currentPreferences.debug.logging.imageProps = debugPreferences["logging"]?["imageProps"] as? Bool ?? false currentPreferences.debug.breakApp = debugPreferences["breakApp"] as? Bool ?? false } @@ -222,7 +223,8 @@ class MalachitePreferencesUtils { debug: MalachitePreferences.debugPreferences( logging: MalachitePreferences.debugPreferences.debug_loggingPreferences( preferences: false, - unified: true + unified: true, + imageProps: false, ), breakApp: false ), diff --git a/Malachite/Views/DeveloperView/DeveloperView+Settings.swift b/Malachite/Views/DeveloperView/DeveloperView+Settings.swift index 4087a98..ac3d058 100644 --- a/Malachite/Views/DeveloperView/DeveloperView+Settings.swift +++ b/Malachite/Views/DeveloperView/DeveloperView+Settings.swift @@ -11,6 +11,7 @@ extension DeveloperView { struct Settings: View { @State private var debugLoggingUnified = false @State private var debugLoggingPreferences = false + @State private var debugLoggingImageProps = false /// A State variable used for determining whether or not to literally break the app. @State private var breakApp = false @@ -38,6 +39,13 @@ extension DeveloperView { { Toggle("developer.option.debug.logging.preferences", isOn: $debugLoggingPreferences) } + MalachiteCellViewUtils( + icon: "camera.badge.ellipsis", + disabled: nil, + dangerous: false) + { + Toggle("developer.option.debug.logging.imageprops", isOn: $debugLoggingImageProps) + } MalachiteCellViewUtils( icon: "iphone.slash", disabled: nil, @@ -89,13 +97,17 @@ extension DeveloperView { if utilities.versionType == "INTERNAL" { debugLoggingUnified = true } else { debugLoggingUnified = utilities.preferences.debug.logging.unified } debugLoggingPreferences = utilities.preferences.debug.logging.preferences + debugLoggingImageProps = utilities.preferences.debug.logging.imageProps breakApp = utilities.preferences.debug.breakApp } + .onChange(of: debugLoggingUnified) {_ in + utilities.preferences.debug.logging.unified = debugLoggingUnified + } .onChange(of: debugLoggingPreferences) {_ in utilities.preferences.debug.logging.preferences = debugLoggingPreferences } - .onChange(of: debugLoggingUnified) {_ in - utilities.preferences.debug.logging.unified = debugLoggingUnified + .onChange(of: debugLoggingImageProps) {_ in + utilities.preferences.debug.logging.imageProps = debugLoggingImageProps } .onChange(of: breakApp) {_ in utilities.preferences.debug.breakApp = breakApp @@ -103,6 +115,7 @@ extension DeveloperView { .onDisappear { utilities.preferences.debug.logging.unified = debugLoggingUnified utilities.preferences.debug.logging.preferences = debugLoggingPreferences + utilities.preferences.debug.logging.imageProps = debugLoggingImageProps utilities.preferences.debug.breakApp = breakApp } } diff --git a/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift b/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift index ee3fcb8..0aff372 100755 --- a/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift +++ b/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift @@ -230,7 +230,7 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { gainMapImage = returnGainMap(properties: &imageProperties, imageData: imageData) } - if MalachiteClassesObject().versionType == "INTERNAL" { + if utilities.preferences.debug.logging.imageProps { for prop in imageProperties { MalachiteClassesObject().internalNSLog("[Capture Photo] \(prop)") } diff --git a/Malachite/Views/QuickHelpView/QuickHelpView.swift b/Malachite/Views/QuickHelpView/QuickHelpView.swift index 355c20a..08461c0 100755 --- a/Malachite/Views/QuickHelpView/QuickHelpView.swift +++ b/Malachite/Views/QuickHelpView/QuickHelpView.swift @@ -30,7 +30,6 @@ struct QuickHelpView: View { Photo() Watermarking() UserInterface() - #warning("Remove this once DeveloperView is out of INTERNAL ring") if utilities.versionType == "DEBUG" { Debugging() } } .navigationTitle("view.title.help") @@ -41,12 +40,14 @@ struct QuickHelpView: View { }) } + @available(*, deprecated, message: "This struct is set to be replaced by DeveloperView in the near future.") struct Debugging: View { /// A variable to hold the debug settings section. Only available with debug and internal builds. var body: some View { Section(header: Text("developer.header.debug"), footer: Text("developer.footer.debug")) { createQuickHelpRow(title: Text("developer.option.debug.logging.unified"), subtitle: Text("developer.detail.debug.logging.unified")) createQuickHelpRow(title: Text("developer.option.debug.logging.preferences"), subtitle: Text("developer.detail.debug.logging.preferences")) + createQuickHelpRow(title: Text("developer.option.debug.logging.imageprops"), subtitle: Text("developer.detail.debug.logging.imageprops")) createQuickHelpRow(title: Text("developer.option.debug.breakapp"), subtitle: Text("developer.detail.debug.breakapp")) createQuickHelpRow(title: Text("developer.option.debug.erase.preferences"), subtitle: Text("developer.detail.debug.erase.preferences")) createQuickHelpRow(title: Text("developer.option.debug.erase.gamekit"), subtitle: Text("developer.detail.debug.erase.gamekit")) From b2ff022794f6fbc3b9e6683fc9e2d659b2306419 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Thu, 2 Oct 2025 01:37:36 -0600 Subject: [PATCH 29/66] Function streamlining in PhotoPreviewView.swift Will be moving these out later, just fixing up watermarking first...then will finish CameraView.swift refactor, and finally get to this file fully. --- .../PhotoPreviewView/PhotoPreviewView.swift | 38 ++++++------------- 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift b/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift index 0aff372..bf1b034 100755 --- a/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift +++ b/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift @@ -214,22 +214,19 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { var data = Data() var rawImage = CIImage() var gainMapImage = CIImage() + var imageProperties = rawImage.properties if enableHDR { rawImage = CIImage(data: imageData)! + gainMapImage = returnGainMap(properties: &imageProperties, imageData: imageData) } else { rawImage = CIImage(data: imageData, options: [.toneMapHDRtoSDR : true])! } - var imageProperties = rawImage.properties let watermarkImage = CIImage(image: self.watermark()) let outputImage = watermarkImage!.composited(over: rawImage) - if enableHDR { - gainMapImage = returnGainMap(properties: &imageProperties, imageData: imageData) - } - if utilities.preferences.debug.logging.imageProps { for prop in imageProperties { MalachiteClassesObject().internalNSLog("[Capture Photo] \(prop)") @@ -238,13 +235,7 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { let outputImageWithProps = outputImage.settingProperties(imageProperties) - if enableHEIC { - data = returnHEIC(imageForRepresentation: outputImageWithProps, imageForGainMap: gainMapImage, imageColorspace: rawImage.colorSpace?.name) - } else { - data = returnJPEG(imageForRepresentation: outputImageWithProps, imageForGainMap: gainMapImage, imageColorspace: rawImage.colorSpace?.name) - } - - return data + return returnImageFile(imageForRepresentation: outputImageWithProps, imageForGainMap: gainMapImage, imageColorspace: rawImage.colorSpace?.name) } /** @@ -283,28 +274,21 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { } /// Function to return a HEIC representation of the passed image with its colorspace and an optional gain map image. - func returnHEIC(imageForRepresentation image: CIImage, imageForGainMap hdrImage: CIImage?, imageColorspace colorSpace: CFString?) -> Data { + func returnImageFile(imageForRepresentation image: CIImage, imageForGainMap hdrImage: CIImage?, imageColorspace colorSpace: CFString?) -> Data { let types = CGImageDestinationCopyTypeIdentifiers() as NSArray - if types.contains("public.heic") { + utilities.debugNSLog("[Capture Photo] Saving JPEG representation") + if types.contains("public.heic") && enableHEIC { if enableHDR && (hdrImage != nil){ return CIContext().heifRepresentation(of: image, format: .RGBA8, colorSpace: CGColorSpace(name: colorSpace!)!, options: [ .hdrGainMapImage : hdrImage! ])! } else { return CIContext().heifRepresentation(of: image, format: .RGBA8, colorSpace: CGColorSpace(name: colorSpace!)!)! } } else { - utilities.debugNSLog("[Capture Photo] Device does not support encoding HEIC, falling back to JPEG") - utilities.preferences.capture.format.heic = false - return returnJPEG(imageForRepresentation: image, imageForGainMap: hdrImage, imageColorspace: colorSpace) - } - } - - /// Function to return a JPEG representation of the passed image with its colorspace and an optional gain map image. - func returnJPEG(imageForRepresentation image: CIImage, imageForGainMap hdrImage: CIImage?, imageColorspace colorSpace: CFString?) -> Data { - utilities.debugNSLog("[Capture Photo] HEIC is disabled, saving JPEG representation") - if enableHDR && (hdrImage != nil) { - return CIContext().jpegRepresentation(of: image, colorSpace: CGColorSpace(name: colorSpace!)!, options: [ .hdrGainMapImage : hdrImage! ])! - } else { - return CIContext().jpegRepresentation(of: image, colorSpace: CGColorSpace(name: colorSpace!)!)! + if enableHDR && (hdrImage != nil) { + return CIContext().jpegRepresentation(of: image, colorSpace: CGColorSpace(name: colorSpace!)!, options: [ .hdrGainMapImage : hdrImage! ])! + } else { + return CIContext().jpegRepresentation(of: image, colorSpace: CGColorSpace(name: colorSpace!)!)! + } } } From 3a1f62275c997f704ced56a40a96d8a3ab876833 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Thu, 2 Oct 2025 15:29:49 -0600 Subject: [PATCH 30/66] Remove unnecessary inout parameters --- Malachite/Utilities/MalachiteFunctionUtils.swift | 12 ++++++------ Malachite/Views/CameraView/CameraView+Controls.swift | 2 +- Malachite/Views/CameraView/CameraView.swift | 10 +++++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Malachite/Utilities/MalachiteFunctionUtils.swift b/Malachite/Utilities/MalachiteFunctionUtils.swift index 9c4b093..260318c 100755 --- a/Malachite/Utilities/MalachiteFunctionUtils.swift +++ b/Malachite/Utilities/MalachiteFunctionUtils.swift @@ -38,7 +38,7 @@ public class MalachiteFunctionUtils : NSObject { } /// Function that handles pinch to zoom. - public func zoom(sender pinch: UIPinchGestureRecognizer, floater float: inout CGFloat, captureDevice device: inout AVCaptureDevice, lastZoomFactor zoomFactor: inout CGFloat, hapticClass haptic: MalachiteHapticUtils) { + public func zoom(sender pinch: UIPinchGestureRecognizer, floater float: inout CGFloat, captureDevice device: AVCaptureDevice, lastZoomFactor zoomFactor: inout CGFloat, hapticClass haptic: MalachiteHapticUtils) { func minMaxZoom(_ factor: CGFloat) -> CGFloat { return min(min(max(factor, 1.0), CGFloat(MalachitePreferencesUtils.shared.preferences.capture.maximumZoom)), device.activeFormat.videoMaxZoomFactor) } @@ -78,7 +78,7 @@ public class MalachiteFunctionUtils : NSObject { } /// Function that handles autofocus and autoexposure - public func pointOfInterestAEAF(sender: UILongPressGestureRecognizer, captureDevice device: inout AVCaptureDevice, button: UIButton, viewForScale view: UIView, hapticClass haptic: MalachiteHapticUtils) { + public func pointOfInterestAEAF(sender: UILongPressGestureRecognizer, captureDevice device: AVCaptureDevice, button: UIButton, viewForScale view: UIView, hapticClass haptic: MalachiteHapticUtils) { let point = sender.location(in: view) if sender.state == UIGestureRecognizer.State.began { haptic.triggerNotificationHaptic(type: .success) @@ -169,7 +169,7 @@ public class MalachiteFunctionUtils : NSObject { } /// Function that handles toggling the flashlight's on state. - public func toggleFlash(captureDevice device: inout AVCaptureDevice, flashlightButton button: UIButton, floater float: Float?, isFlashOn: inout Bool) { + public func toggleFlash(captureDevice device: AVCaptureDevice, flashlightButton button: UIButton, floater float: Float?, isFlashOn: inout Bool) { if device.hasTorch { var buttonImage = UIImage() do { @@ -220,7 +220,7 @@ public class MalachiteFunctionUtils : NSObject { } @available(iOS 18.0, *) - public func addControlsToSession(session: inout AVCaptureSession, controls: [AVCaptureControl]) { + public func addControlsToSession(session: AVCaptureSession, controls: [AVCaptureControl]) { guard session.supportsControls else { return } session.beginConfiguration() @@ -299,7 +299,7 @@ public class MalachiteFunctionUtils : NSObject { } /// Function that handles manual focus. - public func manualFocus(captureDevice device: inout AVCaptureDevice, sender: UISlider, floater float: Float) { + public func manualFocus(captureDevice device: AVCaptureDevice, sender: UISlider, floater float: Float) { do { try device.lockForConfiguration() } catch { @@ -313,7 +313,7 @@ public class MalachiteFunctionUtils : NSObject { } /// Function that handles manual ISO. - public func manualExposure(captureDevice device: inout AVCaptureDevice, sender: UISlider) { + public func manualExposure(captureDevice device: AVCaptureDevice, sender: UISlider) { let minISO = device.activeFormat.minISO let maxISO = device.activeFormat.maxISO diff --git a/Malachite/Views/CameraView/CameraView+Controls.swift b/Malachite/Views/CameraView/CameraView+Controls.swift index 8b3f73e..601087f 100644 --- a/Malachite/Views/CameraView/CameraView+Controls.swift +++ b/Malachite/Views/CameraView/CameraView+Controls.swift @@ -326,7 +326,7 @@ extension CameraView.ControlLayer { if delegate.utilities.versionType == "INTERNAL" { delegate.camera.session.setControlsDelegate(self, queue: delegate.utilities.sessionQueue) - delegate.utilities.function.addControlsToSession(session: &delegate.camera.session, controls: controls) + delegate.utilities.function.addControlsToSession(session: delegate.camera.session, controls: controls) } } } diff --git a/Malachite/Views/CameraView/CameraView.swift b/Malachite/Views/CameraView/CameraView.swift index 560635c..8e0ae75 100755 --- a/Malachite/Views/CameraView/CameraView.swift +++ b/Malachite/Views/CameraView/CameraView.swift @@ -298,7 +298,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa @objc func runFlashlightToggle() { guard var selectedDevice = camera.device else { return } if selectedDevice.isFlashAvailable && !utilities.preferences.debug.breakApp { - utilities.function.toggleFlash(captureDevice: &selectedDevice, + utilities.function.toggleFlash(captureDevice: selectedDevice, flashlightButton: self.controlLayer.buttons.flashlight, floater: flashFloater, isFlashOn: &flashStatus) @@ -380,7 +380,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa guard var selectedDevice = camera.device else { return } utilities.function.zoom(sender: self.controlLayer.recognizers.zoom, floater: &zoomFloater, - captureDevice: &selectedDevice, + captureDevice: selectedDevice, lastZoomFactor: &lastZoomFactor, hapticClass: utilities.haptics) } @@ -389,7 +389,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa @objc func runaeafController() { guard var selectedDevice = camera.device else { return } utilities.function.pointOfInterestAEAF(sender: self.controlLayer.recognizers.continuous, - captureDevice: &selectedDevice, + captureDevice: selectedDevice, button: self.controlLayer.buttons.continuousFeedback, viewForScale: self.view, hapticClass: utilities.haptics) @@ -399,7 +399,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa @objc func runManualExposureController() { guard var selectedDevice = camera.device else { return } if selectedDevice.isExposureModeSupported(.custom) && !utilities.preferences.debug.breakApp { - utilities.function.manualExposure(captureDevice: &selectedDevice, + utilities.function.manualExposure(captureDevice: selectedDevice, sender: self.controlLayer.buttons.exposure.slider) } else { utilities.debugNSLog("[Manual Exposure] Current camera is not capable of adjusting exposure") @@ -455,7 +455,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa @objc func runManualFocusController() { guard var selectedDevice = camera.device else { return } if selectedDevice.isLockingFocusWithCustomLensPositionSupported && !utilities.preferences.debug.breakApp { - utilities.function.manualFocus(captureDevice: &selectedDevice, + utilities.function.manualFocus(captureDevice: selectedDevice, sender: self.controlLayer.buttons.focus.slider, floater: focusFloater ?? self.controlLayer.buttons.focus.slider.value) } else { From fd946c2aaf989c571f87eae57d1db0b388fc1ae4 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Thu, 2 Oct 2025 15:42:24 -0600 Subject: [PATCH 31/66] Move sliderGroup and slider funcs to View+Sliders.swift --- Malachite.xcodeproj/project.pbxproj | 20 +++++- .../{ => ViewUtils}/MalachiteViewUtils.swift | 49 +------------ .../Utilities/ViewUtils/View+Sliders.swift | 70 +++++++++++++++++++ .../CameraView/CameraView+Controls.swift | 15 +--- Malachite/Views/CameraView/CameraView.swift | 36 +++++----- 5 files changed, 111 insertions(+), 79 deletions(-) rename Malachite/Utilities/{ => ViewUtils}/MalachiteViewUtils.swift (93%) create mode 100644 Malachite/Utilities/ViewUtils/View+Sliders.swift diff --git a/Malachite.xcodeproj/project.pbxproj b/Malachite.xcodeproj/project.pbxproj index fc1f1a6..3ed2aaf 100755 --- a/Malachite.xcodeproj/project.pbxproj +++ b/Malachite.xcodeproj/project.pbxproj @@ -26,6 +26,9 @@ 7837C10C2E34C45B009396B0 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78917F5B2B99AE72005E10FA /* WidgetKit.framework */; }; 7837C10E2E34C45B009396B0 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */; }; 7837C1172E34C47D009396B0 /* WidgetBundleWatch.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 7837C1152E34C45B009396B0 /* WidgetBundleWatch.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 7847D5032E8F26EC006759A6 /* View+Sliders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7847D5022E8F26E9006759A6 /* View+Sliders.swift */; }; + 7847D5042E8F26EC006759A6 /* View+Sliders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7847D5022E8F26E9006759A6 /* View+Sliders.swift */; }; + 7847D5052E8F26EC006759A6 /* View+Sliders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7847D5022E8F26E9006759A6 /* View+Sliders.swift */; }; 784EDDD42E5EE52600DFF370 /* PhotoPreviewView+Controls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 784EDDD32E5EE52100DFF370 /* PhotoPreviewView+Controls.swift */; }; 784EDDD52E5EE52600DFF370 /* PhotoPreviewView+Controls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 784EDDD32E5EE52100DFF370 /* PhotoPreviewView+Controls.swift */; }; 784EDDD62E5EE52600DFF370 /* PhotoPreviewView+Controls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 784EDDD32E5EE52100DFF370 /* PhotoPreviewView+Controls.swift */; }; @@ -288,6 +291,7 @@ 782FC7752B2DAFAB007709C1 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; 7837C1152E34C45B009396B0 /* WidgetBundleWatch.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetBundleWatch.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 78474D2B2D7AC879006FBB96 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = ""; }; + 7847D5022E8F26E9006759A6 /* View+Sliders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Sliders.swift"; sourceTree = ""; }; 784EDDD32E5EE52100DFF370 /* PhotoPreviewView+Controls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PhotoPreviewView+Controls.swift"; sourceTree = ""; }; 78562BC22B450A7600920160 /* PRIVACY_POLICY.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = PRIVACY_POLICY.md; sourceTree = SOURCE_ROOT; }; 785F08492B12D41100244EB4 /* Malachite.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Malachite.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -420,6 +424,15 @@ path = CompatibilityUtils; sourceTree = ""; }; + 7847D5012E8F26D1006759A6 /* ViewUtils */ = { + isa = PBXGroup; + children = ( + 7847D5022E8F26E9006759A6 /* View+Sliders.swift */, + 786DAE4A2E4F28B100BE3137 /* MalachiteViewUtils.swift */, + ); + path = ViewUtils; + sourceTree = ""; + }; 785F08402B12D41100244EB4 = { isa = PBXGroup; children = ( @@ -532,12 +545,12 @@ 7866F5D52E62A5F7009AC9BF /* temputils.swift */, 7866F5CC2E62999B009AC9BF /* CameraUtils */, 786DAE432E4F28B100BE3137 /* PreferenceUtils */, + 7847D5012E8F26D1006759A6 /* ViewUtils */, 786DAE442E4F28B100BE3137 /* MalachiteFunctionUtils.swift */, 786DAE462E4F28B100BE3137 /* MalachiteHapticUtils.swift */, 786DAE472E4F28B100BE3137 /* MalachiteIntentUtils.swift */, 786DAE482E4F28B100BE3137 /* MalachiteTooltipUtils.swift */, 786DAE492E4F28B100BE3137 /* MalachiteUtils.swift */, - 786DAE4A2E4F28B100BE3137 /* MalachiteViewUtils.swift */, 7866F5D92E62ADA0009AC9BF /* GameUtils */, ); path = Utilities; @@ -942,6 +955,7 @@ "$PLISTBUDDY -c \"Set :CFBuildUser $CFBUILDUSER\" \"${INFOPLIST}\"", "$PLISTBUDDY -c \"Set :CFBuildHost $CFBUILDHOST\" \"${INFOPLIST}\"", "", + "", ); }; 78EC5D602E4EC1AD005044E2 /* Embed build information */ = { @@ -1034,6 +1048,7 @@ "$PLISTBUDDY -c \"Set :CFBuildUser $CFBUILDUSER\" \"${INFOPLIST}\"", "$PLISTBUDDY -c \"Set :CFBuildHost $CFBUILDHOST\" \"${INFOPLIST}\"", "", + "", ); }; /* End PBXShellScriptBuildPhase section */ @@ -1107,6 +1122,7 @@ 788262662E51309C000085AC /* SettingsView+Preview.swift in Sources */, 788262E02E517252000085AC /* AboutView+Eggs.swift in Sources */, 7866F5CE2E6299A4009AC9BF /* Camera.swift in Sources */, + 7847D5052E8F26EC006759A6 /* View+Sliders.swift in Sources */, 78E1707F2E51CE45009BEF2F /* CompatibilityView+Format.swift in Sources */, 785F084F2B12D41100244EB4 /* SceneDelegate.swift in Sources */, ); @@ -1170,6 +1186,7 @@ 788262652E51309C000085AC /* SettingsView+Preview.swift in Sources */, 788262DE2E517252000085AC /* AboutView+Eggs.swift in Sources */, 7866F5CF2E6299A4009AC9BF /* Camera.swift in Sources */, + 7847D5032E8F26EC006759A6 /* View+Sliders.swift in Sources */, 78E170812E51CE45009BEF2F /* CompatibilityView+Format.swift in Sources */, 786DAE402E4F28A600BE3137 /* CameraView.swift in Sources */, ); @@ -1185,6 +1202,7 @@ 78E170782E51C97B009BEF2F /* CompatibilityView+Camera.swift in Sources */, 788262602E513080000085AC /* SettingsView+About.swift in Sources */, 786DAE4C2E4F28B100BE3137 /* MalachitePreferences.swift in Sources */, + 7847D5042E8F26EC006759A6 /* View+Sliders.swift in Sources */, 78C2EFC42E6972D600C6DD79 /* Init+Internal.swift in Sources */, 788262DF2E517252000085AC /* AboutView+Eggs.swift in Sources */, 788262972E514F38000085AC /* QuickHelpView+Watermarking.swift in Sources */, diff --git a/Malachite/Utilities/MalachiteViewUtils.swift b/Malachite/Utilities/ViewUtils/MalachiteViewUtils.swift similarity index 93% rename from Malachite/Utilities/MalachiteViewUtils.swift rename to Malachite/Utilities/ViewUtils/MalachiteViewUtils.swift index 70d985a..79dce02 100755 --- a/Malachite/Utilities/MalachiteViewUtils.swift +++ b/Malachite/Utilities/ViewUtils/MalachiteViewUtils.swift @@ -12,6 +12,7 @@ import SwiftUI import ObjectiveC.runtime public class MalachiteViewUtils : NSObject { + public var sliders = Sliders() /// Function that returns a buttons for the user interface. public func createAndAddButtonToView(symbolName: String, delegate: UIViewController, view: UIView, utilities: MalachiteClassesObject, action: Selector, dimensions: [ CGFloat ], constraints: MalachiteViewUtils.buttonBuilder.constraints) -> UIButton { let button = UIButton(type: .system) @@ -154,47 +155,6 @@ public class MalachiteViewUtils : NSObject { } } - /// Function that shows and hides slider controllers in the user interface. - func runSliderControllers(sliderIsShown shown: Bool, optionButton option: UIButton, lockButton button: UIButton, associatedSliderButton sliderButton: UIButton) -> Bool { - var factor = CGFloat() - if shown { - factor = 0 - } else { - factor = -220 - } - - UIView.animate(withDuration: 1) { - option.transform = CGAffineTransform(translationX: factor, y: 0) - sliderButton.transform = CGAffineTransform(translationX: factor, y: 0) - } completion: { _ in - UIView.animate(withDuration: 0.25) { - if !shown { - button.isEnabled = true - button.alpha = 1.0 - } else { - button.isEnabled = false - button.alpha = 0.0 - } - } - } - return !shown - } - - /// Function that sets the lock and unlock state of the bassed slider lock buttons. - func runLockControllers(lockIsActive locked: Bool, lockButton button: inout UIButton, associatedSlider slider: UISlider, associatedGestureRecognizer gestureRecognizer: UIGestureRecognizer?, viewForRecognizers view: UIView) -> Bool { - if locked { - button.setImage(UIImage(systemName: "lock.open")?.withRenderingMode(.alwaysTemplate), for: .normal) - slider.isEnabled = true - if let validRecognizer = gestureRecognizer { view.addGestureRecognizer(validRecognizer) } - } else { - button.setImage(UIImage(systemName: "lock")?.withRenderingMode(.alwaysTemplate), for: .normal) - slider.isEnabled = false - if let validRecognizer = gestureRecognizer { view.removeGestureRecognizer(validRecognizer) } - } - - return !locked - } - /// Function that presents an image instead of a null ``cameraSession`` for the iOS simulator. func returnImageForSimulator() -> UIImage { let lobotomized = "iVBORw0KGgoAAAANSUhEUgAAAEEAAABBCAYAAACO98lFAAAAAXNSR0IArs4c6QAAAFBlWElmTU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQAAAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAQaADAAQAAAABAAAAQQAAAADuSo5dAAABWWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgoZXuEHAAAj4klEQVR4AbWbeYxlV53ff3d9e9Wrrbuq2r13e2vbeMNgsD3YeIAhECCACDMwWaQMSUbRzESKIo0iJYrERPkji5RoRokUEqKJNIIkxEYQ1jEYGGODjbd2u/cu91LV1bW//d13783ne169TgMGbAOn+767nXvOb9/OKc9+Te2G6Rtqre7mXf20/3v9tPexNM/9QZpalueWecaRmsd1YDkQ+GZ5YJ7vWc5Lz8/N55HuAy9ci/z4T8tR4ZEdFe+lpy9dav+aQP7VDPsvwOYeG797Xzjxl/Wgmhe8WBiCEUfEEXMUOSoc5WuOEtejQ+9H/UQh6OJ5Xl4N4nyHX8kPhRNfuTuauuejBv1+RQ2e/Eqaf2th4t1bva3/vm7pdIshU4EINy3kmImsPD9l8VTZCpNli2sFy8LU8jy1KAis102sWCxbr9ezohdaY23Temsday5uWX6pacZ/GzBcalZg0AL4j4Vxq1IqfuJYY/VR3mYcb7j90kTYW56/s9NvfnFj0JrvI+JAaHDS/N11m9i9w8bmx218z5RN7Z+HEONWmq1ZbabmiNDvg3xcsG63Y4VSyXqdnlXDgq1fXLXWcsvWzizb+ukV6yyt29alZdu6uGbpcsdMCpEYwuPZdDB2Oo6DB0931s6/USq8YSLMz8+X26sbn+30uh/piRHi/FRs4YGqFfeO2eF7b7fr33Krzd54nfXLqTWDgbX9xNpeZn0wyAz7gLQHiErurjN47EO/CDpGVkpjjsjq6E/vyqadf+m0HX/iebv43IK1z66andhyxMB80NtsvDL+T5Zbm/+Wy9ctFW+ICEeimdsvJ1vfX7FeQVwHUrO5Cdtx22574BMP2/77bra1qGftKLUWsrzJ7+qgbZ2sZ1tpxwZITAYxcgwj+o7S871gxyhGeQTaRScRFS+2ibBiVSYp8ayeFS24ktrJx56zb37my5a+sml2GeVDMoK+2c6g+licTr/3nJ3rasTX2l4vEbzpQuUP2r3OvxMvE4n+DlT+nUfswb/zAdt5z0FbLzZsBcQXbcOu9NetnbatjwSk/DMhLpUZMUv3uIFU5pPm5zIiOtOVI+AIoU3sFa0WjVndr9usN2WTkGW6VbMTX3navv6fPm+DF5fN1ujX8yBWuDJdnTxyunmZh6+tvR4i+LWg8KfNvPcpxznEPz5Ut3f/3ofs7o++w65MJCC+bmebF2zT27JmlFgn7FkXFmU4iYEHq0QIESDPnAQ4ojg4hTyHJIN/IyL5qWcxrjMahBYOkIW0YBUoP+1P2MGxvTaX1KwOqp//N5+1Zx/9ntky1rOHrcj9Xt3iGxatu+CG/wU/r5UI/lhU/IvWoPtR4JJFsrt/50G7/4O/aTtv2mOrXttO9y7Zute0vtd1nO8HmbXjvjVLbWsVOtaIe4QCAOmQFDFkRNRGKuw77nvce4oVeF/oRzbWL1qlF1sMIUqDAs9EFNQD6agjETfVD1iwmdnxHx21R//L/7LN75w10buYWTZRqN18qdc4PpznZ/++FiJ48/H4/1jrb328iwmTDXjgI++0YL5iC5uLdnFlGRfXACd0U7NLjjWqXOO+SbNdsc0+fJvZ7gIESawfDiyBGPT6iYZZlPgPCBmygpW7Jeufb9rqU2fNjl40W8ErMM1ImNzHmSap2e75A7Zn7x7bVZuzl7/+jD3/zSfNurmNWZTNlGb2ne5c+rme4xcSYVdx4tPr3cYfD3DUEuidB2YsrBZsC5/eaAJVCxskZoqxOsTk0agTXI9x3Fqy+L69NvnWPdbbH1irymjECCJEjl0YNt/C1Ldy4lt1Nbb1xxas89gps2MgL+3m5ADQ+GqaQ9e4SisFVpyatB07dthkbdyWz1+0Sy8tQAJDVsqdnbWJ3S838Ls/o43AfdXXNxbm33+ht/Jom5mw4SZVKO4sOb9+FXExQ96hxkt0eoiZdJx7VAJzL0jMpjneVjP7rRssPlB3n2e+DOWQCCJImIY2WIKo3zxq9ti6mfinmEBapG7qJMQJp4m/hwS4wr24o/cwIZgYs0pQtNbimuWtAe4zRFbipZ22f+9RO6qeP9WEwqu2m8rTc4vt9UdxaJYpkIdzmqTbgCUzfHJj2Wwf2O0sgmDRwhqxLrCNmpcRK3OfgqhlfNNDXWpgk+IpGCpnZuGEe7hKCGccBeYY475NGFUIvjgYS1qm5oOt3KokyeQIVxCFi4SU53AP5xh7eQs3vGVhRFweYpAHGb6qP+vZ+S/Q+30c2yNxtd2uAXv0yOyjTP+4xcdWbHBYIY2LBcDV9hKW7Buz8MYpq71p2srXT1gwW7S0DEcjAHPuDxkYTbPt8sTsyAtsE9vRKPYtqEUwNLUgwAziKdRdBPPR8WA1s0petFLIXHwoBlwl7rbUZJnYLvvhWTXFdlxsWePlJdsC4vT4htlpAqmL9JEUtRkY2gf8q5TK/2irs/Uf3cfX/LwqEfZElT+4krT/fUeWSsjvotttY+a9fbdNvXneYiRgMOZZzxk5qO8zCwB6HgCDlGsQwAOBDLZrGH87KELl6U+PDCJIZXTJSWrh8U2ESqi/5VJ23kGoobS4W0dgBVlqQixASiIGjCBI3A6su7Bhq89csvyJy2YvYrPOAU+HgKbfc+PuKk/vOt9euTQcbfg7hOKaJ/vq++rLG5fW27L047w4ULaxdx6yA799p23uSmy92rBWsSslAXBhM1JYIe+w2z5z4t45C/plIOWiwywgTSY9hDi6H7WRbRAh1HypEXPIBAxR1lO1kDGHhB5FnHqqkUSMYhJavY0Knezb4jfOWP9bi0gGqrjYMw+6juXF45t592a6DwfhAsX78VZI8v+2kbdvwZrA/Zrd9I/fY/t/9257ZWrF1ipbuLg2nBty3oHn8HCs4wPdbCPmQmKNrXuFx7rWnS6YXydHIb69qj/DZ2TOfDFUE/eZ++EbSYBTCbJzYHBDOEJDOKki71KMsdywzRRs55E9dt2brrfL6yuoByqCDRmk2fRcPPdsI228zGyuueFHNzfZ3rmLtnxpK+6Yd+eE3fP777axDx+0Z9KTtlnaskHQZWKJLZwSx7ZjfXcPt1Mn4gFhMCiQKrscgdhXJBpxWBOK60JSzZP0bHN/iKD66s3wfY5BGElMhDUNBrhSR0OIQNqdow5SrwEPFZXK8aqA4cPfSlK2Xb1pu7l9nT356S/axb/4kRHUWi2td663g+NP29NO535MEiLf/7ONvHtbXs+s9u5Ddtfff8iey0/aehkC+MSjDjqoDGA+Yl0mghvvFG2ig8p0ieC6FYIcEqAsAgX+BZg/p+DCVjKwzTGH4FCNFBmKFM6mIObeNkddjqHH+orQOaZAUeoGNtWs2GS7ajVytyrRZIn5AuxBCmyaywsZT7aJOVI8SI8iRH8wsHe+7SF75vG/MlvtW94fRNjd0xtZ4zmR+6okTE9P11prja0OyNbeddBu+KP7Lb+3Zif909Yg9HX8xDfHBAsVkC9uRNY4uWbNZ86ZXcAiq4oimdeBMY3u3W8779prjXqXsJn8wQGmGYX8kABc0JSGqkGA7bOuJQwBEhIPioQ7GLZLXVt5jsjxuSWzTRgS4WM7mP8K766btBpzFfePW3cCxItYLOKTARIZwpDJ7rjdmhyw1lcv2ff/2efMziQ2npVbm9aW1cMUb7e80/mwCGBEefVbd1j1zil7Pj9lnUB+RvqYWSGLrQblu8+u2PpXXzF7hiBsgXeK5obSSz+uwSv59nN24d4lm/3kzeYd8K1ZRD0CSR/cYiz5exlOclEJFm2IuK7ER/WLBoFVN0Nr/fCytR45yXxYe8IBN59g1RCoqNUuWeMbS9Z5YLeNv2fewsNF2wp4j3EdeD1roZJnkot250M3mM3DocXEGp1uZa42ffvi5srTI3XwUIWvIjoV/84Z2/3+m619S2ZLhSvbaqCIkcCPRKaykNvK516w4IXUZjdnrNaBmADWX2ZSxfbonGGDXKhLrt8sJ1bfNWnhOO4LwDyZaGyCIlCHuIst5AOE0ZCSHhIXYW9q3YL1nyMGeOSE2XcbtmMwa9PxTssRvN5lkNd8OjTfZRRgZdPSicAmd9UtqaTkKMCE1CGE1kMCq0HBwjXfNk4tWb45IEWPJjtJ7/OOCEcqM7Ob3dY/7xdym3jfYZt77367UFsiHaZogbEJoGiA0ZtulWzpc6ct+/aa/a37P2l/96990u676+1IZNHOv3LOuq1rolLhCJy21rACoaw/B9crqAUxRQ5A0nk02RFlaCT1AdPxL8ZOxDI9Z7q2+cgFK56M7cNv/Rv29z70t+2OI3fY4oVFu3QRVz9yOaKhDjxh2tk0f6KCalCuK2LgJWHYhYFP+tft2s07brJTf/Ui3qJvxG03vzVPP+3Uod3tPdxTGEqyE+2LLZ1H4oItDBuqgF75WGT53/aZVUu+fs72xjfZxx/+mL3r3ocIgfs2N73DTr18wn7wgx8wJTxWXC+cxAgc0epXj1vtxoNWnCk7F+ZcLK8UJA3Vghuuhgdl6X5qJYzelR8tmD21bA8c/ID98af+qd124GZbWlqyhRML9oPv/0AjDD/R5zIzkopnzTb2LdjUXWQNRLIJLhOZkIGxzYDcZI5J54lGifr76z2EeHJWMxNbZ/+QNN1sT2TJ1MCWs4uWRMTjUj7ENyRMlRdYeeIcBkpqNWPliKICLQiwE2RupUrVVYhG0ZwjggCTvXihR1rcsqANQZU4OOrIPgxrjUM2imKoC0cMdQobAHt0GZsDWNVZm5uZ5T02l3l377rOXbsfEXt0aGhUxU6hQqfWYRwy5TRMPyQAYWLLPjHDfoIpbJ9Aa4bpjf47CMG6XnKvPNXYwZ1W3BNbI1zHskqWJd4dFkBYLlC8Ky+AyJ19/qSdOnbczp49a6dPn7Znn33Wjh07Rt+faJpbQ6BVvaWG5c2+E88h2/RChyg1OrjPexbmA0spudsVXkGH088et+8+9h1bOHfeTp04YU8jccPYAUJd20bzLefWv7BmOImhpG17pBQjesVbsl1v3mVGzkOIYS0vPxKer+6c7PWYCQmZ2rfTSntrdi68ZB6WXFZcgY2fJa7e5ywzcC4tLtpnP/Nf7Ynvfc+ajbYjgsT01RuASj3a5BgcPrbFu5r7/v8vxEwHMgB7ecHSBhNJGOHu09//kf3rxr+yO+64wxqbW/alr3wJLRXbweJqdgUFRjSRUDV6FiVIXiZm6iCSRCwaBc9uuP2grc6WrRtRBcv6N4attLPP1XmwDvE0cfkYehQ20XUAJgpTwJFRJcb7IvtuLOby7Fvfety+/fjj8pxXm8sNFNpe25SGCxO+z70OkquYDrF33YYfD3MQfSSkMJO4YuLCIVL022pu2ZNPPmlPPfUUr0fkUn+hpvGvAUIP1cURCeSRrKHgAwUGske9068TRxSG33TybD8pm38TwZ9bNNnIVqwwQE99PIJEFFvg6j9wpy9/jOE0YpNcGPAfIblKfN7wjBjAXQx/htPwq/EJYsIx1R9FkIEDXb3c+ADsQmDNqQGYP64TbFBWcLGUok6GGRFA3w3H1pXaNYTQJaGAsdYxCLvgglpBCI9UXgD3Cec3ksvWl7rzqJ8ls34nSW5wlJtiKKQgw5UYxdJMnMc9+orHCTj6cdPC2zAoIoR8yrXYcvuqzSEEAoxd3BmZj+9OkQYZRCfOLtfgPbPl2AFxT6onGIIJ0JzjlRI5EfHqfMKSw90PSSEPo6fuB7XWfNEu6gwQQHjovSfJSAfYN8bPZHvEDPE5n/SzbHDYDYixT6kaqxLhi3IAo5yeUol1B+vWitdt5hYogE1xgEEI34m6hvoZTcBDN7sVAboOvx0nZHEQeUD8nhAzENPnKYiw/BaoUNJtU40bWC9huaa8RvGGb/dxIH1OO1xcsE0EHgluH/hcUqd7vcLqG99NHqhZM1PkBmM7IN5GHZnHsBMDd5ak89pjOQMJntNgeqBVoTTpcmAD3AERAJT0EHWAqpOJlX4DMd1Df0RugKGRr9dYo6Zr90zSIrGk78TbJ8yfCayRNLc5gtrQSZFh1gUwxqcSxoBYdRZnVXFqU76fPDJh8VvAbJZ3TJtrNcbNdu2MYh6H5itzQPDaXWOWjKMOOVKcYNvELHga+hHCDeFZA3X+XHiTDkGEpM6lo6JHPS5NMIRQi0gJAOnT45r/GqwRN6zyIPL5AfrfwkHEPNRZiiTonBNKFUElASLAIY6PkCfeSzltEr1HtyX1LE44aXQSqXt8VdLjQlNxSliS6vIyncEsvANR+k36XMcBIZzjlzHabu5KKiAs7uH1+8gqCZTaEUmfuNEniSLLFNEN/HSvRR1nAjghAH7IgYLwUv9REw8CqGQl1+NR8/MQU22mSOFaEhN77U5t4uEJSyibN79DiPYyA21g3hjFNQ0skTwAY+6j4P2umnV3dUlpAUqWXXM4yCGI+kqGeSybOKy+arVKL3ICma5Vri/a9PsmbKvMct4TSM1FXilXuLbt5OYI5uqBqkVvLVp7BrXDs1F9QJIZnLFV+htAaFW1nGQMCeDmDilaEDCORlR9TyKKJeejFI6EHgYNMRqgxyl7Cdp+08p7WSStl62/u2P9F5nhCt+LCEJGYgkHw5vQy9vHrDnRt47fgsCIoOIFmqsrEDkGAERYxAMkyRGC6wCiuIEkMIhumcTnSNVKdd/6NzDJUTpCewfzqOt+CH4La9kHC9bDoPZk+Bg/BP6AIM8FVjHRSQ8pL8BIPKD7XnNC8zAO47OtPLlfSPgdxLSD2CJeUqN0Hb3aYGNJsWp+FamoENTC0VbQtM4YewruKlj9lgnqC2WkhyoPmPTkl6u5rQ9WbKWCK2KcDNFWydDVTrnPudF1TtHFSRzPXElNqqeykSiCNGg8LettRAML5+H0zJgV7yzYWFBjQ0dihQJhPjFMwsrWZthghas5NJPJwIqdmLXLiGV9Vsd7XSscxGDAfY+6R9KBcSLAkPm9sBAUTnmDFjtC8AsXNq3SqlhUpjwFFbOz9PoyUVZ1w+Ye2m3daaz3jiKBHKEcXFS1eYBkdDKWZ7AlKpPJuEax7yo62QDKM1cIF1zMwpjU3pkfaJAKBVphSFKDMXRBlwCT+KopNtgmhoI1eiAznKtda6bML/vAXMWSTzCKRMreQFjPD1nFouK0UrT+Qs86L2LYcHrZ32SyCeZG3dMWHfkvMAqh3wjDzHtRSQbwkloWzUMaMpa4eW81RL4xyQhfow7xzHmb+BhbJg4VzJtEtGQfwj7IauUZeKOh2AnUPuMFGEjZGEn7oKPNGODFc23ccuGicOUYyHrrEf2dzeBaWuP6opLDbFnQMDIPe8QsHoRkKDdOWxe8luMo9UoWdVm83ShY4/kt63wTAsh+vBdPTeLVBMmIumTYQdRFBGCLU385mC6ORewh+Adky9bdwZtDWM8ZMn18VhhFNr6jau2QAOc7vP8upTISm/mJXVYosugC9VIlJ0SQPmWs4aoT0ACVjzWOQEKW2JORxVioAOuoy0mE9zHAvnsHAXSvqI77gO+cmmgkqRlnJxVcqISqvvBdT3XB8j07W3pVq29OWvlk1S5+8YINHgEuaGD342HftZNazzpcpy7aK1vzxaYlT4LrIirmx98L/UKyEIiVGUZoBYFb6VmBio64rCRqgBfYd98e22ps2drXyCKfMDtz/KzZW/GQ94zZ2O5xVqAQe3x7h1lTWBKwQJqid2GE/iEWAfVAt2wmFjOVuEsZAu7zLsRqy3XpVZHQDKPs4bOcVPBYIu9cq77lO595Bj1ewMWQrC/qs4INYjLOF759nvoD35C1ykNVH6S48kDBOniWXKrJapTfRn1eQbolIYxRCMKT4am1tcZ4UCDFG0QqjSWLA6s0alYZ96yVtC3EWq9hE+LfoOA5XbP2o5hmuamvMNezjHQbXuC2KauQo3tkaO0SdT0pJ3D2sdDi2gACq/lSGTidUjRRRCX9zGUntlvSpR+f+tTQU75VCyCqSvkjCZJRjSl+VD22YSD2+UJuyz+8YtlLfHuZDxAiO4xq3xdb4ZbIOuN967IWqjCh6FNe2+JiiT7QQY6s4IdP65wX/fgzzbT3KdYxHUXDNTjJThPpnhKcToHEZ5ZoMYayeWxr3101O07fExzUKNaOc79n1cKDsY0dqlowjetDdwfsWRpQyND6g8YJQE7ASHWc4sNlVa1yEUkSwnsFUIFWqVAD4eNjzX2CHa1zuOW2hHL+RmYbpzcsOU7ns3QSUgipC9BuR0LvmzTSQmvX29YDhkw5CvMGXVYj1kEZJyDbXoIhY170gohg43n4583M+1RrE64swIzzFDbwy4QFQKEfhc0kH1OU29/CFrypMWs8AcWO8krUV5zwEhyf7dvm/IaFcxifefYMUKApzZVQF/IBEE7QARFW7tD5bp4pFongUKZITsYUsQddqkvsWKTarLXJvImrXO1awjy9C13KfE3nzdy8FHkcSw9wvoN44Y66eSTH3WLLOkRwPvZCYq9IOG8RSVK3dERDHZFtO9zbseCI0B+M/zCW/1ccfwExPtu3aDe1RscKLc0j01BSdmKtuG6Vw2Wrx+NEZi2qwXDxDACIE6eY7CyIjeE1dlC1uQ6Duovn6GdQZemeXRPFMZbxYyI5EQKkEzK7kL2LCssVPAmhhFglIelJt5BA6oBGvOIQVj4kVRTRR1qkaHE/Yv2mslVuoVCCxG4VN5yrlm5kjEtdDWLi+TZCW3sJ0V3mG2gRh9H//vzgaN8R4Rxb3uqF8f9DSPVBWczOGdLPG+BEMWbTFb3VCzpIagdJZq28bcXdoVUm2St2HZx5mYnO0UdSoQPtcFWoY5xVE+AQYXQejOHxKSWz09lJQyYVgdgZnBrgaQII1O2QyrdAfB1M1/he3OZzh7gYQ/qiXXNunwT6XzlUcdLXKXecC81IkqDw0MByKcNUZPtfd4lM8hSIYDjhAePE/1LMg7/DNludvHm9vX60p50lRxCV9zPwrSHBCWXrEgkNERpQMyBQIMaCSPuwSuwqK7MKHK2TJZ5qskcAiKVzTOSAh4bOqWum0SFEBITudR4do/c6qw9TSpRdkFHirIRtks+uY0vP4apV9pUsYddct0i9g6JPCuzaGoCq0/jZhrXQx0iuFVFh6pyPADvSVPWLK82sK1Jqv8iwLTXXjk0HlUu9fmte0tB9oWXjU+Pm744c5x0B6CoDI73VHHLn2pvQLLAzZA7JmYps9o5Za7HlpnEKEktsGUv1GeezRRAhJhqqOWA5jwgixIW0oCpwKBOVScIDGmLv7/Fs8jBp+STMoTbRIn5RhcjtSBFj+Ha05yHdrnUoIYwboWUnIc4x+sAjTC3epfS7Te3uol0lAtc55ezfLnQ73+qt0fk0MO7jYQlDhruMq9pdghgT8MiCE526jRcJxRdRXgUTeYAIFVLMM8O+5nwrp1xHrE9lpbfeta3FdetusAEIIyj6u1iA/EH2QW6jUMCnd7FHxBmlafKS2XFsSUhJjI3AjN8kEetUmuCBbgBiyKqqQu4AhLXWIbxdgYp3OWU0RYdZE3d7kb1L56HqUbDEK+B3Gkvp+te4c23Ei9G9P1YondkadPa6/PwGGPIQoej1RHssp3WgesxGbKpRhNkkMBBCWaaSmYB8wTUixBhD12NvU72C8aQa7TZl8szjHZrq3J0KSQHuh7+HsAKRaV92gAHiYoGxcWvKIDHInrJKiCpi90lAJLsFFmEbLarJLtDiNQaVRB+mwBgYFCKuOeshSjEqnZI1fgTZ/nJot1RyrFcq71prtb4+QvoniWB7JiePXF5be7EnGZkn33mAqOwIOrWLwgeLql1InVKng/aO8lIPBUSKiKUlAkSRYo+0VSZEOPj8uFUphpRHUNO91Ep9VWFSSzC6ZHQYX6XZyj2Ygwm01Sel+qRvUxKsgIhSTdcuJ4OIqllqiUAVKrnFXNXjDWA+hYSegESPY3SJ8zBvJ6EN7HVK6MaRxv1Y2+x0rkxFpUOtweA26VjWzay+g3KVLDcSJaRCgPHJeiTFPIYgigRVeyC24Og0WUbDyutvGVQ6U1VHkaH66V7qoL76hjqQiyBV9NB9jAiLm9JbGY2Ab50dhlAROhgi8xnE0vUw1nA5qeOEvtG+Ca/HWFvMu8gIpz3rPoUUENYQN9lcYeze9bQn/3W1/RQR9GYu2/flXtj5oyRh/yxGTalqSMrqAY0CGq1IKRbXgrKzDyIGhmFAPSBmvU+IOh3leSZuyQYgLaFEQ6zjO11n2KU+RNb9AAOmPh4Iqo+4nPOdxhEBXSSu8h95g4jvzpIkiKeymerCgZI11ky9Bs9YhcpfoTr1DPZDsQXv+UORP1lM2p+/iv32xasSYc3W0vmJmS/02v3fl35q0S7BDkSFiJlAhDnEJ+UV4pKiPWeVmUiVKV07CQFYF/yCkAimd+qvd8obJFUlpEViX0ByUohS0Jh854NczncxxAogsFJl0HPfF7ABmlNlswK7WHIIQyCKy0YNNhmUZXrvHI7gKEZ7AUCBn2zx7B1p/8M8BoIfb69KBHXZ7DRXpiv1hX6v90FxRS4uofJciCGExJi0VDrqpAGOKI2WOGtrjTgrBLRvCAEecnjUx4n3UDWUD6iemcPJCAObYwtk0aU+7jngBoznxuaZ+itFD3A/HoQLGDMgt9BulIgjJzbJFpGiC9iuE9iuMyCCS6x65Wy8Gh9+vqcdpT/dfiYR1LWZdJ6djKvxoN+/X8sFbnMkhCgh8tpnICuoSFfFS1czEAcFKJyTsZMNSInexNkQ5AS801ueq7+Qly2QaDOCE2sFH6pIaQyfMTKpltSBvs7y8kzqIjURgUWkvE2CpuiSaHVwLrX2CbzLK8CLHShCtPGx8j2XtrZOC6dXaz+XCPqgnfYfmyiUZ0l/707R+RRq+7DfbfWVZIF0BBEEsCpEEl1VeV21CGBTgJcncR4AhGQvcAvurDB8iBBijcEbYB9k4VUH1VlIy60ODSPPQEj9NUdGtarIX8YYmWF/jQhmif6LGOVTuNazjIsLKAHHeLHy15eaW9/iyc9sv5AI+rKdJl8aj6uVPEneNsBQJuQgrhjLH2HwRy2uTO/qAiDsRBu0B7hIGawYHZfOu0hRSNNfuiw1ErGc6+N1EemSOijYEQFFJ2dIQThV3YF75XGqCeQUbPTnPjmIJtiAAV4guBhZ82X+GksS0FZmHFu9VPvEYnvzf/Lk57bXRASN0En736hH9Q0vTd4jG8HKnPURdemuagLikFQhxnXJksdcOy6LAAAvbsYY1RCVkL1QUVnEEXIKSYSglshkANVfRlO1lFEZTnuYNI+P+wvYO2UURwZXkLIt/pSQfcyto4TPEEP7ucrE21OVsd96pbX6BUb7hQ35en1tT2Xnw+ut5a+3YZdb+iKpKatusK/IZinqBWyRKVCtLlfRe5a/tQagAqnshpqMqfTZGVUsvyJBhb0ZUiNCueoz2Otvo7CzyAryIqOrRSDcX5+/ckkaMIEFn6hfsdXT1BaohmktgkiZRLXQm6zU33ymdfmF14rZ6yaCBj44Ob97fWPtmXbWnVZ50iU7M5z2+lZh23+mPAI9KZDyBhAjKIIECCtX8J1Xka3A+OHXeqwKFWOQQ6qKUUy9cdhHO9l1DZVwgeg8bi7r4hlYTwjZOLp5acu6r/AelfBYYImwHWNR6bl6LX6AkiEm8bW3N0QEDX/EjsTNeOkzV/qN32lrJQ/P6bI+EK/v4W8b2Vucs19GW4EVwcJSV6kS4lJ62UeYDXGk+5gJ2QnFBtgASYbyERcA8TykwuTx91AGAdrLfWtQXZLvd5kp76lq2M5C9Q/P9Tb+A0+lWK+rvWEijGY5Up+5fbPZ+L+rg+6sMmY2mbjiidLfaAo7AFGqYxQDUHxW88g44SxEUPXZEUA2gCYfQg/nbdwZG5BDiAEVpkGDeiGZuTyTQx7Dx7KH6GqlYvUJXOCHzi4vq5zzhtovTQTN+lHgeblcf9+VZPCf15LmDu3xcrhVQAeiRBV0HuRFhFKVIoEjAosoijMwjiHZYB/R8OiQIdYpZXotnmr5Ugu9brFXFObQyh3bLW0irpyMo/gjZ1vr0v1tUgqa199+JUS4Zlpvrjp1YydLPu718z/sD3o1qoSufIaUO9YFCIUcB7bRMVzWXxKhEEDxklyjIk4XmNHHhdCcQz4qhkXVHP68VCz+ycLq4sv0+qWQ53vXNPWvq3kHbGKMrYS722nvIPsNDlNzPkTIvxuCaGMIhTKqLaQLnLGcaATVfX4beNEN9jJeJFNcKOfRsZJfOk7acvxEZ1XFdZHoV9r+H5zNSnh7U5I+AAAAAElFTkSuQmCC" @@ -277,13 +237,6 @@ public class MalachiteViewUtils : NSObject { } } - public struct sliderBuilder { - let action: Selector - let dimensions: [ CGFloat ] - let view: UIView - let assign: (UISlider) -> Void - } - public struct tooltipBuilder { let text: String let anchor: CGFloat diff --git a/Malachite/Utilities/ViewUtils/View+Sliders.swift b/Malachite/Utilities/ViewUtils/View+Sliders.swift new file mode 100644 index 0000000..8ad91ab --- /dev/null +++ b/Malachite/Utilities/ViewUtils/View+Sliders.swift @@ -0,0 +1,70 @@ +// +// View+Sliders.swift +// Malachite +// +// Created by Eva Isabella Luna on 10/2/25. +// + +import Foundation +import UIKit + +extension MalachiteViewUtils { + public class Sliders { + /// Function that shows and hides slider controllers in the user interface. + func runControllers(sliderIsShown shown: Bool, optionButton option: UIButton, lockButton button: UIButton, associatedSliderButton sliderButton: UIButton) -> Bool { + var factor = CGFloat() + if shown { + factor = 0 + } else { + factor = -220 + } + + UIView.animate(withDuration: 1) { + option.transform = CGAffineTransform(translationX: factor, y: 0) + sliderButton.transform = CGAffineTransform(translationX: factor, y: 0) + } completion: { _ in + UIView.animate(withDuration: 0.25) { + if !shown { + button.isEnabled = true + button.alpha = 1.0 + } else { + button.isEnabled = false + button.alpha = 0.0 + } + } + } + return !shown + } + + /// Function that sets the lock and unlock state of the bassed slider lock buttons. + func runLocks(lockIsActive locked: Bool, lockButton button: inout UIButton, associatedSlider slider: UISlider, associatedGestureRecognizer gestureRecognizer: UIGestureRecognizer?, viewForRecognizers view: UIView) -> Bool { + if locked { + button.setImage(UIImage(systemName: "lock.open")?.withRenderingMode(.alwaysTemplate), for: .normal) + slider.isEnabled = true + if let validRecognizer = gestureRecognizer { view.addGestureRecognizer(validRecognizer) } + } else { + button.setImage(UIImage(systemName: "lock")?.withRenderingMode(.alwaysTemplate), for: .normal) + slider.isEnabled = false + if let validRecognizer = gestureRecognizer { view.removeGestureRecognizer(validRecognizer) } + } + + return !locked + } + + public struct sliderBuilder { + let action: Selector + let dimensions: [ CGFloat ] + let view: UIView + let assign: (UISlider) -> Void + } + + public struct sliderGroup { + var activator = UIButton() + var sliderShown = Bool() + var lockEnabled = Bool() + var lock = UIButton() + var slider = UISlider() + var container = UIButton() + } + } +} diff --git a/Malachite/Views/CameraView/CameraView+Controls.swift b/Malachite/Views/CameraView/CameraView+Controls.swift index 601087f..0ef03b2 100644 --- a/Malachite/Views/CameraView/CameraView+Controls.swift +++ b/Malachite/Views/CameraView/CameraView+Controls.swift @@ -30,24 +30,15 @@ extension CameraView { /// A `UIButton` that enables the user to change settings within the app. var settings = UIButton() /// A ``sliderGroup`` that enables the user to control manual focus adjustment. - var focus = sliderGroup() + var focus = MalachiteViewUtils.Sliders.sliderGroup() /// A ``sliderGroup`` that enables the user to control manual exposure adjustment. - var exposure = sliderGroup() + var exposure = MalachiteViewUtils.Sliders.sliderGroup() /// A ``sliderGroup`` that enables the user to control the flashlight brightness level. - var flash = sliderGroup() + var flash = MalachiteViewUtils.Sliders.sliderGroup() /// A `UIButton` that contains the blur for the on-screen feedback produced by the auto focus gesture. var continuousFeedback = UIButton() /// The button used to display what camera is in use. var currentCamera = UIButton() - - struct sliderGroup { - var activator = UIButton() - var sliderShown = Bool() - var lockEnabled = Bool() - var lock = UIButton() - var slider = UISlider() - var container = UIButton() - } } struct recognizerGroup { diff --git a/Malachite/Views/CameraView/CameraView.swift b/Malachite/Views/CameraView/CameraView.swift index 8e0ae75..d7b51de 100755 --- a/Malachite/Views/CameraView/CameraView.swift +++ b/Malachite/Views/CameraView/CameraView.swift @@ -296,7 +296,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to toggle the flashlight's on state. @objc func runFlashlightToggle() { - guard var selectedDevice = camera.device else { return } + guard let selectedDevice = camera.device else { return } if selectedDevice.isFlashAvailable && !utilities.preferences.debug.breakApp { utilities.function.toggleFlash(captureDevice: selectedDevice, flashlightButton: self.controlLayer.buttons.flashlight, @@ -377,7 +377,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to zoom in and out with ``zoomRecognizer``. @objc func runZoomController() { - guard var selectedDevice = camera.device else { return } + guard let selectedDevice = camera.device else { return } utilities.function.zoom(sender: self.controlLayer.recognizers.zoom, floater: &zoomFloater, captureDevice: selectedDevice, @@ -387,7 +387,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to autofocus + autoexposure with ``aeafRecognizer``. @objc func runaeafController() { - guard var selectedDevice = camera.device else { return } + guard let selectedDevice = camera.device else { return } utilities.function.pointOfInterestAEAF(sender: self.controlLayer.recognizers.continuous, captureDevice: selectedDevice, button: self.controlLayer.buttons.continuousFeedback, @@ -397,7 +397,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to handle ``exposureSlider`` interaction. @objc func runManualExposureController() { - guard var selectedDevice = camera.device else { return } + guard let selectedDevice = camera.device else { return } if selectedDevice.isExposureModeSupported(.custom) && !utilities.preferences.debug.breakApp { utilities.function.manualExposure(captureDevice: selectedDevice, sender: self.controlLayer.buttons.exposure.slider) @@ -415,7 +415,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa guard let exposure = camera.device?.isExposureModeSupported(.custom) else { return } if exposure && !utilities.preferences.debug.breakApp { self.controlLayer.hideOtherSliders(name: "exposure") - self.controlLayer.buttons.exposure.sliderShown = utilities.views.runSliderControllers(sliderIsShown: self.controlLayer.buttons.exposure.sliderShown, + self.controlLayer.buttons.exposure.sliderShown = utilities.views.sliders.runControllers(sliderIsShown: self.controlLayer.buttons.exposure.sliderShown, optionButton: self.controlLayer.buttons.exposure.activator, lockButton: self.controlLayer.buttons.exposure.lock, associatedSliderButton: self.controlLayer.buttons.exposure.container) @@ -430,12 +430,12 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa @objc func runManualExposureUIHiderWhenUnsupported() { if self.controlLayer.buttons.exposure.sliderShown { - self.controlLayer.buttons.exposure.sliderShown = utilities.views.runSliderControllers(sliderIsShown: self.controlLayer.buttons.exposure.sliderShown, + self.controlLayer.buttons.exposure.sliderShown = utilities.views.sliders.runControllers(sliderIsShown: self.controlLayer.buttons.exposure.sliderShown, optionButton: self.controlLayer.buttons.exposure.activator, lockButton: self.controlLayer.buttons.exposure.lock, associatedSliderButton: self.controlLayer.buttons.exposure.container) } else { - self.controlLayer.buttons.exposure.sliderShown = utilities.views.runSliderControllers(sliderIsShown: true, + self.controlLayer.buttons.exposure.sliderShown = utilities.views.sliders.runControllers(sliderIsShown: true, optionButton: self.controlLayer.buttons.exposure.activator, lockButton: self.controlLayer.buttons.exposure.lock, associatedSliderButton: self.controlLayer.buttons.exposure.container) @@ -444,7 +444,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to lock and unlock the ``exposureSlider``. @objc func runManualExposureLockController() { - self.controlLayer.buttons.exposure.lockEnabled = utilities.views.runLockControllers(lockIsActive: self.controlLayer.buttons.exposure.lockEnabled, + self.controlLayer.buttons.exposure.lockEnabled = utilities.views.sliders.runLocks(lockIsActive: self.controlLayer.buttons.exposure.lockEnabled, lockButton: &self.controlLayer.buttons.exposure.lock, associatedSlider: self.controlLayer.buttons.exposure.slider, associatedGestureRecognizer: nil, @@ -453,7 +453,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to handle ``focusSlider`` interaction. @objc func runManualFocusController() { - guard var selectedDevice = camera.device else { return } + guard let selectedDevice = camera.device else { return } if selectedDevice.isLockingFocusWithCustomLensPositionSupported && !utilities.preferences.debug.breakApp { utilities.function.manualFocus(captureDevice: selectedDevice, sender: self.controlLayer.buttons.focus.slider, @@ -473,7 +473,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa guard let selectedDevice = camera.device else { return } if selectedDevice.isLockingFocusWithCustomLensPositionSupported && !utilities.preferences.debug.breakApp { self.controlLayer.hideOtherSliders(name: "focus") - self.controlLayer.buttons.focus.sliderShown = utilities.views.runSliderControllers(sliderIsShown: self.controlLayer.buttons.focus.sliderShown, + self.controlLayer.buttons.focus.sliderShown = utilities.views.sliders.runControllers(sliderIsShown: self.controlLayer.buttons.focus.sliderShown, optionButton: self.controlLayer.buttons.focus.activator, lockButton: self.controlLayer.buttons.focus.lock, associatedSliderButton: self.controlLayer.buttons.focus.container) @@ -488,12 +488,12 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa @objc func runManualFocusUIHiderWhenUnsupported() { if self.controlLayer.buttons.focus.sliderShown { - self.controlLayer.buttons.focus.sliderShown = utilities.views.runSliderControllers(sliderIsShown: self.controlLayer.buttons.focus.sliderShown, + self.controlLayer.buttons.focus.sliderShown = utilities.views.sliders.runControllers(sliderIsShown: self.controlLayer.buttons.focus.sliderShown, optionButton: self.controlLayer.buttons.focus.activator, lockButton: self.controlLayer.buttons.focus.lock, associatedSliderButton: self.controlLayer.buttons.focus.container) } else { - self.controlLayer.buttons.focus.sliderShown = utilities.views.runSliderControllers(sliderIsShown: true, + self.controlLayer.buttons.focus.sliderShown = utilities.views.sliders.runControllers(sliderIsShown: true, optionButton: self.controlLayer.buttons.focus.activator, lockButton: self.controlLayer.buttons.focus.lock, associatedSliderButton: self.controlLayer.buttons.focus.container) @@ -502,7 +502,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to show and hide the ``focusSliderButton`` and ``focusLockButton``. @objc func runManualFocusLockController() { - self.controlLayer.buttons.focus.lockEnabled = utilities.views.runLockControllers(lockIsActive: self.controlLayer.buttons.focus.lockEnabled, + self.controlLayer.buttons.focus.lockEnabled = utilities.views.sliders.runLocks(lockIsActive: self.controlLayer.buttons.focus.lockEnabled, lockButton: &self.controlLayer.buttons.focus.lock, associatedSlider: self.controlLayer.buttons.focus.slider, associatedGestureRecognizer: self.controlLayer.recognizers.continuous, @@ -511,7 +511,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to handle ``focusSlider`` interaction. @objc func runManualFlashController() { - guard var selectedDevice = camera.device else { return } + guard let selectedDevice = camera.device else { return } if selectedDevice.hasTorch && !utilities.preferences.debug.breakApp { if (self.controlLayer.buttons.flash.slider.value == 0.0 && flashStatus) || (self.controlLayer.buttons.flash.slider.value != 0.0 && !flashStatus) { flashFloater = self.controlLayer.buttons.flash.slider.value @@ -537,7 +537,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa guard let selectedDevice = camera.device else { return } if selectedDevice.hasTorch && !utilities.preferences.debug.breakApp { self.controlLayer.hideOtherSliders(name: "flash") - self.controlLayer.buttons.flash.sliderShown = utilities.views.runSliderControllers(sliderIsShown: self.controlLayer.buttons.flash.sliderShown, + self.controlLayer.buttons.flash.sliderShown = utilities.views.sliders.runControllers(sliderIsShown: self.controlLayer.buttons.flash.sliderShown, optionButton: self.controlLayer.buttons.flash.activator, lockButton: self.controlLayer.buttons.flash.lock, associatedSliderButton: self.controlLayer.buttons.flash.container) @@ -552,12 +552,12 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa @objc func runManualFlashUIHiderWhenUnsupported() { if self.controlLayer.buttons.flash.sliderShown { - self.controlLayer.buttons.flash.sliderShown = utilities.views.runSliderControllers(sliderIsShown: self.controlLayer.buttons.flash.sliderShown, + self.controlLayer.buttons.flash.sliderShown = utilities.views.sliders.runControllers(sliderIsShown: self.controlLayer.buttons.flash.sliderShown, optionButton: self.controlLayer.buttons.flash.activator, lockButton: self.controlLayer.buttons.flash.lock, associatedSliderButton: self.controlLayer.buttons.flash.container) } else { - self.controlLayer.buttons.flash.sliderShown = utilities.views.runSliderControllers(sliderIsShown: true, + self.controlLayer.buttons.flash.sliderShown = utilities.views.sliders.runControllers(sliderIsShown: true, optionButton: self.controlLayer.buttons.flash.activator, lockButton: self.controlLayer.buttons.flash.lock, associatedSliderButton: self.controlLayer.buttons.flash.container) @@ -566,7 +566,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to show and hide the ``focusSliderButton`` and ``focusLockButton``. @objc func runManualFlashLockController() { - self.controlLayer.buttons.flash.lockEnabled = utilities.views.runLockControllers(lockIsActive: self.controlLayer.buttons.flash.lockEnabled, + self.controlLayer.buttons.flash.lockEnabled = utilities.views.sliders.runLocks(lockIsActive: self.controlLayer.buttons.flash.lockEnabled, lockButton: &self.controlLayer.buttons.flash.lock, associatedSlider: self.controlLayer.buttons.flash.slider, associatedGestureRecognizer: self.controlLayer.recognizers.continuous, From c715917b446849567cbaa3dddf5f8f6d5bd2be6d Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Thu, 2 Oct 2025 16:28:39 -0600 Subject: [PATCH 32/66] Streamline runControllers() and runLocks() --- .../Utilities/ViewUtils/View+Sliders.swift | 42 ++++------ .../CameraView/CameraView+Controls.swift | 8 +- Malachite/Views/CameraView/CameraView.swift | 81 +++++-------------- 3 files changed, 38 insertions(+), 93 deletions(-) diff --git a/Malachite/Utilities/ViewUtils/View+Sliders.swift b/Malachite/Utilities/ViewUtils/View+Sliders.swift index 8ad91ab..ec213d0 100644 --- a/Malachite/Utilities/ViewUtils/View+Sliders.swift +++ b/Malachite/Utilities/ViewUtils/View+Sliders.swift @@ -11,44 +11,32 @@ import UIKit extension MalachiteViewUtils { public class Sliders { /// Function that shows and hides slider controllers in the user interface. - func runControllers(sliderIsShown shown: Bool, optionButton option: UIButton, lockButton button: UIButton, associatedSliderButton sliderButton: UIButton) -> Bool { - var factor = CGFloat() - if shown { - factor = 0 - } else { - factor = -220 - } + func runControllers(group: sliderGroup) -> Bool { + let factor = CGFloat(group.sliderShown ? 0 : -220) UIView.animate(withDuration: 1) { - option.transform = CGAffineTransform(translationX: factor, y: 0) - sliderButton.transform = CGAffineTransform(translationX: factor, y: 0) + group.activator.transform = CGAffineTransform(translationX: factor, y: 0) + group.container.transform = CGAffineTransform(translationX: factor, y: 0) } completion: { _ in UIView.animate(withDuration: 0.25) { - if !shown { - button.isEnabled = true - button.alpha = 1.0 - } else { - button.isEnabled = false - button.alpha = 0.0 - } + group.lock.isEnabled = group.sliderShown ? false : true + group.lock.alpha = group.sliderShown ? 0.0 : 1.0 } } - return !shown + + return !group.sliderShown } /// Function that sets the lock and unlock state of the bassed slider lock buttons. - func runLocks(lockIsActive locked: Bool, lockButton button: inout UIButton, associatedSlider slider: UISlider, associatedGestureRecognizer gestureRecognizer: UIGestureRecognizer?, viewForRecognizers view: UIView) -> Bool { - if locked { - button.setImage(UIImage(systemName: "lock.open")?.withRenderingMode(.alwaysTemplate), for: .normal) - slider.isEnabled = true - if let validRecognizer = gestureRecognizer { view.addGestureRecognizer(validRecognizer) } - } else { - button.setImage(UIImage(systemName: "lock")?.withRenderingMode(.alwaysTemplate), for: .normal) - slider.isEnabled = false - if let validRecognizer = gestureRecognizer { view.removeGestureRecognizer(validRecognizer) } + func runLocks(group: sliderGroup, associatedGestureRecognizer gestureRecognizer: UIGestureRecognizer?, viewForRecognizers view: UIView) -> Bool { + group.lock.setImage(UIImage(systemName: (group.lockEnabled ? "lock.open" : "lock"))?.withRenderingMode(.alwaysTemplate), for: .normal) + group.slider.isEnabled = group.lockEnabled ? true : false + if let validRecognizer = gestureRecognizer { + if group.lockEnabled { view.addGestureRecognizer(validRecognizer) } + else { view.removeGestureRecognizer(validRecognizer) } } - return !locked + return !group.lockEnabled } public struct sliderBuilder { diff --git a/Malachite/Views/CameraView/CameraView+Controls.swift b/Malachite/Views/CameraView/CameraView+Controls.swift index 0ef03b2..14abe9e 100644 --- a/Malachite/Views/CameraView/CameraView+Controls.swift +++ b/Malachite/Views/CameraView/CameraView+Controls.swift @@ -127,10 +127,10 @@ extension CameraView { Creates, adds, and constrains the ``UISlider`` objects that are managed by this control layer. */ func initSliders() { - let sliderConfigs: [MalachiteViewUtils.sliderBuilder] = [ - MalachiteViewUtils.sliderBuilder(action: #selector(delegate.runManualFocusController), dimensions: [ 180.0, 80.0 ], view: self.buttons.focus.container, assign: { [self] slider in self.buttons.focus.slider = slider } ), - MalachiteViewUtils.sliderBuilder(action: #selector(delegate.runManualExposureController), dimensions: [ 180.0, 80.0 ], view: self.buttons.exposure.container, assign: { [self] slider in self.buttons.exposure.slider = slider } ), - MalachiteViewUtils.sliderBuilder(action: #selector(delegate.runManualFlashController), dimensions: [ 180.0, 80.0 ], view: self.buttons.flash.container, assign: { [self] slider in self.buttons.flash.slider = slider } ), + let sliderConfigs: [MalachiteViewUtils.Sliders.sliderBuilder] = [ + MalachiteViewUtils.Sliders.sliderBuilder(action: #selector(delegate.runManualFocusController), dimensions: [ 180.0, 80.0 ], view: self.buttons.focus.container, assign: { [self] slider in self.buttons.focus.slider = slider } ), + MalachiteViewUtils.Sliders.sliderBuilder(action: #selector(delegate.runManualExposureController), dimensions: [ 180.0, 80.0 ], view: self.buttons.exposure.container, assign: { [self] slider in self.buttons.exposure.slider = slider } ), + MalachiteViewUtils.Sliders.sliderBuilder(action: #selector(delegate.runManualFlashController), dimensions: [ 180.0, 80.0 ], view: self.buttons.flash.container, assign: { [self] slider in self.buttons.flash.slider = slider } ), ] for config in sliderConfigs { diff --git a/Malachite/Views/CameraView/CameraView.swift b/Malachite/Views/CameraView/CameraView.swift index d7b51de..c5feb1d 100755 --- a/Malachite/Views/CameraView/CameraView.swift +++ b/Malachite/Views/CameraView/CameraView.swift @@ -415,10 +415,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa guard let exposure = camera.device?.isExposureModeSupported(.custom) else { return } if exposure && !utilities.preferences.debug.breakApp { self.controlLayer.hideOtherSliders(name: "exposure") - self.controlLayer.buttons.exposure.sliderShown = utilities.views.sliders.runControllers(sliderIsShown: self.controlLayer.buttons.exposure.sliderShown, - optionButton: self.controlLayer.buttons.exposure.activator, - lockButton: self.controlLayer.buttons.exposure.lock, - associatedSliderButton: self.controlLayer.buttons.exposure.container) + self.controlLayer.buttons.exposure.sliderShown = utilities.views.sliders.runControllers(group: self.controlLayer.buttons.exposure) } else { utilities.debugNSLog("[Manual Focus] Current camera is not capable of adjusting exposure") let alert = utilities.views.createAlertController(title: "alert.title.exposure", message: "alert.detail.exposure", button: self.controlLayer.buttons.exposure.activator, defaultSet: true, action: { _ in @@ -429,26 +426,14 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa } @objc func runManualExposureUIHiderWhenUnsupported() { - if self.controlLayer.buttons.exposure.sliderShown { - self.controlLayer.buttons.exposure.sliderShown = utilities.views.sliders.runControllers(sliderIsShown: self.controlLayer.buttons.exposure.sliderShown, - optionButton: self.controlLayer.buttons.exposure.activator, - lockButton: self.controlLayer.buttons.exposure.lock, - associatedSliderButton: self.controlLayer.buttons.exposure.container) - } else { - self.controlLayer.buttons.exposure.sliderShown = utilities.views.sliders.runControllers(sliderIsShown: true, - optionButton: self.controlLayer.buttons.exposure.activator, - lockButton: self.controlLayer.buttons.exposure.lock, - associatedSliderButton: self.controlLayer.buttons.exposure.container) - } + self.controlLayer.buttons.exposure.sliderShown = utilities.views.sliders.runControllers(group: self.controlLayer.buttons.exposure) } /// Function to lock and unlock the ``exposureSlider``. @objc func runManualExposureLockController() { - self.controlLayer.buttons.exposure.lockEnabled = utilities.views.sliders.runLocks(lockIsActive: self.controlLayer.buttons.exposure.lockEnabled, - lockButton: &self.controlLayer.buttons.exposure.lock, - associatedSlider: self.controlLayer.buttons.exposure.slider, - associatedGestureRecognizer: nil, - viewForRecognizers: self.view) + self.controlLayer.buttons.exposure.lockEnabled = utilities.views.sliders.runLocks(group: self.controlLayer.buttons.exposure, + associatedGestureRecognizer: nil, + viewForRecognizers: self.view) } /// Function to handle ``focusSlider`` interaction. @@ -473,10 +458,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa guard let selectedDevice = camera.device else { return } if selectedDevice.isLockingFocusWithCustomLensPositionSupported && !utilities.preferences.debug.breakApp { self.controlLayer.hideOtherSliders(name: "focus") - self.controlLayer.buttons.focus.sliderShown = utilities.views.sliders.runControllers(sliderIsShown: self.controlLayer.buttons.focus.sliderShown, - optionButton: self.controlLayer.buttons.focus.activator, - lockButton: self.controlLayer.buttons.focus.lock, - associatedSliderButton: self.controlLayer.buttons.focus.container) + self.controlLayer.buttons.focus.sliderShown = utilities.views.sliders.runControllers(group: self.controlLayer.buttons.focus) } else { utilities.debugNSLog("[Manual Focus] Current camera is not capable of adjusting focus") let alert = utilities.views.createAlertController(title: "alert.title.focus", message: "alert.detail.focus", button: self.controlLayer.buttons.focus.activator, defaultSet: true, action: { _ in @@ -487,26 +469,14 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa } @objc func runManualFocusUIHiderWhenUnsupported() { - if self.controlLayer.buttons.focus.sliderShown { - self.controlLayer.buttons.focus.sliderShown = utilities.views.sliders.runControllers(sliderIsShown: self.controlLayer.buttons.focus.sliderShown, - optionButton: self.controlLayer.buttons.focus.activator, - lockButton: self.controlLayer.buttons.focus.lock, - associatedSliderButton: self.controlLayer.buttons.focus.container) - } else { - self.controlLayer.buttons.focus.sliderShown = utilities.views.sliders.runControllers(sliderIsShown: true, - optionButton: self.controlLayer.buttons.focus.activator, - lockButton: self.controlLayer.buttons.focus.lock, - associatedSliderButton: self.controlLayer.buttons.focus.container) - } + self.controlLayer.buttons.focus.sliderShown = utilities.views.sliders.runControllers(group: self.controlLayer.buttons.focus) } /// Function to show and hide the ``focusSliderButton`` and ``focusLockButton``. @objc func runManualFocusLockController() { - self.controlLayer.buttons.focus.lockEnabled = utilities.views.sliders.runLocks(lockIsActive: self.controlLayer.buttons.focus.lockEnabled, - lockButton: &self.controlLayer.buttons.focus.lock, - associatedSlider: self.controlLayer.buttons.focus.slider, - associatedGestureRecognizer: self.controlLayer.recognizers.continuous, - viewForRecognizers: self.view) + self.controlLayer.buttons.focus.lockEnabled = utilities.views.sliders.runLocks(group: self.controlLayer.buttons.focus, + associatedGestureRecognizer: self.controlLayer.recognizers.continuous, + viewForRecognizers: self.view) } /// Function to handle ``focusSlider`` interaction. @@ -537,10 +507,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa guard let selectedDevice = camera.device else { return } if selectedDevice.hasTorch && !utilities.preferences.debug.breakApp { self.controlLayer.hideOtherSliders(name: "flash") - self.controlLayer.buttons.flash.sliderShown = utilities.views.sliders.runControllers(sliderIsShown: self.controlLayer.buttons.flash.sliderShown, - optionButton: self.controlLayer.buttons.flash.activator, - lockButton: self.controlLayer.buttons.flash.lock, - associatedSliderButton: self.controlLayer.buttons.flash.container) + self.controlLayer.buttons.flash.sliderShown = utilities.views.sliders.runControllers(group: self.controlLayer.buttons.flash) } else { utilities.debugNSLog("[Flashlight Level] Device does not have a flashlight") let alert = utilities.views.createAlertController(title: "alert.title.flash", message: "alert.detail.flash", button: self.controlLayer.buttons.flash.activator, defaultSet: true, action: { _ in @@ -551,26 +518,14 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa } @objc func runManualFlashUIHiderWhenUnsupported() { - if self.controlLayer.buttons.flash.sliderShown { - self.controlLayer.buttons.flash.sliderShown = utilities.views.sliders.runControllers(sliderIsShown: self.controlLayer.buttons.flash.sliderShown, - optionButton: self.controlLayer.buttons.flash.activator, - lockButton: self.controlLayer.buttons.flash.lock, - associatedSliderButton: self.controlLayer.buttons.flash.container) - } else { - self.controlLayer.buttons.flash.sliderShown = utilities.views.sliders.runControllers(sliderIsShown: true, - optionButton: self.controlLayer.buttons.flash.activator, - lockButton: self.controlLayer.buttons.flash.lock, - associatedSliderButton: self.controlLayer.buttons.flash.container) - } + self.controlLayer.buttons.flash.sliderShown = utilities.views.sliders.runControllers(group: self.controlLayer.buttons.flash) } /// Function to show and hide the ``focusSliderButton`` and ``focusLockButton``. @objc func runManualFlashLockController() { - self.controlLayer.buttons.flash.lockEnabled = utilities.views.sliders.runLocks(lockIsActive: self.controlLayer.buttons.flash.lockEnabled, - lockButton: &self.controlLayer.buttons.flash.lock, - associatedSlider: self.controlLayer.buttons.flash.slider, - associatedGestureRecognizer: self.controlLayer.recognizers.continuous, - viewForRecognizers: self.view) + self.controlLayer.buttons.flash.lockEnabled = utilities.views.sliders.runLocks(group: self.controlLayer.buttons.flash, + associatedGestureRecognizer: nil, + viewForRecognizers: self.view) } /// Function to handle device rotation. @@ -580,10 +535,12 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa self.controlLayer.buttons.flashlight, self.controlLayer.buttons.capture, self.controlLayer.buttons.settings, + self.controlLayer.buttons.exposure.activator, + self.controlLayer.buttons.exposure.lock, self.controlLayer.buttons.focus.activator, self.controlLayer.buttons.focus.lock, - self.controlLayer.buttons.exposure.activator, - self.controlLayer.buttons.exposure.lock ]) + self.controlLayer.buttons.flash.activator, + self.controlLayer.buttons.flash.lock ]) } } From ee2172e36d22cd72e3dc9f3b65c31db5b9619c10 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Mon, 6 Oct 2025 14:51:03 -0600 Subject: [PATCH 33/66] Refactor sliders, remove LaunchScreen.storyboard, fix bugs Well, bug. Fixed the issue on <=iOS 17 where the buttons would fail to switch --- Malachite.xcodeproj/project.pbxproj | 27 +++++--------- Malachite/Base.lproj/LaunchScreen.storyboard | 25 ------------- .../ViewUtils/MalachiteViewUtils.swift | 3 +- .../Utilities/ViewUtils/View+Sliders.swift | 19 ++++++++-- .../CameraView/CameraView+Controls.swift | 35 ++++++++++++++----- Malachite/Views/CameraView/CameraView.swift | 24 +++++++------ .../PhotoPreviewView/PhotoPreviewView.swift | 2 ++ 7 files changed, 69 insertions(+), 66 deletions(-) delete mode 100755 Malachite/Base.lproj/LaunchScreen.storyboard diff --git a/Malachite.xcodeproj/project.pbxproj b/Malachite.xcodeproj/project.pbxproj index 3ed2aaf..c16a849 100755 --- a/Malachite.xcodeproj/project.pbxproj +++ b/Malachite.xcodeproj/project.pbxproj @@ -34,7 +34,6 @@ 784EDDD62E5EE52600DFF370 /* PhotoPreviewView+Controls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 784EDDD32E5EE52100DFF370 /* PhotoPreviewView+Controls.swift */; }; 785F084D2B12D41100244EB4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 785F084C2B12D41100244EB4 /* AppDelegate.swift */; }; 785F084F2B12D41100244EB4 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 785F084E2B12D41100244EB4 /* SceneDelegate.swift */; }; - 785F08592B12D41300244EB4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 785F08572B12D41300244EB4 /* LaunchScreen.storyboard */; }; 7866F5CE2E6299A4009AC9BF /* Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7866F5CD2E6299A0009AC9BF /* Camera.swift */; }; 7866F5CF2E6299A4009AC9BF /* Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7866F5CD2E6299A0009AC9BF /* Camera.swift */; }; 7866F5D02E6299A4009AC9BF /* Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7866F5CD2E6299A0009AC9BF /* Camera.swift */; }; @@ -297,7 +296,6 @@ 785F08492B12D41100244EB4 /* Malachite.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Malachite.app; sourceTree = BUILT_PRODUCTS_DIR; }; 785F084C2B12D41100244EB4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 785F084E2B12D41100244EB4 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - 785F08582B12D41300244EB4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 785F085A2B12D41300244EB4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7866F5CD2E6299A0009AC9BF /* Camera.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Camera.swift; sourceTree = ""; }; 7866F5D12E62A591009AC9BF /* CameraView+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraView+Notifications.swift"; sourceTree = ""; }; @@ -473,7 +471,6 @@ 786DAE4B2E4F28B100BE3137 /* Utilities */, 786DAE772E4F28B900BE3137 /* CaptureBundle */, 786DAE6C2E4F28B700BE3137 /* WidgetBundle */, - 785F08572B12D41300244EB4 /* LaunchScreen.storyboard */, 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */, 785F084C2B12D41100244EB4 /* AppDelegate.swift */, 785F084E2B12D41100244EB4 /* SceneDelegate.swift */, @@ -842,7 +839,6 @@ isa = PBXResourcesBuildPhase; files = ( 78EC5D5F2E4EC110005044E2 /* Localizable.xcstrings in Resources */, - 785F08592B12D41300244EB4 /* LaunchScreen.storyboard in Resources */, ); }; 78917F572B99AE72005E10FA /* Resources */ = { @@ -956,6 +952,9 @@ "$PLISTBUDDY -c \"Set :CFBuildHost $CFBUILDHOST\" \"${INFOPLIST}\"", "", "", + "", + "", + "", ); }; 78EC5D602E4EC1AD005044E2 /* Embed build information */ = { @@ -1049,6 +1048,9 @@ "$PLISTBUDDY -c \"Set :CFBuildHost $CFBUILDHOST\" \"${INFOPLIST}\"", "", "", + "", + "", + "", ); }; /* End PBXShellScriptBuildPhase section */ @@ -1290,17 +1292,6 @@ }; /* End PBXTargetDependency section */ -/* Begin PBXVariantGroup section */ - 785F08572B12D41300244EB4 /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 785F08582B12D41300244EB4 /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - /* Begin XCBuildConfiguration section */ 7837C1122E34C45B009396B0 /* Debug configuration for PBXNativeTarget "WidgetBundleWatch" */ = { isa = XCBuildConfiguration; @@ -1592,7 +1583,7 @@ INFOPLIST_KEY_UIMainStoryboardFile = ""; INFOPLIST_KEY_UIRequiresFullScreen = NO; INFOPLIST_KEY_UIStatusBarHidden = YES; - INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDefault; + INFOPLIST_KEY_UIStatusBarStyle = ""; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_KEY_UIUserInterfaceStyle = Automatic; @@ -1649,7 +1640,7 @@ INFOPLIST_KEY_UIMainStoryboardFile = ""; INFOPLIST_KEY_UIRequiresFullScreen = NO; INFOPLIST_KEY_UIStatusBarHidden = YES; - INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDefault; + INFOPLIST_KEY_UIStatusBarStyle = ""; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_KEY_UIUserInterfaceStyle = Automatic; @@ -2103,7 +2094,7 @@ INFOPLIST_KEY_UIMainStoryboardFile = ""; INFOPLIST_KEY_UIRequiresFullScreen = NO; INFOPLIST_KEY_UIStatusBarHidden = YES; - INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDefault; + INFOPLIST_KEY_UIStatusBarStyle = ""; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_KEY_UIUserInterfaceStyle = Automatic; diff --git a/Malachite/Base.lproj/LaunchScreen.storyboard b/Malachite/Base.lproj/LaunchScreen.storyboard deleted file mode 100755 index 865e932..0000000 --- a/Malachite/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Malachite/Utilities/ViewUtils/MalachiteViewUtils.swift b/Malachite/Utilities/ViewUtils/MalachiteViewUtils.swift index 79dce02..8342e4c 100755 --- a/Malachite/Utilities/ViewUtils/MalachiteViewUtils.swift +++ b/Malachite/Utilities/ViewUtils/MalachiteViewUtils.swift @@ -28,7 +28,8 @@ public class MalachiteViewUtils : NSObject { button.configuration = .glass() } else { button.insertSubview(returnProperEffectView(viewForBounds: view, effect: UIBlurEffect(style: .systemThinMaterial)), at: 0) - button.tintColor = UIColor(.primary) + if #available(iOS 18.0, *) { button.tintColor = UIColor(.primary) } + else { button.tintColor = UIColor(.white) } } button.isPointerInteractionEnabled = true diff --git a/Malachite/Utilities/ViewUtils/View+Sliders.swift b/Malachite/Utilities/ViewUtils/View+Sliders.swift index ec213d0..c4e9c01 100644 --- a/Malachite/Utilities/ViewUtils/View+Sliders.swift +++ b/Malachite/Utilities/ViewUtils/View+Sliders.swift @@ -10,8 +10,8 @@ import UIKit extension MalachiteViewUtils { public class Sliders { - /// Function that shows and hides slider controllers in the user interface. - func runControllers(group: sliderGroup) -> Bool { + /// Shows and hides slider controllers in the user interface. + func runHiders(group: sliderGroup) -> Bool { let factor = CGFloat(group.sliderShown ? 0 : -220) UIView.animate(withDuration: 1) { @@ -27,7 +27,7 @@ extension MalachiteViewUtils { return !group.sliderShown } - /// Function that sets the lock and unlock state of the bassed slider lock buttons. + /// Sets the lock and unlock state of the passed slider lock buttons. func runLocks(group: sliderGroup, associatedGestureRecognizer gestureRecognizer: UIGestureRecognizer?, viewForRecognizers view: UIView) -> Bool { group.lock.setImage(UIImage(systemName: (group.lockEnabled ? "lock.open" : "lock"))?.withRenderingMode(.alwaysTemplate), for: .normal) group.slider.isEnabled = group.lockEnabled ? true : false @@ -39,6 +39,18 @@ extension MalachiteViewUtils { return !group.lockEnabled } + /// Runs functions or returns an alert controller for the passed ``sliderGroup``. + func runControllers(group: sliderGroup, condition: Bool, action: @escaping () -> Void) -> UIAlertController? { + if condition && !MalachitePreferencesUtils.shared.preferences.debug.breakApp { action() + } else { + return MalachiteViewUtils().createAlertController(title: "alert.title.\(group.name)", message: "alert.detail.\(group.name)", button: group.activator, defaultSet: true, action: { _ in + MalachiteClassesObject().debugNSLog("[Alerts] \(group.name) dialog has been dismissed") + }) + } + + return nil + } + public struct sliderBuilder { let action: Selector let dimensions: [ CGFloat ] @@ -47,6 +59,7 @@ extension MalachiteViewUtils { } public struct sliderGroup { + var name = String() var activator = UIButton() var sliderShown = Bool() var lockEnabled = Bool() diff --git a/Malachite/Views/CameraView/CameraView+Controls.swift b/Malachite/Views/CameraView/CameraView+Controls.swift index 14abe9e..5e0b5a4 100644 --- a/Malachite/Views/CameraView/CameraView+Controls.swift +++ b/Malachite/Views/CameraView/CameraView+Controls.swift @@ -57,6 +57,8 @@ extension CameraView { var focus = UILabel() /// The title for the exposure slider. var exposure = UILabel() + /// The title for the flash slider. + var flash = UILabel() } var buttons = buttonGroup() @@ -128,9 +130,9 @@ extension CameraView { */ func initSliders() { let sliderConfigs: [MalachiteViewUtils.Sliders.sliderBuilder] = [ - MalachiteViewUtils.Sliders.sliderBuilder(action: #selector(delegate.runManualFocusController), dimensions: [ 180.0, 80.0 ], view: self.buttons.focus.container, assign: { [self] slider in self.buttons.focus.slider = slider } ), - MalachiteViewUtils.Sliders.sliderBuilder(action: #selector(delegate.runManualExposureController), dimensions: [ 180.0, 80.0 ], view: self.buttons.exposure.container, assign: { [self] slider in self.buttons.exposure.slider = slider } ), - MalachiteViewUtils.Sliders.sliderBuilder(action: #selector(delegate.runManualFlashController), dimensions: [ 180.0, 80.0 ], view: self.buttons.flash.container, assign: { [self] slider in self.buttons.flash.slider = slider } ), + MalachiteViewUtils.Sliders.sliderBuilder(action: #selector(self.runManualFocusController), dimensions: [ 180.0, 80.0 ], view: self.buttons.focus.container, assign: { [self] slider in self.buttons.focus.slider = slider; self.buttons.focus.name = "focus" } ), + MalachiteViewUtils.Sliders.sliderBuilder(action: #selector(delegate.runManualExposureController), dimensions: [ 180.0, 80.0 ], view: self.buttons.exposure.container, assign: { [self] slider in self.buttons.exposure.slider = slider; self.buttons.exposure.name = "exposure" } ), + MalachiteViewUtils.Sliders.sliderBuilder(action: #selector(delegate.runManualFlashController), dimensions: [ 180.0, 80.0 ], view: self.buttons.flash.container, assign: { [self] slider in self.buttons.flash.slider = slider; self.buttons.flash.name = "flash" } ), ] for config in sliderConfigs { @@ -179,6 +181,7 @@ extension CameraView { let tooltipConfigs: [ MalachiteViewUtils.tooltipBuilder ] = [ MalachiteViewUtils.tooltipBuilder(text: "uibutton.focus.title", anchor: 10, assign: { [self] label in self.titles.focus = label } ), MalachiteViewUtils.tooltipBuilder(text: "uibutton.exposure.title", anchor: 80, assign: { [self] label in self.titles.exposure = label } ), + MalachiteViewUtils.tooltipBuilder(text: "uibutton.flash.title", anchor: 80, assign: { [self] label in self.titles.flash = label } ), ] var labels: [ UILabel ] = [] @@ -209,10 +212,26 @@ extension CameraView { // MARK: ControlLayer - Slider Controls extension CameraView.ControlLayer { - func hideOtherSliders(name: String) { - if name != "focus" && self.buttons.focus.sliderShown { delegate.runManualFocusUIHider() } - if name != "exposure" && self.buttons.exposure.sliderShown { delegate.runManualExposureUIHider() } - if name != "flash" && self.buttons.flash.sliderShown { delegate.runManualFlashUIHider() } + func hideOtherSliders(group: MalachiteViewUtils.Sliders.sliderGroup) { + if group.name != "focus" && self.buttons.focus.sliderShown { delegate.runManualFocusUIHider() } + if group.name != "exposure" && self.buttons.exposure.sliderShown { delegate.runManualExposureUIHider() } + if group.name != "flash" && self.buttons.flash.sliderShown { delegate.runManualFlashUIHider() } + } + + /// Function to handle ``focusSlider`` interaction. + @objc func runManualFocusController() { + guard let selectedDevice = delegate.camera.device else { return } + guard selectedDevice.isLockingFocusWithCustomLensPositionSupported else { + self.buttons.focus.sliderShown = delegate.utilities.views.sliders.runHiders(group: self.buttons.focus) + return + } + if let alert = delegate.utilities.views.sliders.runControllers(group: self.buttons.focus, condition: selectedDevice.isLockingFocusWithCustomLensPositionSupported, action: { [self] in + delegate.utilities.function.manualFocus(captureDevice: selectedDevice, + sender: self.buttons.focus.slider, + floater: delegate.focusFloater ?? self.buttons.focus.slider.value) + }) { + //delegate.present(alert, animated: true, completion: nil) + } } } @@ -259,7 +278,7 @@ extension CameraView.ControlLayer { let focusSlider = AVCaptureSlider("Focus", symbolName: "scope", in: 0.0...1.0) focusSlider.setActionQueue(delegate.utilities.sessionQueue) { [self] position in delegate.focusFloater = position - delegate.runManualFocusController() + self.runManualFocusController() delegate.focusFloater = nil } controls.append(focusSlider) diff --git a/Malachite/Views/CameraView/CameraView.swift b/Malachite/Views/CameraView/CameraView.swift index c5feb1d..08bfe58 100755 --- a/Malachite/Views/CameraView/CameraView.swift +++ b/Malachite/Views/CameraView/CameraView.swift @@ -56,6 +56,8 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa */ override func viewDidLoad() { super.viewDidLoad() + + if #unavailable(iOS 18.0) { overrideUserInterfaceStyle = .dark } self.view.backgroundColor = .black self.notifications = CameraView.Notifications(delegate: self) self.notifications.bringUpNotifications() @@ -414,8 +416,8 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa @objc func runManualExposureUIHider() { guard let exposure = camera.device?.isExposureModeSupported(.custom) else { return } if exposure && !utilities.preferences.debug.breakApp { - self.controlLayer.hideOtherSliders(name: "exposure") - self.controlLayer.buttons.exposure.sliderShown = utilities.views.sliders.runControllers(group: self.controlLayer.buttons.exposure) + self.controlLayer.hideOtherSliders(group: self.controlLayer.buttons.exposure) + self.controlLayer.buttons.exposure.sliderShown = utilities.views.sliders.runHiders(group: self.controlLayer.buttons.exposure) } else { utilities.debugNSLog("[Manual Focus] Current camera is not capable of adjusting exposure") let alert = utilities.views.createAlertController(title: "alert.title.exposure", message: "alert.detail.exposure", button: self.controlLayer.buttons.exposure.activator, defaultSet: true, action: { _ in @@ -426,7 +428,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa } @objc func runManualExposureUIHiderWhenUnsupported() { - self.controlLayer.buttons.exposure.sliderShown = utilities.views.sliders.runControllers(group: self.controlLayer.buttons.exposure) + self.controlLayer.buttons.exposure.sliderShown = utilities.views.sliders.runHiders(group: self.controlLayer.buttons.exposure) } /// Function to lock and unlock the ``exposureSlider``. @@ -457,8 +459,8 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa @objc func runManualFocusUIHider() { guard let selectedDevice = camera.device else { return } if selectedDevice.isLockingFocusWithCustomLensPositionSupported && !utilities.preferences.debug.breakApp { - self.controlLayer.hideOtherSliders(name: "focus") - self.controlLayer.buttons.focus.sliderShown = utilities.views.sliders.runControllers(group: self.controlLayer.buttons.focus) + self.controlLayer.hideOtherSliders(group: self.controlLayer.buttons.focus) + self.controlLayer.buttons.focus.sliderShown = utilities.views.sliders.runHiders(group: self.controlLayer.buttons.focus) } else { utilities.debugNSLog("[Manual Focus] Current camera is not capable of adjusting focus") let alert = utilities.views.createAlertController(title: "alert.title.focus", message: "alert.detail.focus", button: self.controlLayer.buttons.focus.activator, defaultSet: true, action: { _ in @@ -469,7 +471,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa } @objc func runManualFocusUIHiderWhenUnsupported() { - self.controlLayer.buttons.focus.sliderShown = utilities.views.sliders.runControllers(group: self.controlLayer.buttons.focus) + self.controlLayer.buttons.focus.sliderShown = utilities.views.sliders.runHiders(group: self.controlLayer.buttons.focus) } /// Function to show and hide the ``focusSliderButton`` and ``focusLockButton``. @@ -506,8 +508,8 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa @objc func runManualFlashUIHider() { guard let selectedDevice = camera.device else { return } if selectedDevice.hasTorch && !utilities.preferences.debug.breakApp { - self.controlLayer.hideOtherSliders(name: "flash") - self.controlLayer.buttons.flash.sliderShown = utilities.views.sliders.runControllers(group: self.controlLayer.buttons.flash) + self.controlLayer.hideOtherSliders(group: self.controlLayer.buttons.flash) + self.controlLayer.buttons.flash.sliderShown = utilities.views.sliders.runHiders(group: self.controlLayer.buttons.flash) } else { utilities.debugNSLog("[Flashlight Level] Device does not have a flashlight") let alert = utilities.views.createAlertController(title: "alert.title.flash", message: "alert.detail.flash", button: self.controlLayer.buttons.flash.activator, defaultSet: true, action: { _ in @@ -518,7 +520,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa } @objc func runManualFlashUIHiderWhenUnsupported() { - self.controlLayer.buttons.flash.sliderShown = utilities.views.sliders.runControllers(group: self.controlLayer.buttons.flash) + self.controlLayer.buttons.flash.sliderShown = utilities.views.sliders.runHiders(group: self.controlLayer.buttons.flash) } /// Function to show and hide the ``focusSliderButton`` and ``focusLockButton``. @@ -535,10 +537,10 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa self.controlLayer.buttons.flashlight, self.controlLayer.buttons.capture, self.controlLayer.buttons.settings, - self.controlLayer.buttons.exposure.activator, - self.controlLayer.buttons.exposure.lock, self.controlLayer.buttons.focus.activator, self.controlLayer.buttons.focus.lock, + self.controlLayer.buttons.exposure.activator, + self.controlLayer.buttons.exposure.lock, self.controlLayer.buttons.flash.activator, self.controlLayer.buttons.flash.lock ]) } diff --git a/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift b/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift index bf1b034..05720a9 100755 --- a/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift +++ b/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift @@ -80,6 +80,8 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { self.controls = PhotoPreviewView.controls(delegate: self) super.viewDidLoad() + + if #unavailable(iOS 18.0) { overrideUserInterfaceStyle = .dark } self.view.backgroundColor = .red var rotation = -1.0 From e4b62efd7452c0b9d90ded018753ed866a399803 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Tue, 7 Oct 2025 23:53:54 -0600 Subject: [PATCH 34/66] Replace UILaunchStoryboardName with UILaunchScreen --- Malachite.xcodeproj/project.pbxproj | 5 ++--- Malachite/Info.plist | 5 +++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Malachite.xcodeproj/project.pbxproj b/Malachite.xcodeproj/project.pbxproj index c16a849..22f9814 100755 --- a/Malachite.xcodeproj/project.pbxproj +++ b/Malachite.xcodeproj/project.pbxproj @@ -955,6 +955,7 @@ "", "", "", + "", ); }; 78EC5D602E4EC1AD005044E2 /* Embed build information */ = { @@ -1051,6 +1052,7 @@ "", "", "", + "", ); }; /* End PBXShellScriptBuildPhase section */ @@ -1579,7 +1581,6 @@ INFOPLIST_KEY_NSCameraUsageDescription = "Malachite uses the rear camera system on iPhone to present your magnified view."; INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Malachite uses write access to your library in order to save photos."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIMainStoryboardFile = ""; INFOPLIST_KEY_UIRequiresFullScreen = NO; INFOPLIST_KEY_UIStatusBarHidden = YES; @@ -1636,7 +1637,6 @@ INFOPLIST_KEY_NSCameraUsageDescription = "Malachite uses the rear camera system on iPhone to present your magnified view."; INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Malachite uses write access to your library in order to save photos."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIMainStoryboardFile = ""; INFOPLIST_KEY_UIRequiresFullScreen = NO; INFOPLIST_KEY_UIStatusBarHidden = YES; @@ -2090,7 +2090,6 @@ INFOPLIST_KEY_NSCameraUsageDescription = "Malachite uses the rear camera system on iPhone to present your magnified view."; INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Malachite uses write access to your library in order to save photos."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIMainStoryboardFile = ""; INFOPLIST_KEY_UIRequiresFullScreen = NO; INFOPLIST_KEY_UIStatusBarHidden = YES; diff --git a/Malachite/Info.plist b/Malachite/Info.plist index 8a3d3d1..3ef7b68 100755 --- a/Malachite/Info.plist +++ b/Malachite/Info.plist @@ -36,5 +36,10 @@ UIFileSharingEnabled + UILaunchScreen + + UIImageName + + From c2addd2862714867e0e72e9c09223ab79917a7fa Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Tue, 21 Oct 2025 22:55:17 -0600 Subject: [PATCH 35/66] Fix rotation bug introduced by code streamlining Also fix a crash that should theoretically have never been possible to reach but would still have crashed the app. --- Malachite.xcodeproj/project.pbxproj | 5 +++++ .../Views/PhotoPreviewView/PhotoPreviewView.swift | 12 +++--------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Malachite.xcodeproj/project.pbxproj b/Malachite.xcodeproj/project.pbxproj index 22f9814..3a81f08 100755 --- a/Malachite.xcodeproj/project.pbxproj +++ b/Malachite.xcodeproj/project.pbxproj @@ -956,6 +956,7 @@ "", "", "", + "", ); }; 78EC5D602E4EC1AD005044E2 /* Embed build information */ = { @@ -1053,6 +1054,7 @@ "", "", "", + "", ); }; /* End PBXShellScriptBuildPhase section */ @@ -1873,6 +1875,7 @@ }; 78B72BB32E332830002E2D4E /* Debug configuration for PBXNativeTarget "MalachiteWatch" */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -1914,6 +1917,7 @@ }; 78B72BB42E332830002E2D4E /* Internal configuration for PBXNativeTarget "MalachiteWatch" */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -1955,6 +1959,7 @@ }; 78B72BB52E332830002E2D4E /* Release configuration for PBXNativeTarget "MalachiteWatch" */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; diff --git a/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift b/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift index 05720a9..2bc97e0 100755 --- a/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift +++ b/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift @@ -213,18 +213,12 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { - If ``enableHEIC`` is enabled, create a HEIC representation of all above images combined. Otherwise, JPEG is used. */ public func finalizeImageForExport(imageData: Data) -> Data { - var data = Data() - var rawImage = CIImage() + guard let rawImage = CIImage(data: imageData, options: [.toneMapHDRtoSDR : (enableHDR ? true : false)]) else { return Data() } + var gainMapImage = CIImage() var imageProperties = rawImage.properties - if enableHDR { - rawImage = CIImage(data: imageData)! - gainMapImage = returnGainMap(properties: &imageProperties, imageData: imageData) - } else { - rawImage = CIImage(data: imageData, - options: [.toneMapHDRtoSDR : true])! - } + if enableHDR { gainMapImage = returnGainMap(properties: &imageProperties, imageData: imageData) } let watermarkImage = CIImage(image: self.watermark()) let outputImage = watermarkImage!.composited(over: rawImage) From 996bb09adb1e8e27df3f33668835410437dee0b3 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Tue, 21 Oct 2025 23:02:35 -0600 Subject: [PATCH 36/66] Streamline gainMapImage() --- .../Views/PhotoPreviewView/PhotoPreviewView.swift | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift b/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift index 2bc97e0..37f7c70 100755 --- a/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift +++ b/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift @@ -214,12 +214,8 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { */ public func finalizeImageForExport(imageData: Data) -> Data { guard let rawImage = CIImage(data: imageData, options: [.toneMapHDRtoSDR : (enableHDR ? true : false)]) else { return Data() } - - var gainMapImage = CIImage() var imageProperties = rawImage.properties - if enableHDR { gainMapImage = returnGainMap(properties: &imageProperties, imageData: imageData) } - let watermarkImage = CIImage(image: self.watermark()) let outputImage = watermarkImage!.composited(over: rawImage) @@ -231,7 +227,7 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { let outputImageWithProps = outputImage.settingProperties(imageProperties) - return returnImageFile(imageForRepresentation: outputImageWithProps, imageForGainMap: gainMapImage, imageColorspace: rawImage.colorSpace?.name) + return returnImageFile(imageForRepresentation: outputImageWithProps, imageForGainMap: returnGainMap(properties: &imageProperties, imageData: imageData), imageColorspace: rawImage.colorSpace?.name) } /** @@ -274,7 +270,7 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { let types = CGImageDestinationCopyTypeIdentifiers() as NSArray utilities.debugNSLog("[Capture Photo] Saving JPEG representation") if types.contains("public.heic") && enableHEIC { - if enableHDR && (hdrImage != nil){ + if enableHDR && (hdrImage != nil) { return CIContext().heifRepresentation(of: image, format: .RGBA8, colorSpace: CGColorSpace(name: colorSpace!)!, options: [ .hdrGainMapImage : hdrImage! ])! } else { return CIContext().heifRepresentation(of: image, format: .RGBA8, colorSpace: CGColorSpace(name: colorSpace!)!)! @@ -289,7 +285,8 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { } /// Function to extract gain map data from the image. - func returnGainMap(properties props: inout [String: Any], imageData: Data) -> CIImage { + func returnGainMap(properties props: inout [String: Any], imageData: Data) -> CIImage? { + if !enableHDR { return nil } var gainMapImage = CIImage() if let gainMapDataInfo = CGImageSourceCopyAuxiliaryDataInfoAtIndex(CGImageSourceCreateWithData(NSData(data: imageData), nil)!, 0, kCGImageAuxiliaryDataTypeHDRGainMap) as? Dictionary { utilities.debugNSLog("[Capture Photo] Saving gain map properties from image") From 143b6273551640fa56dabd4acc72d8cd03bcdaeb Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Tue, 4 Nov 2025 20:57:52 -0700 Subject: [PATCH 37/66] [Project] Fix WKCompanionAppBundleIdentifier --- Malachite.xcodeproj/project.pbxproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Malachite.xcodeproj/project.pbxproj b/Malachite.xcodeproj/project.pbxproj index 3a81f08..4252686 100755 --- a/Malachite.xcodeproj/project.pbxproj +++ b/Malachite.xcodeproj/project.pbxproj @@ -1893,7 +1893,7 @@ INFOPLIST_FILE = MalachiteWatch/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Malachite Remote"; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; - INFOPLIST_KEY_WKCompanionAppBundleIdentifier = dev.crystll1ne.Malachite; + INFOPLIST_KEY_WKCompanionAppBundleIdentifier = "$(APP_PREFIX).Malachite"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1935,7 +1935,7 @@ INFOPLIST_FILE = MalachiteWatch/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Malachite Remote"; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; - INFOPLIST_KEY_WKCompanionAppBundleIdentifier = dev.crystll1ne.Malachite; + INFOPLIST_KEY_WKCompanionAppBundleIdentifier = "$(APP_PREFIX).Malachite"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1977,7 +1977,7 @@ INFOPLIST_FILE = MalachiteWatch/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Malachite Remote"; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; - INFOPLIST_KEY_WKCompanionAppBundleIdentifier = dev.crystll1ne.Malachite; + INFOPLIST_KEY_WKCompanionAppBundleIdentifier = "$(APP_PREFIX).Malachite"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", From dd9c4ab78804bc16ee086c41e365d242504e8adc Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Tue, 4 Nov 2025 21:04:42 -0700 Subject: [PATCH 38/66] [Compatibility] Fix races, move isSameDevice() out of internal init --- .../CompatibilityUtils/Compatibility.swift | 13 +++++++++++++ Malachite/Utilities/InitUtils/Init+Internal.swift | 14 -------------- Malachite/Utilities/InitUtils/Init.swift | 4 +++- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Malachite/Utilities/CompatibilityUtils/Compatibility.swift b/Malachite/Utilities/CompatibilityUtils/Compatibility.swift index 9835d06..a6d1118 100644 --- a/Malachite/Utilities/CompatibilityUtils/Compatibility.swift +++ b/Malachite/Utilities/CompatibilityUtils/Compatibility.swift @@ -22,6 +22,19 @@ class Compatibility { utilities.preferences.compatibility.hdr = device.activeFormat.isVideoHDRSupported } } + + /// Checks whether or not the current device is the same device as previously recorded in preferences. + public func isSameDevice() { + if utilities.preferences.compatibility.device.changed { utilities.preferences.compatibility.device.changed = false } + if utilities.preferences.compatibility.device.model == utilities.preferences.ext.deviceModel() { + utilities.internalNSLog("[Initialization] This is the same device, can skip compatibility checks.") + return + } + + utilities.internalNSLog("[Initialization] This is a new device, rechecking compatibility.") + utilities.preferences.compatibility.device.model = utilities.preferences.ext.deviceModel() + utilities.preferences.compatibility.device.changed = true + } /** Checks whether or not the current device is capable of encoding High Efficiency Image Format. diff --git a/Malachite/Utilities/InitUtils/Init+Internal.swift b/Malachite/Utilities/InitUtils/Init+Internal.swift index 5fd0efa..0b3529e 100644 --- a/Malachite/Utilities/InitUtils/Init+Internal.swift +++ b/Malachite/Utilities/InitUtils/Init+Internal.swift @@ -17,23 +17,9 @@ extension Init { utilities.internalNSLog("[Initialization] plz subscribe to patreon: https://patreon.com/crystall1nedev") } - /// Checks whether or not the current device is the same device as previously recorded in preferences. - public func isSameDevice() { - if utilities.preferences.compatibility.device.changed { utilities.preferences.compatibility.device.changed = false } - if utilities.preferences.compatibility.device.model == utilities.preferences.ext.deviceModel() { - utilities.internalNSLog("[Initialization] This is the same device, can skip compatibility checks.") - return - } - - utilities.internalNSLog("[Initialization] This is a new device, rechecking compatibility.") - utilities.preferences.compatibility.device.model = utilities.preferences.ext.deviceModel() - utilities.preferences.compatibility.device.changed = true - } - /// Runs all of the initialization functions defined in this class. public func initMalachite() { isEvaBuild() - isSameDevice() } } } diff --git a/Malachite/Utilities/InitUtils/Init.swift b/Malachite/Utilities/InitUtils/Init.swift index 1ab1b8d..fcf17ec 100644 --- a/Malachite/Utilities/InitUtils/Init.swift +++ b/Malachite/Utilities/InitUtils/Init.swift @@ -49,9 +49,11 @@ class Init { startupLog() versionTypeCheck() appExtensionCheck() - self.compatibility.checkDeviceForHEICCompatibility() if utilities.versionType == "DEBUG" || utilities.versionType == "INTERNAL" { debug.initMalachite() } if utilities.versionType == "INTERNAL" { intrnl.initMalachite() } + + self.compatibility.isSameDevice() + self.compatibility.checkDeviceForHEICCompatibility() } } From 830224e7bd3fe600af989f018b7576db850ceab6 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Wed, 21 Jan 2026 20:49:22 -0700 Subject: [PATCH 39/66] [mlchtCamera] Rename from Malachite to mlchtCamera The name stays unique and serves as the final change to Malachite for App Store Connect. --- CHANGELOG.md | 4 +- README.md | 22 +- .../project.pbxproj | 344 +++++++++--------- .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcschemes/mlchtCamera.xcscheme | 18 +- .../xcschemes/mlchtCaptureBundle.xcscheme | 24 +- .../xcschemes/mlchtRemote.xcscheme | 24 +- .../xcschemes/mlchtWidgetBundle.xcscheme | 24 +- {Malachite => mlchtCamera}/AppDelegate.swift | 0 .../Assets/AppIcon.icon/Assets/0.5.png | Bin .../Assets/AppIcon.icon/Assets/0.75.png | Bin .../Assets/AppIcon.icon/Assets/1.png | Bin .../Assets/AppIcon.icon/Assets/Circle.svg | 0 .../AppIcon.icon/Assets/SpokesAndCircle.svg | 0 .../Assets/AppIcon.icon/icon.json | 0 .../AccentColor.colorset/Contents.json | 0 .../AppIcon.appiconset/Contents.json | 0 .../AppIcon.appiconset/MalachiteIcon-Dark.png | Bin .../MalachiteIcon-Tinted.png | Bin .../AppIcon.appiconset/MalachiteIcon.png | Bin .../AppIcon.appiconset/MalachiteIconWatch.png | Bin .../Assets/Assets.xcassets/Contents.json | 0 .../WidgetBackground.colorset/Contents.json | 0 .../asb_approved.appiconset/Contents.json | 0 .../asb_approved.appiconset/asbapproved.png | Bin .../asentientbot.imageset/57969415.jpeg | Bin .../asentientbot.imageset/Contents.json | 0 .../crystall1nedev.imageset/55281754.png | Bin .../crystall1nedev.imageset/Contents.json | 0 .../icon.imageset/Contents.json | 0 .../MalachiteIconMasked_Darwin24.png | Bin .../icon26.imageset/Contents.json | 0 .../MalachiteIconMasked_Darwin25.png | Bin .../thatsniceguy.appiconset/Contents.json | 0 .../thatsniceguy.appiconset/Icon1024x1024.png | Bin .../thatstella7922.imageset/10524728.png | Bin .../thatstella7922.imageset/Contents.json | 0 .../Assets/MalachiteIconMasked_Darwin24.png | Bin .../Assets/MalachiteIconMasked_Darwin25.png | Bin .../CaptureBundle/Info.plist | 0 .../MalachiteCaptureBundle.swift | 0 .../Codesigning.example.xcconfig | 0 {Malachite => mlchtCamera}/Info.plist | 0 .../Localizable.xcstrings | 0 .../SceneDelegate.swift | 0 .../CameraUtils/Camera+Bringup.swift | 0 .../Utilities/CameraUtils/Camera+Input.swift | 0 .../CameraUtils/Camera+Permissions.swift | 0 .../Utilities/CameraUtils/Camera.swift | 0 .../CompatibilityUtils/Compatibility.swift | 0 .../Utilities/GameUtils/Game+View.swift | 0 .../GameUtils/MalachiteGameUtils.swift | 0 .../Utilities/InitUtils/Init+Debug.swift | 0 .../Utilities/InitUtils/Init+Internal.swift | 0 .../Utilities/InitUtils/Init.swift | 0 .../Utilities/MalachiteFunctionUtils.swift | 0 .../Utilities/MalachiteHapticUtils.swift | 0 .../Utilities/MalachiteIntentUtils.swift | 0 .../Utilities/MalachiteTooltipUtils.swift | 0 .../Utilities/MalachiteUtils.swift | 0 .../MalachitePreferences.swift | 0 .../MalachitePreferencesUtils.swift | 0 .../Preferences+Extension.swift | 0 .../ViewUtils/MalachiteViewUtils.swift | 0 .../Utilities/ViewUtils/View+Sliders.swift | 0 .../Utilities/temputils.swift | 0 .../Views/AboutView/AboutView+Credits.swift | 0 .../Views/AboutView/AboutView+Eggs.swift | 0 .../Views/AboutView/AboutView+Info.swift | 0 .../Views/AboutView/AboutView+Story.swift | 0 .../Views/AboutView/AboutView.swift | 0 .../CameraView/CameraView+Controls.swift | 0 .../CameraView/CameraView+Notifications.swift | 0 .../CameraView/CameraView+Overrides.swift | 0 .../Views/CameraView/CameraView+Preview.swift | 0 .../Views/CameraView/CameraView.swift | 0 .../CompatibilityView+Camera.swift | 0 .../CompatibilityView+Format.swift | 0 .../CompatibilityView+Resolutions.swift | 0 .../CompatibilityView/CompatibilityView.swift | 0 .../DeveloperView+BuildInfo.swift | 0 .../DeveloperView+DeviceInfo.swift | 0 .../DeveloperView+Internal.swift | 0 .../DeveloperView+Settings.swift | 0 .../Views/DeveloperView/DeveloperView.swift | 0 .../PhotoPreviewView+Controls.swift | 0 .../PhotoPreviewView/PhotoPreviewView.swift | 0 .../QuickHelpView/QuickHelpView+About.swift | 0 .../QuickHelpView/QuickHelpView+Photo.swift | 0 .../QuickHelpView/QuickHelpView+Preview.swift | 0 .../QuickHelpView+Resolution.swift | 0 .../QuickHelpView/QuickHelpView+UI.swift | 0 .../QuickHelpView+Watermarking.swift | 0 .../Views/QuickHelpView/QuickHelpView.swift | 0 .../SettingsView/SettingsView+About.swift | 0 .../SettingsView/SettingsView+Photo.swift | 0 .../SettingsView/SettingsView+Preview.swift | 0 .../SettingsView+Resolution.swift | 0 .../Views/SettingsView/SettingsView+UI.swift | 0 .../SettingsView+Watermarking.swift | 0 .../Views/SettingsView/SettingsView.swift | 0 .../WidgetBundle/ControlCenterWidget.swift | 0 .../WidgetBundle/Info.plist | 0 .../WidgetBundle/LockScreenWidget.swift | 0 .../WidgetBundle/MalachiteWidgetBundle.swift | 0 .../mlchtCamera.docc}/Malachite.md | 0 .../mlchtCamera.entitlements | 0 .../ContentView.swift | 0 {MalachiteWatch => mlchtRemote}/Info.plist | 0 .../MalachiteWatchApp.swift | 0 111 files changed, 233 insertions(+), 227 deletions(-) rename {Malachite.xcodeproj => mlchtCamera.xcodeproj}/project.pbxproj (91%) rename {Malachite.xcodeproj => mlchtCamera.xcodeproj}/project.xcworkspace/contents.xcworkspacedata (100%) rename {Malachite.xcodeproj => mlchtCamera.xcodeproj}/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename Malachite.xcodeproj/xcshareddata/xcschemes/Malachite.xcscheme => mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtCamera.xcscheme (83%) mode change 100755 => 100644 rename Malachite.xcodeproj/xcshareddata/xcschemes/CaptureBundle.xcscheme => mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtCaptureBundle.xcscheme (81%) rename Malachite.xcodeproj/xcshareddata/xcschemes/MalachiteWatch.xcscheme => mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtRemote.xcscheme (81%) rename Malachite.xcodeproj/xcshareddata/xcschemes/WidgetBundle.xcscheme => mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtWidgetBundle.xcscheme (81%) mode change 100755 => 100644 rename {Malachite => mlchtCamera}/AppDelegate.swift (100%) rename {Malachite => mlchtCamera}/Assets/AppIcon.icon/Assets/0.5.png (100%) rename {Malachite => mlchtCamera}/Assets/AppIcon.icon/Assets/0.75.png (100%) rename {Malachite => mlchtCamera}/Assets/AppIcon.icon/Assets/1.png (100%) rename {Malachite => mlchtCamera}/Assets/AppIcon.icon/Assets/Circle.svg (100%) rename {Malachite => mlchtCamera}/Assets/AppIcon.icon/Assets/SpokesAndCircle.svg (100%) rename {Malachite => mlchtCamera}/Assets/AppIcon.icon/icon.json (100%) rename {Malachite => mlchtCamera}/Assets/Assets.xcassets/AccentColor.colorset/Contents.json (100%) rename {Malachite => mlchtCamera}/Assets/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename {Malachite => mlchtCamera}/Assets/Assets.xcassets/AppIcon.appiconset/MalachiteIcon-Dark.png (100%) rename {Malachite => mlchtCamera}/Assets/Assets.xcassets/AppIcon.appiconset/MalachiteIcon-Tinted.png (100%) rename {Malachite => mlchtCamera}/Assets/Assets.xcassets/AppIcon.appiconset/MalachiteIcon.png (100%) rename {Malachite => mlchtCamera}/Assets/Assets.xcassets/AppIcon.appiconset/MalachiteIconWatch.png (100%) rename {Malachite => mlchtCamera}/Assets/Assets.xcassets/Contents.json (100%) rename {Malachite => mlchtCamera}/Assets/Assets.xcassets/WidgetBackground.colorset/Contents.json (100%) rename {Malachite => mlchtCamera}/Assets/Assets.xcassets/asb_approved.appiconset/Contents.json (100%) rename {Malachite => mlchtCamera}/Assets/Assets.xcassets/asb_approved.appiconset/asbapproved.png (100%) rename {Malachite => mlchtCamera}/Assets/Assets.xcassets/asentientbot.imageset/57969415.jpeg (100%) rename {Malachite => mlchtCamera}/Assets/Assets.xcassets/asentientbot.imageset/Contents.json (100%) rename {Malachite => mlchtCamera}/Assets/Assets.xcassets/crystall1nedev.imageset/55281754.png (100%) rename {Malachite => mlchtCamera}/Assets/Assets.xcassets/crystall1nedev.imageset/Contents.json (100%) rename {Malachite => mlchtCamera}/Assets/Assets.xcassets/icon.imageset/Contents.json (100%) rename {Malachite => mlchtCamera}/Assets/Assets.xcassets/icon.imageset/MalachiteIconMasked_Darwin24.png (100%) rename {Malachite => mlchtCamera}/Assets/Assets.xcassets/icon26.imageset/Contents.json (100%) rename {Malachite => mlchtCamera}/Assets/Assets.xcassets/icon26.imageset/MalachiteIconMasked_Darwin25.png (100%) rename {Malachite => mlchtCamera}/Assets/Assets.xcassets/thatsniceguy.appiconset/Contents.json (100%) rename {Malachite => mlchtCamera}/Assets/Assets.xcassets/thatsniceguy.appiconset/Icon1024x1024.png (100%) rename {Malachite => mlchtCamera}/Assets/Assets.xcassets/thatstella7922.imageset/10524728.png (100%) rename {Malachite => mlchtCamera}/Assets/Assets.xcassets/thatstella7922.imageset/Contents.json (100%) rename {Malachite => mlchtCamera}/Assets/MalachiteIconMasked_Darwin24.png (100%) rename {Malachite => mlchtCamera}/Assets/MalachiteIconMasked_Darwin25.png (100%) rename {Malachite => mlchtCamera}/CaptureBundle/Info.plist (100%) rename {Malachite => mlchtCamera}/CaptureBundle/MalachiteCaptureBundle.swift (100%) rename {Malachite => mlchtCamera}/Codesigning.example.xcconfig (100%) rename {Malachite => mlchtCamera}/Info.plist (100%) rename {Malachite => mlchtCamera}/Localizable.xcstrings (100%) rename {Malachite => mlchtCamera}/SceneDelegate.swift (100%) rename {Malachite => mlchtCamera}/Utilities/CameraUtils/Camera+Bringup.swift (100%) rename {Malachite => mlchtCamera}/Utilities/CameraUtils/Camera+Input.swift (100%) rename {Malachite => mlchtCamera}/Utilities/CameraUtils/Camera+Permissions.swift (100%) rename {Malachite => mlchtCamera}/Utilities/CameraUtils/Camera.swift (100%) rename {Malachite => mlchtCamera}/Utilities/CompatibilityUtils/Compatibility.swift (100%) rename {Malachite => mlchtCamera}/Utilities/GameUtils/Game+View.swift (100%) rename {Malachite => mlchtCamera}/Utilities/GameUtils/MalachiteGameUtils.swift (100%) rename {Malachite => mlchtCamera}/Utilities/InitUtils/Init+Debug.swift (100%) rename {Malachite => mlchtCamera}/Utilities/InitUtils/Init+Internal.swift (100%) rename {Malachite => mlchtCamera}/Utilities/InitUtils/Init.swift (100%) rename {Malachite => mlchtCamera}/Utilities/MalachiteFunctionUtils.swift (100%) rename {Malachite => mlchtCamera}/Utilities/MalachiteHapticUtils.swift (100%) rename {Malachite => mlchtCamera}/Utilities/MalachiteIntentUtils.swift (100%) rename {Malachite => mlchtCamera}/Utilities/MalachiteTooltipUtils.swift (100%) rename {Malachite => mlchtCamera}/Utilities/MalachiteUtils.swift (100%) rename {Malachite => mlchtCamera}/Utilities/PreferenceUtils/MalachitePreferences.swift (100%) rename {Malachite => mlchtCamera}/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift (100%) rename {Malachite => mlchtCamera}/Utilities/PreferenceUtils/Preferences+Extension.swift (100%) rename {Malachite => mlchtCamera}/Utilities/ViewUtils/MalachiteViewUtils.swift (100%) rename {Malachite => mlchtCamera}/Utilities/ViewUtils/View+Sliders.swift (100%) rename {Malachite => mlchtCamera}/Utilities/temputils.swift (100%) rename {Malachite => mlchtCamera}/Views/AboutView/AboutView+Credits.swift (100%) rename {Malachite => mlchtCamera}/Views/AboutView/AboutView+Eggs.swift (100%) rename {Malachite => mlchtCamera}/Views/AboutView/AboutView+Info.swift (100%) rename {Malachite => mlchtCamera}/Views/AboutView/AboutView+Story.swift (100%) rename {Malachite => mlchtCamera}/Views/AboutView/AboutView.swift (100%) rename {Malachite => mlchtCamera}/Views/CameraView/CameraView+Controls.swift (100%) rename {Malachite => mlchtCamera}/Views/CameraView/CameraView+Notifications.swift (100%) rename {Malachite => mlchtCamera}/Views/CameraView/CameraView+Overrides.swift (100%) rename {Malachite => mlchtCamera}/Views/CameraView/CameraView+Preview.swift (100%) rename {Malachite => mlchtCamera}/Views/CameraView/CameraView.swift (100%) rename {Malachite => mlchtCamera}/Views/CompatibilityView/CompatibilityView+Camera.swift (100%) rename {Malachite => mlchtCamera}/Views/CompatibilityView/CompatibilityView+Format.swift (100%) rename {Malachite => mlchtCamera}/Views/CompatibilityView/CompatibilityView+Resolutions.swift (100%) rename {Malachite => mlchtCamera}/Views/CompatibilityView/CompatibilityView.swift (100%) rename {Malachite => mlchtCamera}/Views/DeveloperView/DeveloperView+BuildInfo.swift (100%) rename {Malachite => mlchtCamera}/Views/DeveloperView/DeveloperView+DeviceInfo.swift (100%) rename {Malachite => mlchtCamera}/Views/DeveloperView/DeveloperView+Internal.swift (100%) rename {Malachite => mlchtCamera}/Views/DeveloperView/DeveloperView+Settings.swift (100%) rename {Malachite => mlchtCamera}/Views/DeveloperView/DeveloperView.swift (100%) rename {Malachite => mlchtCamera}/Views/PhotoPreviewView/PhotoPreviewView+Controls.swift (100%) rename {Malachite => mlchtCamera}/Views/PhotoPreviewView/PhotoPreviewView.swift (100%) rename {Malachite => mlchtCamera}/Views/QuickHelpView/QuickHelpView+About.swift (100%) rename {Malachite => mlchtCamera}/Views/QuickHelpView/QuickHelpView+Photo.swift (100%) rename {Malachite => mlchtCamera}/Views/QuickHelpView/QuickHelpView+Preview.swift (100%) rename {Malachite => mlchtCamera}/Views/QuickHelpView/QuickHelpView+Resolution.swift (100%) rename {Malachite => mlchtCamera}/Views/QuickHelpView/QuickHelpView+UI.swift (100%) rename {Malachite => mlchtCamera}/Views/QuickHelpView/QuickHelpView+Watermarking.swift (100%) rename {Malachite => mlchtCamera}/Views/QuickHelpView/QuickHelpView.swift (100%) rename {Malachite => mlchtCamera}/Views/SettingsView/SettingsView+About.swift (100%) rename {Malachite => mlchtCamera}/Views/SettingsView/SettingsView+Photo.swift (100%) rename {Malachite => mlchtCamera}/Views/SettingsView/SettingsView+Preview.swift (100%) rename {Malachite => mlchtCamera}/Views/SettingsView/SettingsView+Resolution.swift (100%) rename {Malachite => mlchtCamera}/Views/SettingsView/SettingsView+UI.swift (100%) rename {Malachite => mlchtCamera}/Views/SettingsView/SettingsView+Watermarking.swift (100%) rename {Malachite => mlchtCamera}/Views/SettingsView/SettingsView.swift (100%) rename {Malachite => mlchtCamera}/WidgetBundle/ControlCenterWidget.swift (100%) rename {Malachite => mlchtCamera}/WidgetBundle/Info.plist (100%) rename {Malachite => mlchtCamera}/WidgetBundle/LockScreenWidget.swift (100%) rename {Malachite => mlchtCamera}/WidgetBundle/MalachiteWidgetBundle.swift (100%) rename {Malachite/Malachite.docc => mlchtCamera/mlchtCamera.docc}/Malachite.md (100%) rename Malachite/Malachite.entitlements => mlchtCamera/mlchtCamera.entitlements (100%) rename {MalachiteWatch => mlchtRemote}/ContentView.swift (100%) rename {MalachiteWatch => mlchtRemote}/Info.plist (100%) rename {MalachiteWatch => mlchtRemote}/MalachiteWatchApp.swift (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 451aaf7..720abc7 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # 1.0.0 (build xxx) - +- **Renamed Malachite to mlchtCamera** + - Malachite Remote also gets a rename to mlchtRemote. + - Issues with App Store Connect and bundle IDs, I love Apple... # 1.0.0 (build 54) diff --git a/README.md b/README.md index fd99142..61520c9 100755 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ -# Malachite -*noun* -1. a crystal with the properties of revealing hidden parts of yourself -2. the name of my app to take back control of your iPhone or iPad's camera lenses. +# mlchtCamera + +*noun*; the name of my app to take back control of your iPhone or iPad's camera lenses. + --- ## What is it? -Malachite is a **work-in-progress** camera app, designed to put pro controls into the hands of even more users. +With a name loosely based on the **malachite crystal** with properties of revealing hidden parts of yourself, mlchtCamera is a **work-in-progress** camera app, designed to put pro controls into the hands of even more users. ## What do I need?[^1] -Malachite will run on any iPhone or iPad with **iOS 15.0** or later. +mlchtCamera will run on any iPhone or iPad with **iOS 15.0** or later. I recommend one of the following configurations... - An iPhone or iPad with one of the following... @@ -43,18 +43,22 @@ Malachite is on TestFlight, but only for **[my Patrons](https://patreon.com/crys ### Building from source 1. Clone this repo 2. Open `Codesigning.example.xcconfig`, make changes, and save it as `Codesigning.xcconfig`. -3. Open `Malachite.xcodeproj` +3. Open `mlchtCamera.xcodeproj` 4. Build! ## What started this one, Eva? So, I live with my love: @ThatStella7922. She and I are both big nerds, and I caught her using her macro lens on an Xbox 360 motherboard to let her work with traces and pads while she was RGH'ing it. The idea for a magnifier app came from how much time it took for her to get into the right camera setting, turn the flashlight on, and still not have much control beyond autofocus and zooming. With Malachite, I strove to solve this problem - and thus, we had Malachite with its original purpose: a macro magnifier. -As I was working on it, I'd drop builds into my Discord server. A few users came in and asked for various features - including image capture. I was originally opposed to it, since it *was* just for magnification... and yet, a few hours later, I'd hooked everything up to add image capture support - saving HEICs to the user's library or directly out of the share sheet. Malachite ended up morphing into a macro photography app that people used and enjoyed - and requested more out of. +As I was working on it, I'd drop builds into my Discord server. A few users came in and asked for various features - including image capture. I was originally opposed to it, since it *was* just for magnification... and yet, a few hours later, I'd hooked everything up to add image capture support - saving HEICs to the user's library or directly out of the share sheet. mlchtCamera ended up morphing into a macro photography app that people used and enjoyed - and requested more out of. At this point, I've added plenty of extras. Camera switching, manual exposure, hardware button controls - those are just a few and I plan to implement so much more in the future. It took me a while to accept it, but this little side-project of mine was becoming something different, and the goal solidified itself as this: creating a powerful pro camera app that truly harnesses iPhone and iPad hardware, while staying simplistic in its design and accessible to anyone who wants to get into photography. To the people who helped get me here (and you know who you are), I thank you for helping me figure it out. -[^1]: Malachite is validated against iPhone SE (1st generation) with no lens attachment, iPhone 8 Plus with no working main camera, iPhone 11, iPhone 16 Pro Max, and iPad Pro (11-inch). Not all features are available across all devices, due to hardware and software limitations. iOS version support may change depending on the difficulty of targeting older iOS versions and/or other factors. +### Addendum + +This app was originally named "Malachite" - directly after the crystal mentioned before. However, bouncing between Apple Developer accounts resulted in issues with the name and bundle identifiers used in the past. The rename to "mlchtCamera" keeps the core name while also making it unique. + +[^1]: mlcht is validated against iPhone SE (1st generation) with no lens attachment, iPhone 8 Plus with no working main camera, iPhone 11, iPhone 17 Pro Max, and iPad Pro (11-inch). Not all features are available across all devices, due to hardware and software limitations. iOS version support may change depending on the difficulty of targeting older iOS versions and/or other factors. [^2]: Pinch-to-zoom will feature haptic feedback when reaching the minimum and maximum zoom levels in a future commit. [^3]: Capturing images in RAW and ProRAW is being looked into. HEIC requires iPhone 7 or later, iPad (6th generation) or later, iPad Air (3rd generation) or later, iPad mini (5th generation) or later, iPad Pro (12.9-inch, 2nd generation) or later, iPad Pro (10.5-inch), iPad Pro (11-inch) or later, or iPod touch (7th generation) diff --git a/Malachite.xcodeproj/project.pbxproj b/mlchtCamera.xcodeproj/project.pbxproj similarity index 91% rename from Malachite.xcodeproj/project.pbxproj rename to mlchtCamera.xcodeproj/project.pbxproj index 4252686..1ee5123 100755 --- a/Malachite.xcodeproj/project.pbxproj +++ b/mlchtCamera.xcodeproj/project.pbxproj @@ -25,7 +25,7 @@ 7837C10B2E34C45B009396B0 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78917F5D2B99AE72005E10FA /* SwiftUI.framework */; }; 7837C10C2E34C45B009396B0 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78917F5B2B99AE72005E10FA /* WidgetKit.framework */; }; 7837C10E2E34C45B009396B0 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */; }; - 7837C1172E34C47D009396B0 /* WidgetBundleWatch.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 7837C1152E34C45B009396B0 /* WidgetBundleWatch.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 7837C1172E34C47D009396B0 /* mlchtWidgetBundleWatch.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 7837C1152E34C45B009396B0 /* mlchtWidgetBundleWatch.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 7847D5032E8F26EC006759A6 /* View+Sliders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7847D5022E8F26E9006759A6 /* View+Sliders.swift */; }; 7847D5042E8F26EC006759A6 /* View+Sliders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7847D5022E8F26E9006759A6 /* View+Sliders.swift */; }; 7847D5052E8F26EC006759A6 /* View+Sliders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7847D5022E8F26E9006759A6 /* View+Sliders.swift */; }; @@ -116,7 +116,7 @@ 78729C562E7368F3001027E9 /* Camera+Input.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78729C552E7368F0001027E9 /* Camera+Input.swift */; }; 78729C572E7368F3001027E9 /* Camera+Input.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78729C552E7368F0001027E9 /* Camera+Input.swift */; }; 78729C582E7368F3001027E9 /* Camera+Input.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78729C552E7368F0001027E9 /* Camera+Input.swift */; }; - 787B1CAA2B8B095E000AFECC /* Malachite.docc in Sources */ = {isa = PBXBuildFile; fileRef = 787B1CA92B8B095E000AFECC /* Malachite.docc */; }; + 787B1CAA2B8B095E000AFECC /* mlchtCamera.docc in Sources */ = {isa = PBXBuildFile; fileRef = 787B1CA92B8B095E000AFECC /* mlchtCamera.docc */; }; 788262602E513080000085AC /* SettingsView+About.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882625F2E51307B000085AC /* SettingsView+About.swift */; }; 788262612E513080000085AC /* SettingsView+About.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882625F2E51307B000085AC /* SettingsView+About.swift */; }; 788262622E513080000085AC /* SettingsView+About.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7882625F2E51307B000085AC /* SettingsView+About.swift */; }; @@ -170,8 +170,8 @@ 788262E42E5172E8000085AC /* AboutView+Credits.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788262E12E5172E3000085AC /* AboutView+Credits.swift */; }; 78917F5C2B99AE72005E10FA /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78917F5B2B99AE72005E10FA /* WidgetKit.framework */; }; 78917F5E2B99AE72005E10FA /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78917F5D2B99AE72005E10FA /* SwiftUI.framework */; }; - 78917F692B99AE73005E10FA /* WidgetBundle.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 78917F592B99AE72005E10FA /* WidgetBundle.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 78A9DD6A2CD55551002C131D /* CaptureBundle.appex in Embed ExtensionKit Extensions */ = {isa = PBXBuildFile; fileRef = 78A9DD612CD55551002C131D /* CaptureBundle.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 78917F692B99AE73005E10FA /* mlchtWidgetBundle.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 78917F592B99AE72005E10FA /* mlchtWidgetBundle.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 78A9DD6A2CD55551002C131D /* mlchtCaptureBundle.appex in Embed ExtensionKit Extensions */ = {isa = PBXBuildFile; fileRef = 78A9DD612CD55551002C131D /* mlchtCaptureBundle.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 78A9DE312CD581F1002C131D /* LockedCameraCapture.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78A9DE302CD581F1002C131D /* LockedCameraCapture.framework */; }; 78A9DE752CD5AE5F002C131D /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */; }; 78AF68682E5D97E500BB9D5C /* CameraView+Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AF68672E5D97E000BB9D5C /* CameraView+Preview.swift */; }; @@ -183,7 +183,7 @@ 78AF687D2E5D99EF00BB9D5C /* Preferences+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AF687C2E5D99E300BB9D5C /* Preferences+Extension.swift */; }; 78AF687E2E5D99EF00BB9D5C /* Preferences+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AF687C2E5D99E300BB9D5C /* Preferences+Extension.swift */; }; 78AF687F2E5D99EF00BB9D5C /* Preferences+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AF687C2E5D99E300BB9D5C /* Preferences+Extension.swift */; }; - 78B72BB12E332830002E2D4E /* MalachiteWatch.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 78B72BA72E33282E002E2D4E /* MalachiteWatch.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 78B72BB12E332830002E2D4E /* mlchtRemote.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 78B72BA72E33282E002E2D4E /* mlchtRemote.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 78C2EFC02E6971E600C6DD79 /* Init+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78C2EFBF2E6971DE00C6DD79 /* Init+Debug.swift */; }; 78C2EFC12E6971E600C6DD79 /* Init+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78C2EFBF2E6971DE00C6DD79 /* Init+Debug.swift */; }; 78C2EFC22E6971E600C6DD79 /* Init+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78C2EFBF2E6971DE00C6DD79 /* Init+Debug.swift */; }; @@ -247,7 +247,7 @@ dstPath = ""; dstSubfolder = PlugIns; files = ( - 7837C1172E34C47D009396B0 /* WidgetBundleWatch.appex in Embed Foundation Extensions */, + 7837C1172E34C47D009396B0 /* mlchtWidgetBundleWatch.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; }; @@ -256,7 +256,7 @@ dstPath = ""; dstSubfolder = PlugIns; files = ( - 78917F692B99AE73005E10FA /* WidgetBundle.appex in Embed Foundation Extensions */, + 78917F692B99AE73005E10FA /* mlchtWidgetBundle.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; }; @@ -265,7 +265,7 @@ dstPath = "$(EXTENSIONS_FOLDER_PATH)"; dstSubfolder = Product; files = ( - 78A9DD6A2CD55551002C131D /* CaptureBundle.appex in Embed ExtensionKit Extensions */, + 78A9DD6A2CD55551002C131D /* mlchtCaptureBundle.appex in Embed ExtensionKit Extensions */, ); name = "Embed ExtensionKit Extensions"; }; @@ -274,7 +274,7 @@ dstPath = "$(CONTENTS_FOLDER_PATH)/Watch"; dstSubfolder = Product; files = ( - 78B72BB12E332830002E2D4E /* MalachiteWatch.app in Embed Watch Content */, + 78B72BB12E332830002E2D4E /* mlchtRemote.app in Embed Watch Content */, ); name = "Embed Watch Content"; }; @@ -288,12 +288,12 @@ 7824CAF02E62E4920072870F /* Camera+Bringup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Camera+Bringup.swift"; sourceTree = ""; }; 782850002E80C04D00826FA7 /* Camera+Permissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Camera+Permissions.swift"; sourceTree = ""; }; 782FC7752B2DAFAB007709C1 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; - 7837C1152E34C45B009396B0 /* WidgetBundleWatch.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetBundleWatch.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 7837C1152E34C45B009396B0 /* mlchtWidgetBundleWatch.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = mlchtWidgetBundleWatch.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 78474D2B2D7AC879006FBB96 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = ""; }; 7847D5022E8F26E9006759A6 /* View+Sliders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Sliders.swift"; sourceTree = ""; }; 784EDDD32E5EE52100DFF370 /* PhotoPreviewView+Controls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PhotoPreviewView+Controls.swift"; sourceTree = ""; }; 78562BC22B450A7600920160 /* PRIVACY_POLICY.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = PRIVACY_POLICY.md; sourceTree = SOURCE_ROOT; }; - 785F08492B12D41100244EB4 /* Malachite.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Malachite.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 785F08492B12D41100244EB4 /* mlchtCamera.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = mlchtCamera.app; sourceTree = BUILT_PRODUCTS_DIR; }; 785F084C2B12D41100244EB4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 785F084E2B12D41100244EB4 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 785F085A2B12D41300244EB4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -330,8 +330,8 @@ 786DAE7A2E4F28BF00BE3137 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 786DAE7B2E4F28BF00BE3137 /* MalachiteWatchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteWatchApp.swift; sourceTree = ""; }; 78729C552E7368F0001027E9 /* Camera+Input.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Camera+Input.swift"; sourceTree = ""; }; - 787B1CA92B8B095E000AFECC /* Malachite.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = Malachite.docc; sourceTree = ""; }; - 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = Malachite/Localizable.xcstrings; sourceTree = SOURCE_ROOT; }; + 787B1CA92B8B095E000AFECC /* mlchtCamera.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = mlchtCamera.docc; sourceTree = ""; }; + 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = mlchtCamera/Localizable.xcstrings; sourceTree = SOURCE_ROOT; }; 7882625F2E51307B000085AC /* SettingsView+About.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsView+About.swift"; sourceTree = ""; }; 788262632E513094000085AC /* SettingsView+Preview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsView+Preview.swift"; sourceTree = ""; }; 7882626B2E5130BB000085AC /* SettingsView+Resolution.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsView+Resolution.swift"; sourceTree = ""; }; @@ -349,16 +349,16 @@ 788262D92E5171EC000085AC /* AboutView+Story.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AboutView+Story.swift"; sourceTree = ""; }; 788262DD2E51724C000085AC /* AboutView+Eggs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AboutView+Eggs.swift"; sourceTree = ""; }; 788262E12E5172E3000085AC /* AboutView+Credits.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AboutView+Credits.swift"; sourceTree = ""; }; - 788589912B8974680018E2DA /* Malachite.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Malachite.entitlements; sourceTree = ""; }; - 78917F592B99AE72005E10FA /* WidgetBundle.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetBundle.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 788589912B8974680018E2DA /* mlchtCamera.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = mlchtCamera.entitlements; sourceTree = ""; }; + 78917F592B99AE72005E10FA /* mlchtWidgetBundle.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = mlchtWidgetBundle.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 78917F5B2B99AE72005E10FA /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; 78917F5D2B99AE72005E10FA /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; - 78A9DD612CD55551002C131D /* CaptureBundle.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.extensionkit-extension"; includeInIndex = 0; path = CaptureBundle.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 78A9DD612CD55551002C131D /* mlchtCaptureBundle.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.extensionkit-extension"; includeInIndex = 0; path = mlchtCaptureBundle.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 78A9DE302CD581F1002C131D /* LockedCameraCapture.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LockedCameraCapture.framework; path = System/Library/Frameworks/LockedCameraCapture.framework; sourceTree = SDKROOT; }; 78AF68672E5D97E000BB9D5C /* CameraView+Preview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraView+Preview.swift"; sourceTree = ""; }; 78AF686B2E5D97E800BB9D5C /* CameraView+Controls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraView+Controls.swift"; sourceTree = ""; }; 78AF687C2E5D99E300BB9D5C /* Preferences+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Preferences+Extension.swift"; sourceTree = ""; }; - 78B72BA72E33282E002E2D4E /* MalachiteWatch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MalachiteWatch.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 78B72BA72E33282E002E2D4E /* mlchtRemote.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = mlchtRemote.app; sourceTree = BUILT_PRODUCTS_DIR; }; 78C2EFBF2E6971DE00C6DD79 /* Init+Debug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Init+Debug.swift"; sourceTree = ""; }; 78C2EFC32E6972D300C6DD79 /* Init+Internal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Init+Internal.swift"; sourceTree = ""; }; 78E142C92DD426260016B3DB /* Codesigning.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Codesigning.xcconfig; sourceTree = ""; }; @@ -440,8 +440,8 @@ 78562BC22B450A7600920160 /* PRIVACY_POLICY.md */, 786DAD902E4F282E00BE3137 /* .github */, 786DAD932E4F283000BE3137 /* ci_scripts */, - 785F084B2B12D41100244EB4 /* Malachite */, - 786DAE7C2E4F28BF00BE3137 /* MalachiteWatch */, + 785F084B2B12D41100244EB4 /* mlchtCamera */, + 786DAE7C2E4F28BF00BE3137 /* mlchtRemote */, 78917F5A2B99AE72005E10FA /* Frameworks */, 785F084A2B12D41100244EB4 /* Products */, ); @@ -450,22 +450,22 @@ 785F084A2B12D41100244EB4 /* Products */ = { isa = PBXGroup; children = ( - 785F08492B12D41100244EB4 /* Malachite.app */, - 78917F592B99AE72005E10FA /* WidgetBundle.appex */, - 78A9DD612CD55551002C131D /* CaptureBundle.appex */, - 78B72BA72E33282E002E2D4E /* MalachiteWatch.app */, - 7837C1152E34C45B009396B0 /* WidgetBundleWatch.appex */, + 785F08492B12D41100244EB4 /* mlchtCamera.app */, + 78917F592B99AE72005E10FA /* mlchtWidgetBundle.appex */, + 78A9DD612CD55551002C131D /* mlchtCaptureBundle.appex */, + 78B72BA72E33282E002E2D4E /* mlchtRemote.app */, + 7837C1152E34C45B009396B0 /* mlchtWidgetBundleWatch.appex */, ); name = Products; sourceTree = ""; }; - 785F084B2B12D41100244EB4 /* Malachite */ = { + 785F084B2B12D41100244EB4 /* mlchtCamera */ = { isa = PBXGroup; children = ( 786DAD982E4F283500BE3137 /* Assets */, 78E142C92DD426260016B3DB /* Codesigning.xcconfig */, - 787B1CA92B8B095E000AFECC /* Malachite.docc */, - 788589912B8974680018E2DA /* Malachite.entitlements */, + 787B1CA92B8B095E000AFECC /* mlchtCamera.docc */, + 788589912B8974680018E2DA /* mlchtCamera.entitlements */, 785F085A2B12D41300244EB4 /* Info.plist */, 786DAE222E4F28A600BE3137 /* Views */, 786DAE4B2E4F28B100BE3137 /* Utilities */, @@ -475,7 +475,7 @@ 785F084C2B12D41100244EB4 /* AppDelegate.swift */, 785F084E2B12D41100244EB4 /* SceneDelegate.swift */, ); - path = Malachite; + path = mlchtCamera; sourceTree = ""; }; 7866F5CC2E62999B009AC9BF /* CameraUtils */ = { @@ -573,14 +573,14 @@ path = CaptureBundle; sourceTree = ""; }; - 786DAE7C2E4F28BF00BE3137 /* MalachiteWatch */ = { + 786DAE7C2E4F28BF00BE3137 /* mlchtRemote */ = { isa = PBXGroup; children = ( 786DAE792E4F28BF00BE3137 /* ContentView.swift */, 786DAE7A2E4F28BF00BE3137 /* Info.plist */, 786DAE7B2E4F28BF00BE3137 /* MalachiteWatchApp.swift */, ); - path = MalachiteWatch; + path = mlchtRemote; sourceTree = ""; }; 786DAE802E4F28D600BE3137 /* SettingsView */ = { @@ -678,9 +678,9 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 7837C1042E34C45B009396B0 /* WidgetBundleWatch */ = { + 7837C1042E34C45B009396B0 /* mlchtWidgetBundleWatch */ = { isa = PBXNativeTarget; - buildConfigurationList = 7837C1112E34C45B009396B0 /* Build configuration list for PBXNativeTarget "WidgetBundleWatch" */; + buildConfigurationList = 7837C1112E34C45B009396B0 /* Build configuration list for PBXNativeTarget "mlchtWidgetBundleWatch" */; buildPhases = ( 7837C1052E34C45B009396B0 /* Sources */, 7837C10A2E34C45B009396B0 /* Frameworks */, @@ -691,14 +691,14 @@ fileSystemSynchronizedGroups = ( 786DAD982E4F283500BE3137 /* Assets */, ); - name = WidgetBundleWatch; + name = mlchtWidgetBundleWatch; productName = MalachiteWidgetBundle; - productReference = 7837C1152E34C45B009396B0 /* WidgetBundleWatch.appex */; + productReference = 7837C1152E34C45B009396B0 /* mlchtWidgetBundleWatch.appex */; productType = "com.apple.product-type.app-extension"; }; - 785F08482B12D41100244EB4 /* Malachite */ = { + 785F08482B12D41100244EB4 /* mlchtCamera */ = { isa = PBXNativeTarget; - buildConfigurationList = 785F085D2B12D41300244EB4 /* Build configuration list for PBXNativeTarget "Malachite" */; + buildConfigurationList = 785F085D2B12D41300244EB4 /* Build configuration list for PBXNativeTarget "mlchtCamera" */; buildPhases = ( 785F08452B12D41100244EB4 /* Sources */, 785F08472B12D41100244EB4 /* Resources */, @@ -717,14 +717,14 @@ fileSystemSynchronizedGroups = ( 786DAD982E4F283500BE3137 /* Assets */, ); - name = Malachite; + name = mlchtCamera; productName = Malachite; - productReference = 785F08492B12D41100244EB4 /* Malachite.app */; + productReference = 785F08492B12D41100244EB4 /* mlchtCamera.app */; productType = "com.apple.product-type.application"; }; - 78917F582B99AE72005E10FA /* WidgetBundle */ = { + 78917F582B99AE72005E10FA /* mlchtWidgetBundle */ = { isa = PBXNativeTarget; - buildConfigurationList = 78917F6D2B99AE73005E10FA /* Build configuration list for PBXNativeTarget "WidgetBundle" */; + buildConfigurationList = 78917F6D2B99AE73005E10FA /* Build configuration list for PBXNativeTarget "mlchtWidgetBundle" */; buildPhases = ( 78917F552B99AE72005E10FA /* Sources */, 78917F562B99AE72005E10FA /* Frameworks */, @@ -735,14 +735,14 @@ fileSystemSynchronizedGroups = ( 786DAD982E4F283500BE3137 /* Assets */, ); - name = WidgetBundle; + name = mlchtWidgetBundle; productName = MalachiteWidgetBundle; - productReference = 78917F592B99AE72005E10FA /* WidgetBundle.appex */; + productReference = 78917F592B99AE72005E10FA /* mlchtWidgetBundle.appex */; productType = "com.apple.product-type.app-extension"; }; - 78A9DD602CD55551002C131D /* CaptureBundle */ = { + 78A9DD602CD55551002C131D /* mlchtCaptureBundle */ = { isa = PBXNativeTarget; - buildConfigurationList = 78A9DD6C2CD55551002C131D /* Build configuration list for PBXNativeTarget "CaptureBundle" */; + buildConfigurationList = 78A9DD6C2CD55551002C131D /* Build configuration list for PBXNativeTarget "mlchtCaptureBundle" */; buildPhases = ( 78A9DD5D2CD55551002C131D /* Sources */, 78A9DD5E2CD55551002C131D /* Frameworks */, @@ -753,14 +753,14 @@ fileSystemSynchronizedGroups = ( 786DAD982E4F283500BE3137 /* Assets */, ); - name = CaptureBundle; + name = mlchtCaptureBundle; productName = MalachiteCaptureBundle; - productReference = 78A9DD612CD55551002C131D /* CaptureBundle.appex */; + productReference = 78A9DD612CD55551002C131D /* mlchtCaptureBundle.appex */; productType = "com.apple.product-type.extensionkit-extension"; }; - 78B72BA62E33282E002E2D4E /* MalachiteWatch */ = { + 78B72BA62E33282E002E2D4E /* mlchtRemote */ = { isa = PBXNativeTarget; - buildConfigurationList = 78B72BB22E332830002E2D4E /* Build configuration list for PBXNativeTarget "MalachiteWatch" */; + buildConfigurationList = 78B72BB22E332830002E2D4E /* Build configuration list for PBXNativeTarget "mlchtRemote" */; buildPhases = ( 78B72BA32E33282E002E2D4E /* Sources */, 78B72BA42E33282E002E2D4E /* Frameworks */, @@ -777,9 +777,9 @@ fileSystemSynchronizedGroups = ( 786DAD982E4F283500BE3137 /* Assets */, ); - name = MalachiteWatch; + name = mlchtRemote; productName = "MalachiteWatch Watch App"; - productReference = 78B72BA72E33282E002E2D4E /* MalachiteWatch.app */; + productReference = 78B72BA72E33282E002E2D4E /* mlchtRemote.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -806,7 +806,7 @@ }; }; }; - buildConfigurationList = 785F08442B12D41100244EB4 /* Build configuration list for PBXProject "Malachite" */; + buildConfigurationList = 785F08442B12D41100244EB4 /* Build configuration list for PBXProject "mlchtCamera" */; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -819,11 +819,11 @@ projectDirPath = ""; projectRoot = ""; targets = ( - 785F08482B12D41100244EB4 /* Malachite */, - 78B72BA62E33282E002E2D4E /* MalachiteWatch */, - 78917F582B99AE72005E10FA /* WidgetBundle */, - 7837C1042E34C45B009396B0 /* WidgetBundleWatch */, - 78A9DD602CD55551002C131D /* CaptureBundle */, + 785F08482B12D41100244EB4 /* mlchtCamera */, + 78B72BA62E33282E002E2D4E /* mlchtRemote */, + 78917F582B99AE72005E10FA /* mlchtWidgetBundle */, + 7837C1042E34C45B009396B0 /* mlchtWidgetBundleWatch */, + 78A9DD602CD55551002C131D /* mlchtCaptureBundle */, ); }; /* End PBXProject section */ @@ -894,7 +894,7 @@ " echo \"Error: INTERNAL build type detected but Eva's GPG key was not found.\"", " echo \"Error: See 'Embed build information' in Build Phases for a solution\"", " # Be aware that I use the INTERNAL target to gate off any unstable/unready features. By removing this line, ", - " # you are acknowledging that you will not receive support for any issues encountered with Malachite until you ", + " # you are acknowledging that you will not receive support for any issues encountered with mlchtCamera until you ", " # are running DEBUG or RELEASE. If you just want a DEBUG build, change the Build Configuration to Debug in ", " # Product > Scheme > Edit Scheme...", " if [[ -z $(system_profiler SPHardwareDataType | grep 'MacVM1,1') ]]; then", @@ -992,7 +992,7 @@ " echo \"Error: INTERNAL build type detected but Eva's GPG key was not found.\"", " echo \"Error: See 'Embed build information' in Build Phases for a solution\"", " # Be aware that I use the INTERNAL target to gate off any unstable/unready features. By removing this line, ", - " # you are acknowledging that you will not receive support for any issues encountered with Malachite until you ", + " # you are acknowledging that you will not receive support for any issues encountered with mlchtCamera until you ", " # are running DEBUG or RELEASE. If you just want a DEBUG build, change the Build Configuration to Debug in ", " # Product > Scheme > Edit Scheme...", " if [[ -z $(system_profiler SPHardwareDataType | grep 'MacVM1,1') ]]; then", @@ -1123,7 +1123,7 @@ 786DAE362E4F28A600BE3137 /* CameraView.swift in Sources */, 785F084D2B12D41100244EB4 /* AppDelegate.swift in Sources */, 788262962E514F38000085AC /* QuickHelpView+Watermarking.swift in Sources */, - 787B1CAA2B8B095E000AFECC /* Malachite.docc in Sources */, + 787B1CAA2B8B095E000AFECC /* mlchtCamera.docc in Sources */, 7824CAF22E62E4970072870F /* Camera+Bringup.swift in Sources */, 788262662E51309C000085AC /* SettingsView+Preview.swift in Sources */, 788262E02E517252000085AC /* AboutView+Eggs.swift in Sources */, @@ -1271,33 +1271,33 @@ /* Begin PBXTargetDependency section */ 7837C1022E34BD60009396B0 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 78917F582B99AE72005E10FA /* WidgetBundle */; + target = 78917F582B99AE72005E10FA /* mlchtWidgetBundle */; targetProxy = 7837C1012E34BD60009396B0 /* PBXContainerItemProxy */; }; 7837C1192E34C47D009396B0 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 7837C1042E34C45B009396B0 /* WidgetBundleWatch */; + target = 7837C1042E34C45B009396B0 /* mlchtWidgetBundleWatch */; targetProxy = 7837C1182E34C47D009396B0 /* PBXContainerItemProxy */; }; 78917F682B99AE73005E10FA /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 78917F582B99AE72005E10FA /* WidgetBundle */; + target = 78917F582B99AE72005E10FA /* mlchtWidgetBundle */; targetProxy = 78917F672B99AE73005E10FA /* PBXContainerItemProxy */; }; 78A9DD692CD55551002C131D /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 78A9DD602CD55551002C131D /* CaptureBundle */; + target = 78A9DD602CD55551002C131D /* mlchtCaptureBundle */; targetProxy = 78A9DD682CD55551002C131D /* PBXContainerItemProxy */; }; 78B72BB02E332830002E2D4E /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 78B72BA62E33282E002E2D4E /* MalachiteWatch */; + target = 78B72BA62E33282E002E2D4E /* mlchtRemote */; targetProxy = 78B72BAF2E332830002E2D4E /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - 7837C1122E34C45B009396B0 /* Debug configuration for PBXNativeTarget "WidgetBundleWatch" */ = { + 7837C1122E34C45B009396B0 /* Debug configuration for PBXNativeTarget "mlchtWidgetBundleWatch" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1313,8 +1313,8 @@ EXCLUDED_ARCHS = arm64e; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = Malachite/WidgetBundle/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = MalachiteWidgetBundle; + INFOPLIST_FILE = mlchtCamera/WidgetBundle/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = mlchtWidgetBundle; INFOPLIST_KEY_NSHumanReadableCopyright = ""; IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -1324,7 +1324,7 @@ ); MARKETING_VERSION = 1.0.0; OTHER_SWIFT_FLAGS = "-DAPP_EXTENSION"; - PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).Malachite.WatchRemote.WidgetBundle"; + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).watchremote.widgetbundle"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = watchos; SKIP_INSTALL = YES; @@ -1341,7 +1341,7 @@ }; name = Debug; }; - 7837C1132E34C45B009396B0 /* Internal configuration for PBXNativeTarget "WidgetBundleWatch" */ = { + 7837C1132E34C45B009396B0 /* Internal configuration for PBXNativeTarget "mlchtWidgetBundleWatch" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1357,8 +1357,8 @@ EXCLUDED_ARCHS = arm64e; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = Malachite/WidgetBundle/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = MalachiteWidgetBundle; + INFOPLIST_FILE = mlchtCamera/WidgetBundle/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = mlchtWidgetBundle; INFOPLIST_KEY_NSHumanReadableCopyright = ""; IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -1368,7 +1368,7 @@ ); MARKETING_VERSION = 1.0.0; OTHER_SWIFT_FLAGS = "-DAPP_EXTENSION"; - PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).Malachite.WatchRemote.WidgetBundle"; + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).watchremote.widgetbundle"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = watchos; SKIP_INSTALL = YES; @@ -1385,7 +1385,7 @@ }; name = Internal; }; - 7837C1142E34C45B009396B0 /* Release configuration for PBXNativeTarget "WidgetBundleWatch" */ = { + 7837C1142E34C45B009396B0 /* Release configuration for PBXNativeTarget "mlchtWidgetBundleWatch" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1401,8 +1401,8 @@ EXCLUDED_ARCHS = arm64e; GCC_PREPROCESSOR_DEFINITIONS = ""; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = Malachite/WidgetBundle/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = MalachiteWidgetBundle; + INFOPLIST_FILE = mlchtCamera/WidgetBundle/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = mlchtWidgetBundle; INFOPLIST_KEY_NSHumanReadableCopyright = ""; IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -1412,7 +1412,7 @@ ); MARKETING_VERSION = 1.0.0; OTHER_SWIFT_FLAGS = "-DAPP_EXTENSION"; - PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).Malachite.WatchRemote.WidgetBundle"; + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).watchremote.widgetbundle"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = watchos; SKIP_INSTALL = YES; @@ -1429,7 +1429,7 @@ }; name = Release; }; - 785F085B2B12D41300244EB4 /* Debug configuration for PBXProject "Malachite" */ = { + 785F085B2B12D41300244EB4 /* Debug configuration for PBXProject "mlchtCamera" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1495,7 +1495,7 @@ }; name = Debug; }; - 785F085C2B12D41300244EB4 /* Release configuration for PBXProject "Malachite" */ = { + 785F085C2B12D41300244EB4 /* Release configuration for PBXProject "mlchtCamera" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1554,7 +1554,7 @@ }; name = Release; }; - 785F085E2B12D41300244EB4 /* Debug configuration for PBXNativeTarget "Malachite" */ = { + 785F085E2B12D41300244EB4 /* Debug configuration for PBXNativeTarget "mlchtCamera" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1563,7 +1563,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; - CODE_SIGN_ENTITLEMENTS = Malachite/Malachite.entitlements; + CODE_SIGN_ENTITLEMENTS = mlchtCamera/mlchtCamera.entitlements; CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; @@ -1575,13 +1575,13 @@ "$(inherited)", ); GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = Malachite/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = Malachite; + INFOPLIST_FILE = mlchtCamera/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = mlchtCamera; INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.photography"; INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; - INFOPLIST_KEY_NSCameraUsageDescription = "Malachite uses the rear camera system on iPhone to present your magnified view."; - INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Malachite uses write access to your library in order to save photos."; + INFOPLIST_KEY_NSCameraUsageDescription = "mlchtCamera uses the rear camera system on iPhone to present your magnified view."; + INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "mlchtCamera uses write access to your library in order to save photos."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UIMainStoryboardFile = ""; INFOPLIST_KEY_UIRequiresFullScreen = NO; @@ -1597,7 +1597,7 @@ ); MARKETING_VERSION = 1.0.0; OTHER_SWIFT_FLAGS = ""; - PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).Malachite"; + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX)"; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; @@ -1606,14 +1606,14 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "MAIN_APP $(inherited)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = ""; - SWIFT_OBJC_INTERFACE_HEADER_NAME = "Malachite-Swift.h"; + SWIFT_OBJC_INTERFACE_HEADER_NAME = "mlchtCamera-Swift.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; - 785F085F2B12D41300244EB4 /* Release configuration for PBXNativeTarget "Malachite" */ = { + 785F085F2B12D41300244EB4 /* Release configuration for PBXNativeTarget "mlchtCamera" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1622,7 +1622,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; - CODE_SIGN_ENTITLEMENTS = Malachite/Malachite.entitlements; + CODE_SIGN_ENTITLEMENTS = mlchtCamera/mlchtCamera.entitlements; CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; @@ -1631,13 +1631,13 @@ ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_PREPROCESSOR_DEFINITIONS = "CFBUILDTYPE=RELEASE"; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = Malachite/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = Malachite; + INFOPLIST_FILE = mlchtCamera/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = mlchtCamera; INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.photography"; INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; - INFOPLIST_KEY_NSCameraUsageDescription = "Malachite uses the rear camera system on iPhone to present your magnified view."; - INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Malachite uses write access to your library in order to save photos."; + INFOPLIST_KEY_NSCameraUsageDescription = "mlchtCamera uses the rear camera system on iPhone to present your magnified view."; + INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "mlchtCamera uses write access to your library in order to save photos."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UIMainStoryboardFile = ""; INFOPLIST_KEY_UIRequiresFullScreen = NO; @@ -1653,7 +1653,7 @@ ); MARKETING_VERSION = 1.0.0; OTHER_SWIFT_FLAGS = ""; - PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).Malachite"; + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX)"; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; @@ -1662,14 +1662,14 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = MAIN_APP; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = ""; - SWIFT_OBJC_INTERFACE_HEADER_NAME = "Malachite-Swift.h"; + SWIFT_OBJC_INTERFACE_HEADER_NAME = "mlchtCamera-Swift.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; - 78917F6B2B99AE73005E10FA /* Debug configuration for PBXNativeTarget "WidgetBundle" */ = { + 78917F6B2B99AE73005E10FA /* Debug configuration for PBXNativeTarget "mlchtWidgetBundle" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1684,8 +1684,8 @@ ENABLE_C_BOUNDS_SAFETY = YES; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = Malachite/WidgetBundle/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = MalachiteWidgetBundle; + INFOPLIST_FILE = mlchtCamera/WidgetBundle/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = mlchtWidgetBundle; INFOPLIST_KEY_NSHumanReadableCopyright = ""; IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -1695,7 +1695,7 @@ ); MARKETING_VERSION = 1.0.0; OTHER_SWIFT_FLAGS = ""; - PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).Malachite.WidgetBundle"; + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).widgetbundle"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; @@ -1711,7 +1711,7 @@ }; name = Debug; }; - 78917F6C2B99AE73005E10FA /* Release configuration for PBXNativeTarget "WidgetBundle" */ = { + 78917F6C2B99AE73005E10FA /* Release configuration for PBXNativeTarget "mlchtWidgetBundle" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1726,8 +1726,8 @@ ENABLE_C_BOUNDS_SAFETY = YES; GCC_PREPROCESSOR_DEFINITIONS = ""; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = Malachite/WidgetBundle/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = MalachiteWidgetBundle; + INFOPLIST_FILE = mlchtCamera/WidgetBundle/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = mlchtWidgetBundle; INFOPLIST_KEY_NSHumanReadableCopyright = ""; IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -1737,7 +1737,7 @@ ); MARKETING_VERSION = 1.0.0; OTHER_SWIFT_FLAGS = ""; - PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).Malachite.WidgetBundle"; + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).widgetbundle"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; @@ -1753,7 +1753,7 @@ }; name = Release; }; - 78A9DD6D2CD55551002C131D /* Debug configuration for PBXNativeTarget "CaptureBundle" */ = { + 78A9DD6D2CD55551002C131D /* Debug configuration for PBXNativeTarget "mlchtCaptureBundle" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1765,11 +1765,11 @@ ENABLE_CPLUSPLUS_BOUNDS_SAFE_BUFFERS = YES; ENABLE_C_BOUNDS_SAFETY = YES; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = Malachite/CaptureBundle/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = MalachiteCaptureBundle; - INFOPLIST_KEY_NSCameraUsageDescription = "Malachite uses the rear camera system on iPhone to present your magnified view."; + INFOPLIST_FILE = mlchtCamera/CaptureBundle/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = mlchtCaptureBundle; + INFOPLIST_KEY_NSCameraUsageDescription = "mlchtCamera uses the rear camera system on iPhone to present your magnified view."; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Malachite uses write access to your library in order to save photos."; + INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "mlchtCamera uses write access to your library in order to save photos."; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 18.0; @@ -1779,7 +1779,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).Malachite.CaptureBundle"; + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).capturebundle"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; @@ -1793,7 +1793,7 @@ }; name = Debug; }; - 78A9DD6E2CD55551002C131D /* Internal configuration for PBXNativeTarget "CaptureBundle" */ = { + 78A9DD6E2CD55551002C131D /* Internal configuration for PBXNativeTarget "mlchtCaptureBundle" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1805,11 +1805,11 @@ ENABLE_CPLUSPLUS_BOUNDS_SAFE_BUFFERS = YES; ENABLE_C_BOUNDS_SAFETY = YES; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = Malachite/CaptureBundle/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = MalachiteCaptureBundle; - INFOPLIST_KEY_NSCameraUsageDescription = "Malachite uses the rear camera system on iPhone to present your magnified view."; + INFOPLIST_FILE = mlchtCamera/CaptureBundle/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = mlchtCaptureBundle; + INFOPLIST_KEY_NSCameraUsageDescription = "mlchtCamera uses the rear camera system on iPhone to present your magnified view."; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Malachite uses write access to your library in order to save photos."; + INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "mlchtCamera uses write access to your library in order to save photos."; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 18.0; @@ -1819,7 +1819,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).Malachite.CaptureBundle"; + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).capturebundle"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; @@ -1833,7 +1833,7 @@ }; name = Internal; }; - 78A9DD6F2CD55551002C131D /* Release configuration for PBXNativeTarget "CaptureBundle" */ = { + 78A9DD6F2CD55551002C131D /* Release configuration for PBXNativeTarget "mlchtCaptureBundle" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1845,11 +1845,11 @@ ENABLE_CPLUSPLUS_BOUNDS_SAFE_BUFFERS = YES; ENABLE_C_BOUNDS_SAFETY = YES; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = Malachite/CaptureBundle/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = MalachiteCaptureBundle; - INFOPLIST_KEY_NSCameraUsageDescription = "Malachite uses the rear camera system on iPhone to present your magnified view."; + INFOPLIST_FILE = mlchtCamera/CaptureBundle/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = mlchtCaptureBundle; + INFOPLIST_KEY_NSCameraUsageDescription = "mlchtCamera uses the rear camera system on iPhone to present your magnified view."; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Malachite uses write access to your library in order to save photos."; + INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "mlchtCamera uses write access to your library in order to save photos."; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 18.0; @@ -1859,7 +1859,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).Malachite.CaptureBundle"; + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).capturebundle"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; @@ -1873,7 +1873,7 @@ }; name = Release; }; - 78B72BB32E332830002E2D4E /* Debug configuration for PBXNativeTarget "MalachiteWatch" */ = { + 78B72BB32E332830002E2D4E /* Debug configuration for PBXNativeTarget "mlchtRemote" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1890,16 +1890,16 @@ ENABLE_USER_SCRIPT_SANDBOXING = NO; EXCLUDED_ARCHS = arm64e; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = MalachiteWatch/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = "Malachite Remote"; + INFOPLIST_FILE = mlchtRemote/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = mlchtRemote; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; - INFOPLIST_KEY_WKCompanionAppBundleIdentifier = "$(APP_PREFIX).Malachite"; + INFOPLIST_KEY_WKCompanionAppBundleIdentifier = "$(APP_PREFIX)"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).Malachite.WatchRemote"; + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).watchremote"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = watchos; SKIP_INSTALL = YES; @@ -1915,7 +1915,7 @@ }; name = Debug; }; - 78B72BB42E332830002E2D4E /* Internal configuration for PBXNativeTarget "MalachiteWatch" */ = { + 78B72BB42E332830002E2D4E /* Internal configuration for PBXNativeTarget "mlchtRemote" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1932,16 +1932,16 @@ ENABLE_USER_SCRIPT_SANDBOXING = NO; EXCLUDED_ARCHS = arm64e; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = MalachiteWatch/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = "Malachite Remote"; + INFOPLIST_FILE = mlchtRemote/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = mlchtRemote; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; - INFOPLIST_KEY_WKCompanionAppBundleIdentifier = "$(APP_PREFIX).Malachite"; + INFOPLIST_KEY_WKCompanionAppBundleIdentifier = "$(APP_PREFIX)"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).Malachite.WatchRemote"; + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).watchremote"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = watchos; SKIP_INSTALL = YES; @@ -1957,7 +1957,7 @@ }; name = Internal; }; - 78B72BB52E332830002E2D4E /* Release configuration for PBXNativeTarget "MalachiteWatch" */ = { + 78B72BB52E332830002E2D4E /* Release configuration for PBXNativeTarget "mlchtRemote" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -1974,16 +1974,16 @@ ENABLE_USER_SCRIPT_SANDBOXING = NO; EXCLUDED_ARCHS = arm64e; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = MalachiteWatch/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = "Malachite Remote"; + INFOPLIST_FILE = mlchtRemote/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = mlchtRemote; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; - INFOPLIST_KEY_WKCompanionAppBundleIdentifier = "$(APP_PREFIX).Malachite"; + INFOPLIST_KEY_WKCompanionAppBundleIdentifier = "$(APP_PREFIX)"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).Malachite.WatchRemote"; + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).watchremote"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = watchos; SKIP_INSTALL = YES; @@ -1999,7 +1999,7 @@ }; name = Release; }; - 78F9DA642CBCDDB900A99638 /* Internal configuration for PBXProject "Malachite" */ = { + 78F9DA642CBCDDB900A99638 /* Internal configuration for PBXProject "mlchtCamera" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -2066,7 +2066,7 @@ }; name = Internal; }; - 78F9DA652CBCDDB900A99638 /* Internal configuration for PBXNativeTarget "Malachite" */ = { + 78F9DA652CBCDDB900A99638 /* Internal configuration for PBXNativeTarget "mlchtCamera" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -2075,7 +2075,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; - CODE_SIGN_ENTITLEMENTS = Malachite/Malachite.entitlements; + CODE_SIGN_ENTITLEMENTS = mlchtCamera/mlchtCamera.entitlements; CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; @@ -2087,13 +2087,13 @@ "$(inherited)", ); GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = Malachite/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = Malachite; + INFOPLIST_FILE = mlchtCamera/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = mlchtCamera; INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.photography"; INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; - INFOPLIST_KEY_NSCameraUsageDescription = "Malachite uses the rear camera system on iPhone to present your magnified view."; - INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Malachite uses write access to your library in order to save photos."; + INFOPLIST_KEY_NSCameraUsageDescription = "mlchtCamera uses the rear camera system on iPhone to present your magnified view."; + INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "mlchtCamera uses write access to your library in order to save photos."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UIMainStoryboardFile = ""; INFOPLIST_KEY_UIRequiresFullScreen = NO; @@ -2109,7 +2109,7 @@ ); MARKETING_VERSION = 1.0.0; OTHER_SWIFT_FLAGS = ""; - PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).Malachite"; + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX)"; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; @@ -2118,14 +2118,14 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "MAIN_APP $(inherited)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = ""; - SWIFT_OBJC_INTERFACE_HEADER_NAME = "Malachite-Swift.h"; + SWIFT_OBJC_INTERFACE_HEADER_NAME = "mlchtCamera-Swift.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Internal; }; - 78F9DA662CBCDDB900A99638 /* Internal configuration for PBXNativeTarget "WidgetBundle" */ = { + 78F9DA662CBCDDB900A99638 /* Internal configuration for PBXNativeTarget "mlchtWidgetBundle" */ = { isa = XCBuildConfiguration; baseConfigurationReference = 78E142C92DD426260016B3DB /* Codesigning.xcconfig */; buildSettings = { @@ -2140,8 +2140,8 @@ ENABLE_C_BOUNDS_SAFETY = YES; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = Malachite/WidgetBundle/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = MalachiteWidgetBundle; + INFOPLIST_FILE = mlchtCamera/WidgetBundle/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = mlchtWidgetBundle; INFOPLIST_KEY_NSHumanReadableCopyright = ""; IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -2151,7 +2151,7 @@ ); MARKETING_VERSION = 1.0.0; OTHER_SWIFT_FLAGS = ""; - PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).Malachite.WidgetBundle"; + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PREFIX).widgetbundle"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; @@ -2170,57 +2170,57 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 7837C1112E34C45B009396B0 /* Build configuration list for PBXNativeTarget "WidgetBundleWatch" */ = { + 7837C1112E34C45B009396B0 /* Build configuration list for PBXNativeTarget "mlchtWidgetBundleWatch" */ = { isa = XCConfigurationList; buildConfigurations = ( - 7837C1122E34C45B009396B0 /* Debug configuration for PBXNativeTarget "WidgetBundleWatch" */, - 7837C1132E34C45B009396B0 /* Internal configuration for PBXNativeTarget "WidgetBundleWatch" */, - 7837C1142E34C45B009396B0 /* Release configuration for PBXNativeTarget "WidgetBundleWatch" */, + 7837C1122E34C45B009396B0 /* Debug configuration for PBXNativeTarget "mlchtWidgetBundleWatch" */, + 7837C1132E34C45B009396B0 /* Internal configuration for PBXNativeTarget "mlchtWidgetBundleWatch" */, + 7837C1142E34C45B009396B0 /* Release configuration for PBXNativeTarget "mlchtWidgetBundleWatch" */, ); defaultConfigurationName = Internal; }; - 785F08442B12D41100244EB4 /* Build configuration list for PBXProject "Malachite" */ = { + 785F08442B12D41100244EB4 /* Build configuration list for PBXProject "mlchtCamera" */ = { isa = XCConfigurationList; buildConfigurations = ( - 785F085B2B12D41300244EB4 /* Debug configuration for PBXProject "Malachite" */, - 78F9DA642CBCDDB900A99638 /* Internal configuration for PBXProject "Malachite" */, - 785F085C2B12D41300244EB4 /* Release configuration for PBXProject "Malachite" */, + 785F085B2B12D41300244EB4 /* Debug configuration for PBXProject "mlchtCamera" */, + 78F9DA642CBCDDB900A99638 /* Internal configuration for PBXProject "mlchtCamera" */, + 785F085C2B12D41300244EB4 /* Release configuration for PBXProject "mlchtCamera" */, ); defaultConfigurationName = Internal; }; - 785F085D2B12D41300244EB4 /* Build configuration list for PBXNativeTarget "Malachite" */ = { + 785F085D2B12D41300244EB4 /* Build configuration list for PBXNativeTarget "mlchtCamera" */ = { isa = XCConfigurationList; buildConfigurations = ( - 785F085E2B12D41300244EB4 /* Debug configuration for PBXNativeTarget "Malachite" */, - 78F9DA652CBCDDB900A99638 /* Internal configuration for PBXNativeTarget "Malachite" */, - 785F085F2B12D41300244EB4 /* Release configuration for PBXNativeTarget "Malachite" */, + 785F085E2B12D41300244EB4 /* Debug configuration for PBXNativeTarget "mlchtCamera" */, + 78F9DA652CBCDDB900A99638 /* Internal configuration for PBXNativeTarget "mlchtCamera" */, + 785F085F2B12D41300244EB4 /* Release configuration for PBXNativeTarget "mlchtCamera" */, ); defaultConfigurationName = Internal; }; - 78917F6D2B99AE73005E10FA /* Build configuration list for PBXNativeTarget "WidgetBundle" */ = { + 78917F6D2B99AE73005E10FA /* Build configuration list for PBXNativeTarget "mlchtWidgetBundle" */ = { isa = XCConfigurationList; buildConfigurations = ( - 78917F6B2B99AE73005E10FA /* Debug configuration for PBXNativeTarget "WidgetBundle" */, - 78F9DA662CBCDDB900A99638 /* Internal configuration for PBXNativeTarget "WidgetBundle" */, - 78917F6C2B99AE73005E10FA /* Release configuration for PBXNativeTarget "WidgetBundle" */, + 78917F6B2B99AE73005E10FA /* Debug configuration for PBXNativeTarget "mlchtWidgetBundle" */, + 78F9DA662CBCDDB900A99638 /* Internal configuration for PBXNativeTarget "mlchtWidgetBundle" */, + 78917F6C2B99AE73005E10FA /* Release configuration for PBXNativeTarget "mlchtWidgetBundle" */, ); defaultConfigurationName = Internal; }; - 78A9DD6C2CD55551002C131D /* Build configuration list for PBXNativeTarget "CaptureBundle" */ = { + 78A9DD6C2CD55551002C131D /* Build configuration list for PBXNativeTarget "mlchtCaptureBundle" */ = { isa = XCConfigurationList; buildConfigurations = ( - 78A9DD6D2CD55551002C131D /* Debug configuration for PBXNativeTarget "CaptureBundle" */, - 78A9DD6E2CD55551002C131D /* Internal configuration for PBXNativeTarget "CaptureBundle" */, - 78A9DD6F2CD55551002C131D /* Release configuration for PBXNativeTarget "CaptureBundle" */, + 78A9DD6D2CD55551002C131D /* Debug configuration for PBXNativeTarget "mlchtCaptureBundle" */, + 78A9DD6E2CD55551002C131D /* Internal configuration for PBXNativeTarget "mlchtCaptureBundle" */, + 78A9DD6F2CD55551002C131D /* Release configuration for PBXNativeTarget "mlchtCaptureBundle" */, ); defaultConfigurationName = Internal; }; - 78B72BB22E332830002E2D4E /* Build configuration list for PBXNativeTarget "MalachiteWatch" */ = { + 78B72BB22E332830002E2D4E /* Build configuration list for PBXNativeTarget "mlchtRemote" */ = { isa = XCConfigurationList; buildConfigurations = ( - 78B72BB32E332830002E2D4E /* Debug configuration for PBXNativeTarget "MalachiteWatch" */, - 78B72BB42E332830002E2D4E /* Internal configuration for PBXNativeTarget "MalachiteWatch" */, - 78B72BB52E332830002E2D4E /* Release configuration for PBXNativeTarget "MalachiteWatch" */, + 78B72BB32E332830002E2D4E /* Debug configuration for PBXNativeTarget "mlchtRemote" */, + 78B72BB42E332830002E2D4E /* Internal configuration for PBXNativeTarget "mlchtRemote" */, + 78B72BB52E332830002E2D4E /* Release configuration for PBXNativeTarget "mlchtRemote" */, ); defaultConfigurationName = Internal; }; diff --git a/Malachite.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/mlchtCamera.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from Malachite.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to mlchtCamera.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/Malachite.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mlchtCamera.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from Malachite.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to mlchtCamera.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/Malachite.xcodeproj/xcshareddata/xcschemes/Malachite.xcscheme b/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtCamera.xcscheme old mode 100755 new mode 100644 similarity index 83% rename from Malachite.xcodeproj/xcshareddata/xcschemes/Malachite.xcscheme rename to mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtCamera.xcscheme index 32432b4..17f3f4d --- a/Malachite.xcodeproj/xcshareddata/xcschemes/Malachite.xcscheme +++ b/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtCamera.xcscheme @@ -16,9 +16,9 @@ + BuildableName = "mlchtCamera.app" + BlueprintName = "mlchtCamera" + ReferencedContainer = "container:mlchtCamera.xcodeproj"> @@ -45,9 +45,9 @@ + BuildableName = "mlchtCamera.app" + BlueprintName = "mlchtCamera" + ReferencedContainer = "container:mlchtCamera.xcodeproj"> @@ -62,9 +62,9 @@ + BuildableName = "mlchtCamera.app" + BlueprintName = "mlchtCamera" + ReferencedContainer = "container:mlchtCamera.xcodeproj"> diff --git a/Malachite.xcodeproj/xcshareddata/xcschemes/CaptureBundle.xcscheme b/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtCaptureBundle.xcscheme similarity index 81% rename from Malachite.xcodeproj/xcshareddata/xcschemes/CaptureBundle.xcscheme rename to mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtCaptureBundle.xcscheme index b3a4a83..d04af59 100644 --- a/Malachite.xcodeproj/xcshareddata/xcschemes/CaptureBundle.xcscheme +++ b/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtCaptureBundle.xcscheme @@ -17,9 +17,9 @@ + BuildableName = "mlchtCaptureBundle.appex" + BlueprintName = "mlchtCaptureBundle" + ReferencedContainer = "container:mlchtCamera.xcodeproj"> + BuildableName = "mlchtCamera.app" + BlueprintName = "mlchtCamera" + ReferencedContainer = "container:mlchtCamera.xcodeproj"> @@ -62,9 +62,9 @@ + BuildableName = "mlchtCamera.app" + BlueprintName = "mlchtCamera" + ReferencedContainer = "container:mlchtCamera.xcodeproj"> @@ -81,9 +81,9 @@ + BuildableName = "mlchtCamera.app" + BlueprintName = "mlchtCamera" + ReferencedContainer = "container:mlchtCamera.xcodeproj"> diff --git a/Malachite.xcodeproj/xcshareddata/xcschemes/MalachiteWatch.xcscheme b/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtRemote.xcscheme similarity index 81% rename from Malachite.xcodeproj/xcshareddata/xcschemes/MalachiteWatch.xcscheme rename to mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtRemote.xcscheme index 9ff921b..56daff8 100644 --- a/Malachite.xcodeproj/xcshareddata/xcschemes/MalachiteWatch.xcscheme +++ b/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtRemote.xcscheme @@ -16,9 +16,9 @@ + BuildableName = "mlchtRemote.app" + BlueprintName = "mlchtRemote" + ReferencedContainer = "container:mlchtCamera.xcodeproj"> + BuildableName = "mlchtCamera.app" + BlueprintName = "mlchtCamera" + ReferencedContainer = "container:mlchtCamera.xcodeproj"> @@ -59,9 +59,9 @@ + BuildableName = "mlchtRemote.app" + BlueprintName = "mlchtRemote" + ReferencedContainer = "container:mlchtCamera.xcodeproj"> @@ -76,9 +76,9 @@ + BuildableName = "mlchtRemote.app" + BlueprintName = "mlchtRemote" + ReferencedContainer = "container:mlchtCamera.xcodeproj"> diff --git a/Malachite.xcodeproj/xcshareddata/xcschemes/WidgetBundle.xcscheme b/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtWidgetBundle.xcscheme old mode 100755 new mode 100644 similarity index 81% rename from Malachite.xcodeproj/xcshareddata/xcschemes/WidgetBundle.xcscheme rename to mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtWidgetBundle.xcscheme index 8541a26..ad20c9e --- a/Malachite.xcodeproj/xcshareddata/xcschemes/WidgetBundle.xcscheme +++ b/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtWidgetBundle.xcscheme @@ -17,9 +17,9 @@ + BuildableName = "mlchtWidgetBundle.appex" + BlueprintName = "mlchtWidgetBundle" + ReferencedContainer = "container:mlchtCamera.xcodeproj"> + BuildableName = "mlchtCamera.app" + BlueprintName = "mlchtCamera" + ReferencedContainer = "container:mlchtCamera.xcodeproj"> @@ -62,9 +62,9 @@ + BuildableName = "mlchtCamera.app" + BlueprintName = "mlchtCamera" + ReferencedContainer = "container:mlchtCamera.xcodeproj"> @@ -81,9 +81,9 @@ + BuildableName = "mlchtCamera.app" + BlueprintName = "mlchtCamera" + ReferencedContainer = "container:mlchtCamera.xcodeproj"> diff --git a/Malachite/AppDelegate.swift b/mlchtCamera/AppDelegate.swift similarity index 100% rename from Malachite/AppDelegate.swift rename to mlchtCamera/AppDelegate.swift diff --git a/Malachite/Assets/AppIcon.icon/Assets/0.5.png b/mlchtCamera/Assets/AppIcon.icon/Assets/0.5.png similarity index 100% rename from Malachite/Assets/AppIcon.icon/Assets/0.5.png rename to mlchtCamera/Assets/AppIcon.icon/Assets/0.5.png diff --git a/Malachite/Assets/AppIcon.icon/Assets/0.75.png b/mlchtCamera/Assets/AppIcon.icon/Assets/0.75.png similarity index 100% rename from Malachite/Assets/AppIcon.icon/Assets/0.75.png rename to mlchtCamera/Assets/AppIcon.icon/Assets/0.75.png diff --git a/Malachite/Assets/AppIcon.icon/Assets/1.png b/mlchtCamera/Assets/AppIcon.icon/Assets/1.png similarity index 100% rename from Malachite/Assets/AppIcon.icon/Assets/1.png rename to mlchtCamera/Assets/AppIcon.icon/Assets/1.png diff --git a/Malachite/Assets/AppIcon.icon/Assets/Circle.svg b/mlchtCamera/Assets/AppIcon.icon/Assets/Circle.svg similarity index 100% rename from Malachite/Assets/AppIcon.icon/Assets/Circle.svg rename to mlchtCamera/Assets/AppIcon.icon/Assets/Circle.svg diff --git a/Malachite/Assets/AppIcon.icon/Assets/SpokesAndCircle.svg b/mlchtCamera/Assets/AppIcon.icon/Assets/SpokesAndCircle.svg similarity index 100% rename from Malachite/Assets/AppIcon.icon/Assets/SpokesAndCircle.svg rename to mlchtCamera/Assets/AppIcon.icon/Assets/SpokesAndCircle.svg diff --git a/Malachite/Assets/AppIcon.icon/icon.json b/mlchtCamera/Assets/AppIcon.icon/icon.json similarity index 100% rename from Malachite/Assets/AppIcon.icon/icon.json rename to mlchtCamera/Assets/AppIcon.icon/icon.json diff --git a/Malachite/Assets/Assets.xcassets/AccentColor.colorset/Contents.json b/mlchtCamera/Assets/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from Malachite/Assets/Assets.xcassets/AccentColor.colorset/Contents.json rename to mlchtCamera/Assets/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/Malachite/Assets/Assets.xcassets/AppIcon.appiconset/Contents.json b/mlchtCamera/Assets/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from Malachite/Assets/Assets.xcassets/AppIcon.appiconset/Contents.json rename to mlchtCamera/Assets/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/Malachite/Assets/Assets.xcassets/AppIcon.appiconset/MalachiteIcon-Dark.png b/mlchtCamera/Assets/Assets.xcassets/AppIcon.appiconset/MalachiteIcon-Dark.png similarity index 100% rename from Malachite/Assets/Assets.xcassets/AppIcon.appiconset/MalachiteIcon-Dark.png rename to mlchtCamera/Assets/Assets.xcassets/AppIcon.appiconset/MalachiteIcon-Dark.png diff --git a/Malachite/Assets/Assets.xcassets/AppIcon.appiconset/MalachiteIcon-Tinted.png b/mlchtCamera/Assets/Assets.xcassets/AppIcon.appiconset/MalachiteIcon-Tinted.png similarity index 100% rename from Malachite/Assets/Assets.xcassets/AppIcon.appiconset/MalachiteIcon-Tinted.png rename to mlchtCamera/Assets/Assets.xcassets/AppIcon.appiconset/MalachiteIcon-Tinted.png diff --git a/Malachite/Assets/Assets.xcassets/AppIcon.appiconset/MalachiteIcon.png b/mlchtCamera/Assets/Assets.xcassets/AppIcon.appiconset/MalachiteIcon.png similarity index 100% rename from Malachite/Assets/Assets.xcassets/AppIcon.appiconset/MalachiteIcon.png rename to mlchtCamera/Assets/Assets.xcassets/AppIcon.appiconset/MalachiteIcon.png diff --git a/Malachite/Assets/Assets.xcassets/AppIcon.appiconset/MalachiteIconWatch.png b/mlchtCamera/Assets/Assets.xcassets/AppIcon.appiconset/MalachiteIconWatch.png similarity index 100% rename from Malachite/Assets/Assets.xcassets/AppIcon.appiconset/MalachiteIconWatch.png rename to mlchtCamera/Assets/Assets.xcassets/AppIcon.appiconset/MalachiteIconWatch.png diff --git a/Malachite/Assets/Assets.xcassets/Contents.json b/mlchtCamera/Assets/Assets.xcassets/Contents.json similarity index 100% rename from Malachite/Assets/Assets.xcassets/Contents.json rename to mlchtCamera/Assets/Assets.xcassets/Contents.json diff --git a/Malachite/Assets/Assets.xcassets/WidgetBackground.colorset/Contents.json b/mlchtCamera/Assets/Assets.xcassets/WidgetBackground.colorset/Contents.json similarity index 100% rename from Malachite/Assets/Assets.xcassets/WidgetBackground.colorset/Contents.json rename to mlchtCamera/Assets/Assets.xcassets/WidgetBackground.colorset/Contents.json diff --git a/Malachite/Assets/Assets.xcassets/asb_approved.appiconset/Contents.json b/mlchtCamera/Assets/Assets.xcassets/asb_approved.appiconset/Contents.json similarity index 100% rename from Malachite/Assets/Assets.xcassets/asb_approved.appiconset/Contents.json rename to mlchtCamera/Assets/Assets.xcassets/asb_approved.appiconset/Contents.json diff --git a/Malachite/Assets/Assets.xcassets/asb_approved.appiconset/asbapproved.png b/mlchtCamera/Assets/Assets.xcassets/asb_approved.appiconset/asbapproved.png similarity index 100% rename from Malachite/Assets/Assets.xcassets/asb_approved.appiconset/asbapproved.png rename to mlchtCamera/Assets/Assets.xcassets/asb_approved.appiconset/asbapproved.png diff --git a/Malachite/Assets/Assets.xcassets/asentientbot.imageset/57969415.jpeg b/mlchtCamera/Assets/Assets.xcassets/asentientbot.imageset/57969415.jpeg similarity index 100% rename from Malachite/Assets/Assets.xcassets/asentientbot.imageset/57969415.jpeg rename to mlchtCamera/Assets/Assets.xcassets/asentientbot.imageset/57969415.jpeg diff --git a/Malachite/Assets/Assets.xcassets/asentientbot.imageset/Contents.json b/mlchtCamera/Assets/Assets.xcassets/asentientbot.imageset/Contents.json similarity index 100% rename from Malachite/Assets/Assets.xcassets/asentientbot.imageset/Contents.json rename to mlchtCamera/Assets/Assets.xcassets/asentientbot.imageset/Contents.json diff --git a/Malachite/Assets/Assets.xcassets/crystall1nedev.imageset/55281754.png b/mlchtCamera/Assets/Assets.xcassets/crystall1nedev.imageset/55281754.png similarity index 100% rename from Malachite/Assets/Assets.xcassets/crystall1nedev.imageset/55281754.png rename to mlchtCamera/Assets/Assets.xcassets/crystall1nedev.imageset/55281754.png diff --git a/Malachite/Assets/Assets.xcassets/crystall1nedev.imageset/Contents.json b/mlchtCamera/Assets/Assets.xcassets/crystall1nedev.imageset/Contents.json similarity index 100% rename from Malachite/Assets/Assets.xcassets/crystall1nedev.imageset/Contents.json rename to mlchtCamera/Assets/Assets.xcassets/crystall1nedev.imageset/Contents.json diff --git a/Malachite/Assets/Assets.xcassets/icon.imageset/Contents.json b/mlchtCamera/Assets/Assets.xcassets/icon.imageset/Contents.json similarity index 100% rename from Malachite/Assets/Assets.xcassets/icon.imageset/Contents.json rename to mlchtCamera/Assets/Assets.xcassets/icon.imageset/Contents.json diff --git a/Malachite/Assets/Assets.xcassets/icon.imageset/MalachiteIconMasked_Darwin24.png b/mlchtCamera/Assets/Assets.xcassets/icon.imageset/MalachiteIconMasked_Darwin24.png similarity index 100% rename from Malachite/Assets/Assets.xcassets/icon.imageset/MalachiteIconMasked_Darwin24.png rename to mlchtCamera/Assets/Assets.xcassets/icon.imageset/MalachiteIconMasked_Darwin24.png diff --git a/Malachite/Assets/Assets.xcassets/icon26.imageset/Contents.json b/mlchtCamera/Assets/Assets.xcassets/icon26.imageset/Contents.json similarity index 100% rename from Malachite/Assets/Assets.xcassets/icon26.imageset/Contents.json rename to mlchtCamera/Assets/Assets.xcassets/icon26.imageset/Contents.json diff --git a/Malachite/Assets/Assets.xcassets/icon26.imageset/MalachiteIconMasked_Darwin25.png b/mlchtCamera/Assets/Assets.xcassets/icon26.imageset/MalachiteIconMasked_Darwin25.png similarity index 100% rename from Malachite/Assets/Assets.xcassets/icon26.imageset/MalachiteIconMasked_Darwin25.png rename to mlchtCamera/Assets/Assets.xcassets/icon26.imageset/MalachiteIconMasked_Darwin25.png diff --git a/Malachite/Assets/Assets.xcassets/thatsniceguy.appiconset/Contents.json b/mlchtCamera/Assets/Assets.xcassets/thatsniceguy.appiconset/Contents.json similarity index 100% rename from Malachite/Assets/Assets.xcassets/thatsniceguy.appiconset/Contents.json rename to mlchtCamera/Assets/Assets.xcassets/thatsniceguy.appiconset/Contents.json diff --git a/Malachite/Assets/Assets.xcassets/thatsniceguy.appiconset/Icon1024x1024.png b/mlchtCamera/Assets/Assets.xcassets/thatsniceguy.appiconset/Icon1024x1024.png similarity index 100% rename from Malachite/Assets/Assets.xcassets/thatsniceguy.appiconset/Icon1024x1024.png rename to mlchtCamera/Assets/Assets.xcassets/thatsniceguy.appiconset/Icon1024x1024.png diff --git a/Malachite/Assets/Assets.xcassets/thatstella7922.imageset/10524728.png b/mlchtCamera/Assets/Assets.xcassets/thatstella7922.imageset/10524728.png similarity index 100% rename from Malachite/Assets/Assets.xcassets/thatstella7922.imageset/10524728.png rename to mlchtCamera/Assets/Assets.xcassets/thatstella7922.imageset/10524728.png diff --git a/Malachite/Assets/Assets.xcassets/thatstella7922.imageset/Contents.json b/mlchtCamera/Assets/Assets.xcassets/thatstella7922.imageset/Contents.json similarity index 100% rename from Malachite/Assets/Assets.xcassets/thatstella7922.imageset/Contents.json rename to mlchtCamera/Assets/Assets.xcassets/thatstella7922.imageset/Contents.json diff --git a/Malachite/Assets/MalachiteIconMasked_Darwin24.png b/mlchtCamera/Assets/MalachiteIconMasked_Darwin24.png similarity index 100% rename from Malachite/Assets/MalachiteIconMasked_Darwin24.png rename to mlchtCamera/Assets/MalachiteIconMasked_Darwin24.png diff --git a/Malachite/Assets/MalachiteIconMasked_Darwin25.png b/mlchtCamera/Assets/MalachiteIconMasked_Darwin25.png similarity index 100% rename from Malachite/Assets/MalachiteIconMasked_Darwin25.png rename to mlchtCamera/Assets/MalachiteIconMasked_Darwin25.png diff --git a/Malachite/CaptureBundle/Info.plist b/mlchtCamera/CaptureBundle/Info.plist similarity index 100% rename from Malachite/CaptureBundle/Info.plist rename to mlchtCamera/CaptureBundle/Info.plist diff --git a/Malachite/CaptureBundle/MalachiteCaptureBundle.swift b/mlchtCamera/CaptureBundle/MalachiteCaptureBundle.swift similarity index 100% rename from Malachite/CaptureBundle/MalachiteCaptureBundle.swift rename to mlchtCamera/CaptureBundle/MalachiteCaptureBundle.swift diff --git a/Malachite/Codesigning.example.xcconfig b/mlchtCamera/Codesigning.example.xcconfig similarity index 100% rename from Malachite/Codesigning.example.xcconfig rename to mlchtCamera/Codesigning.example.xcconfig diff --git a/Malachite/Info.plist b/mlchtCamera/Info.plist similarity index 100% rename from Malachite/Info.plist rename to mlchtCamera/Info.plist diff --git a/Malachite/Localizable.xcstrings b/mlchtCamera/Localizable.xcstrings similarity index 100% rename from Malachite/Localizable.xcstrings rename to mlchtCamera/Localizable.xcstrings diff --git a/Malachite/SceneDelegate.swift b/mlchtCamera/SceneDelegate.swift similarity index 100% rename from Malachite/SceneDelegate.swift rename to mlchtCamera/SceneDelegate.swift diff --git a/Malachite/Utilities/CameraUtils/Camera+Bringup.swift b/mlchtCamera/Utilities/CameraUtils/Camera+Bringup.swift similarity index 100% rename from Malachite/Utilities/CameraUtils/Camera+Bringup.swift rename to mlchtCamera/Utilities/CameraUtils/Camera+Bringup.swift diff --git a/Malachite/Utilities/CameraUtils/Camera+Input.swift b/mlchtCamera/Utilities/CameraUtils/Camera+Input.swift similarity index 100% rename from Malachite/Utilities/CameraUtils/Camera+Input.swift rename to mlchtCamera/Utilities/CameraUtils/Camera+Input.swift diff --git a/Malachite/Utilities/CameraUtils/Camera+Permissions.swift b/mlchtCamera/Utilities/CameraUtils/Camera+Permissions.swift similarity index 100% rename from Malachite/Utilities/CameraUtils/Camera+Permissions.swift rename to mlchtCamera/Utilities/CameraUtils/Camera+Permissions.swift diff --git a/Malachite/Utilities/CameraUtils/Camera.swift b/mlchtCamera/Utilities/CameraUtils/Camera.swift similarity index 100% rename from Malachite/Utilities/CameraUtils/Camera.swift rename to mlchtCamera/Utilities/CameraUtils/Camera.swift diff --git a/Malachite/Utilities/CompatibilityUtils/Compatibility.swift b/mlchtCamera/Utilities/CompatibilityUtils/Compatibility.swift similarity index 100% rename from Malachite/Utilities/CompatibilityUtils/Compatibility.swift rename to mlchtCamera/Utilities/CompatibilityUtils/Compatibility.swift diff --git a/Malachite/Utilities/GameUtils/Game+View.swift b/mlchtCamera/Utilities/GameUtils/Game+View.swift similarity index 100% rename from Malachite/Utilities/GameUtils/Game+View.swift rename to mlchtCamera/Utilities/GameUtils/Game+View.swift diff --git a/Malachite/Utilities/GameUtils/MalachiteGameUtils.swift b/mlchtCamera/Utilities/GameUtils/MalachiteGameUtils.swift similarity index 100% rename from Malachite/Utilities/GameUtils/MalachiteGameUtils.swift rename to mlchtCamera/Utilities/GameUtils/MalachiteGameUtils.swift diff --git a/Malachite/Utilities/InitUtils/Init+Debug.swift b/mlchtCamera/Utilities/InitUtils/Init+Debug.swift similarity index 100% rename from Malachite/Utilities/InitUtils/Init+Debug.swift rename to mlchtCamera/Utilities/InitUtils/Init+Debug.swift diff --git a/Malachite/Utilities/InitUtils/Init+Internal.swift b/mlchtCamera/Utilities/InitUtils/Init+Internal.swift similarity index 100% rename from Malachite/Utilities/InitUtils/Init+Internal.swift rename to mlchtCamera/Utilities/InitUtils/Init+Internal.swift diff --git a/Malachite/Utilities/InitUtils/Init.swift b/mlchtCamera/Utilities/InitUtils/Init.swift similarity index 100% rename from Malachite/Utilities/InitUtils/Init.swift rename to mlchtCamera/Utilities/InitUtils/Init.swift diff --git a/Malachite/Utilities/MalachiteFunctionUtils.swift b/mlchtCamera/Utilities/MalachiteFunctionUtils.swift similarity index 100% rename from Malachite/Utilities/MalachiteFunctionUtils.swift rename to mlchtCamera/Utilities/MalachiteFunctionUtils.swift diff --git a/Malachite/Utilities/MalachiteHapticUtils.swift b/mlchtCamera/Utilities/MalachiteHapticUtils.swift similarity index 100% rename from Malachite/Utilities/MalachiteHapticUtils.swift rename to mlchtCamera/Utilities/MalachiteHapticUtils.swift diff --git a/Malachite/Utilities/MalachiteIntentUtils.swift b/mlchtCamera/Utilities/MalachiteIntentUtils.swift similarity index 100% rename from Malachite/Utilities/MalachiteIntentUtils.swift rename to mlchtCamera/Utilities/MalachiteIntentUtils.swift diff --git a/Malachite/Utilities/MalachiteTooltipUtils.swift b/mlchtCamera/Utilities/MalachiteTooltipUtils.swift similarity index 100% rename from Malachite/Utilities/MalachiteTooltipUtils.swift rename to mlchtCamera/Utilities/MalachiteTooltipUtils.swift diff --git a/Malachite/Utilities/MalachiteUtils.swift b/mlchtCamera/Utilities/MalachiteUtils.swift similarity index 100% rename from Malachite/Utilities/MalachiteUtils.swift rename to mlchtCamera/Utilities/MalachiteUtils.swift diff --git a/Malachite/Utilities/PreferenceUtils/MalachitePreferences.swift b/mlchtCamera/Utilities/PreferenceUtils/MalachitePreferences.swift similarity index 100% rename from Malachite/Utilities/PreferenceUtils/MalachitePreferences.swift rename to mlchtCamera/Utilities/PreferenceUtils/MalachitePreferences.swift diff --git a/Malachite/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift b/mlchtCamera/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift similarity index 100% rename from Malachite/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift rename to mlchtCamera/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift diff --git a/Malachite/Utilities/PreferenceUtils/Preferences+Extension.swift b/mlchtCamera/Utilities/PreferenceUtils/Preferences+Extension.swift similarity index 100% rename from Malachite/Utilities/PreferenceUtils/Preferences+Extension.swift rename to mlchtCamera/Utilities/PreferenceUtils/Preferences+Extension.swift diff --git a/Malachite/Utilities/ViewUtils/MalachiteViewUtils.swift b/mlchtCamera/Utilities/ViewUtils/MalachiteViewUtils.swift similarity index 100% rename from Malachite/Utilities/ViewUtils/MalachiteViewUtils.swift rename to mlchtCamera/Utilities/ViewUtils/MalachiteViewUtils.swift diff --git a/Malachite/Utilities/ViewUtils/View+Sliders.swift b/mlchtCamera/Utilities/ViewUtils/View+Sliders.swift similarity index 100% rename from Malachite/Utilities/ViewUtils/View+Sliders.swift rename to mlchtCamera/Utilities/ViewUtils/View+Sliders.swift diff --git a/Malachite/Utilities/temputils.swift b/mlchtCamera/Utilities/temputils.swift similarity index 100% rename from Malachite/Utilities/temputils.swift rename to mlchtCamera/Utilities/temputils.swift diff --git a/Malachite/Views/AboutView/AboutView+Credits.swift b/mlchtCamera/Views/AboutView/AboutView+Credits.swift similarity index 100% rename from Malachite/Views/AboutView/AboutView+Credits.swift rename to mlchtCamera/Views/AboutView/AboutView+Credits.swift diff --git a/Malachite/Views/AboutView/AboutView+Eggs.swift b/mlchtCamera/Views/AboutView/AboutView+Eggs.swift similarity index 100% rename from Malachite/Views/AboutView/AboutView+Eggs.swift rename to mlchtCamera/Views/AboutView/AboutView+Eggs.swift diff --git a/Malachite/Views/AboutView/AboutView+Info.swift b/mlchtCamera/Views/AboutView/AboutView+Info.swift similarity index 100% rename from Malachite/Views/AboutView/AboutView+Info.swift rename to mlchtCamera/Views/AboutView/AboutView+Info.swift diff --git a/Malachite/Views/AboutView/AboutView+Story.swift b/mlchtCamera/Views/AboutView/AboutView+Story.swift similarity index 100% rename from Malachite/Views/AboutView/AboutView+Story.swift rename to mlchtCamera/Views/AboutView/AboutView+Story.swift diff --git a/Malachite/Views/AboutView/AboutView.swift b/mlchtCamera/Views/AboutView/AboutView.swift similarity index 100% rename from Malachite/Views/AboutView/AboutView.swift rename to mlchtCamera/Views/AboutView/AboutView.swift diff --git a/Malachite/Views/CameraView/CameraView+Controls.swift b/mlchtCamera/Views/CameraView/CameraView+Controls.swift similarity index 100% rename from Malachite/Views/CameraView/CameraView+Controls.swift rename to mlchtCamera/Views/CameraView/CameraView+Controls.swift diff --git a/Malachite/Views/CameraView/CameraView+Notifications.swift b/mlchtCamera/Views/CameraView/CameraView+Notifications.swift similarity index 100% rename from Malachite/Views/CameraView/CameraView+Notifications.swift rename to mlchtCamera/Views/CameraView/CameraView+Notifications.swift diff --git a/Malachite/Views/CameraView/CameraView+Overrides.swift b/mlchtCamera/Views/CameraView/CameraView+Overrides.swift similarity index 100% rename from Malachite/Views/CameraView/CameraView+Overrides.swift rename to mlchtCamera/Views/CameraView/CameraView+Overrides.swift diff --git a/Malachite/Views/CameraView/CameraView+Preview.swift b/mlchtCamera/Views/CameraView/CameraView+Preview.swift similarity index 100% rename from Malachite/Views/CameraView/CameraView+Preview.swift rename to mlchtCamera/Views/CameraView/CameraView+Preview.swift diff --git a/Malachite/Views/CameraView/CameraView.swift b/mlchtCamera/Views/CameraView/CameraView.swift similarity index 100% rename from Malachite/Views/CameraView/CameraView.swift rename to mlchtCamera/Views/CameraView/CameraView.swift diff --git a/Malachite/Views/CompatibilityView/CompatibilityView+Camera.swift b/mlchtCamera/Views/CompatibilityView/CompatibilityView+Camera.swift similarity index 100% rename from Malachite/Views/CompatibilityView/CompatibilityView+Camera.swift rename to mlchtCamera/Views/CompatibilityView/CompatibilityView+Camera.swift diff --git a/Malachite/Views/CompatibilityView/CompatibilityView+Format.swift b/mlchtCamera/Views/CompatibilityView/CompatibilityView+Format.swift similarity index 100% rename from Malachite/Views/CompatibilityView/CompatibilityView+Format.swift rename to mlchtCamera/Views/CompatibilityView/CompatibilityView+Format.swift diff --git a/Malachite/Views/CompatibilityView/CompatibilityView+Resolutions.swift b/mlchtCamera/Views/CompatibilityView/CompatibilityView+Resolutions.swift similarity index 100% rename from Malachite/Views/CompatibilityView/CompatibilityView+Resolutions.swift rename to mlchtCamera/Views/CompatibilityView/CompatibilityView+Resolutions.swift diff --git a/Malachite/Views/CompatibilityView/CompatibilityView.swift b/mlchtCamera/Views/CompatibilityView/CompatibilityView.swift similarity index 100% rename from Malachite/Views/CompatibilityView/CompatibilityView.swift rename to mlchtCamera/Views/CompatibilityView/CompatibilityView.swift diff --git a/Malachite/Views/DeveloperView/DeveloperView+BuildInfo.swift b/mlchtCamera/Views/DeveloperView/DeveloperView+BuildInfo.swift similarity index 100% rename from Malachite/Views/DeveloperView/DeveloperView+BuildInfo.swift rename to mlchtCamera/Views/DeveloperView/DeveloperView+BuildInfo.swift diff --git a/Malachite/Views/DeveloperView/DeveloperView+DeviceInfo.swift b/mlchtCamera/Views/DeveloperView/DeveloperView+DeviceInfo.swift similarity index 100% rename from Malachite/Views/DeveloperView/DeveloperView+DeviceInfo.swift rename to mlchtCamera/Views/DeveloperView/DeveloperView+DeviceInfo.swift diff --git a/Malachite/Views/DeveloperView/DeveloperView+Internal.swift b/mlchtCamera/Views/DeveloperView/DeveloperView+Internal.swift similarity index 100% rename from Malachite/Views/DeveloperView/DeveloperView+Internal.swift rename to mlchtCamera/Views/DeveloperView/DeveloperView+Internal.swift diff --git a/Malachite/Views/DeveloperView/DeveloperView+Settings.swift b/mlchtCamera/Views/DeveloperView/DeveloperView+Settings.swift similarity index 100% rename from Malachite/Views/DeveloperView/DeveloperView+Settings.swift rename to mlchtCamera/Views/DeveloperView/DeveloperView+Settings.swift diff --git a/Malachite/Views/DeveloperView/DeveloperView.swift b/mlchtCamera/Views/DeveloperView/DeveloperView.swift similarity index 100% rename from Malachite/Views/DeveloperView/DeveloperView.swift rename to mlchtCamera/Views/DeveloperView/DeveloperView.swift diff --git a/Malachite/Views/PhotoPreviewView/PhotoPreviewView+Controls.swift b/mlchtCamera/Views/PhotoPreviewView/PhotoPreviewView+Controls.swift similarity index 100% rename from Malachite/Views/PhotoPreviewView/PhotoPreviewView+Controls.swift rename to mlchtCamera/Views/PhotoPreviewView/PhotoPreviewView+Controls.swift diff --git a/Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift b/mlchtCamera/Views/PhotoPreviewView/PhotoPreviewView.swift similarity index 100% rename from Malachite/Views/PhotoPreviewView/PhotoPreviewView.swift rename to mlchtCamera/Views/PhotoPreviewView/PhotoPreviewView.swift diff --git a/Malachite/Views/QuickHelpView/QuickHelpView+About.swift b/mlchtCamera/Views/QuickHelpView/QuickHelpView+About.swift similarity index 100% rename from Malachite/Views/QuickHelpView/QuickHelpView+About.swift rename to mlchtCamera/Views/QuickHelpView/QuickHelpView+About.swift diff --git a/Malachite/Views/QuickHelpView/QuickHelpView+Photo.swift b/mlchtCamera/Views/QuickHelpView/QuickHelpView+Photo.swift similarity index 100% rename from Malachite/Views/QuickHelpView/QuickHelpView+Photo.swift rename to mlchtCamera/Views/QuickHelpView/QuickHelpView+Photo.swift diff --git a/Malachite/Views/QuickHelpView/QuickHelpView+Preview.swift b/mlchtCamera/Views/QuickHelpView/QuickHelpView+Preview.swift similarity index 100% rename from Malachite/Views/QuickHelpView/QuickHelpView+Preview.swift rename to mlchtCamera/Views/QuickHelpView/QuickHelpView+Preview.swift diff --git a/Malachite/Views/QuickHelpView/QuickHelpView+Resolution.swift b/mlchtCamera/Views/QuickHelpView/QuickHelpView+Resolution.swift similarity index 100% rename from Malachite/Views/QuickHelpView/QuickHelpView+Resolution.swift rename to mlchtCamera/Views/QuickHelpView/QuickHelpView+Resolution.swift diff --git a/Malachite/Views/QuickHelpView/QuickHelpView+UI.swift b/mlchtCamera/Views/QuickHelpView/QuickHelpView+UI.swift similarity index 100% rename from Malachite/Views/QuickHelpView/QuickHelpView+UI.swift rename to mlchtCamera/Views/QuickHelpView/QuickHelpView+UI.swift diff --git a/Malachite/Views/QuickHelpView/QuickHelpView+Watermarking.swift b/mlchtCamera/Views/QuickHelpView/QuickHelpView+Watermarking.swift similarity index 100% rename from Malachite/Views/QuickHelpView/QuickHelpView+Watermarking.swift rename to mlchtCamera/Views/QuickHelpView/QuickHelpView+Watermarking.swift diff --git a/Malachite/Views/QuickHelpView/QuickHelpView.swift b/mlchtCamera/Views/QuickHelpView/QuickHelpView.swift similarity index 100% rename from Malachite/Views/QuickHelpView/QuickHelpView.swift rename to mlchtCamera/Views/QuickHelpView/QuickHelpView.swift diff --git a/Malachite/Views/SettingsView/SettingsView+About.swift b/mlchtCamera/Views/SettingsView/SettingsView+About.swift similarity index 100% rename from Malachite/Views/SettingsView/SettingsView+About.swift rename to mlchtCamera/Views/SettingsView/SettingsView+About.swift diff --git a/Malachite/Views/SettingsView/SettingsView+Photo.swift b/mlchtCamera/Views/SettingsView/SettingsView+Photo.swift similarity index 100% rename from Malachite/Views/SettingsView/SettingsView+Photo.swift rename to mlchtCamera/Views/SettingsView/SettingsView+Photo.swift diff --git a/Malachite/Views/SettingsView/SettingsView+Preview.swift b/mlchtCamera/Views/SettingsView/SettingsView+Preview.swift similarity index 100% rename from Malachite/Views/SettingsView/SettingsView+Preview.swift rename to mlchtCamera/Views/SettingsView/SettingsView+Preview.swift diff --git a/Malachite/Views/SettingsView/SettingsView+Resolution.swift b/mlchtCamera/Views/SettingsView/SettingsView+Resolution.swift similarity index 100% rename from Malachite/Views/SettingsView/SettingsView+Resolution.swift rename to mlchtCamera/Views/SettingsView/SettingsView+Resolution.swift diff --git a/Malachite/Views/SettingsView/SettingsView+UI.swift b/mlchtCamera/Views/SettingsView/SettingsView+UI.swift similarity index 100% rename from Malachite/Views/SettingsView/SettingsView+UI.swift rename to mlchtCamera/Views/SettingsView/SettingsView+UI.swift diff --git a/Malachite/Views/SettingsView/SettingsView+Watermarking.swift b/mlchtCamera/Views/SettingsView/SettingsView+Watermarking.swift similarity index 100% rename from Malachite/Views/SettingsView/SettingsView+Watermarking.swift rename to mlchtCamera/Views/SettingsView/SettingsView+Watermarking.swift diff --git a/Malachite/Views/SettingsView/SettingsView.swift b/mlchtCamera/Views/SettingsView/SettingsView.swift similarity index 100% rename from Malachite/Views/SettingsView/SettingsView.swift rename to mlchtCamera/Views/SettingsView/SettingsView.swift diff --git a/Malachite/WidgetBundle/ControlCenterWidget.swift b/mlchtCamera/WidgetBundle/ControlCenterWidget.swift similarity index 100% rename from Malachite/WidgetBundle/ControlCenterWidget.swift rename to mlchtCamera/WidgetBundle/ControlCenterWidget.swift diff --git a/Malachite/WidgetBundle/Info.plist b/mlchtCamera/WidgetBundle/Info.plist similarity index 100% rename from Malachite/WidgetBundle/Info.plist rename to mlchtCamera/WidgetBundle/Info.plist diff --git a/Malachite/WidgetBundle/LockScreenWidget.swift b/mlchtCamera/WidgetBundle/LockScreenWidget.swift similarity index 100% rename from Malachite/WidgetBundle/LockScreenWidget.swift rename to mlchtCamera/WidgetBundle/LockScreenWidget.swift diff --git a/Malachite/WidgetBundle/MalachiteWidgetBundle.swift b/mlchtCamera/WidgetBundle/MalachiteWidgetBundle.swift similarity index 100% rename from Malachite/WidgetBundle/MalachiteWidgetBundle.swift rename to mlchtCamera/WidgetBundle/MalachiteWidgetBundle.swift diff --git a/Malachite/Malachite.docc/Malachite.md b/mlchtCamera/mlchtCamera.docc/Malachite.md similarity index 100% rename from Malachite/Malachite.docc/Malachite.md rename to mlchtCamera/mlchtCamera.docc/Malachite.md diff --git a/Malachite/Malachite.entitlements b/mlchtCamera/mlchtCamera.entitlements similarity index 100% rename from Malachite/Malachite.entitlements rename to mlchtCamera/mlchtCamera.entitlements diff --git a/MalachiteWatch/ContentView.swift b/mlchtRemote/ContentView.swift similarity index 100% rename from MalachiteWatch/ContentView.swift rename to mlchtRemote/ContentView.swift diff --git a/MalachiteWatch/Info.plist b/mlchtRemote/Info.plist similarity index 100% rename from MalachiteWatch/Info.plist rename to mlchtRemote/Info.plist diff --git a/MalachiteWatch/MalachiteWatchApp.swift b/mlchtRemote/MalachiteWatchApp.swift similarity index 100% rename from MalachiteWatch/MalachiteWatchApp.swift rename to mlchtRemote/MalachiteWatchApp.swift From 19e4a47ae45e376d2cb3f6e706c46c3c813b2d56 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Sat, 28 Feb 2026 00:59:29 -0700 Subject: [PATCH 40/66] [ci/privacy_policy/localizable] Finish rebrand, fix Xcode Cloud --- .github/workflows/build.yml | 41 ------------------- PRIVACY_POLICY.md | 4 +- ci_scripts/ci_post_clone.sh | 4 +- ci_scripts/ci_post_xcodebuild.sh | 2 +- mlchtCamera.xcodeproj/project.pbxproj | 18 +++------ mlchtCamera/Codesigning.example.xcconfig | 2 +- mlchtCamera/Info.plist | 6 +-- mlchtCamera/Localizable.xcstrings | 48 +++++++++++------------ mlchtCamera/mlchtCamera.docc/Malachite.md | 6 +-- 9 files changed, 42 insertions(+), 89 deletions(-) delete mode 100755 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100755 index e7a29b2..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Build - -on: - push: - workflow_dispatch: - -jobs: - build: - name: Build for iOS - runs-on: J316sAP - steps: - - name: Remove work folders - run: | - echo "before" - ls -lah ./ - rm -rf ./* || true - rm -rf ./.??* || true - echo "after" - ls -lah ./ - - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Build - env: - password: ${{ secrets.KEYCHAIN_PASSWORD }} - run: | - mv Malachite/Codesigning.example.xcconfig Malachite/Codesigning.xcconfig - xcodebuild -project Malachite.xcodeproj -target Malachite -configuration Internal CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO - mkdir build/Payload && mv build/Internal-iphoneos/Malachite.app build/Payload/Malachite.app - cd build && zip -r Payload.ipa Payload/ - cd .. - - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: Malachite.ipa - path: build/Payload.ipa - - - diff --git a/PRIVACY_POLICY.md b/PRIVACY_POLICY.md index 543edff..5eac8b6 100755 --- a/PRIVACY_POLICY.md +++ b/PRIVACY_POLICY.md @@ -1,5 +1,5 @@ # PRIVACY POLICY -This privacy policy will reference of the following terms: Malachite (the "App"), Adam Tunnicliff (the "Developer"), and the end-user interacting with the App (the "User"). +This privacy policy will reference of the following terms: mlchtCamera (the "App"), Adam Tunnicliff (the "Developer"), and the end-user interacting with the App (the "User"). 1.0: The App does not collect any data, and contains no functionality to do so. @@ -7,4 +7,4 @@ This privacy policy will reference of the following terms: Malachite (the "App") Some of this Apple Inc.-collected information is accessible to Developer for debugging and statistics purposes only, and it cannot be used to identify any user. This information is also not shared with any other entity. -Privacy policy updated on **January 2nd, 2024**. +Privacy policy updated on **January 21st, 2026**. diff --git a/ci_scripts/ci_post_clone.sh b/ci_scripts/ci_post_clone.sh index 4d795e1..de8d3ff 100755 --- a/ci_scripts/ci_post_clone.sh +++ b/ci_scripts/ci_post_clone.sh @@ -1,9 +1,9 @@ #!/bin/zsh # ci_post_xcodebuild.sh -# Malachite +# mlchtCamera # # Created by Eva Isabella Lunaon 5/13/25. # -mv $CI_PRIMARY_REPOSITORY_PATH/Malachite/Codesigning.example.xcconfig $CI_PRIMARY_REPOSITORY_PATH/Malachite/Codesigning.xcconfig +mv $CI_PRIMARY_REPOSITORY_PATH/mlchtCamera/Codesigning.example.xcconfig $CI_PRIMARY_REPOSITORY_PATH/mlchtCamera/Codesigning.xcconfig diff --git a/ci_scripts/ci_post_xcodebuild.sh b/ci_scripts/ci_post_xcodebuild.sh index 836d0f1..86ef2a8 100755 --- a/ci_scripts/ci_post_xcodebuild.sh +++ b/ci_scripts/ci_post_xcodebuild.sh @@ -1,7 +1,7 @@ #!/bin/zsh # ci_post_xcodebuild.sh -# Malachite +# mlchtCamera # # Created by Stella Luna on 1/9/24. # diff --git a/mlchtCamera.xcodeproj/project.pbxproj b/mlchtCamera.xcodeproj/project.pbxproj index 1ee5123..c3da39a 100755 --- a/mlchtCamera.xcodeproj/project.pbxproj +++ b/mlchtCamera.xcodeproj/project.pbxproj @@ -368,11 +368,6 @@ /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ - 786DAD902E4F282E00BE3137 /* .github */ = { - isa = PBXFileSystemSynchronizedRootGroup; - path = .github; - sourceTree = ""; - }; 786DAD932E4F283000BE3137 /* ci_scripts */ = { isa = PBXFileSystemSynchronizedRootGroup; path = ci_scripts; @@ -438,7 +433,6 @@ 78163D8D2CCB7BAE00146126 /* CHANGELOG.md */, 782FC7752B2DAFAB007709C1 /* README.md */, 78562BC22B450A7600920160 /* PRIVACY_POLICY.md */, - 786DAD902E4F282E00BE3137 /* .github */, 786DAD932E4F283000BE3137 /* ci_scripts */, 785F084B2B12D41100244EB4 /* mlchtCamera */, 786DAE7C2E4F28BF00BE3137 /* mlchtRemote */, @@ -1580,8 +1574,8 @@ INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.photography"; INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; - INFOPLIST_KEY_NSCameraUsageDescription = "mlchtCamera uses the rear camera system on iPhone to present your magnified view."; - INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "mlchtCamera uses write access to your library in order to save photos."; + INFOPLIST_KEY_NSCameraUsageDescription = "mlchtCamera uses the rear camera system on iPhone to take pictures."; + INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "mlchtCamera uses write access to your library in order to save pictures."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UIMainStoryboardFile = ""; INFOPLIST_KEY_UIRequiresFullScreen = NO; @@ -1636,8 +1630,8 @@ INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.photography"; INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; - INFOPLIST_KEY_NSCameraUsageDescription = "mlchtCamera uses the rear camera system on iPhone to present your magnified view."; - INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "mlchtCamera uses write access to your library in order to save photos."; + INFOPLIST_KEY_NSCameraUsageDescription = "mlchtCamera uses the rear camera system on iPhone to take pictures."; + INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "mlchtCamera uses write access to your library in order to save pictures."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UIMainStoryboardFile = ""; INFOPLIST_KEY_UIRequiresFullScreen = NO; @@ -2092,8 +2086,8 @@ INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.photography"; INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; - INFOPLIST_KEY_NSCameraUsageDescription = "mlchtCamera uses the rear camera system on iPhone to present your magnified view."; - INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "mlchtCamera uses write access to your library in order to save photos."; + INFOPLIST_KEY_NSCameraUsageDescription = "mlchtCamera uses the rear camera system on iPhone to take pictures."; + INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "mlchtCamera uses write access to your library in order to save pictures."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UIMainStoryboardFile = ""; INFOPLIST_KEY_UIRequiresFullScreen = NO; diff --git a/mlchtCamera/Codesigning.example.xcconfig b/mlchtCamera/Codesigning.example.xcconfig index 7b1a4e5..f589c5a 100644 --- a/mlchtCamera/Codesigning.example.xcconfig +++ b/mlchtCamera/Codesigning.example.xcconfig @@ -12,4 +12,4 @@ CODE_SIGN_STYLE = Automatic; // https://developer.apple.com/account DEVELOPMENT_TEAM = 95J8WZ4TN8; // Change this to anything you want. -APP_PREFIX = dev.crystll1ne; +APP_PREFIX = dev.crystll1ne.mlchtcamera; diff --git a/mlchtCamera/Info.plist b/mlchtCamera/Info.plist index 3ef7b68..518f41f 100755 --- a/mlchtCamera/Info.plist +++ b/mlchtCamera/Info.plist @@ -8,12 +8,12 @@ CFBundleTypeRole Editor CFBundleURLIconFile - AppIcon + MalachiteIconMasked_Darwin25 CFBundleURLName - dev.crystall1ne.Malachite + dev.crystall1ne.mlcht CFBundleURLSchemes - malachite + mlcht diff --git a/mlchtCamera/Localizable.xcstrings b/mlchtCamera/Localizable.xcstrings index da47321..9d4e818 100755 --- a/mlchtCamera/Localizable.xcstrings +++ b/mlchtCamera/Localizable.xcstrings @@ -126,7 +126,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Malachite’s story" + "value" : "mlchtCamera’s story" } } } @@ -147,7 +147,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Malachite started as an app to help my love work on printed circuit boards with better clarity and manual controls that the stock iOS camera app can't provide, and it grew once my Discord community stood by, actually using it and suggesting new features. It's the first real test of my skills in Swift, and leading my own public project, and my goal is to now provide a free, all-inclusive experience to amazing macro photography - powered by your iPhone, iPad, or iPod touch." + "value" : "mlchtCamera started as an app to help my love work on printed circuit boards with better clarity and manual controls that the stock iOS camera app can't provide, and it grew once my Discord community stood by, actually using it and suggesting new features. It's the first real test of my skills in Swift, and leading my own public project, and my goal is to now provide a free, all-inclusive experience to amazing macro photography - powered by your iPhone, iPad, or iPod touch." } } } @@ -202,7 +202,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "In order to change Malachite’s settings, this device will need to be unlocked." + "value" : "In order to change mlchtCamera’s settings, this device will need to be unlocked." } } } @@ -268,7 +268,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Malachite requires photo library access in order to capture images. Go to Settings > Apps > Malachite to enable this permission." + "value" : "mlchtCamera requires photo library access in order to capture images. Go to Settings > Apps > mlcht to enable this permission." } } } @@ -334,7 +334,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Malachite quit unexpectedly." + "value" : "mlchtCamera quit unexpectedly." } } } @@ -355,7 +355,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Malachite" + "value" : "mlchtCamera" } } } @@ -365,7 +365,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Open Malachite" + "value" : "Open mlchtCamera" } } } @@ -375,7 +375,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Capture with Malachite" + "value" : "Capture with mlchtCamera" } } } @@ -385,7 +385,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Add to quickly launch malachite from your control center, lock screen, or Action Button." + "value" : "Add to quickly launch mlchtCamera from your control center, lock screen, or Action Button." } } } @@ -395,7 +395,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Tap to open Malachite directly from your lock screen." + "value" : "Tap to open mlchtCamera directly from your lock screen." } } } @@ -435,7 +435,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "The following screen shows what features of Malachite your current device can take advantage of. " + "value" : "The following screen shows what features of mlchtCamera your current device can take advantage of. " } } } @@ -729,7 +729,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "DEBUG: Erases preferences.plist in Malachite’s home directory." + "value" : "DEBUG: Erases preferences.plist in mlchtCamera’s home directory." } } } @@ -1149,7 +1149,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Enable this if you want to disable Malachite’s haptic feedback on buttons and gestures." + "value" : "Enable this if you want to disable mlchtCamera’s haptic feedback on buttons and gestures." } } } @@ -1169,7 +1169,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Enable this to hide the user interface when Malachite opens, useful for using the app as a magnifier." + "value" : "Enable this to hide the user interface when mlchtCamera opens, useful for using the app as a magnifier." } } } @@ -1199,7 +1199,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Enable this if you want to enable Malachite’s watermarking feature. Great for adding credits or details about the camera." + "value" : "Enable this if you want to enable mlchtCamera's watermarking feature. Great for adding credits or details about the camera." } } } @@ -1220,7 +1220,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "These options control how Malachite captures and saves images." + "value" : "These options control how mlchtCamera captures and saves images." } } } @@ -1252,7 +1252,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "These options control the Malachite’s camera preview." + "value" : "These options control the mlchtCamera’s camera preview." } } } @@ -1272,7 +1272,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "These options control various aspects of Malachite’s user interface." + "value" : "These options control various aspects of mlchtCamera's user interface." } } } @@ -1282,7 +1282,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "These options control Malachite’s watermarking feature." + "value" : "These options control mlchtCamera's watermarking feature." } } } @@ -1662,7 +1662,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Shot on Malachite" + "value" : "Shot on mlchtCamera" } } } @@ -1727,7 +1727,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Takes you to the About page for Malachite." + "value" : "Takes you to the About page for mlchtCamera." } } } @@ -1737,7 +1737,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Lists various features and capabilities of Malachite that your device can take advantage of, and ones it can't." + "value" : "Lists various features and capabilities of mlchtCamera that your device can take advantage of, and ones it can't." } } } @@ -1747,7 +1747,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Extra settings and information, intended for people working on Malachite’s code." + "value" : "Extra settings and information, intended for people working on mlchtCamera’s code." } } } @@ -1757,7 +1757,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "About Malachite" + "value" : "About mlchtCamera" } } } diff --git a/mlchtCamera/mlchtCamera.docc/Malachite.md b/mlchtCamera/mlchtCamera.docc/Malachite.md index 754b17a..4daf145 100755 --- a/mlchtCamera/mlchtCamera.docc/Malachite.md +++ b/mlchtCamera/mlchtCamera.docc/Malachite.md @@ -1,9 +1,9 @@ -# ``Malachite`` +# ``mlchtCamera`` -Malachite is an app designed to bring more control to users for macro photography, while offering playful elements to enjoy. +mlchtCamera is an app designed to bring more control to users for macro photography, while offering playful elements to enjoy. ## Overview -Welcome to the documentation browser for Malachite! While this application is not designed to by used as a library inside of other projects, making its code easy to reference is paramount for code readibility, learning how to create your own Swift applications, or contributing your own fixes and features to Malachite itself. +Welcome to the documentation browser for mlchtCamera! While this application is not designed to by used as a library inside of other projects, making its code easy to reference is paramount for code readibility, learning how to create your own Swift applications, or contributing your own fixes and features to mlchtCamera itself. From d442346d7d82ebaff032359eccf95707fcaadb85 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Mon, 2 Mar 2026 16:31:46 -0700 Subject: [PATCH 41/66] [entitlements] Reintroduce Enhanced Security capability I honestly forgot that it existed because it was broken for a while, tested: - iPhone 7 on 15.1 (D10AP, 19B74) - iPad Pro (12.9-inch) (2nd generation) on 18.0.1 (J120AP, 22A3370) - iPhone SE (2nd generation) on 26.4 (D79AP, 23E5218e) - iPhone 17 Pro Max on 26.4 (V54AP, 23E5218e) --- mlchtCamera.xcodeproj/project.pbxproj | 12 ++++++++++++ mlchtCamera/mlchtCamera.entitlements | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/mlchtCamera.xcodeproj/project.pbxproj b/mlchtCamera.xcodeproj/project.pbxproj index c3da39a..c80ea69 100755 --- a/mlchtCamera.xcodeproj/project.pbxproj +++ b/mlchtCamera.xcodeproj/project.pbxproj @@ -1460,6 +1460,8 @@ COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 95J8WZ4TN8; + ENABLE_ENHANCED_SECURITY = YES; + ENABLE_POINTER_AUTHENTICATION = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -1526,7 +1528,9 @@ COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 95J8WZ4TN8; + ENABLE_ENHANCED_SECURITY = YES; ENABLE_NS_ASSERTIONS = NO; + ENABLE_POINTER_AUTHENTICATION = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -1563,6 +1567,8 @@ DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; ENABLE_CPLUSPLUS_BOUNDS_SAFE_BUFFERS = YES; ENABLE_C_BOUNDS_SAFETY = YES; + ENABLE_ENHANCED_SECURITY = YES; + ENABLE_POINTER_AUTHENTICATION = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_PREPROCESSOR_DEFINITIONS = ( "CFBUILDTYPE=DEBUG", @@ -1622,6 +1628,8 @@ DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; ENABLE_CPLUSPLUS_BOUNDS_SAFE_BUFFERS = YES; ENABLE_C_BOUNDS_SAFETY = YES; + ENABLE_ENHANCED_SECURITY = YES; + ENABLE_POINTER_AUTHENTICATION = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_PREPROCESSOR_DEFINITIONS = "CFBUILDTYPE=RELEASE"; GENERATE_INFOPLIST_FILE = YES; @@ -2030,6 +2038,8 @@ COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 95J8WZ4TN8; + ENABLE_ENHANCED_SECURITY = YES; + ENABLE_POINTER_AUTHENTICATION = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -2075,6 +2085,8 @@ DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; ENABLE_CPLUSPLUS_BOUNDS_SAFE_BUFFERS = YES; ENABLE_C_BOUNDS_SAFETY = YES; + ENABLE_ENHANCED_SECURITY = YES; + ENABLE_POINTER_AUTHENTICATION = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_PREPROCESSOR_DEFINITIONS = ( "CFBUILDTYPE=INTERNAL", diff --git a/mlchtCamera/mlchtCamera.entitlements b/mlchtCamera/mlchtCamera.entitlements index c74150d..b936cff 100755 --- a/mlchtCamera/mlchtCamera.entitlements +++ b/mlchtCamera/mlchtCamera.entitlements @@ -4,5 +4,17 @@ com.apple.developer.game-center + com.apple.security.hardened-process + + com.apple.security.hardened-process.checked-allocations + + com.apple.security.hardened-process.dyld-ro + + com.apple.security.hardened-process.enhanced-security-version-string + 1 + com.apple.security.hardened-process.hardened-heap + + com.apple.security.hardened-process.platform-restrictions-string + 2 From f5b9f9ac71229fe6b7dd6f6d3de4a70f27069e6d Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Mon, 2 Mar 2026 16:36:36 -0700 Subject: [PATCH 42/66] [CameraView+Controls] move AVCaptureSessionControlsDelegate inheritance None of these functions are going to be used pre-iOS 18.0 anyway, so they can safely be moved here under the availability clause. --- mlchtCamera/Views/CameraView/CameraView+Controls.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mlchtCamera/Views/CameraView/CameraView+Controls.swift b/mlchtCamera/Views/CameraView/CameraView+Controls.swift index 5e0b5a4..091fb85 100644 --- a/mlchtCamera/Views/CameraView/CameraView+Controls.swift +++ b/mlchtCamera/Views/CameraView/CameraView+Controls.swift @@ -12,7 +12,7 @@ import UIKit // MARK: ControlLayer - Main extension CameraView { - class ControlLayer: NSObject, AVCaptureSessionControlsDelegate { + class ControlLayer: NSObject { /** The existing instance of ``CameraView`` to act on. */ @@ -237,7 +237,7 @@ extension CameraView.ControlLayer { // MARK: ControlLayer - Camera Control @available(iOS 18.0, *) -extension CameraView.ControlLayer { +extension CameraView.ControlLayer: AVCaptureSessionControlsDelegate { func sessionControlsDidBecomeActive(_ session: AVCaptureSession) { if !self.uiIsHidden { runUIHider() } } From be5ee7ea9ab6f8a841ec932e5939f74f946b85ac Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Mon, 2 Mar 2026 21:23:55 -0700 Subject: [PATCH 43/66] [MalachiteViewUtils] Fix iOS 26.1+ Liquid Glass --- mlchtCamera/Utilities/ViewUtils/MalachiteViewUtils.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mlchtCamera/Utilities/ViewUtils/MalachiteViewUtils.swift b/mlchtCamera/Utilities/ViewUtils/MalachiteViewUtils.swift index 8342e4c..54fe777 100755 --- a/mlchtCamera/Utilities/ViewUtils/MalachiteViewUtils.swift +++ b/mlchtCamera/Utilities/ViewUtils/MalachiteViewUtils.swift @@ -19,14 +19,16 @@ public class MalachiteViewUtils : NSObject { let buttonImage = UIImage(systemName: symbolName)?.withRenderingMode(.alwaysTemplate) button.setImage(buttonImage, for: .normal) button.translatesAutoresizingMaskIntoConstraints = false - button.layer.masksToBounds = true - button.layer.cornerRadius = (dimensions.count > 2) ? dimensions[2] : (dimensions.count > 1) ? dimensions[1] / 2 : dimensions[0] / 2 button.bringSubviewToFront(button.imageView!) button.imageView?.clipsToBounds = false button.imageView?.contentMode = .center if #available(iOS 26.0, *) { + var glass = UIButton.Configuration.glass() + glass.cornerStyle = .capsule button.configuration = .glass() } else { + button.layer.masksToBounds = true + button.layer.cornerRadius = (dimensions.count > 2) ? dimensions[2] : (dimensions.count > 1) ? dimensions[1] / 2 : dimensions[0] / 2 button.insertSubview(returnProperEffectView(viewForBounds: view, effect: UIBlurEffect(style: .systemThinMaterial)), at: 0) if #available(iOS 18.0, *) { button.tintColor = UIColor(.primary) } else { button.tintColor = UIColor(.white) } From 78d0b004ba34a871fc23e2bc922de93aec4204f5 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Mon, 2 Mar 2026 21:50:12 -0700 Subject: [PATCH 44/66] [MalachiteViewUtils] Fix iOS 18 and 26 button colors --- mlchtCamera/Utilities/ViewUtils/MalachiteViewUtils.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mlchtCamera/Utilities/ViewUtils/MalachiteViewUtils.swift b/mlchtCamera/Utilities/ViewUtils/MalachiteViewUtils.swift index 54fe777..7ebbc5f 100755 --- a/mlchtCamera/Utilities/ViewUtils/MalachiteViewUtils.swift +++ b/mlchtCamera/Utilities/ViewUtils/MalachiteViewUtils.swift @@ -25,13 +25,13 @@ public class MalachiteViewUtils : NSObject { if #available(iOS 26.0, *) { var glass = UIButton.Configuration.glass() glass.cornerStyle = .capsule - button.configuration = .glass() + glass.preferredSymbolConfigurationForImage = UIImage.SymbolConfiguration(hierarchicalColor: .label) + button.configuration = glass } else { button.layer.masksToBounds = true button.layer.cornerRadius = (dimensions.count > 2) ? dimensions[2] : (dimensions.count > 1) ? dimensions[1] / 2 : dimensions[0] / 2 button.insertSubview(returnProperEffectView(viewForBounds: view, effect: UIBlurEffect(style: .systemThinMaterial)), at: 0) - if #available(iOS 18.0, *) { button.tintColor = UIColor(.primary) } - else { button.tintColor = UIColor(.white) } + button.tintColor = .label } button.isPointerInteractionEnabled = true From 95c33876e5fa7e318fc8da1734064e58cc290713 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Mon, 2 Mar 2026 21:57:40 -0700 Subject: [PATCH 45/66] [SettingsView+Photo] Fix HDR switch being enabled on unsupported cameras I forgot if I added code to recheck HDR eligibility on camera switching, I'll need to check. --- .../Views/SettingsView/SettingsView+Photo.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mlchtCamera/Views/SettingsView/SettingsView+Photo.swift b/mlchtCamera/Views/SettingsView/SettingsView+Photo.swift index 4de4405..e9d3e66 100644 --- a/mlchtCamera/Views/SettingsView/SettingsView+Photo.swift +++ b/mlchtCamera/Views/SettingsView/SettingsView+Photo.swift @@ -50,7 +50,7 @@ extension SettingsView { disabled: !utilities.preferences.compatibility.hdr, dangerous: false) { - Toggle("settings.option.photo.hdr", isOn: $hdrSwitch) + Toggle("settings.option.photo.hdr", isOn: $hdrSwitch ) } MalachiteCellViewUtils( icon: "plus.viewfinder", @@ -76,7 +76,7 @@ extension SettingsView { utilities.preferences.capture.format.heic = photoFormat == 1 ? true : false } .onChange(of: hdrSwitch) { _ in - utilities.preferences.capture.hdr = hdrSwitch + if utilities.preferences.compatibility.hdr { utilities.preferences.capture.hdr = hdrSwitch } } .onChange(of: continuousAEAF) { _ in switch continuousAEAF { @@ -99,10 +99,10 @@ extension SettingsView { if !utilities.preferences.compatibility.hdr { formatFooterText = (formatFooterText != nil) ? formatFooterText! + "settings.footer.photo.hdr".localized : "settings.footer.photo.hdr".localized + } else { + hdrSwitch = utilities.preferences.capture.hdr } - hdrSwitch = utilities.preferences.capture.hdr - // TODO: Better way to do this photoFormat = utilities.preferences.capture.format.jpeg ? 0 : 1 photoFormat = utilities.preferences.capture.format.heic ? 1 : 0 @@ -122,7 +122,7 @@ extension SettingsView { func onDisappear() { NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.continousAEAFNotification.name, object: nil) - utilities.preferences.capture.hdr = hdrSwitch + if utilities.preferences.compatibility.hdr { utilities.preferences.capture.hdr = hdrSwitch } utilities.preferences.capture.format.jpeg = photoFormat == 0 ? true : false utilities.preferences.capture.format.heic = photoFormat == 1 ? true : false From e06bcfb7828be725b416ec6bdee63191c9bd088c Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Tue, 3 Mar 2026 00:32:09 -0700 Subject: [PATCH 46/66] [Camera+Bringup] Add support for the front camera --- mlchtCamera/Utilities/CameraUtils/Camera+Bringup.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mlchtCamera/Utilities/CameraUtils/Camera+Bringup.swift b/mlchtCamera/Utilities/CameraUtils/Camera+Bringup.swift index df3ee3f..eadd9f9 100644 --- a/mlchtCamera/Utilities/CameraUtils/Camera+Bringup.swift +++ b/mlchtCamera/Utilities/CameraUtils/Camera+Bringup.swift @@ -38,6 +38,14 @@ extension Camera { parent.utilities.debugNSLog("[Camera Initialization] \(device.deviceType.rawValue) available") } + if !currentProcess.isiOSAppOnMac && !currentProcess.isMacCatalystApp { + AVCaptureDevice.DiscoverySession.init(deviceTypes: camerasToDiscover, mediaType: .video, position: .front).devices.forEach { device in + if parent.utilities.preferences.compatibility.device.changed { parent.compatibility.checkCameraCapabilities(device: device) } + camerasFound.append(device) + parent.utilities.debugNSLog("[Camera Initialization] \(device.deviceType.rawValue) available") + } + } + return camerasFound } From e4e6b3f57dd77f583aa93f5e63a0b98b7bf5060a Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Tue, 3 Mar 2026 01:30:02 -0700 Subject: [PATCH 47/66] [CameraView] Enable switching specific cameras without Camera Control I'm replacing the names Apple provides me so that I can put in better ones. To make it more resilient, I plan to use different identifiers for the camera (type and position), but this is quick and dirty for my next commit. --- .../CameraView/CameraView+Controls.swift | 21 +++++++++++++++++++ mlchtCamera/Views/CameraView/CameraView.swift | 4 ++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/mlchtCamera/Views/CameraView/CameraView+Controls.swift b/mlchtCamera/Views/CameraView/CameraView+Controls.swift index 091fb85..ec66e8e 100644 --- a/mlchtCamera/Views/CameraView/CameraView+Controls.swift +++ b/mlchtCamera/Views/CameraView/CameraView+Controls.swift @@ -117,12 +117,33 @@ extension CameraView { MalachiteViewUtils.buttonBuilder(symbolName: "", action: #selector(delegate.stub), dimensions: [ 60.0 ], constraints: buttonConstraints[14], hidden: false, assign: { [self] button in self.buttons.currentCamera = button }), ] + let mappings: [ String : String ] = [ + "Front Camera": "camera.front.wideangle".localized, + "Front Ultra Wide Camera": "camera.front.ultrawide".localized, + "Back Telephoto Camera": "camera.back.telephoto".localized, + "Back Camera": "camera.back.wideangle".localized, + "Back Ultra Wide Camera": "camera.back.ultrawide".localized, + ] + for config in buttonConfigs { let button = delegate.utilities.views.createAndAddButtonToView(symbolName: config.symbolName, delegate: delegate, view: delegate.view, utilities: delegate.utilities, action: config.action, dimensions: config.dimensions, constraints: config.constraints) if delegate.utilities.preferences.userInterface.appLaunch { button.alpha = 0.0; self.uiIsHidden = true } if config.hidden { button.alpha = 0.0 } config.assign(button) } + + let menuActions = delegate.camera.cameras.reversed().map { item in + UIAction(title: (mappings[item.localizedName] ?? item.localizedName), image: nil) { [self] action in + delegate.camera.index = self.delegate.camera.cameras.firstIndex(of: item) + delegate.runInputSwitch() + delegate.camera.index = nil + print("Selected \(mappings[item.localizedName], default: item.localizedName)") + } + } + + let menu = UIMenu(title: "Cameras", children: menuActions) + + self.buttons.camera.menu = menu } /** diff --git a/mlchtCamera/Views/CameraView/CameraView.swift b/mlchtCamera/Views/CameraView/CameraView.swift index 08bfe58..95e41db 100755 --- a/mlchtCamera/Views/CameraView/CameraView.swift +++ b/mlchtCamera/Views/CameraView/CameraView.swift @@ -497,7 +497,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa } else { #warning("refactor to call unsupported codepath") utilities.debugNSLog("[Flashlight Level] Device does not have a flashlight") - let alert = utilities.views.createAlertController(title: "alert.title.flash", message: "alert.detail.flash", button: self.controlLayer.buttons.flash.activator, defaultSet: true, action: { _ in + let alert = utilities.views.createAlertController(title: "alert.title.flashlight", message: "alert.detail.flashlight", button: self.controlLayer.buttons.flash.activator, defaultSet: true, action: { _ in self.utilities.debugNSLog("[Flashlight Level] Dialog has been dismissed") }) self.present(alert, animated: true, completion: nil) @@ -512,7 +512,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa self.controlLayer.buttons.flash.sliderShown = utilities.views.sliders.runHiders(group: self.controlLayer.buttons.flash) } else { utilities.debugNSLog("[Flashlight Level] Device does not have a flashlight") - let alert = utilities.views.createAlertController(title: "alert.title.flash", message: "alert.detail.flash", button: self.controlLayer.buttons.flash.activator, defaultSet: true, action: { _ in + let alert = utilities.views.createAlertController(title: "alert.title.flashlight", message: "alert.detail.flashlight", button: self.controlLayer.buttons.flash.activator, defaultSet: true, action: { _ in self.utilities.debugNSLog("[Flashlight Level] Dialog has been dismissed") }) self.present(alert, animated: true, completion: nil) From a7bb33b71dfe7448bcd0553d042bb9dad4eeaa42 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Tue, 3 Mar 2026 01:39:42 -0700 Subject: [PATCH 48/66] [CompatibilityView+Resolution] Add 48MP checks Also includes various localization changes for future commits --- mlchtCamera/Localizable.xcstrings | 65 +++++++++++++------ .../CompatibilityView+Resolutions.swift | 1 + 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/mlchtCamera/Localizable.xcstrings b/mlchtCamera/Localizable.xcstrings index 9d4e818..0c74eff 100755 --- a/mlchtCamera/Localizable.xcstrings +++ b/mlchtCamera/Localizable.xcstrings @@ -96,7 +96,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Bringing macro camera control back to you." + "value" : "Pro camera features without the pro price." } } } @@ -147,7 +147,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "mlchtCamera started as an app to help my love work on printed circuit boards with better clarity and manual controls that the stock iOS camera app can't provide, and it grew once my Discord community stood by, actually using it and suggesting new features. It's the first real test of my skills in Swift, and leading my own public project, and my goal is to now provide a free, all-inclusive experience to amazing macro photography - powered by your iPhone, iPad, or iPod touch." + "value" : "mlchtCamera started as an app to help my love work on printed circuit boards with better clarity and manual controls that the stock iOS camera app can't provide, and it grew once my Discord community stood by, actually using it and suggesting new features. It's the first real test of my skills in Swift, and leading my own public project, and my goal is to now provide a free, all-inclusive experience to amazing photography - powered by your iPhone, iPad, or iPod touch." } } } @@ -213,7 +213,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Camera switching is only supported on devices with multiple rear cameras." + "value" : "Camera switching is only supported on devices with multiple cameras." } } } @@ -235,7 +235,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "This device does not have a flashlight to turn on." + "value" : "The flashlight couldn’t be turned on for this camera." } } } @@ -290,7 +290,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Unable to switch cameras" + "value" : "Can’t switch cameras" } } } @@ -301,7 +301,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Unable to adjust exposure" + "value" : "Exposure locked" } } } @@ -312,7 +312,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Unable to turn on flashlight" + "value" : "Can’t turn on flashlight" } } } @@ -323,7 +323,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Unable to adjust focus" + "value" : "Focus locked" } } } @@ -345,7 +345,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Unable to capture images" + "value" : "Can’t capture images" } } } @@ -421,6 +421,7 @@ } }, "compatibility.header.resolution" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -490,7 +491,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Ultra wide supports 12MP capture" + "value" : "Ultra Wide supports 12MP capture" } } } @@ -501,7 +502,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Ultra wide does not support 12MP capture" + "value" : "Ultra Wide does not support 12MP capture" } } } @@ -512,7 +513,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Wide angle supports 12MP capture" + "value" : "Main supports 12MP capture" } } } @@ -523,7 +524,29 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Wide angle does not support 12MP capture" + "value" : "Main does not support 12MP capture" + } + } + } + }, + "compatibility.title.48mp.telephoto" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fusion Telephoto supports 48MP capture" + } + } + } + }, + "compatibility.title.48mp.telephoto.unsupported" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Telephoto does not support 48MP capture" } } } @@ -534,7 +557,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Ultra wide supports 48MP capture" + "value" : "Fusion Ultra Wide supports 48MP capture" } } } @@ -545,7 +568,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Ultra wide does not support 48MP capture" + "value" : "Ultra Wide does not support 48MP capture" } } } @@ -556,7 +579,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Wide angle supports 48MP capture" + "value" : "Fusion Main supports 48MP capture" } } } @@ -567,7 +590,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Wide angle does not support 48MP capture" + "value" : "Main does not support 48MP capture" } } } @@ -666,7 +689,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Ultra wide camera detected" + "value" : "Ultra Wide camera detected" } } } @@ -677,7 +700,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "No ultra wide camera detected" + "value" : "No Ultra Wide camera detected" } } } @@ -688,7 +711,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Wide angle camera detected" + "value" : "Main camera detected" } } } @@ -699,7 +722,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "No wide angle camera detected" + "value" : "No Main camera detected" } } } diff --git a/mlchtCamera/Views/CompatibilityView/CompatibilityView+Resolutions.swift b/mlchtCamera/Views/CompatibilityView/CompatibilityView+Resolutions.swift index d6bb838..0496bbd 100644 --- a/mlchtCamera/Views/CompatibilityView/CompatibilityView+Resolutions.swift +++ b/mlchtCamera/Views/CompatibilityView/CompatibilityView+Resolutions.swift @@ -34,6 +34,7 @@ extension CompatibilityView { if utilities.preferences.ext.dictionary.isValid(dictionary: utilities.preferences.compatibility.telephoto) { // Telephoto megapixel capabilities MalachiteCompatibilityViewUtils(title: "compatibility.title.12mp.telephoto", available: utilities.preferences.compatibility.telephoto["12"] ?? false) + MalachiteCompatibilityViewUtils(title: "compatibility.title.48mp.telephoto", available: utilities.preferences.compatibility.telephoto["48"] ?? false) } } } From 26de09d68eb5d8b6f0fee3281bfdef2a5b29f379 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Wed, 4 Mar 2026 03:53:54 -0700 Subject: [PATCH 49/66] [*.entitlements] Create and move entitlements files I was working on other stuff when I noticed these were put into the root of the project. Moderatly annoying. --- mlchtCamera.xcodeproj/project.pbxproj | 27 +++++++++++++++++++ .../mlchtCaptureBundle.entitlements | 18 +++++++++++++ .../mlchtWidgetBundle.entitlements | 18 +++++++++++++ .../mlchtWidgetBundleWatch.entitlements | 5 ++++ 4 files changed, 68 insertions(+) create mode 100644 mlchtCamera/CaptureBundle/mlchtCaptureBundle.entitlements create mode 100644 mlchtCamera/WidgetBundle/mlchtWidgetBundle.entitlements create mode 100644 mlchtCamera/WidgetBundle/mlchtWidgetBundleWatch.entitlements diff --git a/mlchtCamera.xcodeproj/project.pbxproj b/mlchtCamera.xcodeproj/project.pbxproj index c80ea69..3c3b96d 100755 --- a/mlchtCamera.xcodeproj/project.pbxproj +++ b/mlchtCamera.xcodeproj/project.pbxproj @@ -292,6 +292,9 @@ 78474D2B2D7AC879006FBB96 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = ""; }; 7847D5022E8F26E9006759A6 /* View+Sliders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Sliders.swift"; sourceTree = ""; }; 784EDDD32E5EE52100DFF370 /* PhotoPreviewView+Controls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PhotoPreviewView+Controls.swift"; sourceTree = ""; }; + 785345F72F5845F600DA7A99 /* mlchtWidgetBundle.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = mlchtWidgetBundle.entitlements; sourceTree = ""; }; + 785345F82F58461300DA7A99 /* mlchtCaptureBundle.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = mlchtCaptureBundle.entitlements; sourceTree = ""; }; + 785345F92F58467700DA7A99 /* mlchtWidgetBundleWatch.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = mlchtWidgetBundleWatch.entitlements; sourceTree = ""; }; 78562BC22B450A7600920160 /* PRIVACY_POLICY.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = PRIVACY_POLICY.md; sourceTree = SOURCE_ROOT; }; 785F08492B12D41100244EB4 /* mlchtCamera.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = mlchtCamera.app; sourceTree = BUILT_PRODUCTS_DIR; }; 785F084C2B12D41100244EB4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -550,6 +553,8 @@ 786DAE6C2E4F28B700BE3137 /* WidgetBundle */ = { isa = PBXGroup; children = ( + 785345F72F5845F600DA7A99 /* mlchtWidgetBundle.entitlements */, + 785345F92F58467700DA7A99 /* mlchtWidgetBundleWatch.entitlements */, 786DAE682E4F28B700BE3137 /* ControlCenterWidget.swift */, 786DAE692E4F28B700BE3137 /* Info.plist */, 786DAE6A2E4F28B700BE3137 /* LockScreenWidget.swift */, @@ -561,6 +566,7 @@ 786DAE772E4F28B900BE3137 /* CaptureBundle */ = { isa = PBXGroup; children = ( + 785345F82F58461300DA7A99 /* mlchtCaptureBundle.entitlements */, 786DAE752E4F28B900BE3137 /* Info.plist */, 786DAE762E4F28B900BE3137 /* MalachiteCaptureBundle.swift */, ); @@ -1299,6 +1305,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; + CODE_SIGN_ENTITLEMENTS = mlchtCamera/WidgetBundle/mlchtWidgetBundleWatch.entitlements; CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; @@ -1343,6 +1350,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; + CODE_SIGN_ENTITLEMENTS = mlchtCamera/WidgetBundle/mlchtWidgetBundleWatch.entitlements; CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; @@ -1387,6 +1395,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; + CODE_SIGN_ENTITLEMENTS = mlchtCamera/WidgetBundle/mlchtWidgetBundleWatch.entitlements; CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; @@ -1679,11 +1688,14 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; + CODE_SIGN_ENTITLEMENTS = mlchtCamera/WidgetBundle/mlchtWidgetBundle.entitlements; CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; ENABLE_CPLUSPLUS_BOUNDS_SAFE_BUFFERS = YES; ENABLE_C_BOUNDS_SAFETY = YES; + ENABLE_ENHANCED_SECURITY = YES; + ENABLE_POINTER_AUTHENTICATION = YES; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = mlchtCamera/WidgetBundle/Info.plist; @@ -1721,11 +1733,14 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; + CODE_SIGN_ENTITLEMENTS = mlchtCamera/WidgetBundle/mlchtWidgetBundle.entitlements; CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; ENABLE_CPLUSPLUS_BOUNDS_SAFE_BUFFERS = YES; ENABLE_C_BOUNDS_SAFETY = YES; + ENABLE_ENHANCED_SECURITY = YES; + ENABLE_POINTER_AUTHENTICATION = YES; GCC_PREPROCESSOR_DEFINITIONS = ""; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = mlchtCamera/WidgetBundle/Info.plist; @@ -1761,11 +1776,14 @@ buildSettings = { ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; + CODE_SIGN_ENTITLEMENTS = mlchtCamera/CaptureBundle/mlchtCaptureBundle.entitlements; CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; ENABLE_CPLUSPLUS_BOUNDS_SAFE_BUFFERS = YES; ENABLE_C_BOUNDS_SAFETY = YES; + ENABLE_ENHANCED_SECURITY = YES; + ENABLE_POINTER_AUTHENTICATION = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = mlchtCamera/CaptureBundle/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = mlchtCaptureBundle; @@ -1801,11 +1819,14 @@ buildSettings = { ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; + CODE_SIGN_ENTITLEMENTS = mlchtCamera/CaptureBundle/mlchtCaptureBundle.entitlements; CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; ENABLE_CPLUSPLUS_BOUNDS_SAFE_BUFFERS = YES; ENABLE_C_BOUNDS_SAFETY = YES; + ENABLE_ENHANCED_SECURITY = YES; + ENABLE_POINTER_AUTHENTICATION = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = mlchtCamera/CaptureBundle/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = mlchtCaptureBundle; @@ -1841,11 +1862,14 @@ buildSettings = { ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; + CODE_SIGN_ENTITLEMENTS = mlchtCamera/CaptureBundle/mlchtCaptureBundle.entitlements; CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; ENABLE_CPLUSPLUS_BOUNDS_SAFE_BUFFERS = YES; ENABLE_C_BOUNDS_SAFETY = YES; + ENABLE_ENHANCED_SECURITY = YES; + ENABLE_POINTER_AUTHENTICATION = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = mlchtCamera/CaptureBundle/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = mlchtCaptureBundle; @@ -2139,11 +2163,14 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; ASSETCATALOG_OTHER_FLAGS = "--enable-icon-stack-fallback-generation=disabled"; + CODE_SIGN_ENTITLEMENTS = mlchtCamera/WidgetBundle/mlchtWidgetBundle.entitlements; CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; ENABLE_CPLUSPLUS_BOUNDS_SAFE_BUFFERS = YES; ENABLE_C_BOUNDS_SAFETY = YES; + ENABLE_ENHANCED_SECURITY = YES; + ENABLE_POINTER_AUTHENTICATION = YES; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = mlchtCamera/WidgetBundle/Info.plist; diff --git a/mlchtCamera/CaptureBundle/mlchtCaptureBundle.entitlements b/mlchtCamera/CaptureBundle/mlchtCaptureBundle.entitlements new file mode 100644 index 0000000..b85694e --- /dev/null +++ b/mlchtCamera/CaptureBundle/mlchtCaptureBundle.entitlements @@ -0,0 +1,18 @@ + + + + + com.apple.security.hardened-process + + com.apple.security.hardened-process.checked-allocations + + com.apple.security.hardened-process.dyld-ro + + com.apple.security.hardened-process.enhanced-security-version-string + 1 + com.apple.security.hardened-process.hardened-heap + + com.apple.security.hardened-process.platform-restrictions-string + 2 + + diff --git a/mlchtCamera/WidgetBundle/mlchtWidgetBundle.entitlements b/mlchtCamera/WidgetBundle/mlchtWidgetBundle.entitlements new file mode 100644 index 0000000..b85694e --- /dev/null +++ b/mlchtCamera/WidgetBundle/mlchtWidgetBundle.entitlements @@ -0,0 +1,18 @@ + + + + + com.apple.security.hardened-process + + com.apple.security.hardened-process.checked-allocations + + com.apple.security.hardened-process.dyld-ro + + com.apple.security.hardened-process.enhanced-security-version-string + 1 + com.apple.security.hardened-process.hardened-heap + + com.apple.security.hardened-process.platform-restrictions-string + 2 + + diff --git a/mlchtCamera/WidgetBundle/mlchtWidgetBundleWatch.entitlements b/mlchtCamera/WidgetBundle/mlchtWidgetBundleWatch.entitlements new file mode 100644 index 0000000..0c67376 --- /dev/null +++ b/mlchtCamera/WidgetBundle/mlchtWidgetBundleWatch.entitlements @@ -0,0 +1,5 @@ + + + + + From 7fcfeca9499564f8b909bed611c48a1c05e4fc2d Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Wed, 4 Mar 2026 04:49:29 -0700 Subject: [PATCH 50/66] [*.xcscheme] Switch all schemes to using Internal for everything except for archive Really need to finish that .xcconfig defined build configuration setup... --- .../xcshareddata/xcschemes/mlchtCamera.xcscheme | 8 ++++---- .../xcshareddata/xcschemes/mlchtCaptureBundle.xcscheme | 8 ++++---- .../xcshareddata/xcschemes/mlchtRemote.xcscheme | 8 ++++---- .../xcshareddata/xcschemes/mlchtWidgetBundle.xcscheme | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtCamera.xcscheme b/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtCamera.xcscheme index 17f3f4d..9853b18 100644 --- a/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtCamera.xcscheme +++ b/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtCamera.xcscheme @@ -24,7 +24,7 @@ + buildConfiguration = "Internal"> diff --git a/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtCaptureBundle.xcscheme b/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtCaptureBundle.xcscheme index d04af59..8041a61 100644 --- a/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtCaptureBundle.xcscheme +++ b/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtCaptureBundle.xcscheme @@ -39,14 +39,14 @@ + buildConfiguration = "Internal"> + buildConfiguration = "Internal"> + buildConfiguration = "Internal"> Date: Wed, 4 Mar 2026 04:52:32 -0700 Subject: [PATCH 51/66] [pbxproj] Bump to Xcode 26.3 minimum macOS Sequoia 15.2 -> macOS Sequoia 15.6 --- mlchtCamera.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mlchtCamera.xcodeproj/project.pbxproj b/mlchtCamera.xcodeproj/project.pbxproj index 3c3b96d..3e134c8 100755 --- a/mlchtCamera.xcodeproj/project.pbxproj +++ b/mlchtCamera.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 90; + objectVersion = 100; objects = { /* Begin PBXBuildFile section */ @@ -814,7 +814,7 @@ Base, ); mainGroup = 785F08402B12D41100244EB4; - preferredProjectObjectVersion = 90; + preferredProjectObjectVersion = 100; productRefGroup = 785F084A2B12D41100244EB4 /* Products */; projectDirPath = ""; projectRoot = ""; From a3d28bf26981a4d498997b0f2cb3ee83a9c20314 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Wed, 4 Mar 2026 05:03:01 -0700 Subject: [PATCH 52/66] [CHANGELOG] Document currently public changes I haven't finished the menus yet --- CHANGELOG.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 720abc7..f1be902 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,21 @@ # 1.0.0 (build xxx) +**NOTE:** The build number is unrelated to previous build numbers because of an Apple Developer Program account change. +- Bump Xcode project version to Xcode 26.3 + - Xcode 26.3 runs on macOS Sequoia 15.6 and later +- Add support for the front camera! +- Introduce Enhanced Security capability for iOS and iPadOS 26 +- Fixed an issue where HDR would show as enabled in Settings on cameras that do not support it +- Fixed an issue where symbols on buttons in the viewfinder would be hard to see + - Addressed by configuring the color on iOS and iPadOS 17 + - Addressed by using labels on iOS and iPadOS 18 +- Fixed Liquid Glass issues on iOS and iPadOS 26.1 beta 2 and later + - Addressed by removing corner radius and masking - **Renamed Malachite to mlchtCamera** - - Malachite Remote also gets a rename to mlchtRemote. + - Malachite Remote also gets a rename to mlchtRemote - Issues with App Store Connect and bundle IDs, I love Apple... # 1.0.0 (build 54) +**NOTE:** The build number is unrelated to previous build numbers because of an Apple Developer Program account change. - **Increased the minimum version requirement from iOS 14.1 to iOS 15** - Supporting the few users on iOS 14 is no longer worth the extra complexity for my workflow From 9369fafb237f0d82f051073893a2b2ea94223480 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Fri, 6 Mar 2026 00:36:45 -0700 Subject: [PATCH 53/66] [CameraView+Controls] Fix crash on launch Just waiting for cameras to come up before adding the UI menu like a smart person --- .../CameraView/CameraView+Controls.swift | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/mlchtCamera/Views/CameraView/CameraView+Controls.swift b/mlchtCamera/Views/CameraView/CameraView+Controls.swift index ec66e8e..a6c80e8 100644 --- a/mlchtCamera/Views/CameraView/CameraView+Controls.swift +++ b/mlchtCamera/Views/CameraView/CameraView+Controls.swift @@ -117,14 +117,6 @@ extension CameraView { MalachiteViewUtils.buttonBuilder(symbolName: "", action: #selector(delegate.stub), dimensions: [ 60.0 ], constraints: buttonConstraints[14], hidden: false, assign: { [self] button in self.buttons.currentCamera = button }), ] - let mappings: [ String : String ] = [ - "Front Camera": "camera.front.wideangle".localized, - "Front Ultra Wide Camera": "camera.front.ultrawide".localized, - "Back Telephoto Camera": "camera.back.telephoto".localized, - "Back Camera": "camera.back.wideangle".localized, - "Back Ultra Wide Camera": "camera.back.ultrawide".localized, - ] - for config in buttonConfigs { let button = delegate.utilities.views.createAndAddButtonToView(symbolName: config.symbolName, delegate: delegate, view: delegate.view, utilities: delegate.utilities, action: config.action, dimensions: config.dimensions, constraints: config.constraints) if delegate.utilities.preferences.userInterface.appLaunch { button.alpha = 0.0; self.uiIsHidden = true } @@ -132,6 +124,18 @@ extension CameraView { config.assign(button) } + NotificationCenter.default.addObserver(self, selector: #selector(initMenus), name: MalachiteFunctionUtils.Notifications.cameraClassNotification.name, object: nil) + } + + @objc func initMenus() { + NSLog("[ControlLayer] Received notification that cameras were loaded, creating UIMenu") + let mappings: [ String : String ] = [ + "Front Camera": "camera.front.wideangle".localized, + "Front Ultra Wide Camera": "camera.front.ultrawide".localized, + "Back Telephoto Camera": "camera.back.telephoto".localized, + "Back Camera": "camera.back.wideangle".localized, + "Back Ultra Wide Camera": "camera.back.ultrawide".localized, + ] let menuActions = delegate.camera.cameras.reversed().map { item in UIAction(title: (mappings[item.localizedName] ?? item.localizedName), image: nil) { [self] action in delegate.camera.index = self.delegate.camera.cameras.firstIndex(of: item) From 38510cb833c34615bcf5d0627269aba057894e81 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Fri, 6 Mar 2026 14:48:37 -0700 Subject: [PATCH 54/66] [pbxproj] Drop back to Xcode 16.3 as Xcode Cloud isn't up to date The incompetence is maddening. --- mlchtCamera.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mlchtCamera.xcodeproj/project.pbxproj b/mlchtCamera.xcodeproj/project.pbxproj index 3e134c8..3c3b96d 100755 --- a/mlchtCamera.xcodeproj/project.pbxproj +++ b/mlchtCamera.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 100; + objectVersion = 90; objects = { /* Begin PBXBuildFile section */ @@ -814,7 +814,7 @@ Base, ); mainGroup = 785F08402B12D41100244EB4; - preferredProjectObjectVersion = 100; + preferredProjectObjectVersion = 90; productRefGroup = 785F084A2B12D41100244EB4 /* Products */; projectDirPath = ""; projectRoot = ""; From 7f57d4f0f60372e8a4d6a054445769d76c45dfdc Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Fri, 6 Mar 2026 21:09:40 -0700 Subject: [PATCH 55/66] [xcscheme] Switch archiving to Internal Because I need Xcode Cloud to work. Will probably just check what I changed between the three build configurations and make it adjustable in an xcconfig somewhere. --- .../xcschemes/mlchtCamera.xcscheme | 2 +- .../xcschemes/mlchtCaptureBundle.xcscheme | 2 +- .../xcschemes/mlchtRemote.xcscheme | 2 +- .../xcschemes/mlchtWidgetBundle.xcscheme | 2 +- .../xcschemes/mlchtWidgetBundleWatch.xcscheme | 126 ++++++++++++++++++ 5 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtWidgetBundleWatch.xcscheme diff --git a/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtCamera.xcscheme b/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtCamera.xcscheme index 9853b18..5d94408 100644 --- a/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtCamera.xcscheme +++ b/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtCamera.xcscheme @@ -72,7 +72,7 @@ buildConfiguration = "Internal"> diff --git a/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtCaptureBundle.xcscheme b/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtCaptureBundle.xcscheme index 8041a61..a95d031 100644 --- a/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtCaptureBundle.xcscheme +++ b/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtCaptureBundle.xcscheme @@ -91,7 +91,7 @@ buildConfiguration = "Internal"> diff --git a/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtRemote.xcscheme b/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtRemote.xcscheme index 9f0de65..ca40e36 100644 --- a/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtRemote.xcscheme +++ b/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtRemote.xcscheme @@ -86,7 +86,7 @@ buildConfiguration = "Internal"> diff --git a/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtWidgetBundle.xcscheme b/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtWidgetBundle.xcscheme index ae380ea..97ae352 100644 --- a/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtWidgetBundle.xcscheme +++ b/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtWidgetBundle.xcscheme @@ -91,7 +91,7 @@ buildConfiguration = "Internal"> diff --git a/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtWidgetBundleWatch.xcscheme b/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtWidgetBundleWatch.xcscheme new file mode 100644 index 0000000..2be6a12 --- /dev/null +++ b/mlchtCamera.xcodeproj/xcshareddata/xcschemes/mlchtWidgetBundleWatch.xcscheme @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 96587a57a51222194e3880d0e922cd9156992126 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Wed, 11 Mar 2026 01:45:14 -0600 Subject: [PATCH 56/66] [Localizable.xcstrings] Fix extractionState for resolution header What? --- mlchtCamera/Localizable.xcstrings | 1 - 1 file changed, 1 deletion(-) diff --git a/mlchtCamera/Localizable.xcstrings b/mlchtCamera/Localizable.xcstrings index 0c74eff..b1b8273 100755 --- a/mlchtCamera/Localizable.xcstrings +++ b/mlchtCamera/Localizable.xcstrings @@ -421,7 +421,6 @@ } }, "compatibility.header.resolution" : { - "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { From 70e42bc6e28e1a5fb89a5bd0880b6aba54a60cd3 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Wed, 11 Mar 2026 01:46:43 -0600 Subject: [PATCH 57/66] [CameraView] The Great Shuffle - Moved around inits to somehow fix the multi-camera lag issue - Put the controlLayer.initMenus() notification into the right place --- mlchtCamera/Views/CameraView/CameraView+Controls.swift | 2 -- mlchtCamera/Views/CameraView/CameraView+Notifications.swift | 1 + mlchtCamera/Views/CameraView/CameraView.swift | 6 +++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/mlchtCamera/Views/CameraView/CameraView+Controls.swift b/mlchtCamera/Views/CameraView/CameraView+Controls.swift index a6c80e8..911595a 100644 --- a/mlchtCamera/Views/CameraView/CameraView+Controls.swift +++ b/mlchtCamera/Views/CameraView/CameraView+Controls.swift @@ -123,8 +123,6 @@ extension CameraView { if config.hidden { button.alpha = 0.0 } config.assign(button) } - - NotificationCenter.default.addObserver(self, selector: #selector(initMenus), name: MalachiteFunctionUtils.Notifications.cameraClassNotification.name, object: nil) } @objc func initMenus() { diff --git a/mlchtCamera/Views/CameraView/CameraView+Notifications.swift b/mlchtCamera/Views/CameraView/CameraView+Notifications.swift index 169c870..9814f1d 100644 --- a/mlchtCamera/Views/CameraView/CameraView+Notifications.swift +++ b/mlchtCamera/Views/CameraView/CameraView+Notifications.swift @@ -28,6 +28,7 @@ extension CameraView { temputils.notificationBuilder(delegate: delegate, name: MalachiteFunctionUtils.Notifications.continousAEAFNotification.name, action: #selector(changeContinuousAEAF)), temputils.notificationBuilder(delegate: delegate, name: MalachiteFunctionUtils.Notifications.aeafTapGestureNotification.name, action: #selector(changeAEAFRecognizer)), temputils.notificationBuilder(delegate: delegate.utilities.function, name: MalachiteFunctionUtils.Notifications.idleTimerNotification.name, action: #selector(delegate.utilities.function.changeIdleTimerState)), + temputils.notificationBuilder(delegate: delegate.controlLayer ?? CameraView.ControlLayer(delegate: delegate), name: MalachiteFunctionUtils.Notifications.cameraClassNotification.name, action:#selector(delegate.controlLayer.initMenus)) ] if #available(iOS 16.0, *) { diff --git a/mlchtCamera/Views/CameraView/CameraView.swift b/mlchtCamera/Views/CameraView/CameraView.swift index 95e41db..b6311a3 100755 --- a/mlchtCamera/Views/CameraView/CameraView.swift +++ b/mlchtCamera/Views/CameraView/CameraView.swift @@ -59,13 +59,13 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa if #unavailable(iOS 18.0) { overrideUserInterfaceStyle = .dark } self.view.backgroundColor = .black + self.notifications = CameraView.Notifications(delegate: self) - self.notifications.bringUpNotifications() + self.controlLayer = CameraView.ControlLayer(delegate: self) + self.notifications.bringUpNotifications() self.camera = Camera(utilities: utilities) - self.preview = CameraView.Preview(delegate: self) - self.controlLayer = CameraView.ControlLayer(delegate: self) } @objc func cameraClassDidLoad() { From e1b2e64eacc29e0365cc2d32da6cabb43e899253 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Thu, 12 Mar 2026 01:55:16 -0600 Subject: [PATCH 58/66] [mlchtRemote] Basic mlchtRemote implementation This is nowhere near final, don't get your hopes up. I also really hate the code. --- mlchtCamera.xcodeproj/project.pbxproj | 76 ++++++++++++++-- mlchtCamera/SceneDelegate.swift | 12 ++- mlchtCamera/Utilities/MalachiteUtils.swift | 2 + mlchtCamera/Utilities/WatchUtils/Watch.swift | 87 +++++++++++++++++++ mlchtCamera/Utilities/temputils.swift | 5 ++ .../CameraView/CameraView+Controls.swift | 2 - .../CameraView/CameraView+Notifications.swift | 6 +- mlchtCamera/Views/CameraView/CameraView.swift | 22 +++-- mlchtRemote/ContentView.swift | 24 ----- .../Connection+Notifications.swift | 12 +++ .../ConnectionUtils/Connection.swift | 66 ++++++++++++++ mlchtRemote/Utilities/Misc.swift | 20 +++++ mlchtRemote/Views/Content+Controls.swift | 46 ++++++++++ mlchtRemote/Views/Content+Debug.swift | 20 +++++ mlchtRemote/Views/Content.swift | 80 +++++++++++++++++ ...achiteWatchApp.swift => mlchtRemote.swift} | 8 +- 16 files changed, 439 insertions(+), 49 deletions(-) create mode 100644 mlchtCamera/Utilities/WatchUtils/Watch.swift delete mode 100644 mlchtRemote/ContentView.swift create mode 100644 mlchtRemote/Utilities/ConnectionUtils/Connection+Notifications.swift create mode 100644 mlchtRemote/Utilities/ConnectionUtils/Connection.swift create mode 100644 mlchtRemote/Utilities/Misc.swift create mode 100644 mlchtRemote/Views/Content+Controls.swift create mode 100644 mlchtRemote/Views/Content+Debug.swift create mode 100644 mlchtRemote/Views/Content.swift rename mlchtRemote/{MalachiteWatchApp.swift => mlchtRemote.swift} (53%) diff --git a/mlchtCamera.xcodeproj/project.pbxproj b/mlchtCamera.xcodeproj/project.pbxproj index 3c3b96d..d5336e5 100755 --- a/mlchtCamera.xcodeproj/project.pbxproj +++ b/mlchtCamera.xcodeproj/project.pbxproj @@ -111,8 +111,7 @@ 786DAE722E4F28B700BE3137 /* LockScreenWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE6A2E4F28B700BE3137 /* LockScreenWidget.swift */; }; 786DAE732E4F28B700BE3137 /* MalachiteWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE6B2E4F28B700BE3137 /* MalachiteWidgetBundle.swift */; }; 786DAE782E4F28B900BE3137 /* MalachiteCaptureBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE762E4F28B900BE3137 /* MalachiteCaptureBundle.swift */; }; - 786DAE7D2E4F28BF00BE3137 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE792E4F28BF00BE3137 /* ContentView.swift */; }; - 786DAE7E2E4F28BF00BE3137 /* MalachiteWatchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE7B2E4F28BF00BE3137 /* MalachiteWatchApp.swift */; }; + 786DAE7E2E4F28BF00BE3137 /* mlchtRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786DAE7B2E4F28BF00BE3137 /* mlchtRemote.swift */; }; 78729C562E7368F3001027E9 /* Camera+Input.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78729C552E7368F0001027E9 /* Camera+Input.swift */; }; 78729C572E7368F3001027E9 /* Camera+Input.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78729C552E7368F0001027E9 /* Camera+Input.swift */; }; 78729C582E7368F3001027E9 /* Camera+Input.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78729C552E7368F0001027E9 /* Camera+Input.swift */; }; @@ -191,6 +190,15 @@ 78C2EFC52E6972D600C6DD79 /* Init+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78C2EFC32E6972D300C6DD79 /* Init+Internal.swift */; }; 78C2EFC62E6972D600C6DD79 /* Init+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78C2EFC32E6972D300C6DD79 /* Init+Internal.swift */; }; 78CAC36A2CCD99B600A35AE8 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */; }; + 78D3F0042F6284F400760240 /* Watch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D3F0032F6284F200760240 /* Watch.swift */; }; + 78D3F0052F6284F400760240 /* Watch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D3F0032F6284F200760240 /* Watch.swift */; }; + 78D3F0062F6284F400760240 /* Watch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D3F0032F6284F200760240 /* Watch.swift */; }; + 78D3F00A2F629B1C00760240 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D3F0092F629B1A00760240 /* Connection.swift */; }; + 78D3F00C2F629B5700760240 /* Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D3F00B2F629B5500760240 /* Misc.swift */; }; + 78D3F00E2F629BC200760240 /* Connection+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D3F00D2F629BBD00760240 /* Connection+Notifications.swift */; }; + 78D3F0212F62A50A00760240 /* Content.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D3F01D2F62A50A00760240 /* Content.swift */; }; + 78D3F0222F62A50A00760240 /* Content+Controls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D3F01E2F62A50A00760240 /* Content+Controls.swift */; }; + 78D3F0232F62A50A00760240 /* Content+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D3F01F2F62A50A00760240 /* Content+Debug.swift */; }; 78E170772E51C97B009BEF2F /* CompatibilityView+Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E170762E51C971009BEF2F /* CompatibilityView+Camera.swift */; }; 78E170782E51C97B009BEF2F /* CompatibilityView+Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E170762E51C971009BEF2F /* CompatibilityView+Camera.swift */; }; 78E170792E51C97B009BEF2F /* CompatibilityView+Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E170762E51C971009BEF2F /* CompatibilityView+Camera.swift */; }; @@ -329,9 +337,8 @@ 786DAE6B2E4F28B700BE3137 /* MalachiteWidgetBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteWidgetBundle.swift; sourceTree = ""; }; 786DAE752E4F28B900BE3137 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 786DAE762E4F28B900BE3137 /* MalachiteCaptureBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteCaptureBundle.swift; sourceTree = ""; }; - 786DAE792E4F28BF00BE3137 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 786DAE7A2E4F28BF00BE3137 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 786DAE7B2E4F28BF00BE3137 /* MalachiteWatchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MalachiteWatchApp.swift; sourceTree = ""; }; + 786DAE7B2E4F28BF00BE3137 /* mlchtRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = mlchtRemote.swift; sourceTree = ""; }; 78729C552E7368F0001027E9 /* Camera+Input.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Camera+Input.swift"; sourceTree = ""; }; 787B1CA92B8B095E000AFECC /* mlchtCamera.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = mlchtCamera.docc; sourceTree = ""; }; 7881A98B2C1F77AB00B1F83B /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = mlchtCamera/Localizable.xcstrings; sourceTree = SOURCE_ROOT; }; @@ -364,6 +371,13 @@ 78B72BA72E33282E002E2D4E /* mlchtRemote.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = mlchtRemote.app; sourceTree = BUILT_PRODUCTS_DIR; }; 78C2EFBF2E6971DE00C6DD79 /* Init+Debug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Init+Debug.swift"; sourceTree = ""; }; 78C2EFC32E6972D300C6DD79 /* Init+Internal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Init+Internal.swift"; sourceTree = ""; }; + 78D3F0032F6284F200760240 /* Watch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Watch.swift; sourceTree = ""; }; + 78D3F0092F629B1A00760240 /* Connection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Connection.swift; sourceTree = ""; }; + 78D3F00B2F629B5500760240 /* Misc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Misc.swift; sourceTree = ""; }; + 78D3F00D2F629BBD00760240 /* Connection+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Connection+Notifications.swift"; sourceTree = ""; }; + 78D3F01D2F62A50A00760240 /* Content.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Content.swift; sourceTree = ""; }; + 78D3F01E2F62A50A00760240 /* Content+Controls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Content+Controls.swift"; sourceTree = ""; }; + 78D3F01F2F62A50A00760240 /* Content+Debug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Content+Debug.swift"; sourceTree = ""; }; 78E142C92DD426260016B3DB /* Codesigning.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Codesigning.xcconfig; sourceTree = ""; }; 78E170762E51C971009BEF2F /* CompatibilityView+Camera.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CompatibilityView+Camera.swift"; sourceTree = ""; }; 78E1707A2E51CC5C009BEF2F /* CompatibilityView+Resolutions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CompatibilityView+Resolutions.swift"; sourceTree = ""; }; @@ -534,6 +548,7 @@ 786DAE4B2E4F28B100BE3137 /* Utilities */ = { isa = PBXGroup; children = ( + 78D3F0022F6284F000760240 /* WatchUtils */, 7818E23A2E8097110046F621 /* CompatibilityUtils */, 78C2EFBE2E6971CB00C6DD79 /* InitUtils */, 7866F5D52E62A5F7009AC9BF /* temputils.swift */, @@ -576,9 +591,10 @@ 786DAE7C2E4F28BF00BE3137 /* mlchtRemote */ = { isa = PBXGroup; children = ( - 786DAE792E4F28BF00BE3137 /* ContentView.swift */, + 78D3F0202F62A50A00760240 /* Views */, + 78D3F0072F629AF800760240 /* Utilities */, 786DAE7A2E4F28BF00BE3137 /* Info.plist */, - 786DAE7B2E4F28BF00BE3137 /* MalachiteWatchApp.swift */, + 786DAE7B2E4F28BF00BE3137 /* mlchtRemote.swift */, ); path = mlchtRemote; sourceTree = ""; @@ -664,6 +680,42 @@ path = InitUtils; sourceTree = ""; }; + 78D3F0022F6284F000760240 /* WatchUtils */ = { + isa = PBXGroup; + children = ( + 78D3F0032F6284F200760240 /* Watch.swift */, + ); + path = WatchUtils; + sourceTree = ""; + }; + 78D3F0072F629AF800760240 /* Utilities */ = { + isa = PBXGroup; + children = ( + 78D3F00B2F629B5500760240 /* Misc.swift */, + 78D3F0082F629B1700760240 /* ConnectionUtils */, + ); + path = Utilities; + sourceTree = ""; + }; + 78D3F0082F629B1700760240 /* ConnectionUtils */ = { + isa = PBXGroup; + children = ( + 78D3F00D2F629BBD00760240 /* Connection+Notifications.swift */, + 78D3F0092F629B1A00760240 /* Connection.swift */, + ); + path = ConnectionUtils; + sourceTree = ""; + }; + 78D3F0202F62A50A00760240 /* Views */ = { + isa = PBXGroup; + children = ( + 78D3F01D2F62A50A00760240 /* Content.swift */, + 78D3F01E2F62A50A00760240 /* Content+Controls.swift */, + 78D3F01F2F62A50A00760240 /* Content+Debug.swift */, + ); + path = Views; + sourceTree = ""; + }; 78E170752E51C312009BEF2F /* CompatibilityView */ = { isa = PBXGroup; children = ( @@ -1085,6 +1137,7 @@ 788262E32E5172E8000085AC /* AboutView+Credits.swift in Sources */, 786DAE572E4F28B100BE3137 /* MalachiteFunctionUtils.swift in Sources */, 788262932E514F2D000085AC /* QuickHelpView+Photo.swift in Sources */, + 78D3F0062F6284F400760240 /* Watch.swift in Sources */, 786DAE582E4F28B100BE3137 /* MalachiteGameUtils.swift in Sources */, 786DAE592E4F28B100BE3137 /* MalachiteHapticUtils.swift in Sources */, 78E170772E51C97B009BEF2F /* CompatibilityView+Camera.swift in Sources */, @@ -1149,6 +1202,7 @@ 788262E22E5172E8000085AC /* AboutView+Credits.swift in Sources */, 786DAE602E4F28B100BE3137 /* MalachiteFunctionUtils.swift in Sources */, 788262922E514F2D000085AC /* QuickHelpView+Photo.swift in Sources */, + 78D3F0052F6284F400760240 /* Watch.swift in Sources */, 786DAE612E4F28B100BE3137 /* MalachiteGameUtils.swift in Sources */, 786DAE622E4F28B100BE3137 /* MalachiteHapticUtils.swift in Sources */, 78E170792E51C97B009BEF2F /* CompatibilityView+Camera.swift in Sources */, @@ -1209,6 +1263,7 @@ 788262602E513080000085AC /* SettingsView+About.swift in Sources */, 786DAE4C2E4F28B100BE3137 /* MalachitePreferences.swift in Sources */, 7847D5042E8F26EC006759A6 /* View+Sliders.swift in Sources */, + 78D3F0042F6284F400760240 /* Watch.swift in Sources */, 78C2EFC42E6972D600C6DD79 /* Init+Internal.swift in Sources */, 788262DF2E517252000085AC /* AboutView+Eggs.swift in Sources */, 788262972E514F38000085AC /* QuickHelpView+Watermarking.swift in Sources */, @@ -1262,8 +1317,13 @@ 78B72BA32E33282E002E2D4E /* Sources */ = { isa = PBXSourcesBuildPhase; files = ( - 786DAE7D2E4F28BF00BE3137 /* ContentView.swift in Sources */, - 786DAE7E2E4F28BF00BE3137 /* MalachiteWatchApp.swift in Sources */, + 78D3F0212F62A50A00760240 /* Content.swift in Sources */, + 78D3F0222F62A50A00760240 /* Content+Controls.swift in Sources */, + 78D3F0232F62A50A00760240 /* Content+Debug.swift in Sources */, + 78D3F00E2F629BC200760240 /* Connection+Notifications.swift in Sources */, + 78D3F00A2F629B1C00760240 /* Connection.swift in Sources */, + 786DAE7E2E4F28BF00BE3137 /* mlchtRemote.swift in Sources */, + 78D3F00C2F629B5700760240 /* Misc.swift in Sources */, ); }; /* End PBXSourcesBuildPhase section */ diff --git a/mlchtCamera/SceneDelegate.swift b/mlchtCamera/SceneDelegate.swift index 7fb3e68..3a950f9 100755 --- a/mlchtCamera/SceneDelegate.swift +++ b/mlchtCamera/SceneDelegate.swift @@ -6,12 +6,14 @@ // import UIKit +import WatchConnectivity class SceneDelegate: UIResponder, UIWindowSceneDelegate { + let utilities = MalachiteClassesObject() + var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } - let utilities = MalachiteClassesObject() let initialization = Init(utilities: utilities) let rootVC = CameraView() @@ -22,4 +24,12 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window?.rootViewController = rootVC window?.makeKeyAndVisible() } + + func sceneDidBecomeActive(_ scene: UIScene) { + utilities.watch.notifyOfForegroundChange() + } + + func sceneWillResignActive(_ scene: UIScene) { + utilities.watch.notifyOfForegroundChange() + } } diff --git a/mlchtCamera/Utilities/MalachiteUtils.swift b/mlchtCamera/Utilities/MalachiteUtils.swift index 534ead0..f052be5 100755 --- a/mlchtCamera/Utilities/MalachiteUtils.swift +++ b/mlchtCamera/Utilities/MalachiteUtils.swift @@ -27,6 +27,8 @@ public class MalachiteClassesObject : NSObject { public let tooltips = MalachiteTooltipUtils() /// An instance of ``MalachiteGameUtils`` public let games = MalachiteGameUtils() + /// An instance of ``Watch`` + public let watch = Watch() /// Private static storage for the session queue. @available(iOS 18.0, *) diff --git a/mlchtCamera/Utilities/WatchUtils/Watch.swift b/mlchtCamera/Utilities/WatchUtils/Watch.swift new file mode 100644 index 0000000..e64004d --- /dev/null +++ b/mlchtCamera/Utilities/WatchUtils/Watch.swift @@ -0,0 +1,87 @@ +// +// Watch.swift +// mlchtCamera +// +// Created by Eva Isabella Luna on 3/11/26. +// + +import Foundation +import WatchConnectivity + +public class Watch: NSObject, WCSessionDelegate { + var isForegrounded = false + + public func bringUpCompanionConnection() { + guard WCSession.isSupported() else { return } + let session = WCSession.default + session.delegate = self + session.activate() + } + + public func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: (any Error)?) { + guard activationState == .activated else { return } + handle(session.receivedApplicationContext) + sendForegroundState() + } + + public func sessionDidBecomeInactive(_ session: WCSession) { + // stub + } + + public func sessionDidDeactivate(_ session: WCSession) { + // stub + } + + public func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) { + handle(applicationContext) + } + + public func session(_ session: WCSession, didReceiveMessage message: [String : Any]) { + handle(message) + } + + public func sessionReachabilityDidChange(_ session: WCSession) { + if session.isReachable { sendForegroundState() } + } + + private func handle(_ dict: [String: Any]) { + if dict["watchLoaded"] as? Bool == true { sendForegroundState() } + + switch dict["pressed"] as? String { + case "capture": NotificationCenter.default.post(name: Notifications.buttonPressed.capture.name, object: nil) + case "cameras": NotificationCenter.default.post(name: Notifications.buttonPressed.cameras.name, object: nil) + case "flashlight": NotificationCenter.default.post(name: Notifications.buttonPressed.flashlight.name, object: nil) + case "settings": NotificationCenter.default.post(name: Notifications.buttonPressed.settings.name, object: nil) + default: print("bruh") + } + + } + + public func notifyOfForegroundChange() { + isForegrounded = !isForegrounded + sendForegroundState() + } + + public func sendForegroundState() { + if WCSession.default.isReachable { + WCSession.default.sendMessage([ "isForeground": isForegrounded ], replyHandler: nil, errorHandler: nil) + } else { + do { + try WCSession.default.updateApplicationContext([ "isForeground": isForegrounded ]) + } catch { + print(error) + } + } + } +} + +extension Watch { + class Notifications { + enum buttonPressed: String, NotificationName { + case capture + case cameras + case flashlight + case settings + } + } +} diff --git a/mlchtCamera/Utilities/temputils.swift b/mlchtCamera/Utilities/temputils.swift index af210ab..f4617ca 100644 --- a/mlchtCamera/Utilities/temputils.swift +++ b/mlchtCamera/Utilities/temputils.swift @@ -17,3 +17,8 @@ class temputils { let action: Selector } } + +final class CompanionState: ObservableObject { + static let shared = CompanionState() + @Published var isForeground: Bool = false +} diff --git a/mlchtCamera/Views/CameraView/CameraView+Controls.swift b/mlchtCamera/Views/CameraView/CameraView+Controls.swift index 911595a..f514130 100644 --- a/mlchtCamera/Views/CameraView/CameraView+Controls.swift +++ b/mlchtCamera/Views/CameraView/CameraView+Controls.swift @@ -401,5 +401,3 @@ extension CameraView.ControlLayer { eventInteraction = interaction } } - - diff --git a/mlchtCamera/Views/CameraView/CameraView+Notifications.swift b/mlchtCamera/Views/CameraView/CameraView+Notifications.swift index 9814f1d..b3b148f 100644 --- a/mlchtCamera/Views/CameraView/CameraView+Notifications.swift +++ b/mlchtCamera/Views/CameraView/CameraView+Notifications.swift @@ -28,7 +28,11 @@ extension CameraView { temputils.notificationBuilder(delegate: delegate, name: MalachiteFunctionUtils.Notifications.continousAEAFNotification.name, action: #selector(changeContinuousAEAF)), temputils.notificationBuilder(delegate: delegate, name: MalachiteFunctionUtils.Notifications.aeafTapGestureNotification.name, action: #selector(changeAEAFRecognizer)), temputils.notificationBuilder(delegate: delegate.utilities.function, name: MalachiteFunctionUtils.Notifications.idleTimerNotification.name, action: #selector(delegate.utilities.function.changeIdleTimerState)), - temputils.notificationBuilder(delegate: delegate.controlLayer ?? CameraView.ControlLayer(delegate: delegate), name: MalachiteFunctionUtils.Notifications.cameraClassNotification.name, action:#selector(delegate.controlLayer.initMenus)) + temputils.notificationBuilder(delegate: delegate.controlLayer ?? CameraView.ControlLayer(delegate: delegate), name: MalachiteFunctionUtils.Notifications.cameraClassNotification.name, action:#selector(delegate.controlLayer.initMenus)), + temputils.notificationBuilder(delegate: delegate, name: Watch.Notifications.buttonPressed.capture.name, action:#selector(delegate.runImageCapture)), + temputils.notificationBuilder(delegate: delegate, name: Watch.Notifications.buttonPressed.cameras.name, action:#selector(delegate.runInputSwitch)), + temputils.notificationBuilder(delegate: delegate, name: Watch.Notifications.buttonPressed.flashlight.name, action:#selector(delegate.runFlashlightToggle)), + temputils.notificationBuilder(delegate: delegate, name: Watch.Notifications.buttonPressed.settings.name, action:#selector(delegate.presentSettingsView)) ] if #available(iOS 16.0, *) { diff --git a/mlchtCamera/Views/CameraView/CameraView.swift b/mlchtCamera/Views/CameraView/CameraView.swift index b6311a3..6af52d6 100755 --- a/mlchtCamera/Views/CameraView/CameraView.swift +++ b/mlchtCamera/Views/CameraView/CameraView.swift @@ -15,7 +15,7 @@ import GameKit class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCapturePhotoCaptureDelegate { /// An instance of ``MalachiteClassesObject`` for reuse across the app. - public var utilities = MalachiteClassesObject() + public var utilities: MalachiteClassesObject! /// An instance of MalachiteKit's ``Camera`` class. var camera: Camera! @@ -66,6 +66,8 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa self.notifications.bringUpNotifications() self.camera = Camera(utilities: utilities) self.preview = CameraView.Preview(delegate: self) + + self.utilities.watch.bringUpCompanionConnection() } @objc func cameraClassDidLoad() { @@ -236,7 +238,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa let alert = utilities.views.createAlertController(title: "alert.title.app_extensions.settings", message: "alert.detail.app_extensions.settings", button: self.controlLayer.buttons.settings, defaultSet: true, action: { _ in self.utilities.debugNSLog("[Settings] Dialog has been dismissed") }) - self.present(alert, animated: true, completion: nil) + DispatchQueue.main.async { self.present(alert, animated: true, completion: nil) } return #elseif MAIN_APP var aboutView = SettingsView(dismissAction: {self.dismiss( animated: true, completion: nil )}) @@ -250,7 +252,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa self.controlLayer.buttons.settings } } - self.present(hostingController, animated: true, completion: nil) + DispatchQueue.main.async { self.present(hostingController, animated: true, completion: nil) } #endif } @@ -309,17 +311,19 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa let alert = utilities.views.createAlertController(title: "alert.title.flashlight", message: "alert.detail.flashlight", button: self.controlLayer.buttons.flashlight, defaultSet: true, action: { _ in self.utilities.debugNSLog("[Flashlight] Dialog has been dismissed") }) - self.present(alert, animated: true, completion: nil) + DispatchQueue.main.async { self.present(alert, animated: true, completion: nil) } } } /// Function to take an image. @objc func runImageCapture() { - self.controlLayer.buttons.capture.isEnabled = false - progressIndicator = UIActivityIndicatorView(frame: self.controlLayer.buttons.capture.frame) - self.view.addSubview(progressIndicator) - self.controlLayer.buttons.capture.setImage(nil, for: .normal) - progressIndicator.startAnimating() + DispatchQueue.main.async { [self] in + self.controlLayer.buttons.capture.isEnabled = false + progressIndicator = UIActivityIndicatorView(frame: self.controlLayer.buttons.capture.frame) + self.view.addSubview(progressIndicator) + self.controlLayer.buttons.capture.setImage(nil, for: .normal) + progressIndicator.startAnimating() + } let status = PHPhotoLibrary.authorizationStatus(for: .addOnly) diff --git a/mlchtRemote/ContentView.swift b/mlchtRemote/ContentView.swift deleted file mode 100644 index f7a8301..0000000 --- a/mlchtRemote/ContentView.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// ContentView.swift -// MalachiteWatch Watch App -// -// Created by Eva Isabella Luna on 7/24/25. -// - -import SwiftUI - -struct ContentView: View { - var body: some View { - VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundStyle(.tint) - Text("Hello, world!") - } - .padding() - } -} - -#Preview { - ContentView() -} diff --git a/mlchtRemote/Utilities/ConnectionUtils/Connection+Notifications.swift b/mlchtRemote/Utilities/ConnectionUtils/Connection+Notifications.swift new file mode 100644 index 0000000..2fcdd16 --- /dev/null +++ b/mlchtRemote/Utilities/ConnectionUtils/Connection+Notifications.swift @@ -0,0 +1,12 @@ +// +// Connection+Notifications.swift +// mlchtCamera +// +// Created by Eva Isabella Luna on 3/12/26. +// + +extension Connection { + public enum Notifications: String, NotificationName { + case phoneForegroundChanged + } +} diff --git a/mlchtRemote/Utilities/ConnectionUtils/Connection.swift b/mlchtRemote/Utilities/ConnectionUtils/Connection.swift new file mode 100644 index 0000000..f37fb7b --- /dev/null +++ b/mlchtRemote/Utilities/ConnectionUtils/Connection.swift @@ -0,0 +1,66 @@ +// +// Connection.swift +// mlchtCamera +// +// Created by Eva Isabella Luna on 3/12/26. +// + +import Foundation +import WatchConnectivity + +class Connection: NSObject, WCSessionDelegate { + func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: (any Error)?) { + sendWatchLoaded() + } + + func sessionReachabilityDidChange(_ session: WCSession) { + if session.isReachable { sendWatchLoaded() } + } + + func session(_ session: WCSession, didReceiveMessage message: [String : Any]) { + handle(message) + } + + func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) { + handle(applicationContext) + } + + private func handle(_ dict: [String: Any]) { + if let isForeground = dict["isForeground"] as? Bool { + isPhoneForeground = isForeground + DispatchQueue.main.async { NotificationCenter.default.post(name: Connection.Notifications.phoneForegroundChanged.name, object: nil) } + } + } + + static let shared = Connection() + var isPhoneForeground = false + var isSupported = false + + public func bringUpWatchRemote() { + guard WCSession.isSupported() else { return } + let session = WCSession.default + session.delegate = self + session.activate() + } + + private func sendWatchLoaded() { + let session = WCSession.default + if session.isReachable { + session.sendMessage(["watchLoaded": true], replyHandler: nil) { error in + print("Failed to send watchLoaded: \(error)") + } + } else { + do { + try session.updateApplicationContext(["watchLoaded": true]) + } catch { + print("Failed to update context with watchLoaded: \(error)") + } + } + } + + func sendButtonPress(key: String) { + WCSession.default.sendMessage(["pressed": key], replyHandler: nil) { error in + print("Failed to send: \(error)") + } + } +} diff --git a/mlchtRemote/Utilities/Misc.swift b/mlchtRemote/Utilities/Misc.swift new file mode 100644 index 0000000..d91ebf2 --- /dev/null +++ b/mlchtRemote/Utilities/Misc.swift @@ -0,0 +1,20 @@ +// +// Misc.swift +// mlchtCamera +// +// Created by Eva Isabella Luna on 3/12/26. +// + +import SwiftUI + +/// An extension that enables Notification posting and getting. +extension RawRepresentable where RawValue == String, Self: NotificationName { + var name: Notification.Name { + get { return Notification.Name(self.rawValue) } + } +} + +/// A protocol that enables Notification posting and getting. +protocol NotificationName { + var name: Notification.Name { get } +} diff --git a/mlchtRemote/Views/Content+Controls.swift b/mlchtRemote/Views/Content+Controls.swift new file mode 100644 index 0000000..4bbd005 --- /dev/null +++ b/mlchtRemote/Views/Content+Controls.swift @@ -0,0 +1,46 @@ +// +// Content+Controls.swift +// mlchtCamera +// +// Created by Eva Isabella Luna on 3/12/26. +// + +import SwiftUI + +extension Content { + struct Controls: View { + @Binding var isPresented: Bool + @Binding var hasSeenOnce: Bool + + var mainAction: some View { + Button { + Connection.shared.sendButtonPress(key: "capture") + } label: { + Text("Take picture") + } + } + + var body: some View { + if #available(watchOS 11.0, *) { + mainAction.handGestureShortcut(.primaryAction) + } else { + mainAction + } + Button { + Connection.shared.sendButtonPress(key: "settings") + } label: { + Text("Open Settings") + } + Button { + Connection.shared.sendButtonPress(key: "flashlight") + } label: { + Text("Toggle flashlight") + } + Button { + Connection.shared.sendButtonPress(key: "cameras") + } label: { + Text("Switch cameras") + } + } + } +} diff --git a/mlchtRemote/Views/Content+Debug.swift b/mlchtRemote/Views/Content+Debug.swift new file mode 100644 index 0000000..4a942c9 --- /dev/null +++ b/mlchtRemote/Views/Content+Debug.swift @@ -0,0 +1,20 @@ +// +// Content+Debug.swift +// mlchtCamera +// +// Created by Eva Isabella Luna on 3/12/26. +// + +import SwiftUI + +extension Content { + struct Debug: View { + @Binding var isPresented: Bool + + var body: some View { + Button("DEBUG") { + isPresented.toggle() + } + } + } +} diff --git a/mlchtRemote/Views/Content.swift b/mlchtRemote/Views/Content.swift new file mode 100644 index 0000000..a59da44 --- /dev/null +++ b/mlchtRemote/Views/Content.swift @@ -0,0 +1,80 @@ +// +// ContentView.swift +// MalachiteWatch Watch App +// +// Created by Eva Isabella Luna on 7/24/25. +// + +// this entire app is buttcheeks + +import Foundation +import SwiftUI + +struct CompanionWaitView: View { + @Environment(\.dismiss) var dismiss + + var body: some View { + VStack { + if #available(watchOS 8.0, *) { + Image(systemName: "exclamationmark.triangle.fill") + .imageScale(.large) + .foregroundStyle(.tint) + } else { + Image(systemName: "exclamationmark.triangle.fill") + .imageScale(.large) + .foregroundColor(.accentColor) + } + Text("") + Text("Keep mlchtCamera open on your phone to use this app.") + .padding(10) + .multilineTextAlignment(.center) + } + } +} + +struct Content: View { + @State private var hasSeenOnce = false + @State private var isPresented = false + + var body: some View { + if #available(watchOS 9.0, *) { + NavigationStack { guts } + } else { + NavigationView { guts }.navigationViewStyle(.stack) + } + } + + var guts: some View { + List { + Controls(isPresented: $isPresented, hasSeenOnce: $hasSeenOnce) + Debug(isPresented: $isPresented) + } + .listStyle(.carousel) + .onAppear { + if !hasSeenOnce { + Connection.shared.bringUpWatchRemote() + isPresented = true + } + } + .onReceive(NotificationCenter.default.publisher(for: Connection.Notifications.phoneForegroundChanged.name)) { _ in + if !hasSeenOnce { hasSeenOnce = true } + if isPresented == !Connection.shared.isPhoneForeground { + isPresented = Connection.shared.isPhoneForeground + } + isPresented = !Connection.shared.isPhoneForeground + } + .fullScreenCover(isPresented: $isPresented) { + CompanionWaitView() + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button {} label: { + ProgressView() + } + .onTapGesture(count: 5) { + isPresented = false + } + } + } + } + } +} diff --git a/mlchtRemote/MalachiteWatchApp.swift b/mlchtRemote/mlchtRemote.swift similarity index 53% rename from mlchtRemote/MalachiteWatchApp.swift rename to mlchtRemote/mlchtRemote.swift index 3c10178..72d3c60 100644 --- a/mlchtRemote/MalachiteWatchApp.swift +++ b/mlchtRemote/mlchtRemote.swift @@ -1,6 +1,6 @@ // -// MalachiteWatchApp.swift -// MalachiteWatch Watch App +// mlchtRemote.swift +// mlchtRemote // // Created by Eva Isabella Luna on 7/24/25. // @@ -8,10 +8,10 @@ import SwiftUI @main -struct MalachiteWatch_Watch_AppApp: App { +struct mlchtRemote: App { var body: some Scene { WindowGroup { - ContentView() + Content() } } } From 24858b790b411f27110ebb14feb8020c48bbf169 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Thu, 12 Mar 2026 02:11:41 -0600 Subject: [PATCH 59/66] [Content] Pretty up Remote cells --- mlchtCamera.xcodeproj/project.pbxproj | 12 +++++ mlchtRemote/Utilities/ViewUtils/View.swift | 41 +++++++++++++++ mlchtRemote/Views/Content+Controls.swift | 58 ++++++++++++++++------ mlchtRemote/Views/Content+Debug.swift | 10 +++- mlchtRemote/Views/Content.swift | 2 +- 5 files changed, 104 insertions(+), 19 deletions(-) create mode 100644 mlchtRemote/Utilities/ViewUtils/View.swift diff --git a/mlchtCamera.xcodeproj/project.pbxproj b/mlchtCamera.xcodeproj/project.pbxproj index d5336e5..8630f18 100755 --- a/mlchtCamera.xcodeproj/project.pbxproj +++ b/mlchtCamera.xcodeproj/project.pbxproj @@ -199,6 +199,7 @@ 78D3F0212F62A50A00760240 /* Content.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D3F01D2F62A50A00760240 /* Content.swift */; }; 78D3F0222F62A50A00760240 /* Content+Controls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D3F01E2F62A50A00760240 /* Content+Controls.swift */; }; 78D3F0232F62A50A00760240 /* Content+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D3F01F2F62A50A00760240 /* Content+Debug.swift */; }; + 78D3F0252F62AA9000760240 /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D3F0242F62AA8E00760240 /* View.swift */; }; 78E170772E51C97B009BEF2F /* CompatibilityView+Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E170762E51C971009BEF2F /* CompatibilityView+Camera.swift */; }; 78E170782E51C97B009BEF2F /* CompatibilityView+Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E170762E51C971009BEF2F /* CompatibilityView+Camera.swift */; }; 78E170792E51C97B009BEF2F /* CompatibilityView+Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E170762E51C971009BEF2F /* CompatibilityView+Camera.swift */; }; @@ -378,6 +379,7 @@ 78D3F01D2F62A50A00760240 /* Content.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Content.swift; sourceTree = ""; }; 78D3F01E2F62A50A00760240 /* Content+Controls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Content+Controls.swift"; sourceTree = ""; }; 78D3F01F2F62A50A00760240 /* Content+Debug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Content+Debug.swift"; sourceTree = ""; }; + 78D3F0242F62AA8E00760240 /* View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = ""; }; 78E142C92DD426260016B3DB /* Codesigning.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Codesigning.xcconfig; sourceTree = ""; }; 78E170762E51C971009BEF2F /* CompatibilityView+Camera.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CompatibilityView+Camera.swift"; sourceTree = ""; }; 78E1707A2E51CC5C009BEF2F /* CompatibilityView+Resolutions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CompatibilityView+Resolutions.swift"; sourceTree = ""; }; @@ -691,6 +693,7 @@ 78D3F0072F629AF800760240 /* Utilities */ = { isa = PBXGroup; children = ( + 78D3F0262F62AA9600760240 /* ViewUtils */, 78D3F00B2F629B5500760240 /* Misc.swift */, 78D3F0082F629B1700760240 /* ConnectionUtils */, ); @@ -716,6 +719,14 @@ path = Views; sourceTree = ""; }; + 78D3F0262F62AA9600760240 /* ViewUtils */ = { + isa = PBXGroup; + children = ( + 78D3F0242F62AA8E00760240 /* View.swift */, + ); + path = ViewUtils; + sourceTree = ""; + }; 78E170752E51C312009BEF2F /* CompatibilityView */ = { isa = PBXGroup; children = ( @@ -1317,6 +1328,7 @@ 78B72BA32E33282E002E2D4E /* Sources */ = { isa = PBXSourcesBuildPhase; files = ( + 78D3F0252F62AA9000760240 /* View.swift in Sources */, 78D3F0212F62A50A00760240 /* Content.swift in Sources */, 78D3F0222F62A50A00760240 /* Content+Controls.swift in Sources */, 78D3F0232F62A50A00760240 /* Content+Debug.swift in Sources */, diff --git a/mlchtRemote/Utilities/ViewUtils/View.swift b/mlchtRemote/Utilities/ViewUtils/View.swift new file mode 100644 index 0000000..7a24a44 --- /dev/null +++ b/mlchtRemote/Utilities/ViewUtils/View.swift @@ -0,0 +1,41 @@ +// +// View.swift +// mlchtCamera +// +// Created by Eva Isabella Luna on 3/12/26. +// + +import SwiftUI + +/// Common cell content view to prevent the code from becoming massive +struct CellViewUtils: View { + var icon: String + var disabled: Bool + var dangerous: Bool + let content: Content + + init( + icon: String, + disabled: Bool?, + dangerous: Bool, + @ViewBuilder content: () -> Content + ) { + self.icon = icon + self.disabled = disabled ?? false + self.dangerous = dangerous + self.content = content() + } + + var body: some View { + VStack() { + HStack(spacing: 10) { + Image(systemName: icon) + .frame(maxWidth: 20) + .foregroundStyle(dangerous ? .red : Color.accentColor) + .symbolRenderingMode(.hierarchical) + content + .disabled(disabled) + } + } + } +} diff --git a/mlchtRemote/Views/Content+Controls.swift b/mlchtRemote/Views/Content+Controls.swift index 4bbd005..7ef6be9 100644 --- a/mlchtRemote/Views/Content+Controls.swift +++ b/mlchtRemote/Views/Content+Controls.swift @@ -13,10 +13,16 @@ extension Content { @Binding var hasSeenOnce: Bool var mainAction: some View { - Button { - Connection.shared.sendButtonPress(key: "capture") - } label: { - Text("Take picture") + CellViewUtils( + icon: "camera.aperture", + disabled: nil, + dangerous: false) + { + Button { + Connection.shared.sendButtonPress(key: "capture") + } label: { + Text("Take picture") + } } } @@ -26,20 +32,40 @@ extension Content { } else { mainAction } - Button { - Connection.shared.sendButtonPress(key: "settings") - } label: { - Text("Open Settings") + CellViewUtils( + icon: "gear", + disabled: nil, + dangerous: false) + { + Button { + Connection.shared.sendButtonPress(key: "settings") + } label: { + Text("Open Settings") + } } - Button { - Connection.shared.sendButtonPress(key: "flashlight") - } label: { - Text("Toggle flashlight") + + CellViewUtils( + icon: "flashlight.off.fill", + disabled: nil, + dangerous: false) + { + Button { + Connection.shared.sendButtonPress(key: "flashlight") + } label: { + Text("Toggle flashlight") + } } - Button { - Connection.shared.sendButtonPress(key: "cameras") - } label: { - Text("Switch cameras") + + CellViewUtils( + icon: "camera.fill", + disabled: nil, + dangerous: false) + { + Button { + Connection.shared.sendButtonPress(key: "cameras") + } label: { + Text("Switch cameras") + } } } } diff --git a/mlchtRemote/Views/Content+Debug.swift b/mlchtRemote/Views/Content+Debug.swift index 4a942c9..0b4ff42 100644 --- a/mlchtRemote/Views/Content+Debug.swift +++ b/mlchtRemote/Views/Content+Debug.swift @@ -12,8 +12,14 @@ extension Content { @Binding var isPresented: Bool var body: some View { - Button("DEBUG") { - isPresented.toggle() + CellViewUtils( + icon: "wrench.and.screwdriver", + disabled: nil, + dangerous: false) + { + Button("DEBUG") { + isPresented.toggle() + } } } } diff --git a/mlchtRemote/Views/Content.swift b/mlchtRemote/Views/Content.swift index a59da44..f8fadcb 100644 --- a/mlchtRemote/Views/Content.swift +++ b/mlchtRemote/Views/Content.swift @@ -70,7 +70,7 @@ struct Content: View { Button {} label: { ProgressView() } - .onTapGesture(count: 5) { + .onTapGesture(count: 3) { isPresented = false } } From fc9fe10b6c5d592b131a497dc6cc9c107125e56a Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Thu, 12 Mar 2026 02:18:58 -0600 Subject: [PATCH 60/66] [Content+Controls] Fix double-tap gesture issue with that last commit --- mlchtRemote/Views/Content+Controls.swift | 26 +++++++++++++----------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/mlchtRemote/Views/Content+Controls.swift b/mlchtRemote/Views/Content+Controls.swift index 7ef6be9..7df7dfe 100644 --- a/mlchtRemote/Views/Content+Controls.swift +++ b/mlchtRemote/Views/Content+Controls.swift @@ -13,24 +13,26 @@ extension Content { @Binding var hasSeenOnce: Bool var mainAction: some View { - CellViewUtils( - icon: "camera.aperture", - disabled: nil, - dangerous: false) - { - Button { - Connection.shared.sendButtonPress(key: "capture") - } label: { - Text("Take picture") - } + Button { + Connection.shared.sendButtonPress(key: "capture") + } label: { + Text("Take picture") } } var body: some View { if #available(watchOS 11.0, *) { - mainAction.handGestureShortcut(.primaryAction) + CellViewUtils( + icon: "camera.aperture", + disabled: nil, + dangerous: false) + { mainAction.handGestureShortcut(.primaryAction) } } else { - mainAction + CellViewUtils( + icon: "camera.aperture", + disabled: nil, + dangerous: false) + { mainAction } } CellViewUtils( icon: "gear", From c9e07ad5b6f4d1d82350af0cb90910c93cf90f98 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Thu, 12 Mar 2026 02:45:56 -0600 Subject: [PATCH 61/66] [SettingsView/CameraView] Bug fixes - Fix flash brightness overlapping with focus title - Fix photo section footer duplication issue --- mlchtCamera/Localizable.xcstrings | 11 +++++++++++ .../Views/CameraView/CameraView+Controls.swift | 2 +- .../Views/SettingsView/SettingsView+Photo.swift | 2 ++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/mlchtCamera/Localizable.xcstrings b/mlchtCamera/Localizable.xcstrings index b1b8273..513ea44 100755 --- a/mlchtCamera/Localizable.xcstrings +++ b/mlchtCamera/Localizable.xcstrings @@ -1711,6 +1711,17 @@ } } }, + "uibutton.flash.title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Flash brightness" + } + } + } + }, "uibutton.focus.title" : { "extractionState" : "manual", "localizations" : { diff --git a/mlchtCamera/Views/CameraView/CameraView+Controls.swift b/mlchtCamera/Views/CameraView/CameraView+Controls.swift index f514130..2b1f630 100644 --- a/mlchtCamera/Views/CameraView/CameraView+Controls.swift +++ b/mlchtCamera/Views/CameraView/CameraView+Controls.swift @@ -204,7 +204,7 @@ extension CameraView { let tooltipConfigs: [ MalachiteViewUtils.tooltipBuilder ] = [ MalachiteViewUtils.tooltipBuilder(text: "uibutton.focus.title", anchor: 10, assign: { [self] label in self.titles.focus = label } ), MalachiteViewUtils.tooltipBuilder(text: "uibutton.exposure.title", anchor: 80, assign: { [self] label in self.titles.exposure = label } ), - MalachiteViewUtils.tooltipBuilder(text: "uibutton.flash.title", anchor: 80, assign: { [self] label in self.titles.flash = label } ), + MalachiteViewUtils.tooltipBuilder(text: "uibutton.flash.title", anchor: 150, assign: { [self] label in self.titles.flash = label } ), ] var labels: [ UILabel ] = [] diff --git a/mlchtCamera/Views/SettingsView/SettingsView+Photo.swift b/mlchtCamera/Views/SettingsView/SettingsView+Photo.swift index e9d3e66..0a35d88 100644 --- a/mlchtCamera/Views/SettingsView/SettingsView+Photo.swift +++ b/mlchtCamera/Views/SettingsView/SettingsView+Photo.swift @@ -120,6 +120,8 @@ extension SettingsView { } func onDisappear() { + formatFooterText = "" + NotificationCenter.default.post(name: MalachiteFunctionUtils.Notifications.continousAEAFNotification.name, object: nil) if utilities.preferences.compatibility.hdr { utilities.preferences.capture.hdr = hdrSwitch } From ce0666dede11c74e69c49be946cb071f1c6154d0 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Fri, 13 Mar 2026 03:07:11 -0600 Subject: [PATCH 62/66] [PhotoPreviewView] Fix watermarking and gain map rotation issues I spent a few hours on this, I just needed to apply rotations in different places. Showed me another bug I need to fix, also switched the font to SF Mono from Menlo. --- .../PhotoPreviewView/PhotoPreviewView.swift | 133 ++++++++++++------ 1 file changed, 92 insertions(+), 41 deletions(-) diff --git a/mlchtCamera/Views/PhotoPreviewView/PhotoPreviewView.swift b/mlchtCamera/Views/PhotoPreviewView/PhotoPreviewView.swift index 37f7c70..2c1cde1 100755 --- a/mlchtCamera/Views/PhotoPreviewView/PhotoPreviewView.swift +++ b/mlchtCamera/Views/PhotoPreviewView/PhotoPreviewView.swift @@ -213,21 +213,43 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { - If ``enableHEIC`` is enabled, create a HEIC representation of all above images combined. Otherwise, JPEG is used. */ public func finalizeImageForExport(imageData: Data) -> Data { - guard let rawImage = CIImage(data: imageData, options: [.toneMapHDRtoSDR : (enableHDR ? true : false)]) else { return Data() } + guard let rawImage = CIImage(data: imageData, options: [.toneMapHDRtoSDR : (enableHDR ? true : false)]) else { return Data() } + + let exifOrientationKey = kCGImagePropertyOrientation as String + let exifOrientationValue = (rawImage.properties[exifOrientationKey] as? NSNumber)?.intValue + let orientedCI: CIImage + if let exif = exifOrientationValue, let cgOrientation = CGImagePropertyOrientation(rawValue: UInt32(exif)) { + orientedCI = rawImage.oriented(cgOrientation) + } else { + orientedCI = rawImage + } + + let context = CIContext() + guard let cg = context.createCGImage(orientedCI, from: orientedCI.extent) else { return Data() } + let upright = CIImage(cgImage: cg) + var imageProperties = rawImage.properties - - let watermarkImage = CIImage(image: self.watermark()) - let outputImage = watermarkImage!.composited(over: rawImage) - + imageProperties[exifOrientationKey] = nil + + let canvasSize = upright.extent.size + let watermarkUIImage = self.watermark(canvasSize: canvasSize) + let watermarkImage = CIImage(image: watermarkUIImage) + + let outputImage = (watermarkImage ?? CIImage()).composited(over: upright) + if utilities.preferences.debug.logging.imageProps { for prop in imageProperties { MalachiteClassesObject().internalNSLog("[Capture Photo] \(prop)") } } - + let outputImageWithProps = outputImage.settingProperties(imageProperties) - - return returnImageFile(imageForRepresentation: outputImageWithProps, imageForGainMap: returnGainMap(properties: &imageProperties, imageData: imageData), imageColorspace: rawImage.colorSpace?.name) + + return returnImageFile( + imageForRepresentation: outputImageWithProps, + imageForGainMap: returnGainMap(properties: &imageProperties, imageData: imageData), + imageColorspace: rawImage.colorSpace?.name + ) } /** @@ -235,34 +257,46 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { FIX: A lot of things here, primarly watermark and image rotation being misaligned. */ - func watermark() -> UIImage - { - let imageView = UIImageView() - imageView.backgroundColor = UIColor.clear - - if photoImage.size.width < photoImage.size.height { - imageView.frame = CGRect(x:0, y:0, width:photoImage.size.height, height:photoImage.size.width) - } else { - imageView.frame = CGRect(x:0, y:0, width:photoImage.size.width, height:photoImage.size.height) + func watermark(canvasSize: CGSize, inset: CGPoint = CGPoint(x: 20, y: 20)) -> UIImage { + let imageView = UIImageView(frame: CGRect(origin: .zero, size: canvasSize)) + imageView.backgroundColor = .clear + + guard utilities.preferences.watermark.enabled else { + UIGraphicsBeginImageContextWithOptions(canvasSize, false, 1) + let img = UIGraphicsGetImageFromCurrentImageContext()! + UIGraphicsEndImageContext() + return img } + + let text = utilities.preferences.watermark.text + let font = UIFont.monospacedSystemFont(ofSize: 70, weight: .regular) + + let label = UILabel() + label.textAlignment = .right + label.textColor = .white + label.text = text + label.font = font + label.backgroundColor = .clear + label.numberOfLines = 1 + + label.sizeToFit() - if utilities.preferences.watermark.enabled { - utilities.debugNSLog("[Watermarking] User has opted to show a watermark") - var label = UILabel() - label = UILabel(frame: CGRect(x:50, y:20, width:photoImage.size.width - 100, height:120)) - label.textAlignment = .left - label.textColor = .white - label.text = utilities.preferences.watermark.text - label.font = UIFont(name: "Menlo", size: 70) - - imageView.addSubview(label) - } + let originalSize = label.bounds.size - UIGraphicsBeginImageContext(imageView.bounds.size) + label.frame = CGRect(origin: .zero, size: originalSize) + label.transform = CGAffineTransform(rotationAngle: .pi / 2) + let finalX = canvasSize.width - inset.x - originalSize.height + let finalY = inset.y + label.frame.origin = CGPoint(x: finalX, y: finalY) + + imageView.addSubview(label) + + UIGraphicsBeginImageContextWithOptions(canvasSize, false, 1) imageView.layer.render(in: UIGraphicsGetCurrentContext()!) - let imageWithText = UIGraphicsGetImageFromCurrentImageContext() - UIGraphicsEndImageContext(); - return imageWithText! + let imageWithText = UIGraphicsGetImageFromCurrentImageContext()! + UIGraphicsEndImageContext() + + return imageWithText } /// Function to return a HEIC representation of the passed image with its colorspace and an optional gain map image. @@ -286,9 +320,16 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { /// Function to extract gain map data from the image. func returnGainMap(properties props: inout [String: Any], imageData: Data) -> CIImage? { - if !enableHDR { return nil } + if !enableHDR { return nil } var gainMapImage = CIImage() - if let gainMapDataInfo = CGImageSourceCopyAuxiliaryDataInfoAtIndex(CGImageSourceCreateWithData(NSData(data: imageData), nil)!, 0, kCGImageAuxiliaryDataTypeHDRGainMap) as? Dictionary { + + guard let source = CGImageSourceCreateWithData(NSData(data: imageData), nil) else { return nil } + + let propsDict = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [CFString: Any] + let orientationCF = propsDict?[kCGImagePropertyOrientation] as? NSNumber + let sourceOrientation = orientationCF.flatMap { CGImagePropertyOrientation(rawValue: $0.uint32Value) } + + if let gainMapDataInfo = CGImageSourceCopyAuxiliaryDataInfoAtIndex(source, 0, kCGImageAuxiliaryDataTypeHDRGainMap) as? Dictionary { utilities.debugNSLog("[Capture Photo] Saving gain map properties from image") let gainMapData = gainMapDataInfo[kCGImageAuxiliaryDataInfoData] as! Data let gainMapDescription = gainMapDataInfo[kCGImageAuxiliaryDataInfoDataDescription]! as! [String: Int] @@ -297,28 +338,37 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { bytesPerRow: gainMapDescription["BytesPerRow"]!, size: gainMapSize, format: .L8, colorSpace: nil) - let gainMapcgImage = CIContext().createCGImage(gainMapciImage, - from: CGRect(origin: CGPoint(x: 0, y: 0), size: gainMapSize))! + + let orientedGainMap: CIImage + if let srcOri = sourceOrientation { + orientedGainMap = gainMapciImage.oriented(srcOri) + } else { + orientedGainMap = gainMapciImage + } + + let context = CIContext() + let gainMapcgImage = context.createCGImage(orientedGainMap, + from: CGRect(origin: CGPoint(x: 0, y: 0), size: orientedGainMap.extent.size))! let gainMapOutputData = NSMutableData() let gainMapDest = CGImageDestinationCreateWithData(gainMapOutputData, UTType.bmp.identifier as CFString, 1, nil) CGImageDestinationAddImage(gainMapDest!, gainMapcgImage, [:] as CFDictionary) CGImageDestinationFinalize(gainMapDest!) - + gainMapImage = CIImage(data: gainMapOutputData as Data)! - + var applDict = extractEXIFData(properties: props, dictionary: kCGImagePropertyMakerAppleDictionary) var exifDict = extractEXIFData(properties: props, dictionary: kCGImagePropertyExifDictionary) - + applDict["33"] = 0.0 applDict["48"] = 0.0 exifDict["CustomRendered"] = 2 - + props[kCGImagePropertyMakerAppleDictionary as String] = applDict props[kCGImagePropertyExifDictionary as String] = exifDict } else { utilities.debugNSLog("[Capture Photo] Couldn't save the gain map properties. Opting to ignore.") } - + return gainMapImage } @@ -367,3 +417,4 @@ final class dataToShareable: NSObject, UIActivityItemSource { return metadata } } + From 6c1748eb22d45ef0f5bfcf7944558f37a0e173f3 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Mon, 16 Mar 2026 00:35:55 -0600 Subject: [PATCH 63/66] [PhotoPreviewView] Fix JPEG rotation and HEIF exif issues from last commit Also see more MakerApple krap --- .../PhotoPreviewView/PhotoPreviewView.swift | 71 +++++++++++++------ 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/mlchtCamera/Views/PhotoPreviewView/PhotoPreviewView.swift b/mlchtCamera/Views/PhotoPreviewView/PhotoPreviewView.swift index 2c1cde1..d991b79 100755 --- a/mlchtCamera/Views/PhotoPreviewView/PhotoPreviewView.swift +++ b/mlchtCamera/Views/PhotoPreviewView/PhotoPreviewView.swift @@ -227,9 +227,14 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { let context = CIContext() guard let cg = context.createCGImage(orientedCI, from: orientedCI.extent) else { return Data() } let upright = CIImage(cgImage: cg) - + var imageProperties = rawImage.properties - imageProperties[exifOrientationKey] = nil + if var tiff = imageProperties[kCGImagePropertyTIFFDictionary as String] as? [String: Any] { + tiff[kCGImagePropertyTIFFOrientation as String] = 1 + imageProperties[kCGImagePropertyTIFFDictionary as String] = tiff + } + imageProperties[kCGImagePropertyOrientation as String] = 1 + let canvasSize = upright.extent.size let watermarkUIImage = self.watermark(canvasSize: canvasSize) @@ -237,18 +242,20 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { let outputImage = (watermarkImage ?? CIImage()).composited(over: upright) + let gainMap = returnGainMap(properties: &imageProperties, imageData: imageData) + let outputImageWithProps = outputImage.settingProperties(imageProperties) + if utilities.preferences.debug.logging.imageProps { for prop in imageProperties { MalachiteClassesObject().internalNSLog("[Capture Photo] \(prop)") } } - - let outputImageWithProps = outputImage.settingProperties(imageProperties) - + return returnImageFile( imageForRepresentation: outputImageWithProps, - imageForGainMap: returnGainMap(properties: &imageProperties, imageData: imageData), - imageColorspace: rawImage.colorSpace?.name + imageForGainMap: gainMap, + imageColorspace: rawImage.colorSpace?.name, + imageProperties: imageProperties ) } @@ -300,23 +307,36 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { } /// Function to return a HEIC representation of the passed image with its colorspace and an optional gain map image. - func returnImageFile(imageForRepresentation image: CIImage, imageForGainMap hdrImage: CIImage?, imageColorspace colorSpace: CFString?) -> Data { + func returnImageFile(imageForRepresentation image: CIImage, imageForGainMap hdrImage: CIImage?, imageColorspace colorSpace: CFString?, imageProperties: [String: Any]) -> Data { + let context = CIContext() + + let finalSpace = CGColorSpace(name: colorSpace ?? CGColorSpace.sRGB) ?? CGColorSpace(name: CGColorSpace.sRGB)! + let metadataKey = CIImageRepresentationOption(rawValue: kCGImageDestinationMetadata as String) + + var options: [CIImageRepresentationOption: Any] = [ metadataKey: imageProperties ] + if enableHDR, let hdr = hdrImage { options[.hdrGainMapImage] = hdr } + let types = CGImageDestinationCopyTypeIdentifiers() as NSArray - utilities.debugNSLog("[Capture Photo] Saving JPEG representation") - if types.contains("public.heic") && enableHEIC { - if enableHDR && (hdrImage != nil) { - return CIContext().heifRepresentation(of: image, format: .RGBA8, colorSpace: CGColorSpace(name: colorSpace!)!, options: [ .hdrGainMapImage : hdrImage! ])! - } else { - return CIContext().heifRepresentation(of: image, format: .RGBA8, colorSpace: CGColorSpace(name: colorSpace!)!)! - } + let useHEIC = enableHEIC && types.contains("public.heic") + + if useHEIC { + utilities.debugNSLog("[Capture Photo] Saving HEIC representation") + return context.heifRepresentation( + of: image, + format: .RGBAh, + colorSpace: finalSpace, + options: options + ) ?? Data() } else { - if enableHDR && (hdrImage != nil) { - return CIContext().jpegRepresentation(of: image, colorSpace: CGColorSpace(name: colorSpace!)!, options: [ .hdrGainMapImage : hdrImage! ])! - } else { - return CIContext().jpegRepresentation(of: image, colorSpace: CGColorSpace(name: colorSpace!)!)! - } + utilities.debugNSLog("[Capture Photo] Saving JPEG representation") + return context.jpegRepresentation( + of: image, + colorSpace: finalSpace, + options: options + ) ?? Data() } } + /// Function to extract gain map data from the image. func returnGainMap(properties props: inout [String: Any], imageData: Data) -> CIImage? { @@ -328,8 +348,12 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { let propsDict = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [CFString: Any] let orientationCF = propsDict?[kCGImagePropertyOrientation] as? NSNumber let sourceOrientation = orientationCF.flatMap { CGImagePropertyOrientation(rawValue: $0.uint32Value) } + let key: CFString + + if #available(iOS 18.0, *) { key = kCGImageAuxiliaryDataTypeISOGainMap } + else { key = kCGImageAuxiliaryDataTypeHDRGainMap } - if let gainMapDataInfo = CGImageSourceCopyAuxiliaryDataInfoAtIndex(source, 0, kCGImageAuxiliaryDataTypeHDRGainMap) as? Dictionary { + if let gainMapDataInfo = CGImageSourceCopyAuxiliaryDataInfoAtIndex(source, 0, key) as? Dictionary { utilities.debugNSLog("[Capture Photo] Saving gain map properties from image") let gainMapData = gainMapDataInfo[kCGImageAuxiliaryDataInfoData] as! Data let gainMapDescription = gainMapDataInfo[kCGImageAuxiliaryDataInfoDataDescription]! as! [String: Int] @@ -359,8 +383,9 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { var applDict = extractEXIFData(properties: props, dictionary: kCGImagePropertyMakerAppleDictionary) var exifDict = extractEXIFData(properties: props, dictionary: kCGImagePropertyExifDictionary) - applDict["33"] = 0.0 - applDict["48"] = 0.0 + applDict["33"] = 0.0 + applDict["48"] = 0.0 + applDict["HDRImageType"] = 3 exifDict["CustomRendered"] = 2 props[kCGImagePropertyMakerAppleDictionary as String] = applDict From 8d2770d0b44fcfbd3aaa3e7235bf31cc8e3b9aa1 Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Mon, 16 Mar 2026 00:39:02 -0600 Subject: [PATCH 64/66] [MalachitePreferences] New developer option to force rechecking device capabilities Some other code cleanup I had staged, disable MP switching on the front camera, fix telephoto camera setting being inconsistent. --- mlchtCamera/Localizable.xcstrings | 10 ++++++ .../Utilities/CameraUtils/Camera+Input.swift | 3 +- .../CompatibilityUtils/Compatibility.swift | 33 ++++++++++++------- .../MalachitePreferences.swift | 5 +++ .../MalachitePreferencesUtils.swift | 4 +++ .../DeveloperView+Settings.swift | 12 +++++++ .../PhotoPreviewView/PhotoPreviewView.swift | 5 +-- .../SettingsView+Resolution.swift | 8 ++--- 8 files changed, 61 insertions(+), 19 deletions(-) diff --git a/mlchtCamera/Localizable.xcstrings b/mlchtCamera/Localizable.xcstrings index 513ea44..e6b9578 100755 --- a/mlchtCamera/Localizable.xcstrings +++ b/mlchtCamera/Localizable.xcstrings @@ -876,6 +876,16 @@ } } }, + "developer.option.debug.forcecheck" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Force check compatibility" + } + } + } + }, "developer.option.debug.logging.imageprops" : { "localizations" : { "en" : { diff --git a/mlchtCamera/Utilities/CameraUtils/Camera+Input.swift b/mlchtCamera/Utilities/CameraUtils/Camera+Input.swift index dcdcff0..a83fd08 100644 --- a/mlchtCamera/Utilities/CameraUtils/Camera+Input.swift +++ b/mlchtCamera/Utilities/CameraUtils/Camera+Input.swift @@ -133,6 +133,8 @@ extension Camera { */ @available(iOS 16.0, *) @objc public func switchInputMegapixels(device: AVCaptureDevice, photoOutput: AVCapturePhotoOutput) { + guard device.position == .back else { return } + let maxDimensions = device.activeFormat.supportedMaxPhotoDimensions[device.activeFormat.supportedMaxPhotoDimensions.count - 1] var mpSetting = Int() @@ -157,7 +159,6 @@ extension Camera { if maxDimensions.width == 4032 && maxDimensions.height == 3024 { photoOutput.maxPhotoDimensions = CMVideoDimensions(width: 4032, height: 3024) } default: parent.utilities.debugNSLog("[Camera Input] Switching \(device.deviceType.rawValue) to 8MP mode") - if maxDimensions.width == 3264 && maxDimensions.height == 2448 { photoOutput.maxPhotoDimensions = CMVideoDimensions(width: 3264, height: 2448) } } } } diff --git a/mlchtCamera/Utilities/CompatibilityUtils/Compatibility.swift b/mlchtCamera/Utilities/CompatibilityUtils/Compatibility.swift index a6d1118..a34e44d 100644 --- a/mlchtCamera/Utilities/CompatibilityUtils/Compatibility.swift +++ b/mlchtCamera/Utilities/CompatibilityUtils/Compatibility.swift @@ -26,7 +26,8 @@ class Compatibility { /// Checks whether or not the current device is the same device as previously recorded in preferences. public func isSameDevice() { if utilities.preferences.compatibility.device.changed { utilities.preferences.compatibility.device.changed = false } - if utilities.preferences.compatibility.device.model == utilities.preferences.ext.deviceModel() { + if (utilities.preferences.compatibility.device.model == utilities.preferences.ext.deviceModel()) && + !utilities.preferences.debug.compatibility.forcecheck { utilities.internalNSLog("[Initialization] This is the same device, can skip compatibility checks.") return } @@ -44,7 +45,8 @@ class Compatibility { HEIC is supported on Apple devices with the A10 Fusion chip or later. */ func checkDeviceForHEICCompatibility() { - if !utilities.preferences.compatibility.device.changed { return } + if !utilities.preferences.debug.compatibility.forcecheck && + utilities.preferences.compatibility.device.changed { return } let supportedTypeIdentifiers = CGImageDestinationCopyTypeIdentifiers() as NSArray if utilities.preferences.compatibility.jpeg != supportedTypeIdentifiers.contains("public.jpeg") { @@ -59,6 +61,10 @@ class Compatibility { /// Determines what resolutions that the passed ``AVCaptureDevice`` is capable of shooting. func checkCameraCapabilities(device: AVCaptureDevice) { + // Front camera resolution control is not supported as of now + // Even the iPhone 17 seems to only go up to 12MP. + guard device.position == .back else { return } + var tmpDictionary = Dictionary() for format in device.formats { var maxDimensions: CMVideoDimensions @@ -70,18 +76,21 @@ class Compatibility { if format == device.formats[0] { utilities.debugNSLog("[Compatibility] Querying supported modes of \(device.deviceType.rawValue)") } if maxDimensions.width == 3264 && maxDimensions.height == 2448 { tmpDictionary["8"] = true } if maxDimensions.width == 4032 && maxDimensions.height == 3024 { tmpDictionary["12"] = true } - if maxDimensions.width == 8064 && maxDimensions.height == 6048 { tmpDictionary["48"] = true } - switch device.deviceType { - case .builtInUltraWideCamera: - utilities.preferences.compatibility.ultrawide = tmpDictionary - case .builtInWideAngleCamera: - utilities.preferences.compatibility.wideangle = tmpDictionary - case .builtInTelephotoCamera: - utilities.preferences.compatibility.telephoto = tmpDictionary - default: - break + if maxDimensions.width == 8064 && maxDimensions.height == 6048 { tmpDictionary["48"] = true } } + + switch device.deviceType { + case .builtInUltraWideCamera: + utilities.preferences.compatibility.ultrawide = tmpDictionary + case .builtInWideAngleCamera: + utilities.preferences.compatibility.wideangle = tmpDictionary + case .builtInTelephotoCamera: + utilities.preferences.compatibility.telephoto = tmpDictionary + default: + break + } + utilities.debugNSLog("[Compatibility] \(device.deviceType.rawValue): \(tmpDictionary)") } } diff --git a/mlchtCamera/Utilities/PreferenceUtils/MalachitePreferences.swift b/mlchtCamera/Utilities/PreferenceUtils/MalachitePreferences.swift index 8beff6d..ad9a5f7 100755 --- a/mlchtCamera/Utilities/PreferenceUtils/MalachitePreferences.swift +++ b/mlchtCamera/Utilities/PreferenceUtils/MalachitePreferences.swift @@ -102,6 +102,7 @@ struct MalachitePreferences: Codable { var debug: debugPreferences struct debugPreferences: Codable { + var compatibility: debug_compatibilityPreferences var logging: debug_loggingPreferences var breakApp: Bool @@ -110,6 +111,10 @@ struct MalachitePreferences: Codable { var unified: Bool var imageProps: Bool } + + struct debug_compatibilityPreferences: Codable { + var forcecheck: Bool + } } var evaintrnl: evaintrnlPreferences diff --git a/mlchtCamera/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift b/mlchtCamera/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift index afca2b6..ec8cfdf 100755 --- a/mlchtCamera/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift +++ b/mlchtCamera/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift @@ -149,6 +149,7 @@ class MalachitePreferencesUtils { currentPreferences.debug.logging.preferences = debugPreferences["logging"]?["preferences"] as? Bool ?? false currentPreferences.debug.logging.unified = debugPreferences["logging"]?["unified"] as? Bool ?? true currentPreferences.debug.logging.imageProps = debugPreferences["logging"]?["imageProps"] as? Bool ?? false + currentPreferences.debug.compatibility.forcecheck = debugPreferences["compatibility"]?["forcecheck"] as? Bool ?? false currentPreferences.debug.breakApp = debugPreferences["breakApp"] as? Bool ?? false } @@ -221,6 +222,9 @@ class MalachitePreferencesUtils { hapticFeedback: false ), debug: MalachitePreferences.debugPreferences( + compatibility: MalachitePreferences.debugPreferences.debug_compatibilityPreferences( + forcecheck: false + ), logging: MalachitePreferences.debugPreferences.debug_loggingPreferences( preferences: false, unified: true, diff --git a/mlchtCamera/Views/DeveloperView/DeveloperView+Settings.swift b/mlchtCamera/Views/DeveloperView/DeveloperView+Settings.swift index ac3d058..7951a11 100644 --- a/mlchtCamera/Views/DeveloperView/DeveloperView+Settings.swift +++ b/mlchtCamera/Views/DeveloperView/DeveloperView+Settings.swift @@ -12,6 +12,7 @@ extension DeveloperView { @State private var debugLoggingUnified = false @State private var debugLoggingPreferences = false @State private var debugLoggingImageProps = false + @State private var forceCompatibilityRechecks = false /// A State variable used for determining whether or not to literally break the app. @State private var breakApp = false @@ -46,6 +47,13 @@ extension DeveloperView { { Toggle("developer.option.debug.logging.imageprops", isOn: $debugLoggingImageProps) } + MalachiteCellViewUtils( + icon: "checkmark.seal", + disabled: nil, + dangerous: false) + { + Toggle("developer.option.debug.forcecheck", isOn: $forceCompatibilityRechecks) + } MalachiteCellViewUtils( icon: "iphone.slash", disabled: nil, @@ -98,6 +106,7 @@ extension DeveloperView { else { debugLoggingUnified = utilities.preferences.debug.logging.unified } debugLoggingPreferences = utilities.preferences.debug.logging.preferences debugLoggingImageProps = utilities.preferences.debug.logging.imageProps + forceCompatibilityRechecks = utilities.preferences.debug.compatibility.forcecheck breakApp = utilities.preferences.debug.breakApp } .onChange(of: debugLoggingUnified) {_ in @@ -109,6 +118,9 @@ extension DeveloperView { .onChange(of: debugLoggingImageProps) {_ in utilities.preferences.debug.logging.imageProps = debugLoggingImageProps } + .onChange(of: forceCompatibilityRechecks) {_ in + utilities.preferences.debug.compatibility.forcecheck = forceCompatibilityRechecks + } .onChange(of: breakApp) {_ in utilities.preferences.debug.breakApp = breakApp } diff --git a/mlchtCamera/Views/PhotoPreviewView/PhotoPreviewView.swift b/mlchtCamera/Views/PhotoPreviewView/PhotoPreviewView.swift index d991b79..86b0a65 100755 --- a/mlchtCamera/Views/PhotoPreviewView/PhotoPreviewView.swift +++ b/mlchtCamera/Views/PhotoPreviewView/PhotoPreviewView.swift @@ -215,8 +215,7 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { public func finalizeImageForExport(imageData: Data) -> Data { guard let rawImage = CIImage(data: imageData, options: [.toneMapHDRtoSDR : (enableHDR ? true : false)]) else { return Data() } - let exifOrientationKey = kCGImagePropertyOrientation as String - let exifOrientationValue = (rawImage.properties[exifOrientationKey] as? NSNumber)?.intValue + let exifOrientationValue = (rawImage.properties[kCGImagePropertyOrientation as String] as? NSNumber)?.intValue let orientedCI: CIImage if let exif = exifOrientationValue, let cgOrientation = CGImagePropertyOrientation(rawValue: UInt32(exif)) { orientedCI = rawImage.oriented(cgOrientation) @@ -229,10 +228,12 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { let upright = CIImage(cgImage: cg) var imageProperties = rawImage.properties + if var tiff = imageProperties[kCGImagePropertyTIFFDictionary as String] as? [String: Any] { tiff[kCGImagePropertyTIFFOrientation as String] = 1 imageProperties[kCGImagePropertyTIFFDictionary as String] = tiff } + imageProperties[kCGImagePropertyOrientation as String] = 1 diff --git a/mlchtCamera/Views/SettingsView/SettingsView+Resolution.swift b/mlchtCamera/Views/SettingsView/SettingsView+Resolution.swift index d5d6bfe..11ec376 100644 --- a/mlchtCamera/Views/SettingsView/SettingsView+Resolution.swift +++ b/mlchtCamera/Views/SettingsView/SettingsView+Resolution.swift @@ -81,14 +81,14 @@ extension SettingsView { dangerous: false) { Picker("settings.option.resolution.telephoto", selection: $telephotoMegapixelCount) { - if let mp = utilities.preferences.compatibility.telephoto["48"] { if mp { - Text("settings.option.resolution.48") - .tag(1) - } } if let mp = utilities.preferences.compatibility.telephoto["12"] { if mp { Text("settings.option.resolution.12") .tag(0) } } + if let mp = utilities.preferences.compatibility.telephoto["48"] { if mp { + Text("settings.option.resolution.48") + .tag(1) + } } } } } From fe80e19f1b25a3e7b05b2771ae2254c3b2b9530e Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Mon, 16 Mar 2026 01:11:53 -0600 Subject: [PATCH 65/66] [PhotoPreviewView] Switch to .RGBAf --- mlchtCamera/Views/PhotoPreviewView/PhotoPreviewView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mlchtCamera/Views/PhotoPreviewView/PhotoPreviewView.swift b/mlchtCamera/Views/PhotoPreviewView/PhotoPreviewView.swift index 86b0a65..5de2e61 100755 --- a/mlchtCamera/Views/PhotoPreviewView/PhotoPreviewView.swift +++ b/mlchtCamera/Views/PhotoPreviewView/PhotoPreviewView.swift @@ -311,7 +311,7 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { func returnImageFile(imageForRepresentation image: CIImage, imageForGainMap hdrImage: CIImage?, imageColorspace colorSpace: CFString?, imageProperties: [String: Any]) -> Data { let context = CIContext() - let finalSpace = CGColorSpace(name: colorSpace ?? CGColorSpace.sRGB) ?? CGColorSpace(name: CGColorSpace.sRGB)! + var finalSpace = CGColorSpace(name: colorSpace ?? CGColorSpace.sRGB)! let metadataKey = CIImageRepresentationOption(rawValue: kCGImageDestinationMetadata as String) var options: [CIImageRepresentationOption: Any] = [ metadataKey: imageProperties ] @@ -324,7 +324,7 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { utilities.debugNSLog("[Capture Photo] Saving HEIC representation") return context.heifRepresentation( of: image, - format: .RGBAh, + format: .RGBAf, colorSpace: finalSpace, options: options ) ?? Data() From cc58163132eb2a56a518b136fbce57e752570b7f Mon Sep 17 00:00:00 2001 From: Eva Isabella Luna Date: Tue, 17 Mar 2026 03:53:40 -0600 Subject: [PATCH 66/66] [Location] WIP Internal geotagging support WIP mainly because I need to refactor to MalachiteKit still, some stuff is still in temp locations but otherwise it should be fine. Defaults to off, embeds a lot of data. --- mlchtCamera.xcodeproj/project.pbxproj | 31 ++++++++++++---- mlchtCamera/Localizable.xcstrings | 10 ++++++ .../Utilities/CameraUtils/Camera.swift | 1 - .../Utilities/LocationUtils/Location.swift | 35 +++++++++++++++++++ .../MalachitePreferences.swift | 1 + .../MalachitePreferencesUtils.swift | 4 ++- mlchtCamera/Utilities/WatchUtils/Watch.swift | 2 +- mlchtCamera/Views/CameraView/CameraView.swift | 19 +++++++--- .../DeveloperView+Internal.swift | 19 +++++++++- .../Views/DeveloperView/DeveloperView.swift | 6 ++-- .../PhotoPreviewView/PhotoPreviewView.swift | 35 +++++++++++++++++-- .../SettingsView/SettingsView+About.swift | 8 +++-- .../Views/SettingsView/SettingsView.swift | 6 ++-- 13 files changed, 154 insertions(+), 23 deletions(-) create mode 100644 mlchtCamera/Utilities/LocationUtils/Location.swift diff --git a/mlchtCamera.xcodeproj/project.pbxproj b/mlchtCamera.xcodeproj/project.pbxproj index 8630f18..bfe53af 100755 --- a/mlchtCamera.xcodeproj/project.pbxproj +++ b/mlchtCamera.xcodeproj/project.pbxproj @@ -34,6 +34,9 @@ 784EDDD62E5EE52600DFF370 /* PhotoPreviewView+Controls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 784EDDD32E5EE52100DFF370 /* PhotoPreviewView+Controls.swift */; }; 785F084D2B12D41100244EB4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 785F084C2B12D41100244EB4 /* AppDelegate.swift */; }; 785F084F2B12D41100244EB4 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 785F084E2B12D41100244EB4 /* SceneDelegate.swift */; }; + 7863E4E32F695699008566AE /* Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7863E4E22F695697008566AE /* Location.swift */; }; + 7863E4E42F695699008566AE /* Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7863E4E22F695697008566AE /* Location.swift */; }; + 7863E4E52F695699008566AE /* Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7863E4E22F695697008566AE /* Location.swift */; }; 7866F5CE2E6299A4009AC9BF /* Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7866F5CD2E6299A0009AC9BF /* Camera.swift */; }; 7866F5CF2E6299A4009AC9BF /* Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7866F5CD2E6299A0009AC9BF /* Camera.swift */; }; 7866F5D02E6299A4009AC9BF /* Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7866F5CD2E6299A0009AC9BF /* Camera.swift */; }; @@ -309,6 +312,7 @@ 785F084C2B12D41100244EB4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 785F084E2B12D41100244EB4 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 785F085A2B12D41300244EB4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 7863E4E22F695697008566AE /* Location.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Location.swift; sourceTree = ""; }; 7866F5CD2E6299A0009AC9BF /* Camera.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Camera.swift; sourceTree = ""; }; 7866F5D12E62A591009AC9BF /* CameraView+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraView+Notifications.swift"; sourceTree = ""; }; 7866F5D52E62A5F7009AC9BF /* temputils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = temputils.swift; sourceTree = ""; }; @@ -491,6 +495,14 @@ path = mlchtCamera; sourceTree = ""; }; + 7863E4E12F69567C008566AE /* LocationUtils */ = { + isa = PBXGroup; + children = ( + 7863E4E22F695697008566AE /* Location.swift */, + ); + path = LocationUtils; + sourceTree = ""; + }; 7866F5CC2E62999B009AC9BF /* CameraUtils */ = { isa = PBXGroup; children = ( @@ -555,6 +567,7 @@ 78C2EFBE2E6971CB00C6DD79 /* InitUtils */, 7866F5D52E62A5F7009AC9BF /* temputils.swift */, 7866F5CC2E62999B009AC9BF /* CameraUtils */, + 7863E4E12F69567C008566AE /* LocationUtils */, 786DAE432E4F28B100BE3137 /* PreferenceUtils */, 7847D5012E8F26D1006759A6 /* ViewUtils */, 786DAE442E4F28B100BE3137 /* MalachiteFunctionUtils.swift */, @@ -1151,6 +1164,7 @@ 78D3F0062F6284F400760240 /* Watch.swift in Sources */, 786DAE582E4F28B100BE3137 /* MalachiteGameUtils.swift in Sources */, 786DAE592E4F28B100BE3137 /* MalachiteHapticUtils.swift in Sources */, + 7863E4E52F695699008566AE /* Location.swift in Sources */, 78E170772E51C97B009BEF2F /* CompatibilityView+Camera.swift in Sources */, 786DAE5A2E4F28B100BE3137 /* MalachiteIntentUtils.swift in Sources */, 7818E23D2E80971C0046F621 /* Compatibility.swift in Sources */, @@ -1216,6 +1230,7 @@ 78D3F0052F6284F400760240 /* Watch.swift in Sources */, 786DAE612E4F28B100BE3137 /* MalachiteGameUtils.swift in Sources */, 786DAE622E4F28B100BE3137 /* MalachiteHapticUtils.swift in Sources */, + 7863E4E42F695699008566AE /* Location.swift in Sources */, 78E170792E51C97B009BEF2F /* CompatibilityView+Camera.swift in Sources */, 786DAE632E4F28B100BE3137 /* MalachiteIntentUtils.swift in Sources */, 7818E23C2E80971C0046F621 /* Compatibility.swift in Sources */, @@ -1322,6 +1337,7 @@ 786DAE2A2E4F28A600BE3137 /* QuickHelpView.swift in Sources */, 7882628B2E514F18000085AC /* QuickHelpView+Preview.swift in Sources */, 786DAE2B2E4F28A600BE3137 /* SettingsView.swift in Sources */, + 7863E4E32F695699008566AE /* Location.swift in Sources */, 786DAE2C2E4F28A600BE3137 /* CameraView.swift in Sources */, ); }; @@ -1661,8 +1677,9 @@ INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.photography"; INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; - INFOPLIST_KEY_NSCameraUsageDescription = "mlchtCamera uses the rear camera system on iPhone to take pictures."; - INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "mlchtCamera uses write access to your library in order to save pictures."; + INFOPLIST_KEY_NSCameraUsageDescription = "mlchtCamera uses the camera systems in order to show a viewfinder and take pictures."; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "mlchtCamera can optionally embed your location in the pictures you take. You don't need to enable this if you never plan to embed locations, and can additionally configure this using the in-app Settings menu."; + INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "mlchtCamera uses write access to your library in order to save pictures. You don't need to enable this if you plan to use the app as a viewfinder."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UIMainStoryboardFile = ""; INFOPLIST_KEY_UIRequiresFullScreen = NO; @@ -1719,8 +1736,9 @@ INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.photography"; INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; - INFOPLIST_KEY_NSCameraUsageDescription = "mlchtCamera uses the rear camera system on iPhone to take pictures."; - INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "mlchtCamera uses write access to your library in order to save pictures."; + INFOPLIST_KEY_NSCameraUsageDescription = "mlchtCamera uses the camera systems in order to show a viewfinder and take pictures."; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "mlchtCamera can optionally embed your location in the pictures you take. You don't need to enable this if you never plan to embed locations, and can additionally configure this using the in-app Settings menu."; + INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "mlchtCamera uses write access to your library in order to save pictures. You don't need to enable this if you plan to use the app as a viewfinder."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UIMainStoryboardFile = ""; INFOPLIST_KEY_UIRequiresFullScreen = NO; @@ -2194,8 +2212,9 @@ INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.photography"; INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; - INFOPLIST_KEY_NSCameraUsageDescription = "mlchtCamera uses the rear camera system on iPhone to take pictures."; - INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "mlchtCamera uses write access to your library in order to save pictures."; + INFOPLIST_KEY_NSCameraUsageDescription = "mlchtCamera uses the camera systems in order to show a viewfinder and take pictures."; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "mlchtCamera can optionally embed your location in the pictures you take. You don't need to enable this if you never plan to embed locations, and can additionally configure this using the in-app Settings menu."; + INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "mlchtCamera uses write access to your library in order to save pictures. You don't need to enable this if you plan to use the app as a viewfinder."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UIMainStoryboardFile = ""; INFOPLIST_KEY_UIRequiresFullScreen = NO; diff --git a/mlchtCamera/Localizable.xcstrings b/mlchtCamera/Localizable.xcstrings index e6b9578..bec90f1 100755 --- a/mlchtCamera/Localizable.xcstrings +++ b/mlchtCamera/Localizable.xcstrings @@ -1046,6 +1046,16 @@ } } }, + "internal.option.location" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enable geotagging" + } + } + } + }, "internal.option.settingsgesture" : { "localizations" : { "en" : { diff --git a/mlchtCamera/Utilities/CameraUtils/Camera.swift b/mlchtCamera/Utilities/CameraUtils/Camera.swift index 10ff4b3..078855e 100644 --- a/mlchtCamera/Utilities/CameraUtils/Camera.swift +++ b/mlchtCamera/Utilities/CameraUtils/Camera.swift @@ -66,7 +66,6 @@ class Camera: NSObject { */ var index: Int? - /** Initailizer function for the ``Camera`` class. diff --git a/mlchtCamera/Utilities/LocationUtils/Location.swift b/mlchtCamera/Utilities/LocationUtils/Location.swift new file mode 100644 index 0000000..2103535 --- /dev/null +++ b/mlchtCamera/Utilities/LocationUtils/Location.swift @@ -0,0 +1,35 @@ +// +// Location.swift +// mlchtCamera +// +// Created by Eva Isabella Luna on 3/17/26. +// + +import CoreLocation + +class Location: NSObject, CLLocationManagerDelegate { + /// An instance of ``MalachiteClassesObject`` for use in this class and its children. + var utilities: MalachiteClassesObject + + public var location = CLLocationManager() + + public var locationEnabled: Bool { + get { return location.authorizationStatus == .authorizedAlways || location.authorizationStatus == .authorizedWhenInUse } + } + + init( + utilities: MalachiteClassesObject + ) { + self.utilities = utilities + super.init() + + self.location.delegate = self + self.location.desiredAccuracy = kCLLocationAccuracyBest + self.location.requestWhenInUseAuthorization() + } + + public func startLocationServices() { + self.location.startUpdatingLocation() + self.location.startUpdatingHeading() + } +} diff --git a/mlchtCamera/Utilities/PreferenceUtils/MalachitePreferences.swift b/mlchtCamera/Utilities/PreferenceUtils/MalachitePreferences.swift index ad9a5f7..f2a077c 100755 --- a/mlchtCamera/Utilities/PreferenceUtils/MalachitePreferences.swift +++ b/mlchtCamera/Utilities/PreferenceUtils/MalachitePreferences.swift @@ -124,5 +124,6 @@ struct MalachitePreferences: Codable { var settingsGesture: Int var cameraControlEnabled: Bool var cameraControlOptions: [ String ] + var locationEnabled: Bool } } diff --git a/mlchtCamera/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift b/mlchtCamera/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift index ec8cfdf..72d21cc 100755 --- a/mlchtCamera/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift +++ b/mlchtCamera/Utilities/PreferenceUtils/MalachitePreferencesUtils.swift @@ -158,6 +158,7 @@ class MalachitePreferencesUtils { currentPreferences.evaintrnl.settingsGesture = evaintrnlPreferences["settingsGesture"] as? Int ?? 2 currentPreferences.evaintrnl.cameraControlEnabled = evaintrnlPreferences["cameraControlEnabled"] as? Bool ?? true currentPreferences.evaintrnl.cameraControlOptions = evaintrnlPreferences["cameraControlOptions"] as? [ String ] ?? [ "zoom", "focus", "cameras", "flash", "flashLevel", "exposureBias" ] + currentPreferences.evaintrnl.locationEnabled = evaintrnlPreferences["locationEnabled"] as? Bool ?? false } return currentPreferences @@ -236,7 +237,8 @@ class MalachitePreferencesUtils { blockAccidentalGestures: true, settingsGesture: 2, cameraControlEnabled: true, - cameraControlOptions: [ "zoom", "focus", "cameras", "flash", "flashLevel", "exposureBias" ] + cameraControlOptions: [ "zoom", "focus", "cameras", "flash", "flashLevel", "exposureBias" ], + locationEnabled: false ) ) } diff --git a/mlchtCamera/Utilities/WatchUtils/Watch.swift b/mlchtCamera/Utilities/WatchUtils/Watch.swift index e64004d..375283e 100644 --- a/mlchtCamera/Utilities/WatchUtils/Watch.swift +++ b/mlchtCamera/Utilities/WatchUtils/Watch.swift @@ -52,7 +52,7 @@ public class Watch: NSObject, WCSessionDelegate { case "cameras": NotificationCenter.default.post(name: Notifications.buttonPressed.cameras.name, object: nil) case "flashlight": NotificationCenter.default.post(name: Notifications.buttonPressed.flashlight.name, object: nil) case "settings": NotificationCenter.default.post(name: Notifications.buttonPressed.settings.name, object: nil) - default: print("bruh") + default: NSLog("wip") } } diff --git a/mlchtCamera/Views/CameraView/CameraView.swift b/mlchtCamera/Views/CameraView/CameraView.swift index 6af52d6..a5e62fd 100755 --- a/mlchtCamera/Views/CameraView/CameraView.swift +++ b/mlchtCamera/Views/CameraView/CameraView.swift @@ -19,6 +19,8 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// An instance of MalachiteKit's ``Camera`` class. var camera: Camera! + /// An instance of MalachiteKit's ``Location`` class. + var location: Location! /// An instance of ``CameraView/ControlLayer`` for this view. var controlLayer: CameraView.ControlLayer! @@ -78,6 +80,11 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa utilities.debugNSLog("[Initialization] Starting session stream") self.camera.queue.async { self.camera.session.startRunning() } + + if utilities.versionType == "INTERNAL" { + self.location = Location(utilities: utilities) + if self.location.locationEnabled { self.location.startLocationServices() } + } } else { utilities.debugNSLog("[Initialization] No cameras detected, skipping to user interface bringup") } @@ -212,7 +219,7 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa /// Function to change the video stabilization mode for the ``cameraPreview``. @objc func changeStabilizerMode() { - guard let connection = self.preview.previewLayer.connection else { print("bruh"); return } + guard let connection = self.preview.previewLayer.connection else { return } if utilities.preferences.preview.stablize { if #available(iOS 17.0, *) { if ((camera.device?.activeFormat.isVideoStabilizationModeSupported(.previewOptimized)) != nil) { @@ -241,9 +248,10 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa DispatchQueue.main.async { self.present(alert, animated: true, completion: nil) } return #elseif MAIN_APP - var aboutView = SettingsView(dismissAction: {self.dismiss( animated: true, completion: nil )}) - aboutView.utilities = self.utilities - let hostingController = UIHostingController(rootView: aboutView) + var view = SettingsView(dismissAction: {self.dismiss( animated: true, completion: nil )}) + view.utilities = self.utilities + view.location = self.location + let hostingController = UIHostingController(rootView: view) hostingController.modalPresentationStyle = UIModalPresentationStyle.popover hostingController.popoverPresentationController?.sourceView = self.controlLayer.buttons.settings hostingController.isModalInPresentation = true @@ -345,6 +353,9 @@ class CameraView: UIViewController, AVCaptureMetadataOutputObjectsDelegate, AVCa let previewImage = UIImage(ciImage: CIImage(data: imageData, options: [.applyOrientationProperty: true, .properties: [kCGImagePropertyOrientation: CGImagePropertyOrientation(getterForOrientation!.imageOrientation).rawValue]])!) let photoPreview = PhotoPreviewView() + #warning("really need to get SceneDelegate.utilities up...") + photoPreview.utilities = utilities + photoPreview.location = location photoPreview.photoImageData = imageData photoPreview.photoImageView.frame = view.frame photoPreview.photoImage = previewImage diff --git a/mlchtCamera/Views/DeveloperView/DeveloperView+Internal.swift b/mlchtCamera/Views/DeveloperView/DeveloperView+Internal.swift index 21feab2..1366c70 100644 --- a/mlchtCamera/Views/DeveloperView/DeveloperView+Internal.swift +++ b/mlchtCamera/Views/DeveloperView/DeveloperView+Internal.swift @@ -15,13 +15,18 @@ extension DeveloperView { @State private var blockAccidentalGestures = Bool() /// A State variable used for determining whether or not to enable the Camera Control. @State private var cameraControlEnabled = Bool() + /// A State variable used for determining whether or not to enable geotagging images. + @State private var locationEnabled = Bool() var utilities: MalachiteClassesObject + var location: Location init( - utilities: MalachiteClassesObject + utilities: MalachiteClassesObject, + location: Location ) { self.utilities = utilities + self.location = location } var body: some View { @@ -56,6 +61,13 @@ extension DeveloperView { Toggle("internal.option.cameracontrol", isOn: $cameraControlEnabled) } } + MalachiteCellViewUtils( + icon: "", + disabled: !location.locationEnabled, + dangerous: false) + { + Toggle("internal.option.location", isOn: $locationEnabled) + } #warning("do camera control options") } .onAppear(perform: onAppear) @@ -71,12 +83,16 @@ extension DeveloperView { .onChange(of: cameraControlEnabled) {_ in utilities.preferences.evaintrnl.cameraControlEnabled = cameraControlEnabled } + .onChange(of: locationEnabled) {_ in + if location.locationEnabled { utilities.preferences.evaintrnl.locationEnabled = locationEnabled } + } } func onAppear() { settingsGestureFingers = utilities.preferences.evaintrnl.settingsGesture blockAccidentalGestures = utilities.preferences.evaintrnl.blockAccidentalGestures cameraControlEnabled = utilities.preferences.evaintrnl.cameraControlEnabled + locationEnabled = location.locationEnabled ? utilities.preferences.evaintrnl.locationEnabled : false } func onDisappear() { @@ -85,6 +101,7 @@ extension DeveloperView { utilities.preferences.evaintrnl.settingsGesture = settingsGestureFingers utilities.preferences.evaintrnl.blockAccidentalGestures = blockAccidentalGestures utilities.preferences.evaintrnl.cameraControlEnabled = cameraControlEnabled + if location.locationEnabled { utilities.preferences.evaintrnl.locationEnabled = locationEnabled } } } } diff --git a/mlchtCamera/Views/DeveloperView/DeveloperView.swift b/mlchtCamera/Views/DeveloperView/DeveloperView.swift index e854221..f6f7db4 100644 --- a/mlchtCamera/Views/DeveloperView/DeveloperView.swift +++ b/mlchtCamera/Views/DeveloperView/DeveloperView.swift @@ -12,12 +12,14 @@ struct DeveloperView: View { /// A State variable used for determining whether or not this view is being presented as a modal. var dismissAction: (() -> Void) /// A variable to hold the existing instance of ``MalachiteClassesObject``. - var utilities = MalachiteClassesObject() + var utilities: MalachiteClassesObject + + var location: Location var body: some View { Form { Settings(utilities: utilities) - if utilities.versionType == "INTERNAL" { InternalSettings(utilities: utilities)} + if utilities.versionType == "INTERNAL" { InternalSettings(utilities: utilities, location: location)} BuildInfo(utilities: utilities) DeviceInfo(utilities: utilities) } diff --git a/mlchtCamera/Views/PhotoPreviewView/PhotoPreviewView.swift b/mlchtCamera/Views/PhotoPreviewView/PhotoPreviewView.swift index 5de2e61..e03dc46 100755 --- a/mlchtCamera/Views/PhotoPreviewView/PhotoPreviewView.swift +++ b/mlchtCamera/Views/PhotoPreviewView/PhotoPreviewView.swift @@ -12,7 +12,9 @@ import LinkPresentation class PhotoPreviewView : UIViewController, UIScrollViewDelegate { /// A variable to hold the existing instance of ``MalachiteClassesObject``. - var utilities = MalachiteClassesObject() + var utilities: MalachiteClassesObject! + /// A variable to hold the existing instance of ``Location``. + var location: Location! /// The scroll view that holds the image view for zooming and panning. var photoScrollView = UIScrollView() @@ -235,7 +237,36 @@ class PhotoPreviewView : UIViewController, UIScrollViewDelegate { } imageProperties[kCGImagePropertyOrientation as String] = 1 - + + if location.locationEnabled && utilities.versionType == "INTERNAL" && utilities.preferences.evaintrnl.locationEnabled, let loc = location.location.location { + let gps = NSMutableDictionary() + let formatter = DateFormatter() + + // This is actually slightly more verbose than the stock camera app LOL + gps[kCGImagePropertyGPSAltitude] = (loc.altitude >= 0.0) ? loc.altitude : -loc.altitude + gps[kCGImagePropertyGPSAltitudeRef] = (loc.altitude >= 0.0) ? 0 : 1 + formatter.dateFormat = "yyyy:MM:dd" + gps[kCGImagePropertyGPSDateStamp] = formatter.string(from:loc.timestamp) + gps[kCGImagePropertyGPSDOP] = loc.horizontalAccuracy + gps[kCGImagePropertyGPSHPositioningError] = loc.horizontalAccuracy + gps[kCGImagePropertyGPSLatitudeRef] = (loc.coordinate.latitude >= 0.0) ? "N" : "S" + gps[kCGImagePropertyGPSLatitude] = (loc.coordinate.latitude >= 0.0) ? loc.coordinate.latitude : -loc.coordinate.latitude + gps[kCGImagePropertyGPSLongitudeRef] = (loc.coordinate.longitude >= 0.0) ? "E" : "W" + gps[kCGImagePropertyGPSLongitude] = (loc.coordinate.longitude >= 0.0) ? loc.coordinate.longitude : -loc.coordinate.longitude + gps[kCGImagePropertyGPSSpeedRef] = "K" + gps[kCGImagePropertyGPSSpeed] = loc.speed + formatter.dateFormat = "HH:mm:ss" + gps[kCGImagePropertyGPSTimeStamp] = formatter.string(from:loc.timestamp) + + if let heading = location.location.heading { + gps[kCGImagePropertyGPSDestBearingRef] = "T" + gps[kCGImagePropertyGPSDestBearing] = heading.trueHeading + gps[kCGImagePropertyGPSImgDirectionRef] = "T" + gps[kCGImagePropertyGPSImgDirection] = heading.trueHeading + } + + imageProperties[kCGImagePropertyGPSDictionary as String] = gps + } let canvasSize = upright.extent.size let watermarkUIImage = self.watermark(canvasSize: canvasSize) diff --git a/mlchtCamera/Views/SettingsView/SettingsView+About.swift b/mlchtCamera/Views/SettingsView/SettingsView+About.swift index 6952c90..ce7098b 100644 --- a/mlchtCamera/Views/SettingsView/SettingsView+About.swift +++ b/mlchtCamera/Views/SettingsView/SettingsView+About.swift @@ -12,13 +12,15 @@ extension SettingsView { /// A State variable used for determining whether or not this view is being presented as a modal. var dismissAction: (() -> Void) /// A variable to hold the existing instance of ``MalachiteClassesObject``. - var utilities = MalachiteClassesObject() - + var utilities: MalachiteClassesObject + var location: Location init( utilities: MalachiteClassesObject, + location: Location, dismissAction: @escaping (() -> Void) ) { self.utilities = utilities + self.location = location self.dismissAction = dismissAction } @@ -48,7 +50,7 @@ extension SettingsView { disabled: nil, dangerous: false) { - NavigationLink(destination: DeveloperView(dismissAction: dismissAction)) { + NavigationLink(destination: DeveloperView(dismissAction: dismissAction, utilities: utilities, location: location)) { Text("view.title.developer") } } diff --git a/mlchtCamera/Views/SettingsView/SettingsView.swift b/mlchtCamera/Views/SettingsView/SettingsView.swift index ff3b11c..2b3497f 100755 --- a/mlchtCamera/Views/SettingsView/SettingsView.swift +++ b/mlchtCamera/Views/SettingsView/SettingsView.swift @@ -13,7 +13,9 @@ struct SettingsView: View { /// A State variable used for determining whether or not to literally break the app. @State private var breakApp = false /// A variable to hold the existing instance of ``MalachiteClassesObject``. - var utilities = MalachiteClassesObject() + var utilities: MalachiteClassesObject! + /// A variable to hold the existing instance of ``Location``. + var location: Location! /// A variable used to hold the function for dismissing with the toolbar item. var dismissAction: (() -> Void) @@ -37,7 +39,7 @@ struct SettingsView: View { var guts: some View { Form { - About(utilities: utilities, dismissAction: dismissAction) + About(utilities: utilities, location: location, dismissAction: dismissAction) Preview(utilities: utilities, dismissAction: dismissAction) Resolution(utilities: utilities, dismissAction: dismissAction) Photo(utilities: utilities, dismissAction: dismissAction)