diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml index e5792b55..9718c667 100644 --- a/.github/workflows/beta.yml +++ b/.github/workflows/beta.yml @@ -27,6 +27,9 @@ jobs: - name: Change version to tag run: sed -e '/MARKETING_VERSION = .*/s/= .*/= ${{ github.ref_name }}/' -i '' Build.xcconfig + - name: Change default icon to beta icon + run: sed -e 's/= Neon/= Starburst/' -i '' ./AltStore.xcodeproj/project.pbxproj + - name: Get version id: version run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index aea20363..6091dbff 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -36,6 +36,9 @@ jobs: - name: Increase nightly build number and set as version run: bash .github/workflows/increase-nightly-build-num.sh + - name: Change default icon to nightly icon + run: sed -e 's/= Neon/= Steel/' -i '' ./AltStore.xcodeproj/project.pbxproj + - name: Get version id: version run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 6befcfbb..6b2b7251 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -27,6 +27,9 @@ jobs: env: COMMIT: ${{ github.event.pull_request.head.sha }} + - name: Change default icon to alpha icon + run: sed -e 's/= Neon/= Storm/' -i '' ./AltStore.xcodeproj/project.pbxproj + - name: Get version id: version run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index 90e289e4..7343eb52 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -17,15 +17,92 @@ 191E607E290B2EA7001A3B7C /* jplist.c in Sources */ = {isa = PBXBuildFile; fileRef = 191E5FCF290A651D001A3B7C /* jplist.c */; }; 1920B04F2924AC8300744F60 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 1920B04E2924AC8300744F60 /* Settings.bundle */; }; 19B9B7452845E6DF0076EF69 /* SelectTeamViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B9B7442845E6DF0076EF69 /* SelectTeamViewController.swift */; }; + 1F07F550295455A300F7BE95 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1F07F552295455A300F7BE95 /* Localizable.strings */; }; + 1F07F556295458D800F7BE95 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F07F554295458D800F7BE95 /* Assets.swift */; }; + 1F07F557295458D800F7BE95 /* Localizations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F07F555295458D800F7BE95 /* Localizations.swift */; }; + 1F07F5672955D16A00F7BE95 /* SFSafeSymbols in Frameworks */ = {isa = PBXBuildFile; productRef = 1F07F5662955D16A00F7BE95 /* SFSafeSymbols */; }; + 1F07F5692955D3EC00F7BE95 /* SFSafeSymbols in Frameworks */ = {isa = PBXBuildFile; productRef = 1F07F5682955D3EC00F7BE95 /* SFSafeSymbols */; }; + 1F07F56B2955F11500F7BE95 /* AppScreenshotsPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F07F56A2955F11500F7BE95 /* AppScreenshotsPreview.swift */; }; + 1F07F56F2955FB2000F7BE95 /* AppIDsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F07F56E2955FB2000F7BE95 /* AppIDsView.swift */; }; + 1F0DD81C2932D2FF007608A4 /* AppScreenshotsScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0DD81B2932D2FF007608A4 /* AppScreenshotsScrollView.swift */; }; + 1F0DD8212933B749007608A4 /* AppPermissionsGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0DD8202933B749007608A4 /* AppPermissionsGrid.swift */; }; + 1F0DD83F29367F6C007608A4 /* ConnectAppleIDView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0DD83E29367F6C007608A4 /* ConnectAppleIDView.swift */; }; + 1F0DD84129368056007608A4 /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0DD84029368056007608A4 /* EnvironmentValues.swift */; }; + 1F0DD8432936B0F9007608A4 /* RoundedTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0DD8422936B0F9007608A4 /* RoundedTextField.swift */; }; + 1F0DD8452936B3FE007608A4 /* FilledButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0DD8442936B3FE007608A4 /* FilledButtonStyle.swift */; }; + 1F1295812989B51F0048FCB9 /* ExpandableText in Frameworks */ = {isa = PBXBuildFile; productRef = 1F1295802989B51F0048FCB9 /* ExpandableText */; }; + 1F180F92298E7A1B00D1C98B /* StoreApp+Trusted.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F180F91298E7A1B00D1C98B /* StoreApp+Trusted.swift */; }; + 1F180F94298E7A2500D1C98B /* Source+Trusted.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F180F93298E7A2500D1C98B /* Source+Trusted.swift */; }; + 1F1D669E29A234CE0095BFCD /* WriteAppReviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1D669D29A234CE0095BFCD /* WriteAppReviewView.swift */; }; + 1F2EF787297C4D40002FD839 /* LicensesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F2EF786297C4D40002FD839 /* LicensesView.swift */; }; + 1F44634529744E570070E514 /* HintView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F44634429744E570070E514 /* HintView.swift */; }; + 1F545E83298D79E400589F68 /* ErrorLogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F545E82298D79E400589F68 /* ErrorLogView.swift */; }; + 1F545E85298D84CF00589F68 /* FilePreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F545E84298D84CF00589F68 /* FilePreviewView.swift */; }; + 1F545E87298D86D800589F68 /* ModalNavigationLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F545E86298D86D800589F68 /* ModalNavigationLink.swift */; }; + 1F5DF9D82974426300DDAA47 /* AppScreenshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F5DF9D72974426300DDAA47 /* AppScreenshot.swift */; }; + 1F6284D5295209DA0060AAD8 /* AppAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F6284D4295209DA0060AAD8 /* AppAction.swift */; }; + 1F6284D7295218980060AAD8 /* DocumentPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F6284D6295218980060AAD8 /* DocumentPicker.swift */; }; + 1F6284D929523D340060AAD8 /* SideloadingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F6284D829523D340060AAD8 /* SideloadingManager.swift */; }; + 1F66F5BA2938CA5700A910CA /* VisualEffectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F66F5B92938CA5700A910CA /* VisualEffectView.swift */; }; + 1F66F5BC2938F03700A910CA /* Modifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F66F5BB2938F03700A910CA /* Modifiers.swift */; }; + 1F66F5BE2938F06100A910CA /* StoreApp+Filterable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F66F5BD2938F06100A910CA /* StoreApp+Filterable.swift */; }; + 1F66F5C02938F07C00A910CA /* Filterable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F66F5BF2938F07C00A910CA /* Filterable.swift */; }; + 1F6E08DA292806E0005059C0 /* AppRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F6E08D9292806E0005059C0 /* AppRowView.swift */; }; + 1F6E08DC292807D3005059C0 /* AppIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F6E08DB292807D3005059C0 /* AppIconView.swift */; }; + 1F6E08E029280B12005059C0 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F6E08DF29280B12005059C0 /* SafariView.swift */; }; + 1F6E08E429280D1E005059C0 /* PillButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F6E08E329280D1E005059C0 /* PillButtonStyle.swift */; }; + 1F6E08E629280F4B005059C0 /* RatingStars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F6E08E529280F4B005059C0 /* RatingStars.swift */; }; + 1F6E08E829282174005059C0 /* ConfirmAddSourceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F6E08E729282174005059C0 /* ConfirmAddSourceView.swift */; }; + 1F74FF1E295263510047C051 /* AsyncImage in Frameworks */ = {isa = PBXBuildFile; productRef = 1F74FF1D295263510047C051 /* AsyncImage */; }; + 1F943C692927F8F200ABE095 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FAFC5B52927E06300B8D837 /* RootView.swift */; }; + 1F943C6A2927F8F700ABE095 /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F943C672927F39400ABE095 /* ViewModel.swift */; }; + 1F943C6B2927F8F700ABE095 /* NavigationTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FAFC5C32927E18100B8D837 /* NavigationTab.swift */; }; + 1F943C6C2927F90400ABE095 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FAFC5C02927E13C00B8D837 /* SettingsView.swift */; }; + 1F943C6D2927F90400ABE095 /* NewsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F943C652927F36600ABE095 /* NewsViewModel.swift */; }; + 1F943C6E2927F90400ABE095 /* NewsItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F943C632927EF4200ABE095 /* NewsItemView.swift */; }; + 1F943C6F2927F90400ABE095 /* NewsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FAFC5B82927E0EE00B8D837 /* NewsView.swift */; }; + 1F943C702927F90400ABE095 /* BrowseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FAFC5BA2927E0F800B8D837 /* BrowseView.swift */; }; + 1F943C712927F90400ABE095 /* MyAppsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FAFC5BD2927E10D00B8D837 /* MyAppsView.swift */; }; + 1F981B1129AA0FAE0014950E /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F981B1029AA0FAE0014950E /* OnboardingView.swift */; }; + 1F981B1329AA101F0014950E /* OnboardingStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F981B1229AA101F0014950E /* OnboardingStepView.swift */; }; + 1F981B1529AA1E070014950E /* AppIconsShowcase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F981B1429AA1E070014950E /* AppIconsShowcase.swift */; }; + 1F981B1729AA34A70014950E /* AppStoreProductView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F981B1629AA34A70014950E /* AppStoreProductView.swift */; }; + 1FA1C8CA294906890083119D /* MyAppsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA1C8C9294906890083119D /* MyAppsViewModel.swift */; }; + 1FA5A6CA298E8B2F007BA946 /* RefreshAttemptsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA5A6C9298E8B2F007BA946 /* RefreshAttemptsView.swift */; }; + 1FA5A6CC298E8FE4007BA946 /* MailComposeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA5A6CB298E8FE4007BA946 /* MailComposeView.swift */; }; + 1FB84BA62928DE08006A5CF4 /* AppDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB84BA52928DE08006A5CF4 /* AppDetailView.swift */; }; + 1FB96FBE292A20E5007E68D1 /* ObservableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB96FBD292A20E5007E68D1 /* ObservableScrollView.swift */; }; + 1FB96FC0292A63F2007E68D1 /* AppPillButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB96FBF292A63F2007E68D1 /* AppPillButton.swift */; }; + 1FB96FC3292A6D7E007E68D1 /* DateFormatterHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB96FC2292A6D7E007E68D1 /* DateFormatterHelper.swift */; }; + 1FB96FC5292A7251007E68D1 /* BrowseAppPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB96FC4292A7251007E68D1 /* BrowseAppPreviewView.swift */; }; + 1FB96FC7292A853D007E68D1 /* SourcesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB96FC6292A853D007E68D1 /* SourcesView.swift */; }; + 1FB96FC9292ABDD0007E68D1 /* AddSourceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB96FC8292ABDD0007E68D1 /* AddSourceView.swift */; }; + 1FB96FCF292BBBCA007E68D1 /* SiriShortcutSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB96FCE292BBBC9007E68D1 /* SiriShortcutSetupView.swift */; }; + 1FB96FEC292C171D007E68D1 /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB96FEB292C171D007E68D1 /* NotificationManager.swift */; }; + 1FB96FF3292D0539007E68D1 /* PillButtonProgressViewStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB96FF2292D0539007E68D1 /* PillButtonProgressViewStyle.swift */; }; + 992C896029A6A56500FB3501 /* LocalConsole in Frameworks */ = {isa = PBXBuildFile; productRef = 992C895F29A6A56500FB3501 /* LocalConsole */; }; + 994D6E9B29E326080045B3F7 /* minimuxer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99F87D1729D8E4C900B40039 /* minimuxer.swift */; }; + 994D6EB529E35C130045B3F7 /* StoreApp+SideStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994D6EB429E35C130045B3F7 /* StoreApp+SideStore.swift */; }; + 99BCB7DF29A2AC050041D1A7 /* AdvancedSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99BCB7DE29A2AC050041D1A7 /* AdvancedSettingsView.swift */; }; + 1FF8C6182A1780C60041352C /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FF8C6172A1780C60041352C /* ActivityView.swift */; }; + 1FF8C61B2A1782F10041352C /* Reachability in Frameworks */ = {isa = PBXBuildFile; productRef = 1FF8C61A2A1782F10041352C /* Reachability */; }; + 1FFA56C2299994390011B6F5 /* OutputCapturer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FFA56C1299994390011B6F5 /* OutputCapturer.swift */; }; + 1FFEF104298552DB0098374C /* AppVersionHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FFEF103298552DB0098374C /* AppVersionHistoryView.swift */; }; 4879A95F2861046500FC1BBD /* AltSign in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A95E2861046500FC1BBD /* AltSign */; }; 4879A9622861049C00FC1BBD /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A9612861049C00FC1BBD /* OpenSSL */; }; 990D2AE22A1910CD0055D93C /* UnstableFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 990D2AE12A1910CD0055D93C /* UnstableFeatures.swift */; }; 990D2AF02A192E060055D93C /* UIApplication+Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 990D2AEF2A192E060055D93C /* UIApplication+Alert.swift */; }; 9922FFEC29B501C50020F868 /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = 9922FFEB29B501C50020F868 /* Starscream */; }; 99C4EF4D2979132100CB538D /* SemanticVersion in Frameworks */ = {isa = PBXBuildFile; productRef = 99C4EF4C2979132100CB538D /* SemanticVersion */; }; + 99D87A60299F1B1100ED09A9 /* DevModeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99D87A5F299F1B1100ED09A9 /* DevModeView.swift */; }; + 99D87A62299F3EC300ED09A9 /* FileExplorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99D87A61299F3EC300ED09A9 /* FileExplorer.swift */; }; + 99D87A6529A04D5E00ED09A9 /* Inject in Frameworks */ = {isa = PBXBuildFile; productRef = 99D87A6429A04D5E00ED09A9 /* Inject */; }; + 99DE640129A1271100B920BF /* AsyncFallibleButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99DE640029A1271100B920BF /* AsyncFallibleButton.swift */; }; + 99DE640329A1624500B920BF /* View+Hidden.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99DE640229A1624500B920BF /* View+Hidden.swift */; }; + 99DE640629A1753800B920BF /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 99DE640529A1753800B920BF /* ZIPFoundation */; }; + 99E59E1D299BFE5D00FAF33D /* AppIconsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E59E1C299BFE5D00FAF33D /* AppIconsView.swift */; }; 99F87D0529D8B4E200B40039 /* minimuxer-helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9961EC2D29BE9F2E00AF2C6F /* minimuxer-helpers.swift */; }; 99F87D1829D8E4C900B40039 /* SwiftBridgeCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99F87D1629D8E4C900B40039 /* SwiftBridgeCore.swift */; }; - 99F87D1929D8E4C900B40039 /* minimuxer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99F87D1729D8E4C900B40039 /* minimuxer.swift */; }; B3146ED2284F581E00BBC3FD /* Roxas.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3146ECD284F580500BBC3FD /* Roxas.framework */; }; B3146ED3284F581E00BBC3FD /* Roxas.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B3146ECD284F580500BBC3FD /* Roxas.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; B33FFBA8295F8E98002259E6 /* libfragmentzip.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B343F894295F7F9B002B1159 /* libfragmentzip.a */; }; @@ -515,7 +592,77 @@ 19B9B7442845E6DF0076EF69 /* SelectTeamViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectTeamViewController.swift; sourceTree = ""; }; 990D2AE12A1910CD0055D93C /* UnstableFeatures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnstableFeatures.swift; sourceTree = ""; }; 990D2AEF2A192E060055D93C /* UIApplication+Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Alert.swift"; sourceTree = ""; }; + 1F07F551295455A300F7BE95 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 1F07F554295458D800F7BE95 /* Assets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Assets.swift; sourceTree = ""; }; + 1F07F555295458D800F7BE95 /* Localizations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Localizations.swift; sourceTree = ""; }; + 1F07F56A2955F11500F7BE95 /* AppScreenshotsPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScreenshotsPreview.swift; sourceTree = ""; }; + 1F07F56E2955FB2000F7BE95 /* AppIDsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIDsView.swift; sourceTree = ""; }; + 1F0DD81B2932D2FF007608A4 /* AppScreenshotsScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScreenshotsScrollView.swift; sourceTree = ""; }; + 1F0DD8202933B749007608A4 /* AppPermissionsGrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPermissionsGrid.swift; sourceTree = ""; }; + 1F0DD83E29367F6C007608A4 /* ConnectAppleIDView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectAppleIDView.swift; sourceTree = ""; }; + 1F0DD84029368056007608A4 /* EnvironmentValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentValues.swift; sourceTree = ""; }; + 1F0DD8422936B0F9007608A4 /* RoundedTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedTextField.swift; sourceTree = ""; }; + 1F0DD8442936B3FE007608A4 /* FilledButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilledButtonStyle.swift; sourceTree = ""; }; + 1F180F91298E7A1B00D1C98B /* StoreApp+Trusted.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StoreApp+Trusted.swift"; sourceTree = ""; }; + 1F180F93298E7A2500D1C98B /* Source+Trusted.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Source+Trusted.swift"; sourceTree = ""; }; + 1F1D669D29A234CE0095BFCD /* WriteAppReviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteAppReviewView.swift; sourceTree = ""; }; + 1F2EF786297C4D40002FD839 /* LicensesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicensesView.swift; sourceTree = ""; }; + 1F44634429744E570070E514 /* HintView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HintView.swift; sourceTree = ""; }; + 1F545E82298D79E400589F68 /* ErrorLogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorLogView.swift; sourceTree = ""; }; + 1F545E84298D84CF00589F68 /* FilePreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewView.swift; sourceTree = ""; }; + 1F545E86298D86D800589F68 /* ModalNavigationLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalNavigationLink.swift; sourceTree = ""; }; + 1F5DF9D72974426300DDAA47 /* AppScreenshot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppScreenshot.swift; sourceTree = ""; }; + 1F6284D4295209DA0060AAD8 /* AppAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAction.swift; sourceTree = ""; }; + 1F6284D6295218980060AAD8 /* DocumentPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentPicker.swift; sourceTree = ""; }; + 1F6284D829523D340060AAD8 /* SideloadingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideloadingManager.swift; sourceTree = ""; }; + 1F66F5B92938CA5700A910CA /* VisualEffectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisualEffectView.swift; sourceTree = ""; }; + 1F66F5BB2938F03700A910CA /* Modifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modifiers.swift; sourceTree = ""; }; + 1F66F5BD2938F06100A910CA /* StoreApp+Filterable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StoreApp+Filterable.swift"; sourceTree = ""; }; + 1F66F5BF2938F07C00A910CA /* Filterable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Filterable.swift; sourceTree = ""; }; + 1F6E08D9292806E0005059C0 /* AppRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRowView.swift; sourceTree = ""; }; + 1F6E08DB292807D3005059C0 /* AppIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconView.swift; sourceTree = ""; }; + 1F6E08DF29280B12005059C0 /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = ""; }; + 1F6E08E329280D1E005059C0 /* PillButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillButtonStyle.swift; sourceTree = ""; }; + 1F6E08E529280F4B005059C0 /* RatingStars.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RatingStars.swift; sourceTree = ""; }; + 1F6E08E729282174005059C0 /* ConfirmAddSourceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmAddSourceView.swift; sourceTree = ""; }; + 1F943C632927EF4200ABE095 /* NewsItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsItemView.swift; sourceTree = ""; }; + 1F943C652927F36600ABE095 /* NewsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsViewModel.swift; sourceTree = ""; }; + 1F943C672927F39400ABE095 /* ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModel.swift; sourceTree = ""; }; + 1F981B1029AA0FAE0014950E /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; + 1F981B1229AA101F0014950E /* OnboardingStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingStepView.swift; sourceTree = ""; }; + 1F981B1429AA1E070014950E /* AppIconsShowcase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconsShowcase.swift; sourceTree = ""; }; + 1F981B1629AA34A70014950E /* AppStoreProductView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStoreProductView.swift; sourceTree = ""; }; + 1FA1C8C9294906890083119D /* MyAppsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyAppsViewModel.swift; sourceTree = ""; }; + 1FA5A6C9298E8B2F007BA946 /* RefreshAttemptsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshAttemptsView.swift; sourceTree = ""; }; + 1FA5A6CB298E8FE4007BA946 /* MailComposeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailComposeView.swift; sourceTree = ""; }; + 1FAFC5A42927E00000B8D837 /* SideStoreUIApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideStoreUIApp.swift; sourceTree = ""; }; + 1FAFC5B52927E06300B8D837 /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = ""; }; + 1FAFC5B82927E0EE00B8D837 /* NewsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsView.swift; sourceTree = ""; }; + 1FAFC5BA2927E0F800B8D837 /* BrowseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowseView.swift; sourceTree = ""; }; + 1FAFC5BD2927E10D00B8D837 /* MyAppsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyAppsView.swift; sourceTree = ""; }; + 1FAFC5C02927E13C00B8D837 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + 1FAFC5C32927E18100B8D837 /* NavigationTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationTab.swift; sourceTree = ""; }; + 1FB84BA52928DE08006A5CF4 /* AppDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDetailView.swift; sourceTree = ""; }; + 1FB96FBD292A20E5007E68D1 /* ObservableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservableScrollView.swift; sourceTree = ""; }; + 1FB96FBF292A63F2007E68D1 /* AppPillButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPillButton.swift; sourceTree = ""; }; + 1FB96FC2292A6D7E007E68D1 /* DateFormatterHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateFormatterHelper.swift; sourceTree = ""; }; + 1FB96FC4292A7251007E68D1 /* BrowseAppPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowseAppPreviewView.swift; sourceTree = ""; }; + 1FB96FC6292A853D007E68D1 /* SourcesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourcesView.swift; sourceTree = ""; }; + 1FB96FC8292ABDD0007E68D1 /* AddSourceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSourceView.swift; sourceTree = ""; }; + 1FB96FCE292BBBC9007E68D1 /* SiriShortcutSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiriShortcutSetupView.swift; sourceTree = ""; }; + 1FB96FEB292C171D007E68D1 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = ""; }; + 1FB96FF2292D0539007E68D1 /* PillButtonProgressViewStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillButtonProgressViewStyle.swift; sourceTree = ""; }; + 994D6EB429E35C130045B3F7 /* StoreApp+SideStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StoreApp+SideStore.swift"; sourceTree = ""; }; + 1FF8C6172A1780C60041352C /* ActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityView.swift; sourceTree = ""; }; + 1FFA56C1299994390011B6F5 /* OutputCapturer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutputCapturer.swift; sourceTree = ""; }; + 1FFEF103298552DB0098374C /* AppVersionHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersionHistoryView.swift; sourceTree = ""; }; 9961EC2D29BE9F2E00AF2C6F /* minimuxer-helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "minimuxer-helpers.swift"; path = "Dependencies/minimuxer/minimuxer-helpers.swift"; sourceTree = SOURCE_ROOT; }; + 99BCB7DE29A2AC050041D1A7 /* AdvancedSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsView.swift; sourceTree = ""; }; + 99D87A5F299F1B1100ED09A9 /* DevModeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevModeView.swift; sourceTree = ""; }; + 99D87A61299F3EC300ED09A9 /* FileExplorer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileExplorer.swift; sourceTree = ""; }; + 99DE640029A1271100B920BF /* AsyncFallibleButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncFallibleButton.swift; sourceTree = ""; }; + 99DE640229A1624500B920BF /* View+Hidden.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Hidden.swift"; sourceTree = ""; }; + 99E59E1C299BFE5D00FAF33D /* AppIconsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconsView.swift; sourceTree = ""; }; 99F87D1629D8E4C900B40039 /* SwiftBridgeCore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftBridgeCore.swift; path = Dependencies/minimuxer/SwiftBridgeCore.swift; sourceTree = SOURCE_ROOT; }; 99F87D1729D8E4C900B40039 /* minimuxer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = minimuxer.swift; path = Dependencies/minimuxer/minimuxer.swift; sourceTree = SOURCE_ROOT; }; B3146EC6284F580500BBC3FD /* Roxas.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Roxas.xcodeproj; path = Dependencies/Roxas/Roxas.xcodeproj; sourceTree = ""; }; @@ -853,6 +1000,7 @@ D5F2F6A82720B7C20081CCF5 /* PatchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchViewController.swift; sourceTree = ""; }; D5F99A1728D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore10ToAltStore11.xcmappingmodel; sourceTree = ""; }; D5F99A1928D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreApp10ToStoreApp11Policy.swift; sourceTree = ""; }; + DFEE02F82957998D00518C34 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/Localizable.strings"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -903,6 +1051,7 @@ B3C395F1284F2DE700DA9E2F /* KeychainAccess in Frameworks */, 99C4EF4D2979132100CB538D /* SemanticVersion in Frameworks */, 4879A95F2861046500FC1BBD /* AltSign in Frameworks */, + 1F07F5692955D3EC00F7BE95 /* SFSafeSymbols in Frameworks */, B39575F5284F29E20080B4FF /* Roxas.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -919,20 +1068,27 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 99D87A6529A04D5E00ED09A9 /* Inject in Frameworks */, B33FFBA8295F8E98002259E6 /* libfragmentzip.a in Frameworks */, 191E5FB4290A5DA0001A3B7C /* libminimuxer.a in Frameworks */, 19104DBC2909C4E500C49C7B /* libEmotionalDamage.a in Frameworks */, + 1F1295812989B51F0048FCB9 /* ExpandableText in Frameworks */, 19104D952909BAEA00C49C7B /* libimobiledevice.a in Frameworks */, B3146ED2284F581E00BBC3FD /* Roxas.framework in Frameworks */, D533E8B72727841800A9B5DD /* libAppleArchive.tbd in Frameworks */, B3C395F9284F362400DA9E2F /* AppCenterCrashes in Frameworks */, 9922FFEC29B501C50020F868 /* Starscream in Frameworks */, + 1FF8C61B2A1782F10041352C /* Reachability in Frameworks */, D533E8BE2727BBF800A9B5DD /* libcurl.a in Frameworks */, 4879A9622861049C00FC1BBD /* OpenSSL in Frameworks */, B3C395F4284F35DD00DA9E2F /* Nuke in Frameworks */, + 1F74FF1E295263510047C051 /* AsyncImage in Frameworks */, BF1614F1250822F100767AEA /* Roxas.framework in Frameworks */, B3C395F7284F362400DA9E2F /* AppCenterAnalytics in Frameworks */, + 1F07F5672955D16A00F7BE95 /* SFSafeSymbols in Frameworks */, BF66EE852501AE50007EE018 /* AltStoreCore.framework in Frameworks */, + 992C896029A6A56500FB3501 /* LocalConsole in Frameworks */, + 99DE640629A1753800B920BF /* ZIPFoundation in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -983,6 +1139,198 @@ path = "Unstable Features"; sourceTree = ""; }; + 1F07F553295458D800F7BE95 /* Generated */ = { + isa = PBXGroup; + children = ( + 1F07F554295458D800F7BE95 /* Assets.swift */, + 1F07F555295458D800F7BE95 /* Localizations.swift */, + ); + path = Generated; + sourceTree = ""; + }; + 1F6E08DD29280AF1005059C0 /* View Extensions */ = { + isa = PBXGroup; + children = ( + 1FB96FF1292D051F007E68D1 /* Styles */, + 1F6E08DE29280AFF005059C0 /* UIView Representables */, + 1F0DD84029368056007608A4 /* EnvironmentValues.swift */, + 1F66F5BB2938F03700A910CA /* Modifiers.swift */, + ); + path = "View Extensions"; + sourceTree = ""; + }; + 1F6E08DE29280AFF005059C0 /* UIView Representables */ = { + isa = PBXGroup; + children = ( + 1F6E08DF29280B12005059C0 /* SafariView.swift */, + 1FB96FCE292BBBC9007E68D1 /* SiriShortcutSetupView.swift */, + 1F66F5B92938CA5700A910CA /* VisualEffectView.swift */, + 1F6284D6295218980060AAD8 /* DocumentPicker.swift */, + 1F545E84298D84CF00589F68 /* FilePreviewView.swift */, + 1FA5A6CB298E8FE4007BA946 /* MailComposeView.swift */, + 1F981B1629AA34A70014950E /* AppStoreProductView.swift */, + 1FF8C6172A1780C60041352C /* ActivityView.swift */, + ); + path = "UIView Representables"; + sourceTree = ""; + }; + 1F981B0F29AA0F9B0014950E /* Onboarding */ = { + isa = PBXGroup; + children = ( + 1F981B1029AA0FAE0014950E /* OnboardingView.swift */, + 1F981B1229AA101F0014950E /* OnboardingStepView.swift */, + 1F981B1429AA1E070014950E /* AppIconsShowcase.swift */, + ); + path = Onboarding; + sourceTree = ""; + }; + 1FAFC5B02927E01400B8D837 /* Views */ = { + isa = PBXGroup; + children = ( + 1FAFC5B52927E06300B8D837 /* RootView.swift */, + 1FAFC5B12927E02E00B8D837 /* Authentication */, + 1F981B0F29AA0F9B0014950E /* Onboarding */, + 1FAFC5B22927E03300B8D837 /* News */, + 1FAFC5B32927E03D00B8D837 /* Browse */, + 1FAFC5BC2927E0FD00B8D837 /* My Apps */, + 1FAFC5BF2927E12B00B8D837 /* Settings */, + 1FAFC5B42927E04B00B8D837 /* App Detail */, + ); + path = Views; + sourceTree = ""; + }; + 1FAFC5B12927E02E00B8D837 /* Authentication */ = { + isa = PBXGroup; + children = ( + ); + path = Authentication; + sourceTree = ""; + }; + 1FAFC5B22927E03300B8D837 /* News */ = { + isa = PBXGroup; + children = ( + 1FAFC5B82927E0EE00B8D837 /* NewsView.swift */, + 1F943C652927F36600ABE095 /* NewsViewModel.swift */, + 1F943C632927EF4200ABE095 /* NewsItemView.swift */, + ); + path = News; + sourceTree = ""; + }; + 1FAFC5B32927E03D00B8D837 /* Browse */ = { + isa = PBXGroup; + children = ( + 1FAFC5BA2927E0F800B8D837 /* BrowseView.swift */, + 1FB96FC6292A853D007E68D1 /* SourcesView.swift */, + 1FB96FC8292ABDD0007E68D1 /* AddSourceView.swift */, + 1F6E08E729282174005059C0 /* ConfirmAddSourceView.swift */, + 1FB96FC4292A7251007E68D1 /* BrowseAppPreviewView.swift */, + ); + path = Browse; + sourceTree = ""; + }; + 1FAFC5B42927E04B00B8D837 /* App Detail */ = { + isa = PBXGroup; + children = ( + 1FB84BA52928DE08006A5CF4 /* AppDetailView.swift */, + 1F0DD81B2932D2FF007608A4 /* AppScreenshotsScrollView.swift */, + 1F0DD8202933B749007608A4 /* AppPermissionsGrid.swift */, + 1F07F56A2955F11500F7BE95 /* AppScreenshotsPreview.swift */, + 1FFEF103298552DB0098374C /* AppVersionHistoryView.swift */, + 1F1D669D29A234CE0095BFCD /* WriteAppReviewView.swift */, + ); + path = "App Detail"; + sourceTree = ""; + }; + 1FAFC5B72927E06C00B8D837 /* App */ = { + isa = PBXGroup; + children = ( + 1FAFC5A42927E00000B8D837 /* SideStoreUIApp.swift */, + ); + path = App; + sourceTree = ""; + }; + 1FAFC5BC2927E0FD00B8D837 /* My Apps */ = { + isa = PBXGroup; + children = ( + 1FAFC5BD2927E10D00B8D837 /* MyAppsView.swift */, + 1FA1C8C9294906890083119D /* MyAppsViewModel.swift */, + 1F6284D4295209DA0060AAD8 /* AppAction.swift */, + 1F07F56E2955FB2000F7BE95 /* AppIDsView.swift */, + ); + path = "My Apps"; + sourceTree = ""; + }; + 1FAFC5BF2927E12B00B8D837 /* Settings */ = { + isa = PBXGroup; + children = ( + 1FAFC5C02927E13C00B8D837 /* SettingsView.swift */, + 1F0DD83E29367F6C007608A4 /* ConnectAppleIDView.swift */, + 1F2EF786297C4D40002FD839 /* LicensesView.swift */, + 1F545E82298D79E400589F68 /* ErrorLogView.swift */, + 1FA5A6C9298E8B2F007BA946 /* RefreshAttemptsView.swift */, + 99E59E1C299BFE5D00FAF33D /* AppIconsView.swift */, + 99D87A5F299F1B1100ED09A9 /* DevModeView.swift */, + 99BCB7DE29A2AC050041D1A7 /* AdvancedSettingsView.swift */, + ); + path = Settings; + sourceTree = ""; + }; + 1FAFC5C22927E17100B8D837 /* Protocols */ = { + isa = PBXGroup; + children = ( + 1FAFC5C32927E18100B8D837 /* NavigationTab.swift */, + 1F943C672927F39400ABE095 /* ViewModel.swift */, + 1F66F5BF2938F07C00A910CA /* Filterable.swift */, + ); + path = Protocols; + sourceTree = ""; + }; + 1FB84BA72928E073006A5CF4 /* View Components */ = { + isa = PBXGroup; + children = ( + 1F6E08D9292806E0005059C0 /* AppRowView.swift */, + 1F6E08DB292807D3005059C0 /* AppIconView.swift */, + 1FB96FBF292A63F2007E68D1 /* AppPillButton.swift */, + 1F5DF9D72974426300DDAA47 /* AppScreenshot.swift */, + 1F44634429744E570070E514 /* HintView.swift */, + 1FB96FBD292A20E5007E68D1 /* ObservableScrollView.swift */, + 1F6E08E529280F4B005059C0 /* RatingStars.swift */, + 1F0DD8422936B0F9007608A4 /* RoundedTextField.swift */, + 1F545E86298D86D800589F68 /* ModalNavigationLink.swift */, + 99D87A61299F3EC300ED09A9 /* FileExplorer.swift */, + 99DE640029A1271100B920BF /* AsyncFallibleButton.swift */, + ); + path = "View Components"; + sourceTree = ""; + }; + 1FB96FC1292A6D6C007E68D1 /* Helper */ = { + isa = PBXGroup; + children = ( + 1FB96FC2292A6D7E007E68D1 /* DateFormatterHelper.swift */, + 1F6284D829523D340060AAD8 /* SideloadingManager.swift */, + ); + path = Helper; + sourceTree = ""; + }; + 1FB96FEA292C1704007E68D1 /* Manager */ = { + isa = PBXGroup; + children = ( + 1FB96FEB292C171D007E68D1 /* NotificationManager.swift */, + 1FFA56C1299994390011B6F5 /* OutputCapturer.swift */, + ); + path = Manager; + sourceTree = ""; + }; + 1FB96FF1292D051F007E68D1 /* Styles */ = { + isa = PBXGroup; + children = ( + 1F6E08E329280D1E005059C0 /* PillButtonStyle.swift */, + 1FB96FF2292D0539007E68D1 /* PillButtonProgressViewStyle.swift */, + 1F0DD8442936B3FE007608A4 /* FilledButtonStyle.swift */, + ); + path = Styles; + sourceTree = ""; + }; 99F87D1429D8E3F100B40039 /* Generated */ = { isa = PBXGroup; children = ( @@ -990,6 +1338,7 @@ 99F87D1729D8E4C900B40039 /* minimuxer.swift */, ); name = Generated; + path = minimuxer; sourceTree = ""; }; B3146EC7284F580500BBC3FD /* Products */ = { @@ -1558,6 +1907,14 @@ isa = PBXGroup; children = ( 990D2AE02A1910920055D93C /* Unstable Features */, + 1FAFC5B72927E06C00B8D837 /* App */, + 1FB96FC1292A6D6C007E68D1 /* Helper */, + 1F07F553295458D800F7BE95 /* Generated */, + 1FB96FEA292C1704007E68D1 /* Manager */, + 1FAFC5C22927E17100B8D837 /* Protocols */, + 1FAFC5B02927E01400B8D837 /* Views */, + 1FB84BA72928E073006A5CF4 /* View Components */, + 1F6E08DD29280AF1005059C0 /* View Extensions */, B39F16112918D7B5002E9404 /* Consts */, BF219A7E22CAC431007676A6 /* AltStore.entitlements */, BFD2476D2284B9A500981D42 /* AppDelegate.swift */, @@ -1635,6 +1992,7 @@ BF44EEF2246B3A17002A52F2 /* AltBackup.ipa */, BFD247762284B9A700981D42 /* Assets.xcassets */, BF770E6822BD57DD002A40FE /* Silence.m4a */, + 1F07F552295455A300F7BE95 /* Localizable.strings */, ); path = Resources; sourceTree = ""; @@ -1659,6 +2017,11 @@ D57F2C9326E01BC700B9FA39 /* UIDevice+Vibration.swift */, B376FE3D29258C8900E18883 /* OSLog+SideStore.swift */, 990D2AEF2A192E060055D93C /* UIApplication+Alert.swift */, + 1F66F5BD2938F06100A910CA /* StoreApp+Filterable.swift */, + 1F180F91298E7A1B00D1C98B /* StoreApp+Trusted.swift */, + 1F180F93298E7A2500D1C98B /* Source+Trusted.swift */, + 99DE640229A1624500B920BF /* View+Hidden.swift */, + 994D6EB429E35C130045B3F7 /* StoreApp+SideStore.swift */, ); path = Extensions; sourceTree = ""; @@ -1958,6 +2321,7 @@ B3C395F0284F2DE700DA9E2F /* KeychainAccess */, 4879A95E2861046500FC1BBD /* AltSign */, 99C4EF4C2979132100CB538D /* SemanticVersion */, + 1F07F5682955D3EC00F7BE95 /* SFSafeSymbols */, ); productName = AltStoreCore; productReference = BF66EE7E2501AE50007EE018 /* AltStoreCore.framework */; @@ -2007,7 +2371,14 @@ B3C395F6284F362400DA9E2F /* AppCenterAnalytics */, B3C395F8284F362400DA9E2F /* AppCenterCrashes */, 4879A9612861049C00FC1BBD /* OpenSSL */, + 99D87A6429A04D5E00ED09A9 /* Inject */, + 99DE640529A1753800B920BF /* ZIPFoundation */, + 992C895F29A6A56500FB3501 /* LocalConsole */, 9922FFEB29B501C50020F868 /* Starscream */, + 1F74FF1D295263510047C051 /* AsyncImage */, + 1F07F5662955D16A00F7BE95 /* SFSafeSymbols */, + 1F1295802989B51F0048FCB9 /* ExpandableText */, + 1FF8C61A2A1782F10041352C /* Reachability */, ); productName = AltStore; productReference = BFD2476A2284B9A500981D42 /* SideStore.app */; @@ -2019,7 +2390,7 @@ BFD247622284B9A500981D42 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1400; + LastSwiftUpdateCheck = 1410; LastUpgradeCheck = 1020; ORGANIZATIONNAME = SideStore; TargetAttributes = { @@ -2067,6 +2438,7 @@ knownRegions = ( en, Base, + "es-419", ); mainGroup = BFD247612284B9A500981D42; packageReferences = ( @@ -2079,7 +2451,14 @@ 4879A95D2861046500FC1BBD /* XCRemoteSwiftPackageReference "AltSign" */, 4879A9602861049C00FC1BBD /* XCRemoteSwiftPackageReference "OpenSSL" */, 99C4EF472978D52400CB538D /* XCRemoteSwiftPackageReference "SemanticVersion" */, + 99D87A6329A04D5E00ED09A9 /* XCRemoteSwiftPackageReference "Inject" */, + 99DE640429A1753800B920BF /* XCRemoteSwiftPackageReference "ZIPFoundation" */, + 992C895E29A6A56500FB3501 /* XCRemoteSwiftPackageReference "LocalConsole" */, 9922FFEA29B501C50020F868 /* XCRemoteSwiftPackageReference "Starscream" */, + 1F74FF1C295263510047C051 /* XCRemoteSwiftPackageReference "AsyncImage" */, + 1F07F5652955D16A00F7BE95 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */, + 1F12957F2989B51F0048FCB9 /* XCRemoteSwiftPackageReference "ExpandableText" */, + 1FF8C6192A1782F10041352C /* XCRemoteSwiftPackageReference "Reachability" */, ); productRefGroup = BFD2476B2284B9A500981D42 /* Products */; projectDirPath = ""; @@ -2218,6 +2597,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1F07F550295455A300F7BE95 /* Localizable.strings in Resources */, BFC57A702416FC7600EB891E /* InstalledAppsCollectionHeaderView.xib in Resources */, BFB4323F22DE852000B7F8BC /* UpdateCollectionViewCell.xib in Resources */, BFE60738231ADF49002B0E8E /* Settings.storyboard in Resources */, @@ -2273,7 +2653,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 99F87D1929D8E4C900B40039 /* minimuxer.swift in Sources */, + 994D6E9B29E326080045B3F7 /* minimuxer.swift in Sources */, 99F87D1829D8E4C900B40039 /* SwiftBridgeCore.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2470,58 +2850,100 @@ buildActionMask = 2147483647; files = ( BFDB6A0F22AB2776007EA6D6 /* SendAppOperation.swift in Sources */, + 99DE640129A1271100B920BF /* AsyncFallibleButton.swift in Sources */, + 1F6E08E029280B12005059C0 /* SafariView.swift in Sources */, + 1F943C6C2927F90400ABE095 /* SettingsView.swift in Sources */, BFDB6A0D22AAFC1A007EA6D6 /* OperationError.swift in Sources */, BF74989B23621C0700CED65F /* ForwardingNavigationController.swift in Sources */, + 1F66F5C02938F07C00A910CA /* Filterable.swift in Sources */, BF3D649D22E7AC1B00E9056B /* PermissionPopoverViewController.swift in Sources */, + 99D87A60299F1B1100ED09A9 /* DevModeView.swift in Sources */, D57F2C9426E01BC700B9FA39 /* UIDevice+Vibration.swift in Sources */, + 1F07F556295458D800F7BE95 /* Assets.swift in Sources */, BFD2478F2284C8F900981D42 /* Button.swift in Sources */, + 1F943C6F2927F90400ABE095 /* NewsView.swift in Sources */, BF56D2AC23DF8E170006506D /* FetchAppIDsOperation.swift in Sources */, 990D2AE22A1910CD0055D93C /* UnstableFeatures.swift in Sources */, BFC1F38D22AEE3A4003AC21A /* DownloadAppOperation.swift in Sources */, + 1F6E08DA292806E0005059C0 /* AppRowView.swift in Sources */, + 1FB96FC3292A6D7E007E68D1 /* DateFormatterHelper.swift in Sources */, + 1F545E87298D86D800589F68 /* ModalNavigationLink.swift in Sources */, + 1F943C6D2927F90400ABE095 /* NewsViewModel.swift in Sources */, + 1FB96FF3292D0539007E68D1 /* PillButtonProgressViewStyle.swift in Sources */, + 1F6E08E829282174005059C0 /* ConfirmAddSourceView.swift in Sources */, + 1F180F92298E7A1B00D1C98B /* StoreApp+Trusted.swift in Sources */, + 1F6284D929523D340060AAD8 /* SideloadingManager.swift in Sources */, BFE6073A231ADF82002B0E8E /* SettingsViewController.swift in Sources */, + 1F943C6A2927F8F700ABE095 /* ViewModel.swift in Sources */, + 1F0DD8432936B0F9007608A4 /* RoundedTextField.swift in Sources */, D57F2C9126E0070200B9FA39 /* EnableJITOperation.swift in Sources */, BF8CAE4E248AEABA004D6CCE /* UIDevice+Jailbreak.swift in Sources */, + 1FB96FC7292A853D007E68D1 /* SourcesView.swift in Sources */, + 1F66F5BE2938F06100A910CA /* StoreApp+Filterable.swift in Sources */, D5E1E7C128077DE90016FC96 /* FetchTrustedSourcesOperation.swift in Sources */, BFE338DF22F0EADB002E24B9 /* FetchSourceOperation.swift in Sources */, D54DED1428CBC44B008B27A0 /* ErrorLogTableViewCell.swift in Sources */, BFB6B21E231870160022A802 /* NewsViewController.swift in Sources */, BFC57A652416C72400EB891E /* DeactivateAppOperation.swift in Sources */, + 1F943C712927F90400ABE095 /* MyAppsView.swift in Sources */, + 1FA1C8CA294906890083119D /* MyAppsViewModel.swift in Sources */, + 1F981B1529AA1E070014950E /* AppIconsShowcase.swift in Sources */, BF3BEFC124086A1E00DE7D55 /* RefreshAppOperation.swift in Sources */, + 1FFEF104298552DB0098374C /* AppVersionHistoryView.swift in Sources */, BFE60740231AFD2A002B0E8E /* InsetGroupTableViewCell.swift in Sources */, BF0DCA662433BDF500E3A595 /* AnalyticsManager.swift in Sources */, BFCCB51A245E3401001853EA /* VerifyAppOperation.swift in Sources */, BFF0B6982322CAB8007A79E1 /* InstructionsViewController.swift in Sources */, BF9ABA4522DCFF43008935CF /* BrowseViewController.swift in Sources */, BF770E5422BC044E002A40FE /* OperationContexts.swift in Sources */, + 1F0DD84129368056007608A4 /* EnvironmentValues.swift in Sources */, BFD2478C2284C4C300981D42 /* AppIconImageView.swift in Sources */, BF8F69C422E662D300049BA1 /* AppViewController.swift in Sources */, BFF0B68E23219520007A79E1 /* PatreonViewController.swift in Sources */, BFF00D302501BD7D00746320 /* Intents.intentdefinition in Sources */, BF6C336224197D700034FD24 /* NSError+AltStore.swift in Sources */, D5DAE0942804B0B80034D8D4 /* ScreenshotProcessor.swift in Sources */, + 1FB96FBE292A20E5007E68D1 /* ObservableScrollView.swift in Sources */, BFD2476E2284B9A500981D42 /* AppDelegate.swift in Sources */, 990D2AF02A192E060055D93C /* UIApplication+Alert.swift in Sources */, + 1FFA56C2299994390011B6F5 /* OutputCapturer.swift in Sources */, BF41B806233423AE00C593A3 /* TabBarController.swift in Sources */, + 1FB96FC9292ABDD0007E68D1 /* AddSourceView.swift in Sources */, + 1F6E08DC292807D3005059C0 /* AppIconView.swift in Sources */, + 1F6284D5295209DA0060AAD8 /* AppAction.swift in Sources */, BFE00A202503097F00EB4D0C /* INInteraction+AltStore.swift in Sources */, BFDB6A0B22AAEDB7007EA6D6 /* Operation.swift in Sources */, + 1FB96FEC292C171D007E68D1 /* NotificationManager.swift in Sources */, BF770E6722BD57C4002A40FE /* BackgroundTaskManager.swift in Sources */, + 1F07F56B2955F11500F7BE95 /* AppScreenshotsPreview.swift in Sources */, + 1F981B1329AA101F0014950E /* OnboardingStepView.swift in Sources */, BF44EEFC246B4550002A52F2 /* RemoveAppOperation.swift in Sources */, BF3D64B022E8D4B800E9056B /* AppContentViewControllerCells.swift in Sources */, + 1F981B1729AA34A70014950E /* AppStoreProductView.swift in Sources */, BFC57A6E2416FC5D00EB891E /* InstalledAppsCollectionHeaderView.swift in Sources */, B3EE16B62925E27D00B3B1F5 /* AnisetteManager.swift in Sources */, + 1FF8C6182A1780C60041352C /* ActivityView.swift in Sources */, + 1F943C6E2927F90400ABE095 /* NewsItemView.swift in Sources */, BF88F97224F8727D00BB75DF /* AppManagerErrors.swift in Sources */, B39F16152918D7DA002E9404 /* Consts+Proxy.swift in Sources */, + 1F6E08E629280F4B005059C0 /* RatingStars.swift in Sources */, + 1F07F557295458D800F7BE95 /* Localizations.swift in Sources */, BF6C8FAE2429597900125131 /* BannerCollectionViewCell.swift in Sources */, + 1FB96FC0292A63F2007E68D1 /* AppPillButton.swift in Sources */, BF6F439223644C6E00A0B879 /* RefreshAltStoreViewController.swift in Sources */, BFE60742231B07E6002B0E8E /* SettingsHeaderFooterView.swift in Sources */, BFE338E822F10E56002E24B9 /* LaunchViewController.swift in Sources */, BFA8172B23C5633D001B5953 /* FetchAnisetteDataOperation.swift in Sources */, BF9ABA4722DD0638008935CF /* BrowseCollectionViewCell.swift in Sources */, + 99D87A62299F3EC300ED09A9 /* FileExplorer.swift in Sources */, BFD6B03322DFF20800B86064 /* MyAppsComponents.swift in Sources */, BF41B808233433C100C593A3 /* LoadingState.swift in Sources */, BFF0B69A2322D7D0007A79E1 /* UIScreen+CompactHeight.swift in Sources */, D5F2F6A92720B7C20081CCF5 /* PatchViewController.swift in Sources */, B39F16132918D7C5002E9404 /* Consts.swift in Sources */, + 1F0DD8212933B749007608A4 /* AppPermissionsGrid.swift in Sources */, + 1F545E83298D79E400589F68 /* ErrorLogView.swift in Sources */, + 1F0DD8452936B3FE007608A4 /* FilledButtonStyle.swift in Sources */, BF8F69C222E659F700049BA1 /* AppContentViewController.swift in Sources */, BF08858522DE7EC800DE9F1E /* UpdateCollectionViewCell.swift in Sources */, BFB39B5C252BC10E00D1BE50 /* Managed.swift in Sources */, @@ -2533,20 +2955,40 @@ BF02419622F2199300129732 /* RefreshAttemptsViewController.swift in Sources */, B376FE3E29258C8900E18883 /* OSLog+SideStore.swift in Sources */, BF08858322DE795100DE9F1E /* MyAppsViewController.swift in Sources */, + 1F943C6B2927F8F700ABE095 /* NavigationTab.swift in Sources */, BFC84A4D2421A19100853474 /* SourcesViewController.swift in Sources */, BFF0B696232242D3007A79E1 /* LicensesViewController.swift in Sources */, D57FE84428C7DB7100216002 /* ErrorLogViewController.swift in Sources */, + 1F44634529744E570070E514 /* HintView.swift in Sources */, + 1F6E08E429280D1E005059C0 /* PillButtonStyle.swift in Sources */, + 1F6284D7295218980060AAD8 /* DocumentPicker.swift in Sources */, BFBE0007250AD0E70080826E /* ViewAppIntentHandler.swift in Sources */, + 99E59E1D299BFE5D00FAF33D /* AppIconsView.swift in Sources */, + 99BCB7DF29A2AC050041D1A7 /* AdvancedSettingsView.swift in Sources */, + 1F0DD83F29367F6C007608A4 /* ConnectAppleIDView.swift in Sources */, + 1F07F56F2955FB2000F7BE95 /* AppIDsView.swift in Sources */, BFDB6A0822AAED73007EA6D6 /* ResignAppOperation.swift in Sources */, D593F1942717749A006E82DE /* PatchAppOperation.swift in Sources */, BF770E5122BB1CF6002A40FE /* InstallAppOperation.swift in Sources */, BF9ABA4B22DD1380008935CF /* NavigationBar.swift in Sources */, BF6C8FAC242935ED00125131 /* NSAttributedString+Markdown.m in Sources */, BFF00D322501BDA100746320 /* BackgroundRefreshAppsOperation.swift in Sources */, + 1F1D669E29A234CE0095BFCD /* WriteAppReviewView.swift in Sources */, + 1F66F5BC2938F03700A910CA /* Modifiers.swift in Sources */, + 994D6EB529E35C130045B3F7 /* StoreApp+SideStore.swift in Sources */, + 99DE640329A1624500B920BF /* View+Hidden.swift in Sources */, + 1FA5A6CA298E8B2F007BA946 /* RefreshAttemptsView.swift in Sources */, + 1F5DF9D82974426300DDAA47 /* AppScreenshot.swift in Sources */, + 1F66F5BA2938CA5700A910CA /* VisualEffectView.swift in Sources */, BF0C4EBD22A1BD8B009A2DD7 /* AppManager.swift in Sources */, + 1F180F94298E7A2500D1C98B /* Source+Trusted.swift in Sources */, BF2901312318F7A800D88A45 /* AppBannerView.swift in Sources */, BFF00D342501BDCF00746320 /* IntentHandler.swift in Sources */, + 1F0DD81C2932D2FF007608A4 /* AppScreenshotsScrollView.swift in Sources */, BFDBBD80246CB84F004ED2F3 /* RemoveAppBackupOperation.swift in Sources */, + 1F545E85298D84CF00589F68 /* FilePreviewView.swift in Sources */, + 1FB96FCF292BBBCA007E68D1 /* SiriShortcutSetupView.swift in Sources */, + 1F2EF787297C4D40002FD839 /* LicensesView.swift in Sources */, BFF0B6942321CB85007A79E1 /* AuthenticationViewController.swift in Sources */, BF3432FB246B894F0052F4A1 /* BackupAppOperation.swift in Sources */, BF9ABA4922DD0742008935CF /* ScreenshotCollectionViewCell.swift in Sources */, @@ -2557,10 +2999,16 @@ BF4B78FE24B3D1DB008AB4AC /* SceneDelegate.swift in Sources */, BF6C8FB02429599900125131 /* TextCollectionReusableView.swift in Sources */, BF663C4F2433ED8200DAA738 /* FileManager+DirectorySize.swift in Sources */, + 1F943C702927F90400ABE095 /* BrowseView.swift in Sources */, + 1F981B1129AA0FAE0014950E /* OnboardingView.swift in Sources */, + 1F943C692927F8F200ABE095 /* RootView.swift in Sources */, + 1FB84BA62928DE08006A5CF4 /* AppDetailView.swift in Sources */, D57DF63F271E51E400677701 /* ALTAppPatcher.m in Sources */, BFB6B220231870B00022A802 /* NewsCollectionViewCell.swift in Sources */, + 1FA5A6CC298E8FE4007BA946 /* MailComposeView.swift in Sources */, BF3BEFBF2408673400DE7D55 /* FetchProvisioningProfilesOperation.swift in Sources */, BFF0B69023219C6D007A79E1 /* PatreonComponents.swift in Sources */, + 1FB96FC5292A7251007E68D1 /* BrowseAppPreviewView.swift in Sources */, BFBE0004250ACFFB0080826E /* ViewApp.intentdefinition in Sources */, BF56D2AF23DF9E310006506D /* AppIDsViewController.swift in Sources */, ); @@ -2620,6 +3068,15 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + 1F07F552295455A300F7BE95 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + 1F07F551295455A300F7BE95 /* en */, + DFEE02F82957998D00518C34 /* es-419 */, + ); + name = Localizable.strings; + sourceTree = ""; + }; BF580488246A28F9008AE704 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -3091,6 +3548,7 @@ baseConfigurationReference = B3C39607284F4C8400DA9E2F /* Build.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -3159,6 +3617,7 @@ baseConfigurationReference = B3C39607284F4C8400DA9E2F /* Build.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -3222,7 +3681,10 @@ baseConfigurationReference = B3C3960B284F4C9800DA9E2F /* AltStore.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "Neon Storm Steel Starburst Honeydew Midnight Sky Vista"; + ASSETCATALOG_COMPILER_APPICON_NAME = Neon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = AltStore/AltStore.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; @@ -3240,9 +3702,14 @@ "$(PROJECT_DIR)/Dependencies/fragmentzip", "$(PROJECT_DIR)/Dependencies/libcurl", ); + OTHER_LDFLAGS = ( + "-Xlinker", + "-interposable", + ); PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + STRIP_STYLE = debugging; SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; SWIFT_OBJC_BRIDGING_HEADER = "AltStore/AltStore-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -3256,7 +3723,10 @@ baseConfigurationReference = B3C3960B284F4C9800DA9E2F /* AltStore.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "Neon Storm Steel Starburst Honeydew Midnight Sky Vista"; + ASSETCATALOG_COMPILER_APPICON_NAME = Neon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = AltStore/AltStore.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; @@ -3274,9 +3744,11 @@ "$(PROJECT_DIR)/Dependencies/fragmentzip", "$(PROJECT_DIR)/Dependencies/libcurl", ); + OTHER_LDFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + STRIP_STYLE = debugging; SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; SWIFT_OBJC_BRIDGING_HEADER = "AltStore/AltStore-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -3371,6 +3843,38 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 1F07F5652955D16A00F7BE95 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/SFSafeSymbols/SFSafeSymbols"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 4.0.0; + }; + }; + 1F12957F2989B51F0048FCB9 /* XCRemoteSwiftPackageReference "ExpandableText" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/fabianthdev/ExpandableText"; + requirement = { + branch = main; + kind = branch; + }; + }; + 1F74FF1C295263510047C051 /* XCRemoteSwiftPackageReference "AsyncImage" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/fabianthdev/AsyncImage"; + requirement = { + branch = main; + kind = branch; + }; + }; + 1FF8C6192A1782F10041352C /* XCRemoteSwiftPackageReference "Reachability" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/ashleymills/Reachability.swift"; + requirement = { + branch = master; + kind = branch; + }; + }; 4879A95D2861046500FC1BBD /* XCRemoteSwiftPackageReference "AltSign" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/SideStore/AltSign"; @@ -3387,6 +3891,14 @@ minimumVersion = 1.1.180; }; }; + 992C895E29A6A56500FB3501 /* XCRemoteSwiftPackageReference "LocalConsole" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/naturecodevoid/LocalConsole.git"; + requirement = { + branch = main; + kind = branch; + }; + }; 9922FFEA29B501C50020F868 /* XCRemoteSwiftPackageReference "Starscream" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/daltoniam/Starscream.git"; @@ -3403,6 +3915,22 @@ minimumVersion = 0.3.5; }; }; + 99D87A6329A04D5E00ED09A9 /* XCRemoteSwiftPackageReference "Inject" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/krzysztofzablocki/Inject.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; + }; + }; + 99DE640429A1753800B920BF /* XCRemoteSwiftPackageReference "ZIPFoundation" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/weichsel/ZIPFoundation.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.9.9; + }; + }; B3C395EF284F2DE700DA9E2F /* XCRemoteSwiftPackageReference "KeychainAccess" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/kishikawakatsumi/KeychainAccess.git"; @@ -3464,6 +3992,31 @@ package = 4879A9602861049C00FC1BBD /* XCRemoteSwiftPackageReference "OpenSSL" */; productName = OpenSSL; }; + 1F07F5662955D16A00F7BE95 /* SFSafeSymbols */ = { + isa = XCSwiftPackageProductDependency; + package = 1F07F5652955D16A00F7BE95 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */; + productName = SFSafeSymbols; + }; + 1F07F5682955D3EC00F7BE95 /* SFSafeSymbols */ = { + isa = XCSwiftPackageProductDependency; + package = 1F07F5652955D16A00F7BE95 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */; + productName = SFSafeSymbols; + }; + 1F1295802989B51F0048FCB9 /* ExpandableText */ = { + isa = XCSwiftPackageProductDependency; + package = 1F12957F2989B51F0048FCB9 /* XCRemoteSwiftPackageReference "ExpandableText" */; + productName = ExpandableText; + }; + 1F74FF1D295263510047C051 /* AsyncImage */ = { + isa = XCSwiftPackageProductDependency; + package = 1F74FF1C295263510047C051 /* XCRemoteSwiftPackageReference "AsyncImage" */; + productName = AsyncImage; + }; + 1FF8C61A2A1782F10041352C /* Reachability */ = { + isa = XCSwiftPackageProductDependency; + package = 1FF8C6192A1782F10041352C /* XCRemoteSwiftPackageReference "Reachability" */; + productName = Reachability; + }; 4879A95E2861046500FC1BBD /* AltSign */ = { isa = XCSwiftPackageProductDependency; package = 4879A95D2861046500FC1BBD /* XCRemoteSwiftPackageReference "AltSign" */; @@ -3474,6 +4027,11 @@ package = 4879A9602861049C00FC1BBD /* XCRemoteSwiftPackageReference "OpenSSL" */; productName = OpenSSL; }; + 992C895F29A6A56500FB3501 /* LocalConsole */ = { + isa = XCSwiftPackageProductDependency; + package = 992C895E29A6A56500FB3501 /* XCRemoteSwiftPackageReference "LocalConsole" */; + productName = LocalConsole; + }; 9922FFEB29B501C50020F868 /* Starscream */ = { isa = XCSwiftPackageProductDependency; package = 9922FFEA29B501C50020F868 /* XCRemoteSwiftPackageReference "Starscream" */; @@ -3489,6 +4047,16 @@ package = 99C4EF472978D52400CB538D /* XCRemoteSwiftPackageReference "SemanticVersion" */; productName = SemanticVersion; }; + 99D87A6429A04D5E00ED09A9 /* Inject */ = { + isa = XCSwiftPackageProductDependency; + package = 99D87A6329A04D5E00ED09A9 /* XCRemoteSwiftPackageReference "Inject" */; + productName = Inject; + }; + 99DE640529A1753800B920BF /* ZIPFoundation */ = { + isa = XCSwiftPackageProductDependency; + package = 99DE640429A1753800B920BF /* XCRemoteSwiftPackageReference "ZIPFoundation" */; + productName = ZIPFoundation; + }; B3C395F0284F2DE700DA9E2F /* KeychainAccess */ = { isa = XCSwiftPackageProductDependency; package = B3C395EF284F2DE700DA9E2F /* XCRemoteSwiftPackageReference "KeychainAccess" */; diff --git a/AltStore.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/AltStore.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 93b37443..049d888f 100644 --- a/AltStore.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/AltStore.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -18,6 +18,33 @@ "version" : "4.4.2" } }, + { + "identity" : "asyncimage", + "kind" : "remoteSourceControl", + "location" : "https://github.com/fabianthdev/AsyncImage", + "state" : { + "branch" : "main", + "revision" : "018a4fffea025066d795ebb025c2769183f3fffb" + } + }, + { + "identity" : "expandabletext", + "kind" : "remoteSourceControl", + "location" : "https://github.com/fabianthdev/ExpandableText", + "state" : { + "branch" : "main", + "revision" : "a375f5b8c73f0af69aa7add890378fdf404a29bc" + } + }, + { + "identity" : "inject", + "kind" : "remoteSourceControl", + "location" : "https://github.com/krzysztofzablocki/Inject.git", + "state" : { + "revision" : "abcc4b091fd384cfd09b149a60298b75dc87c5b9", + "version" : "1.2.3" + } + }, { "identity" : "keychainaccess", "kind" : "remoteSourceControl", @@ -36,6 +63,15 @@ "version" : "4.2.0" } }, + { + "identity" : "localconsole", + "kind" : "remoteSourceControl", + "location" : "https://github.com/naturecodevoid/LocalConsole.git", + "state" : { + "branch" : "main", + "revision" : "4ead9c3e565190172caac62b5179347e02999365" + } + }, { "identity" : "nuke", "kind" : "remoteSourceControl", @@ -63,6 +99,15 @@ "version" : "1.10.1" } }, + { + "identity" : "reachability.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ashleymills/Reachability.swift", + "state" : { + "branch" : "master", + "revision" : "a81b7367f2c46875f29577e03a60c39cdfad0c8d" + } + }, { "identity" : "semanticversion", "kind" : "remoteSourceControl", @@ -72,6 +117,15 @@ "version" : "0.3.5" } }, + { + "identity" : "sfsafesymbols", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SFSafeSymbols/SFSafeSymbols", + "state" : { + "revision" : "50bc33264e6c0972f905b61af656201cf6091de8", + "version" : "4.0.0" + } + }, { "identity" : "sparkle", "kind" : "remoteSourceControl", @@ -98,6 +152,15 @@ "branch" : "master", "revision" : "10a9150ef32d444af326beba76356ae9af95a3e7" } + }, + { + "identity" : "zipfoundation", + "kind" : "remoteSourceControl", + "location" : "https://github.com/weichsel/ZIPFoundation.git", + "state" : { + "revision" : "43ec568034b3731101dbf7670765d671c30f54f3", + "version" : "0.9.16" + } } ], "version" : 2 diff --git a/AltStore/App/SideStoreUIApp.swift b/AltStore/App/SideStoreUIApp.swift new file mode 100644 index 00000000..9ba48190 --- /dev/null +++ b/AltStore/App/SideStoreUIApp.swift @@ -0,0 +1,18 @@ +// +// SideStoreUIApp.swift +// SideStoreUI +// +// Created by Fabian Thies on 18.11.22. +// Copyright © 2022 Fabian Thies. All rights reserved. +// + +import SwiftUI + +@main +struct SideStoreUIApp: App { + var body: some Scene { + WindowGroup { + RootView() + } + } +} diff --git a/AltStore/AppDelegate.swift b/AltStore/AppDelegate.swift index 8ece8b2a..cb017fe1 100644 --- a/AltStore/AppDelegate.swift +++ b/AltStore/AppDelegate.swift @@ -10,6 +10,7 @@ import UIKit import UserNotifications import AVFoundation import Intents +import LocalConsole import AltStoreCore import AltSign @@ -58,11 +59,17 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Copy STDOUT and STDERR to the logging console + _ = OutputCapturer.shared + // Register default settings before doing anything else. UserDefaults.registerDefaults() UnstableFeatures.load() + LCManager.shared.isVisible = UserDefaults.standard.isConsoleEnabled + LCManager.shared.isCharacterLimitDisabled = true // we want all logs exported + DatabaseManager.shared.start { (error) in if let error = error { diff --git a/AltStore/Extensions/Source+Trusted.swift b/AltStore/Extensions/Source+Trusted.swift new file mode 100644 index 00000000..66a337f9 --- /dev/null +++ b/AltStore/Extensions/Source+Trusted.swift @@ -0,0 +1,19 @@ +// +// Source+Trusted.swift +// SideStore +// +// Created by Fabian Thies on 04.02.23. +// Copyright © 2023 SideStore. All rights reserved. +// + +import AltStoreCore + +extension Source { + var isOfficial: Bool { + self.identifier == Source.altStoreIdentifier + } + + var isTrusted: Bool { + UserDefaults.shared.trustedSourceIDs?.contains(self.identifier) ?? false + } +} diff --git a/AltStore/Extensions/StoreApp+Filterable.swift b/AltStore/Extensions/StoreApp+Filterable.swift new file mode 100644 index 00000000..9b228b36 --- /dev/null +++ b/AltStore/Extensions/StoreApp+Filterable.swift @@ -0,0 +1,17 @@ +// +// StoreApp+Searchable.swift +// SideStore +// +// Created by Fabian Thies on 01.12.22. +// Copyright © 2022 SideStore. All rights reserved. +// + +import AltStoreCore + +extension StoreApp: Filterable { + func matches(_ searchText: String) -> Bool { + searchText.isEmpty || + self.name.lowercased().contains(searchText.lowercased()) || + self.developerName.lowercased().contains(searchText.lowercased()) + } +} diff --git a/AltStore/Extensions/StoreApp+SideStore.swift b/AltStore/Extensions/StoreApp+SideStore.swift new file mode 100644 index 00000000..8a63d012 --- /dev/null +++ b/AltStore/Extensions/StoreApp+SideStore.swift @@ -0,0 +1,15 @@ +// +// StoreApp+SideStore.swift +// SideStore +// +// Created by naturecodevoid on 4/9/23. +// Copyright © 2023 SideStore. All rights reserved. +// + +import AltStoreCore + +extension StoreApp { + var isSideStore: Bool { + self.bundleIdentifier == Bundle.Info.appbundleIdentifier + } +} diff --git a/AltStore/Extensions/StoreApp+Trusted.swift b/AltStore/Extensions/StoreApp+Trusted.swift new file mode 100644 index 00000000..90c80d87 --- /dev/null +++ b/AltStore/Extensions/StoreApp+Trusted.swift @@ -0,0 +1,19 @@ +// +// StoreApp+Trusted.swift +// SideStore +// +// Created by Fabian Thies on 04.02.23. +// Copyright © 2023 SideStore. All rights reserved. +// + +import AltStoreCore + +extension StoreApp { + var isFromOfficialSource: Bool { + self.source?.isOfficial ?? false + } + + var isFromTrustedSource: Bool { + self.source?.isTrusted ?? false + } +} diff --git a/AltStore/Extensions/View+Hidden.swift b/AltStore/Extensions/View+Hidden.swift new file mode 100644 index 00000000..f15f56de --- /dev/null +++ b/AltStore/Extensions/View+Hidden.swift @@ -0,0 +1,22 @@ +// +// View+Hidden.swift +// SideStore +// +// Created by naturecodevoid on 2/18/23. +// Copyright © 2023 SideStore. All rights reserved. +// + +import SwiftUI + +// https://stackoverflow.com/a/59228385 (modified) +extension View { + @ViewBuilder func isHidden(_ hidden: Binding, remove: Bool = false) -> some View { + if hidden.wrappedValue { + if !remove { + self.hidden() + } + } else { + self + } + } +} diff --git a/AltStore/Generated/Assets.swift b/AltStore/Generated/Assets.swift new file mode 100644 index 00000000..21e92448 --- /dev/null +++ b/AltStore/Generated/Assets.swift @@ -0,0 +1,207 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +#if os(macOS) + import AppKit +#elseif os(iOS) + import UIKit +#elseif os(tvOS) || os(watchOS) + import UIKit +#endif +#if canImport(SwiftUI) + import SwiftUI +#endif + +// Deprecated typealiases +@available(*, deprecated, renamed: "ColorAsset.Color", message: "This typealias will be removed in SwiftGen 7.0") +internal typealias AssetColorTypeAlias = ColorAsset.Color +@available(*, deprecated, renamed: "ImageAsset.Image", message: "This typealias will be removed in SwiftGen 7.0") +internal typealias AssetImageTypeAlias = ImageAsset.Image + +// swiftlint:disable superfluous_disable_command file_length implicit_return + +// MARK: - Asset Catalogs + +// swiftlint:disable identifier_name line_length nesting type_body_length type_name +internal enum Asset { + internal static let back = ImageAsset(name: "Back") + internal static let betaBadge = ImageAsset(name: "BetaBadge") + internal static let accentColor = ColorAsset(name: "AccentColor") + internal static let background = ColorAsset(name: "Background") + internal static let blurTint = ColorAsset(name: "BlurTint") + internal static let settingsBackground = ColorAsset(name: "SettingsBackground") + internal static let settingsHighlighted = ColorAsset(name: "SettingsHighlighted") + internal static let honeydewImage = ImageAsset(name: "Honeydew-image") + internal static let midnightImage = ImageAsset(name: "Midnight-image") + internal static let neonImage = ImageAsset(name: "Neon-image") + internal static let next = ImageAsset(name: "Next") + internal static let skyImage = ImageAsset(name: "Sky-image") + internal static let starburstImage = ImageAsset(name: "Starburst-image") + internal static let steelImage = ImageAsset(name: "Steel-image") + internal static let stormImage = ImageAsset(name: "Storm-image") + internal static let browse = ImageAsset(name: "Browse") + internal static let myApps = ImageAsset(name: "MyApps") + internal static let news = ImageAsset(name: "News") + internal static let settings = ImageAsset(name: "Settings") + internal static let vistaImage = ImageAsset(name: "Vista-image") +} +// swiftlint:enable identifier_name line_length nesting type_body_length type_name + +// MARK: - Implementation Details + +internal final class ColorAsset { + internal fileprivate(set) var name: String + + #if os(macOS) + internal typealias Color = NSColor + #elseif os(iOS) || os(tvOS) || os(watchOS) + internal typealias Color = UIColor + #endif + + @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) + internal private(set) lazy var color: Color = { + guard let color = Color(asset: self) else { + fatalError("Unable to load color asset named \(name).") + } + return color + }() + + #if os(iOS) || os(tvOS) + @available(iOS 11.0, tvOS 11.0, *) + internal func color(compatibleWith traitCollection: UITraitCollection) -> Color { + let bundle = BundleToken.bundle + guard let color = Color(named: name, in: bundle, compatibleWith: traitCollection) else { + fatalError("Unable to load color asset named \(name).") + } + return color + } + #endif + + #if canImport(SwiftUI) + @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) + internal private(set) lazy var swiftUIColor: SwiftUI.Color = { + SwiftUI.Color(asset: self) + }() + #endif + + fileprivate init(name: String) { + self.name = name + } +} + +internal extension ColorAsset.Color { + @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) + convenience init?(asset: ColorAsset) { + let bundle = BundleToken.bundle + #if os(iOS) || os(tvOS) + self.init(named: asset.name, in: bundle, compatibleWith: nil) + #elseif os(macOS) + self.init(named: NSColor.Name(asset.name), bundle: bundle) + #elseif os(watchOS) + self.init(named: asset.name) + #endif + } +} + +#if canImport(SwiftUI) +@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) +internal extension SwiftUI.Color { + init(asset: ColorAsset) { + let bundle = BundleToken.bundle + self.init(asset.name, bundle: bundle) + } +} +#endif + +internal struct ImageAsset { + internal fileprivate(set) var name: String + + #if os(macOS) + internal typealias Image = NSImage + #elseif os(iOS) || os(tvOS) || os(watchOS) + internal typealias Image = UIImage + #endif + + @available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.7, *) + internal var image: Image { + let bundle = BundleToken.bundle + #if os(iOS) || os(tvOS) + let image = Image(named: name, in: bundle, compatibleWith: nil) + #elseif os(macOS) + let name = NSImage.Name(self.name) + let image = (bundle == .main) ? NSImage(named: name) : bundle.image(forResource: name) + #elseif os(watchOS) + let image = Image(named: name) + #endif + guard let result = image else { + fatalError("Unable to load image asset named \(name).") + } + return result + } + + #if os(iOS) || os(tvOS) + @available(iOS 8.0, tvOS 9.0, *) + internal func image(compatibleWith traitCollection: UITraitCollection) -> Image { + let bundle = BundleToken.bundle + guard let result = Image(named: name, in: bundle, compatibleWith: traitCollection) else { + fatalError("Unable to load image asset named \(name).") + } + return result + } + #endif + + #if canImport(SwiftUI) + @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) + internal var swiftUIImage: SwiftUI.Image { + SwiftUI.Image(asset: self) + } + #endif +} + +internal extension ImageAsset.Image { + @available(iOS 8.0, tvOS 9.0, watchOS 2.0, *) + @available(macOS, deprecated, + message: "This initializer is unsafe on macOS, please use the ImageAsset.image property") + convenience init?(asset: ImageAsset) { + #if os(iOS) || os(tvOS) + let bundle = BundleToken.bundle + self.init(named: asset.name, in: bundle, compatibleWith: nil) + #elseif os(macOS) + self.init(named: NSImage.Name(asset.name)) + #elseif os(watchOS) + self.init(named: asset.name) + #endif + } +} + +#if canImport(SwiftUI) +@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) +internal extension SwiftUI.Image { + init(asset: ImageAsset) { + let bundle = BundleToken.bundle + self.init(asset.name, bundle: bundle) + } + + init(asset: ImageAsset, label: Text) { + let bundle = BundleToken.bundle + self.init(asset.name, bundle: bundle, label: label) + } + + init(decorative asset: ImageAsset) { + let bundle = BundleToken.bundle + self.init(decorative: asset.name, bundle: bundle) + } +} +#endif + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type diff --git a/AltStore/Generated/Localizations.swift b/AltStore/Generated/Localizations.swift new file mode 100644 index 00000000..84d72524 --- /dev/null +++ b/AltStore/Generated/Localizations.swift @@ -0,0 +1,431 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +import Foundation + +// swiftlint:disable superfluous_disable_command file_length implicit_return prefer_self_in_static_references + +// MARK: - Strings + +// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length +// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces +internal enum L10n { + internal enum Action { + /// Cancel + internal static let cancel = L10n.tr("Localizable", "Action.cancel", fallback: "Cancel") + /// Close + internal static let close = L10n.tr("Localizable", "Action.close", fallback: "Close") + /// General Actions + internal static let done = L10n.tr("Localizable", "Action.done", fallback: "Done") + /// Enable + internal static let enable = L10n.tr("Localizable", "Action.enable", fallback: "Enable") + /// Submit + internal static let submit = L10n.tr("Localizable", "Action.submit", fallback: "Submit") + /// Try Again + internal static let tryAgain = L10n.tr("Localizable", "Action.tryAgain", fallback: "Try Again") + } + internal enum AddSourceView { + /// Continue + internal static let `continue` = L10n.tr("Localizable", "AddSourceView.continue", fallback: "Continue") + /// AddSourceView + internal static let sourceURL = L10n.tr("Localizable", "AddSourceView.sourceURL", fallback: "Source URL") + /// Please enter the source url here. Then, tap continue to validate and add the source in the next step. + internal static let sourceWarning = L10n.tr("Localizable", "AddSourceView.sourceWarning", fallback: "Please enter the source url here. Then, tap continue to validate and add the source in the next step.") + /// Be careful with unvalidated third-party sources! Make sure to only add sources that you trust. + internal static let sourceWarningContinued = L10n.tr("Localizable", "AddSourceView.sourceWarningContinued", fallback: "Be careful with unvalidated third-party sources! Make sure to only add sources that you trust.") + /// Add Source + internal static let title = L10n.tr("Localizable", "AddSourceView.title", fallback: "Add Source") + } + internal enum AdvancedSettingsView { + /// Anisette Server + internal static let anisette = L10n.tr("Localizable", "AdvancedSettingsView.anisette", fallback: "Anisette Server") + /// Danger Zone + internal static let dangerZone = L10n.tr("Localizable", "AdvancedSettingsView.dangerZone", fallback: "Danger Zone") + /// If you disable "Use preferred servers" then SideStore will use the server you input into the "Anisette URL" box rather than one selected in "Anisette Server". + internal static let dangerZoneInfo = L10n.tr("Localizable", "AdvancedSettingsView.dangerZoneInfo", fallback: "If you disable \"Use preferred servers\" then SideStore will use the server you input into the \"Anisette URL\" box rather than one selected in \"Anisette Server\".") + /// AdvancedSettingsView + internal static let title = L10n.tr("Localizable", "AdvancedSettingsView.title", fallback: "Advanced Settings") + internal enum DangerZone { + /// Anisette URL + internal static let anisetteURL = L10n.tr("Localizable", "AdvancedSettingsView.DangerZone.anisetteURL", fallback: "Anisette URL") + /// Use preferred servers + internal static let usePreferred = L10n.tr("Localizable", "AdvancedSettingsView.DangerZone.usePreferred", fallback: "Use preferred servers") + } + } + internal enum AppAction { + /// Activate + internal static let activate = L10n.tr("Localizable", "AppAction.activate", fallback: "Activate") + /// Backup + internal static let backup = L10n.tr("Localizable", "AppAction.backup", fallback: "Backup") + /// Customize icon + internal static let chooseCustomIcon = L10n.tr("Localizable", "AppAction.chooseCustomIcon", fallback: "Customize icon") + /// Deactivate + internal static let deactivate = L10n.tr("Localizable", "AppAction.deactivate", fallback: "Deactivate") + /// Activate JIT + internal static let enableJIT = L10n.tr("Localizable", "AppAction.enableJIT", fallback: "Activate JIT") + /// Export backup + internal static let exportBackup = L10n.tr("Localizable", "AppAction.exportBackup", fallback: "Export backup") + /// AppAction + internal static let install = L10n.tr("Localizable", "AppAction.install", fallback: "Install") + /// Open + internal static let `open` = L10n.tr("Localizable", "AppAction.open", fallback: "Open") + /// Refresh + internal static let refresh = L10n.tr("Localizable", "AppAction.refresh", fallback: "Refresh") + /// Remove + internal static let remove = L10n.tr("Localizable", "AppAction.remove", fallback: "Remove") + /// Reset icon + internal static let resetIcon = L10n.tr("Localizable", "AppAction.resetIcon", fallback: "Reset icon") + /// Restore backup + internal static let restoreBackup = L10n.tr("Localizable", "AppAction.restoreBackup", fallback: "Restore backup") + } + internal enum AppDetailView { + /// Information + internal static let information = L10n.tr("Localizable", "AppDetailView.information", fallback: "Information") + /// More... + internal static let more = L10n.tr("Localizable", "AppDetailView.more", fallback: "More...") + /// The app requires no permissions. + internal static let noPermissions = L10n.tr("Localizable", "AppDetailView.noPermissions", fallback: "The app requires no permissions.") + /// No screenshots available for this app. + internal static let noScreenshots = L10n.tr("Localizable", "AppDetailView.noScreenshots", fallback: "No screenshots available for this app.") + /// No version information + internal static let noVersionInformation = L10n.tr("Localizable", "AppDetailView.noVersionInformation", fallback: "No version information") + /// Permissions + internal static let permissions = L10n.tr("Localizable", "AppDetailView.permissions", fallback: "Permissions") + /// Ratings & Reviews + internal static let reviews = L10n.tr("Localizable", "AppDetailView.reviews", fallback: "Ratings & Reviews") + /// Version %@ + internal static func version(_ p1: Any) -> String { + return L10n.tr("Localizable", "AppDetailView.version", String(describing: p1), fallback: "Version %@") + } + /// What's New + internal static let whatsNew = L10n.tr("Localizable", "AppDetailView.whatsNew", fallback: "What's New") + internal enum Badge { + /// AppDetailView + internal static let official = L10n.tr("Localizable", "AppDetailView.Badge.official", fallback: "Official App") + /// From Trusted Source + internal static let trusted = L10n.tr("Localizable", "AppDetailView.Badge.trusted", fallback: "From Trusted Source") + } + internal enum Information { + /// Compatibility + internal static let compatibility = L10n.tr("Localizable", "AppDetailView.Information.compatibility", fallback: "Compatibility") + /// Requires iOS %@ or higher + internal static func compatibilityAtLeast(_ p1: Any) -> String { + return L10n.tr("Localizable", "AppDetailView.Information.compatibilityAtLeast", String(describing: p1), fallback: "Requires iOS %@ or higher") + } + /// Unknown + internal static let compatibilityCompatible = L10n.tr("Localizable", "AppDetailView.Information.compatibilityCompatible", fallback: "Unknown") + /// Requires iOS %@ or lower + internal static func compatibilityOrLower(_ p1: Any) -> String { + return L10n.tr("Localizable", "AppDetailView.Information.compatibilityOrLower", String(describing: p1), fallback: "Requires iOS %@ or lower") + } + /// Unknown + internal static let compatibilityUnknown = L10n.tr("Localizable", "AppDetailView.Information.compatibilityUnknown", fallback: "Unknown") + /// Developer + internal static let developer = L10n.tr("Localizable", "AppDetailView.Information.developer", fallback: "Developer") + /// Latest Version + internal static let latestVersion = L10n.tr("Localizable", "AppDetailView.Information.latestVersion", fallback: "Latest Version") + /// Size + internal static let size = L10n.tr("Localizable", "AppDetailView.Information.size", fallback: "Size") + /// Source + internal static let source = L10n.tr("Localizable", "AppDetailView.Information.source", fallback: "Source") + } + internal enum Reviews { + /// out of %d + internal static func outOf(_ p1: Int) -> String { + return L10n.tr("Localizable", "AppDetailView.Reviews.outOf", p1, fallback: "out of %d") + } + /// %d Ratings + internal static func ratings(_ p1: Int) -> String { + return L10n.tr("Localizable", "AppDetailView.Reviews.ratings", p1, fallback: "%d Ratings") + } + /// See All + internal static let seeAll = L10n.tr("Localizable", "AppDetailView.Reviews.seeAll", fallback: "See All") + } + internal enum WhatsNew { + /// Show project on GitHub + internal static let showOnGithub = L10n.tr("Localizable", "AppDetailView.WhatsNew.showOnGithub", fallback: "Show project on GitHub") + /// Version History + internal static let versionHistory = L10n.tr("Localizable", "AppDetailView.WhatsNew.versionHistory", fallback: "Version History") + } + } + internal enum AppIDsView { + /// Each app and app extension installed with SideStore must register an App ID with Apple. + /// + /// App IDs for paid developer accounts never expire, and there is no limit to how many you can create. + internal static let description = L10n.tr("Localizable", "AppIDsView.description", fallback: "Each app and app extension installed with SideStore must register an App ID with Apple.\n\nApp IDs for paid developer accounts never expire, and there is no limit to how many you can create.") + /// AppIDsView + internal static let title = L10n.tr("Localizable", "AppIDsView.title", fallback: "App IDs") + } + internal enum AppIconsView { + /// AppIconsView + internal static let title = L10n.tr("Localizable", "AppIconsView.title", fallback: "App Icon") + } + internal enum AppPermissionGrid { + /// AppPermissionGrid + internal static let usageDescription = L10n.tr("Localizable", "AppPermissionGrid.usageDescription", fallback: "Usage Description") + } + internal enum AppPillButton { + /// AppPillButton + internal static let free = L10n.tr("Localizable", "AppPillButton.free", fallback: "Free") + /// Open + internal static let `open` = L10n.tr("Localizable", "AppPillButton.open", fallback: "Open") + } + internal enum AppRowView { + /// AppRowView + internal static let sideloaded = L10n.tr("Localizable", "AppRowView.sideloaded", fallback: "Sideloaded") + } + internal enum AsyncFallibleButton { + /// AsyncFallibleButton + internal static let error = L10n.tr("Localizable", "AsyncFallibleButton.error", fallback: "An error occurred") + } + internal enum BrowseView { + /// Search + internal static let search = L10n.tr("Localizable", "BrowseView.search", fallback: "Search") + /// BrowseView + internal static let title = L10n.tr("Localizable", "BrowseView.title", fallback: "Browse") + internal enum Actions { + /// Sources + internal static let sources = L10n.tr("Localizable", "BrowseView.Actions.sources", fallback: "Sources") + } + internal enum Categories { + /// Games and + /// Emulators + internal static let gamesAndEmulators = L10n.tr("Localizable", "BrowseView.Categories.gamesAndEmulators", fallback: "Games and\nEmulators") + } + internal enum Hints { + internal enum NoApps { + /// Add Source + internal static let addSource = L10n.tr("Localizable", "BrowseView.Hints.NoApps.addSource", fallback: "Add Source") + /// Apps are provided by "sources". The specification for them is an open standard, so everyone can create their own source. To get you started, we have compiled a list of "Trusted Sources" which you can check out by tapping the button below. + internal static let text = L10n.tr("Localizable", "BrowseView.Hints.NoApps.text", fallback: "Apps are provided by \"sources\". The specification for them is an open standard, so everyone can create their own source. To get you started, we have compiled a list of \"Trusted Sources\" which you can check out by tapping the button below.") + /// You don't have any apps yet. + internal static let title = L10n.tr("Localizable", "BrowseView.Hints.NoApps.title", fallback: "You don't have any apps yet.") + } + } + internal enum Section { + internal enum AllApps { + /// All Apps + internal static let title = L10n.tr("Localizable", "BrowseView.Section.AllApps.title", fallback: "All Apps") + } + internal enum PromotedCategories { + /// Show all + internal static let showAll = L10n.tr("Localizable", "BrowseView.Section.PromotedCategories.showAll", fallback: "Show all") + /// Promoted Categories + internal static let title = L10n.tr("Localizable", "BrowseView.Section.PromotedCategories.title", fallback: "Promoted Categories") + } + } + } + internal enum ConfirmAddSourceView { + /// Add Source + internal static let addSource = L10n.tr("Localizable", "ConfirmAddSourceView.addSource", fallback: "Add Source") + /// ConfirmAddSourceView + internal static let apps = L10n.tr("Localizable", "ConfirmAddSourceView.apps", fallback: "Apps") + /// News Items + internal static let newsItems = L10n.tr("Localizable", "ConfirmAddSourceView.newsItems", fallback: "News Items") + /// Source Contents + internal static let sourceContents = L10n.tr("Localizable", "ConfirmAddSourceView.sourceContents", fallback: "Source Contents") + /// Source Identifier + internal static let sourceIdentifier = L10n.tr("Localizable", "ConfirmAddSourceView.sourceIdentifier", fallback: "Source Identifier") + /// Source Information + internal static let sourceInfo = L10n.tr("Localizable", "ConfirmAddSourceView.sourceInfo", fallback: "Source Information") + /// Source URL + internal static let sourceURL = L10n.tr("Localizable", "ConfirmAddSourceView.sourceURL", fallback: "Source URL") + } + internal enum ConnectAppleIDView { + /// Apple ID + internal static let appleID = L10n.tr("Localizable", "ConnectAppleIDView.appleID", fallback: "Apple ID") + /// Cancel + internal static let cancel = L10n.tr("Localizable", "ConnectAppleIDView.cancel", fallback: "Cancel") + /// Connect Your Apple ID + internal static let connectYourAppleID = L10n.tr("Localizable", "ConnectAppleIDView.connectYourAppleID", fallback: "Connect Your Apple ID") + /// Failed to Sign In + internal static let failedToSignIn = L10n.tr("Localizable", "ConnectAppleIDView.failedToSignIn", fallback: "Failed to Sign In") + /// Your Apple ID is used to configure apps so they can be installed on this device. Your credentials will be stored securely in this device's Keychain and sent only to Apple for authentication. + internal static let footer = L10n.tr("Localizable", "ConnectAppleIDView.footer", fallback: "Your Apple ID is used to configure apps so they can be installed on this device. Your credentials will be stored securely in this device's Keychain and sent only to Apple for authentication.") + /// Password + internal static let password = L10n.tr("Localizable", "ConnectAppleIDView.password", fallback: "Password") + /// Sign In + internal static let signIn = L10n.tr("Localizable", "ConnectAppleIDView.signIn", fallback: "Sign In") + /// ConnectAppleIDView + internal static let startWithSignIn = L10n.tr("Localizable", "ConnectAppleIDView.startWithSignIn", fallback: "Sign in with your Apple ID to get started.") + /// Why do we need this? + internal static let whyDoWeNeedThis = L10n.tr("Localizable", "ConnectAppleIDView.whyDoWeNeedThis", fallback: "Why do we need this?") + } + internal enum DevModeView { + /// Console + internal static let console = L10n.tr("Localizable", "DevModeView.console", fallback: "Console") + /// Data File Explorer + internal static let dataExplorer = L10n.tr("Localizable", "DevModeView.dataExplorer", fallback: "Data File Explorer") + /// Skip Resign should only be used when you have an IPA that you have self signed. Otherwise, it will break things, and might make SideStore crash (there is absolutely no error handling and everything is expected to work). + internal static let footer = L10n.tr("Localizable", "DevModeView.footer", fallback: "Skip Resign should only be used when you have an IPA that you have self signed. Otherwise, it will break things, and might make SideStore crash (there is absolutely no error handling and everything is expected to work).") + /// Incorrect password. + internal static let incorrectPassword = L10n.tr("Localizable", "DevModeView.incorrectPassword", fallback: "Incorrect password.") + /// minimuxer debug actions + internal static let minimuxer = L10n.tr("Localizable", "DevModeView.minimuxer", fallback: "minimuxer debug actions") + /// Password + internal static let password = L10n.tr("Localizable", "DevModeView.password", fallback: "Password") + /// SideStore's Developer Mode gives access to a menu with some debugging actions commonly used by developers. **However, some of them can break SideStore if used in the wrong way.** + /// + /// You should only enable Developer Mode if you meet one of the following requirements: + /// - You are a SideStore developer or contributor + /// - You were asked to do this by a helper when getting support + /// - You were asked to do this when you reported a bug or helped a developer test a change + /// + /// **_We will not provide support if you break SideStore with Developer Mode._** + internal static let prompt = L10n.tr("Localizable", "DevModeView.prompt", fallback: "SideStore's Developer Mode gives access to a menu with some debugging actions commonly used by developers. **However, some of them can break SideStore if used in the wrong way.**\n\nYou should only enable Developer Mode if you meet one of the following requirements:\n- You are a SideStore developer or contributor\n- You were asked to do this by a helper when getting support\n- You were asked to do this when you reported a bug or helped a developer test a change\n\n**_We will not provide support if you break SideStore with Developer Mode._**") + /// Read the text! + internal static let read = L10n.tr("Localizable", "DevModeView.read", fallback: "Read the text!") + /// Skip Resign + internal static let skipResign = L10n.tr("Localizable", "DevModeView.skipResign", fallback: "Skip Resign") + /// DevModeView + internal static let title = L10n.tr("Localizable", "DevModeView.title", fallback: "Developer Mode") + /// Temporary File Explorer + internal static let tmpExplorer = L10n.tr("Localizable", "DevModeView.tmpExplorer", fallback: "Temporary File Explorer") + internal enum Minimuxer { + /// AFC File Explorer (check footer for notes) + internal static let afcExplorer = L10n.tr("Localizable", "DevModeView.Minimuxer.afcExplorer", fallback: "AFC File Explorer (check footer for notes)") + /// Dump provisioning profiles to Documents directory + internal static let dumpProfiles = L10n.tr("Localizable", "DevModeView.Minimuxer.dumpProfiles", fallback: "Dump provisioning profiles to Documents directory") + /// Notes on AFC File Explorer: + /// - If nothing shows up, check minimuxer logs for error + /// - It is currently extremely very unoptimized and may be very slow; a new AFC client is created for every action + /// - It is currently limited to a maximum depth of 3 to ensure it doesn't take too long to iterate over everything when you open it + /// - Very buggy + /// - There are multiple unimplemented actions + internal static let footer = L10n.tr("Localizable", "DevModeView.Minimuxer.footer", fallback: "Notes on AFC File Explorer:\n- If nothing shows up, check minimuxer logs for error\n- It is currently extremely very unoptimized and may be very slow; a new AFC client is created for every action\n- It is currently limited to a maximum depth of 3 to ensure it doesn't take too long to iterate over everything when you open it\n- Very buggy\n- There are multiple unimplemented actions") + } + } + internal enum MyAppsView { + /// MyAppsView + internal static let active = L10n.tr("Localizable", "MyAppsView.active", fallback: "Active") + /// App IDs Remaining + internal static let appIDsRemaining = L10n.tr("Localizable", "MyAppsView.appIDsRemaining", fallback: "App IDs Remaining") + /// apps + internal static let apps = L10n.tr("Localizable", "MyAppsView.apps", fallback: "apps") + /// Failed to refresh + internal static let failedToRefresh = L10n.tr("Localizable", "MyAppsView.failedToRefresh", fallback: "Failed to refresh") + /// My Apps + internal static let myApps = L10n.tr("Localizable", "MyAppsView.myApps", fallback: "My Apps") + /// Refresh All + internal static let refreshAll = L10n.tr("Localizable", "MyAppsView.refreshAll", fallback: "Refresh All") + /// Sideloading in progress... + internal static let sideloading = L10n.tr("Localizable", "MyAppsView.sideloading", fallback: "Sideloading in progress...") + /// Keep this lowercase + internal static let viewAppIDs = L10n.tr("Localizable", "MyAppsView.viewAppIDs", fallback: "View App IDs") + internal enum Hints { + internal enum NoUpdates { + /// Dismiss for now + internal static let dismissForNow = L10n.tr("Localizable", "MyAppsView.Hints.NoUpdates.dismissForNow", fallback: "Dismiss for now") + /// Don't show this again + internal static let dontShowAgain = L10n.tr("Localizable", "MyAppsView.Hints.NoUpdates.dontShowAgain", fallback: "Don't show this again") + /// You will be notified once updates for your apps are available. The updates will then be shown here. + internal static let text = L10n.tr("Localizable", "MyAppsView.Hints.NoUpdates.text", fallback: "You will be notified once updates for your apps are available. The updates will then be shown here.") + /// All Apps are Up To Date + internal static let title = L10n.tr("Localizable", "MyAppsView.Hints.NoUpdates.title", fallback: "All Apps are Up To Date") + } + } + } + internal enum NewsView { + /// NewsView + internal static let title = L10n.tr("Localizable", "NewsView.title", fallback: "News") + internal enum Section { + internal enum FromSources { + /// From your Sources + internal static let title = L10n.tr("Localizable", "NewsView.Section.FromSources.title", fallback: "From your Sources") + } + } + } + internal enum RootView { + /// Browse + internal static let browse = L10n.tr("Localizable", "RootView.browse", fallback: "Browse") + /// My Apps + internal static let myApps = L10n.tr("Localizable", "RootView.myApps", fallback: "My Apps") + /// RootView + internal static let news = L10n.tr("Localizable", "RootView.news", fallback: "News") + /// Settings + internal static let settings = L10n.tr("Localizable", "RootView.settings", fallback: "Settings") + } + internal enum SettingsView { + /// Add to Siri... + internal static let addToSiri = L10n.tr("Localizable", "SettingsView.addToSiri", fallback: "Add to Siri...") + /// Background Refresh + internal static let backgroundRefresh = L10n.tr("Localizable", "SettingsView.backgroundRefresh", fallback: "Background Refresh") + /// Connect your Apple ID + internal static let connectAppleID = L10n.tr("Localizable", "SettingsView.connectAppleID", fallback: "Connect your Apple ID") + /// Credits + internal static let credits = L10n.tr("Localizable", "SettingsView.credits", fallback: "Credits") + /// Debug + internal static let debug = L10n.tr("Localizable", "SettingsView.debug", fallback: "Debug") + /// Debug Logging + internal static let debugLogging = L10n.tr("Localizable", "SettingsView.debugLogging", fallback: "Debug Logging") + /// Export Logs + internal static let exportLogs = L10n.tr("Localizable", "SettingsView.exportLogs", fallback: "Export Logs") + /// Refreshing Apps + internal static let refreshingApps = L10n.tr("Localizable", "SettingsView.refreshingApps", fallback: "Refreshing Apps") + /// Enable Background Refresh to automatically refresh apps in the background when connected to WiFi and with Wireguard active. + internal static let refreshingAppsFooter = L10n.tr("Localizable", "SettingsView.refreshingAppsFooter", fallback: "Enable Background Refresh to automatically refresh apps in the background when connected to WiFi and with Wireguard active.") + /// Reset Image Cache + internal static let resetImageCache = L10n.tr("Localizable", "SettingsView.resetImageCache", fallback: "Reset Image Cache") + /// SwiftUI Redesign + internal static let swiftUIRedesign = L10n.tr("Localizable", "SettingsView.swiftUIRedesign", fallback: "SwiftUI Redesign") + /// Switch to UIKit + internal static let switchToUIKit = L10n.tr("Localizable", "SettingsView.switchToUIKit", fallback: "Switch to UIKit") + /// Settings + internal static let title = L10n.tr("Localizable", "SettingsView.title", fallback: "Settings") + internal enum ConnectedAppleID { + /// E-Mail + internal static let eMail = L10n.tr("Localizable", "SettingsView.ConnectedAppleID.eMail", fallback: "E-Mail") + /// SettingsView + internal static let name = L10n.tr("Localizable", "SettingsView.ConnectedAppleID.name", fallback: "Name") + /// Sign Out + internal static let signOut = L10n.tr("Localizable", "SettingsView.ConnectedAppleID.signOut", fallback: "Sign Out") + /// Connected Apple ID + internal static let text = L10n.tr("Localizable", "SettingsView.ConnectedAppleID.text", fallback: "Connected Apple ID") + /// Type + internal static let type = L10n.tr("Localizable", "SettingsView.ConnectedAppleID.type", fallback: "Type") + internal enum Footer { + /// Your Apple ID is required to sign the apps you install with SideStore. + internal static let p1 = L10n.tr("Localizable", "SettingsView.ConnectedAppleID.Footer.p1", fallback: "Your Apple ID is required to sign the apps you install with SideStore.") + /// Your credentials are only sent to Apple's servers and are not accessible by the SideStore Team. Once successfully logged in, the login details are stored securely on your device. + internal static let p2 = L10n.tr("Localizable", "SettingsView.ConnectedAppleID.Footer.p2", fallback: "Your credentials are only sent to Apple's servers and are not accessible by the SideStore Team. Once successfully logged in, the login details are stored securely on your device.") + } + } + } + internal enum SourcesView { + /// Done + internal static let done = L10n.tr("Localizable", "SourcesView.done", fallback: "Done") + /// Remove + internal static let remove = L10n.tr("Localizable", "SourcesView.remove", fallback: "Remove") + /// SideStore has reviewed these sources to make sure they meet our safety standards. + internal static let reviewedText = L10n.tr("Localizable", "SourcesView.reviewedText", fallback: "SideStore has reviewed these sources to make sure they meet our safety standards.") + /// Sources + internal static let sources = L10n.tr("Localizable", "SourcesView.sources", fallback: "Sources") + /// SourcesView + internal static let sourcesDescription = L10n.tr("Localizable", "SourcesView.sourcesDescription", fallback: "Sources control what apps are available to download through SideStore.") + /// Trusted Sources + internal static let trustedSources = L10n.tr("Localizable", "SourcesView.trustedSources", fallback: "Trusted Sources") + } +} +// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length +// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces + +// MARK: - Implementation Details + +extension L10n { + private static func tr(_ table: String, _ key: String, _ args: CVarArg..., fallback value: String) -> String { + let format = BundleToken.bundle.localizedString(forKey: key, value: value, table: table) + return String(format: format, locale: Locale.current, arguments: args) + } +} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type diff --git a/AltStore/Helper/DateFormatterHelper.swift b/AltStore/Helper/DateFormatterHelper.swift new file mode 100644 index 00000000..b099e464 --- /dev/null +++ b/AltStore/Helper/DateFormatterHelper.swift @@ -0,0 +1,72 @@ +// +// DateFormatterHelper.swift +// SideStore +// +// Created by Fabian Thies on 20.11.22. +// Copyright © 2022 Fabian Thies. All rights reserved. +// + +import Foundation + +struct DateFormatterHelper { + + private static let appExpirationDateFormatter: DateComponentsFormatter = { + let dateComponentsFormatter = DateComponentsFormatter() + dateComponentsFormatter.zeroFormattingBehavior = [.pad] + dateComponentsFormatter.collapsesLargestUnit = false + return dateComponentsFormatter + }() + + private static let relativeDateFormatter: RelativeDateTimeFormatter = { + let formatter = RelativeDateTimeFormatter() + formatter.unitsStyle = .abbreviated + return formatter + }() + + private static let mediumDateFormatter: DateFormatter = { + let dateFormatter = DateFormatter() + dateFormatter.dateStyle = .medium + dateFormatter.timeStyle = .none + dateFormatter.doesRelativeDateFormatting = true + return dateFormatter + }() + + private static let timeFormatter: DateFormatter = { + let dateFormatter = DateFormatter() + dateFormatter.dateStyle = .none + dateFormatter.timeStyle = .short + return dateFormatter + }() + + + + static func string(forExpirationDate date: Date) -> String { + let startDate = Date() + let interval = date.timeIntervalSince(startDate) + guard interval > 0 else { + return "EXPIRED" + } + + if interval < (24 * 60 * 60) { + self.appExpirationDateFormatter.unitsStyle = .positional + self.appExpirationDateFormatter.allowedUnits = [.minute, .second] + } else { + self.appExpirationDateFormatter.unitsStyle = .full + self.appExpirationDateFormatter.allowedUnits = [.day] + } + + return self.appExpirationDateFormatter.string(from: startDate, to: date) ?? "" + } + + static func string(forRelativeDate date: Date, to referenceDate: Date = Date()) -> String { + self.relativeDateFormatter.localizedString(for: date, relativeTo: referenceDate) + } + + static func string(for date: Date) -> String { + self.mediumDateFormatter.string(from: date) + } + + static func timeString(for date: Date) -> String { + self.timeFormatter.string(from: date) + } +} diff --git a/AltStore/Helper/SideloadingManager.swift b/AltStore/Helper/SideloadingManager.swift new file mode 100644 index 00000000..09dfcfb4 --- /dev/null +++ b/AltStore/Helper/SideloadingManager.swift @@ -0,0 +1,254 @@ +// +// SideloadingManager.swift +// SideStore +// +// Created by Fabian Thies on 20.12.22. +// Copyright © 2022 SideStore. All rights reserved. +// + +import Foundation +import SwiftUI +import CoreData +import AltStoreCore +import CAltSign +import Roxas + +// TODO: Move this to the AppManager +class SideloadingManager { + class Context { + var fileURL: URL? + var application: ALTApplication? + var installedApp: InstalledApp? { + didSet { + self.installedAppContext = self.installedApp?.managedObjectContext + } + } + private var installedAppContext: NSManagedObjectContext? + var error: Error? + } + + + public static let shared = SideloadingManager() + + @Published + public var progress: Progress? + + private let operationQueue = OperationQueue() + + private init() {} + + + // TODO: Refactor & convert to async + func sideloadApp(at url: URL, completion: @escaping (Result) -> Void) { + self.progress = Progress.discreteProgress(totalUnitCount: 100) + + let temporaryDirectory = FileManager.default.uniqueTemporaryURL() + let unzippedAppDirectory = temporaryDirectory.appendingPathComponent("App") + + let context = Context() + + let downloadOperation: RSTAsyncBlockOperation? + + if url.isFileURL { + downloadOperation = nil + context.fileURL = url + self.progress?.totalUnitCount -= 20 + } else { + let downloadProgress = Progress.discreteProgress(totalUnitCount: 100) + + downloadOperation = RSTAsyncBlockOperation { (operation) in + let downloadTask = URLSession.shared.downloadTask(with: url) { (fileURL, response, error) in + do + { + let (fileURL, _) = try Result((fileURL, response), error).get() + + try FileManager.default.createDirectory(at: temporaryDirectory, withIntermediateDirectories: true, attributes: nil) + + let destinationURL = temporaryDirectory.appendingPathComponent("App.ipa") + try FileManager.default.moveItem(at: fileURL, to: destinationURL) + + context.fileURL = destinationURL + } + catch + { + context.error = error + } + operation.finish() + } + downloadProgress.addChild(downloadTask.progress, withPendingUnitCount: 100) + downloadTask.resume() + } + self.progress?.addChild(downloadProgress, withPendingUnitCount: 20) + } + + let unzipProgress = Progress.discreteProgress(totalUnitCount: 1) + let unzipAppOperation = BlockOperation { + do + { + if let error = context.error + { + throw error + } + + guard let fileURL = context.fileURL else { throw OperationError.invalidParameters } + defer { + try? FileManager.default.removeItem(at: fileURL) + } + + try FileManager.default.createDirectory(at: unzippedAppDirectory, withIntermediateDirectories: true, attributes: nil) + let unzippedApplicationURL = try FileManager.default.unzipAppBundle(at: fileURL, toDirectory: unzippedAppDirectory) + + guard let application = ALTApplication(fileURL: unzippedApplicationURL) else { throw OperationError.invalidApp } + context.application = application + + unzipProgress.completedUnitCount = 1 + } + catch + { + context.error = error + } + } + self.progress?.addChild(unzipProgress, withPendingUnitCount: 10) + + if let downloadOperation = downloadOperation + { + unzipAppOperation.addDependency(downloadOperation) + } + + let removeAppExtensionsProgress = Progress.discreteProgress(totalUnitCount: 1) + + let removeAppExtensionsOperation = RSTAsyncBlockOperation { [weak self] (operation) in + do + { + if let error = context.error + { + throw error + } + + guard let application = context.application else { throw OperationError.invalidParameters } + + DispatchQueue.main.async { + self?.removeAppExtensions(from: application) { (result) in + switch result + { + case .success: removeAppExtensionsProgress.completedUnitCount = 1 + case .failure(let error): context.error = error + } + operation.finish() + } + } + } + catch + { + context.error = error + operation.finish() + } + } + removeAppExtensionsOperation.addDependency(unzipAppOperation) + self.progress?.addChild(removeAppExtensionsProgress, withPendingUnitCount: 5) + + let installProgress = Progress.discreteProgress(totalUnitCount: 100) + let installAppOperation = RSTAsyncBlockOperation { (operation) in + do + { + if let error = context.error + { + throw error + } + + guard let application = context.application else { throw OperationError.invalidParameters } + + let group = AppManager.shared.install(application, presentingViewController: nil) { (result) in + switch result + { + case .success(let installedApp): context.installedApp = installedApp + case .failure(let error): context.error = error + } + operation.finish() + } + installProgress.addChild(group.progress, withPendingUnitCount: 100) + } + catch + { + context.error = error + operation.finish() + } + } + installAppOperation.completionBlock = { + try? FileManager.default.removeItem(at: temporaryDirectory) + + DispatchQueue.main.async { + self.progress = nil + + switch Result(context.installedApp, context.error) + { + case .success(let app): + completion(.success(())) + + app.managedObjectContext?.perform { + print("Successfully installed app:", app.bundleIdentifier) + } + + case .failure(OperationError.cancelled): + completion(.failure((OperationError.cancelled))) + + case .failure(let error): + NotificationManager.shared.reportError(error: error) + + completion(.failure(error)) + } + } + } + self.progress?.addChild(installProgress, withPendingUnitCount: 65) + installAppOperation.addDependency(removeAppExtensionsOperation) + + let operations = [downloadOperation, unzipAppOperation, removeAppExtensionsOperation, installAppOperation].compactMap { $0 } + self.operationQueue.addOperations(operations, waitUntilFinished: false) + } + + + // TODO: Refactor + private func removeAppExtensions(from application: ALTApplication, completion: @escaping (Result) -> Void) + { + guard !application.appExtensions.isEmpty else { return completion(.success(())) } + + let firstSentence: String + + if UserDefaults.standard.activeAppLimitIncludesExtensions + { + firstSentence = NSLocalizedString("Non-developer Apple IDs are limited to 3 active apps and app extensions.", comment: "") + } + else + { + firstSentence = NSLocalizedString("Non-developer Apple IDs are limited to creating 10 App IDs per week.", comment: "") + } + + let message = firstSentence + " " + NSLocalizedString("Would you like to remove this app's extensions so they don't count towards your limit?", comment: "") + + let alertController = UIAlertController(title: NSLocalizedString("App Contains Extensions", comment: ""), message: message, preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style, handler: { (action) in + completion(.failure(OperationError.cancelled)) + })) + alertController.addAction(UIAlertAction(title: NSLocalizedString("Keep App Extensions", comment: ""), style: .default) { (action) in + completion(.success(())) + }) + alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove App Extensions", comment: ""), style: .destructive) { (action) in + do + { + for appExtension in application.appExtensions + { + try FileManager.default.removeItem(at: appExtension.fileURL) + } + + completion(.success(())) + } + catch + { + completion(.failure(error)) + } + }) + + let rootViewController = UIApplication.shared.keyWindow?.rootViewController + rootViewController?.present(alertController, animated: true, completion: nil) + } +} diff --git a/AltStore/Info.plist b/AltStore/Info.plist index a54f4f93..7c12f901 100644 --- a/AltStore/Info.plist +++ b/AltStore/Info.plist @@ -204,6 +204,8 @@ + UISupportsDocumentBrowser + UIFileSharingEnabled diff --git a/AltStore/LaunchViewController.swift b/AltStore/LaunchViewController.swift index 9c0abd06..74f9b088 100644 --- a/AltStore/LaunchViewController.swift +++ b/AltStore/LaunchViewController.swift @@ -7,6 +7,7 @@ // import UIKit +import SwiftUI import Roxas import EmotionalDamage import minimuxer @@ -42,7 +43,10 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg { defer { // Create destinationViewController now so view controllers can register for receiving Notifications. - self.destinationViewController = self.storyboard!.instantiateViewController(withIdentifier: "tabBarController") as! TabBarController +// self.destinationViewController = self.storyboard!.instantiateViewController(withIdentifier: "tabBarController") as! TabBarController + let rootView = RootView() + .environment(\.managedObjectContext, DatabaseManager.shared.viewContext) + self.destinationViewController = UIHostingController(rootView: rootView) } super.viewDidLoad() } @@ -50,15 +54,29 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(true) #if !targetEnvironment(simulator) + if !UserDefaults.standard.onboardingComplete { + self.showOnboarding() + return + } + start_em_proxy(bind_addr: Consts.Proxy.serverURL) guard let pf = fetchPairingFile() else { - displayError("Device pairing file not found.") + self.showOnboarding(step: .pairing) return } start_minimuxer_threads(pf) #endif } + + func showOnboarding(step: OnboardingView.OnboardingStep = .welcome) { + let onboardingView = OnboardingView(onDismiss: { self.dismiss(animated: true) }, currentStep: step) + .environment(\.managedObjectContext, DatabaseManager.shared.viewContext) + let navigationController = UINavigationController(rootViewController: UIHostingController(rootView: onboardingView)) + navigationController.isNavigationBarHidden = true + navigationController.isModalInPresentation = true + self.present(navigationController, animated: true) + } func fetchPairingFile() -> String? { let filename = "ALTPairingFile.mobiledevicepairing" @@ -155,6 +173,7 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg try! FileManager.default.removeItem(at: FileManager.default.documentsDirectory.appendingPathComponent("\(pairingFileName)")) displayError("minimuxer failed to start, please restart SideStore. \((error as? LocalizedError)?.failureReason ?? "UNKNOWN ERROR!!!!!! REPORT TO GITHUB ISSUES!")") } + set_debug(UserDefaults.shared.isDebugLoggingEnabled) start_auto_mounter(documentsDirectory) } } diff --git a/AltStore/Manager/NotificationManager.swift b/AltStore/Manager/NotificationManager.swift new file mode 100644 index 00000000..032d03e1 --- /dev/null +++ b/AltStore/Manager/NotificationManager.swift @@ -0,0 +1,80 @@ +// +// NotificationManager.swift +// SideStore +// +// Created by Fabian Thies on 21.11.22. +// Copyright © 2022 Fabian Thies. All rights reserved. +// + +import SwiftUI +import AltStoreCore + +class NotificationManager: ObservableObject { + + struct Notification: Identifiable { + let id: UUID + let title: String + let message: String? + } + + static let shared = NotificationManager() + + @Published + var notifications: [UUID: Notification] = [:] + + private init() {} + + func reportError(error: Error) { + if case OperationError.cancelled = error { + // Ignore + return + } + + var error = error as NSError + var underlyingError = error.underlyingError + + if + let unwrappedUnderlyingError = underlyingError, + error.domain == AltServerErrorDomain && error.code == ALTServerError.Code.underlyingError.rawValue + { + // Treat underlyingError as the primary error. + + error = unwrappedUnderlyingError as NSError + underlyingError = nil + } + + let text: String + let detailText: String? + + if let failure = error.localizedFailure + { + text = failure + detailText = error.localizedFailureReason ?? error.localizedRecoverySuggestion ?? underlyingError?.localizedDescription ?? error.localizedDescription + } + else if let reason = error.localizedFailureReason + { + text = reason + detailText = error.localizedRecoverySuggestion ?? underlyingError?.localizedDescription + } + else + { + text = error.localizedDescription + detailText = underlyingError?.localizedDescription ?? error.localizedRecoverySuggestion + } + + self.showNotification(title: text, detailText: detailText) + } + + func showNotification(title: String, detailText: String? = nil) { + let notificationId = UUID() + + DispatchQueue.main.async { + self.notifications[notificationId] = Notification(id: notificationId, title: title, message: detailText) + } + + let dismissWorkItem = DispatchWorkItem { + self.notifications.removeValue(forKey: notificationId) + } + DispatchQueue.main.asyncAfter(deadline: .now().advanced(by: .seconds(5)), execute: dismissWorkItem) + } +} diff --git a/AltStore/Manager/OutputCapturer.swift b/AltStore/Manager/OutputCapturer.swift new file mode 100644 index 00000000..e0af952d --- /dev/null +++ b/AltStore/Manager/OutputCapturer.swift @@ -0,0 +1,56 @@ +// +// OutputCapturer.swift +// SideStore +// +// Created by Fabian Thies on 12.02.23. +// Copyright © 2023 SideStore. All rights reserved. +// + +import Foundation +import LocalConsole + +class OutputCapturer { + + public static let shared = OutputCapturer() + + private let consoleManager = LCManager.shared + + private var inputPipe = Pipe() + private var errorPipe = Pipe() + private var outputPipe = Pipe() + + private init() { + // Setup pipe file handlers + self.inputPipe.fileHandleForReading.readabilityHandler = { [weak self] fileHandle in + self?.handle(data: fileHandle.availableData) + } + self.errorPipe.fileHandleForReading.readabilityHandler = { [weak self] fileHandle in + self?.handle(data: fileHandle.availableData, isError: true) + } + + // Keep STDOUT + dup2(STDOUT_FILENO, self.outputPipe.fileHandleForWriting.fileDescriptor) + + // Intercept STDOUT and STDERR + dup2(self.inputPipe.fileHandleForWriting.fileDescriptor, STDOUT_FILENO) + dup2(self.errorPipe.fileHandleForWriting.fileDescriptor, STDERR_FILENO) + } + + deinit { + try? self.inputPipe.fileHandleForReading.close() + try? self.errorPipe.fileHandleForReading.close() + } + + private func handle(data: Data, isError: Bool = false) { + // Write output to STDOUT + self.outputPipe.fileHandleForWriting.write(data) + + guard let string = String(data: data, encoding: .utf8) else { + return + } + + DispatchQueue.main.async { + self.consoleManager.print(string) + } + } +} diff --git a/AltStore/Operations/AuthenticationOperation.swift b/AltStore/Operations/AuthenticationOperation.swift index af510de2..5cd8e5ca 100644 --- a/AltStore/Operations/AuthenticationOperation.swift +++ b/AltStore/Operations/AuthenticationOperation.swift @@ -7,6 +7,7 @@ // import Foundation +import SwiftUI import Roxas import Network @@ -39,17 +40,17 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A let context: AuthenticatedOperationContext private weak var presentingViewController: UIViewController? - - private lazy var navigationController: UINavigationController = { - let navigationController = self.storyboard.instantiateViewController(withIdentifier: "navigationController") as! UINavigationController - if #available(iOS 13.0, *) - { - navigationController.isModalInPresentation = true - } - return navigationController - }() - - private lazy var storyboard = UIStoryboard(name: "Authentication", bundle: nil) + +// private lazy var navigationController: UINavigationController = { +// let navigationController = self.storyboard.instantiateViewController(withIdentifier: "navigationController") as! UINavigationController +// if #available(iOS 13.0, *) +// { +// navigationController.isModalInPresentation = true +// } +// return navigationController +// }() +// +// private lazy var storyboard = UIStoryboard(name: "Authentication", bundle: nil) private var appleIDEmailAddress: String? private var appleIDPassword: String? @@ -266,7 +267,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A super.finish(result) DispatchQueue.main.async { - self.navigationController.dismiss(animated: true, completion: nil) +// self.navigationController.dismiss(animated: true, completion: nil) + self.dismiss() } } } @@ -276,7 +278,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A super.finish(result) DispatchQueue.main.async { - self.navigationController.dismiss(animated: true, completion: nil) +// self.navigationController.dismiss(animated: true, completion: nil) + self.dismiss() } } } @@ -287,25 +290,33 @@ private extension AuthenticationOperation { func present(_ viewController: UIViewController) -> Bool { - guard let presentingViewController = self.presentingViewController else { return false } - - self.navigationController.view.tintColor = .white - - if self.navigationController.viewControllers.isEmpty - { - guard presentingViewController.presentedViewController == nil else { return false } - - self.navigationController.setViewControllers([viewController], animated: false) - presentingViewController.present(self.navigationController, animated: true, completion: nil) - } - else - { - viewController.navigationItem.leftBarButtonItem = nil - self.navigationController.pushViewController(viewController, animated: true) - } + UIApplication.shared.keyWindow?.rootViewController?.present(viewController, animated: true) +// guard let presentingViewController = self.presentingViewController else { return false } +// +// self.navigationController.view.tintColor = .white +// +// if self.navigationController.viewControllers.isEmpty +// { +// guard presentingViewController.presentedViewController == nil else { return false } +// +// self.navigationController.setViewControllers([viewController], animated: false) +// presentingViewController.present(self.navigationController, animated: true, completion: nil) +// } +// else +// { +// viewController.navigationItem.leftBarButtonItem = nil +// self.navigationController.pushViewController(viewController, animated: true) +// } return true } + + func dismiss() { + if let presentingViewController { + presentingViewController.dismiss(animated: true) + } +// UIApplication.shared.keyWindow?.rootViewController?.presentedViewController?.dismiss(animated: true) + } } private extension AuthenticationOperation @@ -315,29 +326,29 @@ private extension AuthenticationOperation func authenticate() { DispatchQueue.main.async { - let authenticationViewController = self.storyboard.instantiateViewController(withIdentifier: "authenticationViewController") as! AuthenticationViewController - authenticationViewController.authenticationHandler = { (appleID, password, completionHandler) in - self.authenticate(appleID: appleID, password: password) { (result) in - completionHandler(result) + let viewController = UIHostingController(rootView: NavigationView { + ConnectAppleIDView { appleID, password, completionHandler in + self.authenticate(appleID: appleID, password: password) { (result) in + completionHandler(result) + } + } completionHandler: { result in + if let (account, session, password) = result + { + // We presented the Auth UI and the user signed in. + // In this case, we'll assume we should show the instructions again. + self.shouldShowInstructions = true + + self.appleIDPassword = password + completionHandler(.success((account, session))) + } + else + { + completionHandler(.failure(OperationError.cancelled)) + } } - } - authenticationViewController.completionHandler = { (result) in - if let (account, session, password) = result - { - // We presented the Auth UI and the user signed in. - // In this case, we'll assume we should show the instructions again. - self.shouldShowInstructions = true - - self.appleIDPassword = password - completionHandler(.success((account, session))) - } - else - { - completionHandler(.failure(OperationError.cancelled)) - } - } + }.navigationViewStyle(StackNavigationViewStyle())) - if !self.present(authenticationViewController) + if !self.present(viewController) { completionHandler(.failure(OperationError.notAuthenticated)) } @@ -379,8 +390,8 @@ private extension AuthenticationOperation case .success(let anisetteData): let verificationHandler: ((@escaping (String?) -> Void) -> Void)? - if let presentingViewController = self.presentingViewController - { +// if let presentingViewController = self.presentingViewController +// { verificationHandler = { (completionHandler) in DispatchQueue.main.async { let alertController = UIAlertController(title: NSLocalizedString("Please enter the 6-digit verification code that was sent to your Apple devices.", comment: ""), message: nil, preferredStyle: .alert) @@ -406,22 +417,23 @@ private extension AuthenticationOperation completionHandler(nil) }) - if self.navigationController.presentingViewController != nil - { - self.navigationController.present(alertController, animated: true, completion: nil) - } - else - { - presentingViewController.present(alertController, animated: true, completion: nil) + let keyWindow = UIApplication.shared.windows.filter { $0.isKeyWindow }.first + + if var topController = keyWindow?.rootViewController { + while let presentedViewController = topController.presentedViewController { + topController = presentedViewController + } + + topController.present(alertController, animated: true, completion: nil) } } } - } - else - { - // No view controller to present security code alert, so don't provide verificationHandler. - verificationHandler = nil - } +// } +// else +// { +// // No view controller to present security code alert, so don't provide verificationHandler. +// verificationHandler = nil +// } ALTAppleAPI.shared.authenticate(appleID: appleID, password: password, anisetteData: anisetteData, verificationHandler: verificationHandler) { (account, session, error) in @@ -452,15 +464,15 @@ private extension AuthenticationOperation } } else { DispatchQueue.main.async { - let selectTeamViewController = self.storyboard.instantiateViewController(withIdentifier: "selectTeamViewController") as! SelectTeamViewController - - selectTeamViewController.teams = teams - selectTeamViewController.completionHandler = completionHandler - - if !self.present(selectTeamViewController) - { - return completionHandler(.failure(AuthenticationError.noTeam)) - } +// let selectTeamViewController = self.storyboard.instantiateViewController(withIdentifier: "selectTeamViewController") as! SelectTeamViewController +// +// selectTeamViewController.teams = teams +// selectTeamViewController.completionHandler = completionHandler +// +// if !self.present(selectTeamViewController) +// { +// return completionHandler(.failure(AuthenticationError.noTeam)) +// } } } } @@ -642,20 +654,21 @@ private extension AuthenticationOperation func showInstructionsIfNecessary(completionHandler: @escaping (Bool) -> Void) { - guard self.shouldShowInstructions else { return completionHandler(false) } - - DispatchQueue.main.async { - let instructionsViewController = self.storyboard.instantiateViewController(withIdentifier: "instructionsViewController") as! InstructionsViewController - instructionsViewController.showsBottomButton = true - instructionsViewController.completionHandler = { - completionHandler(true) - } - - if !self.present(instructionsViewController) - { - completionHandler(false) - } - } + return completionHandler(false) +// guard self.shouldShowInstructions else { return completionHandler(false) } +// +// DispatchQueue.main.async { +// let instructionsViewController = self.storyboard.instantiateViewController(withIdentifier: "instructionsViewController") as! InstructionsViewController +// instructionsViewController.showsBottomButton = true +// instructionsViewController.completionHandler = { +// completionHandler(true) +// } +// +// if !self.present(instructionsViewController) +// { +// completionHandler(false) +// } +// } } func showRefreshScreenIfNecessary(signer: ALTSigner, session: ALTAppleAPISession, completionHandler: @escaping (Bool) -> Void) diff --git a/AltStore/Operations/FetchTrustedSourcesOperation.swift b/AltStore/Operations/FetchTrustedSourcesOperation.swift index 9fced831..ac58069b 100644 --- a/AltStore/Operations/FetchTrustedSourcesOperation.swift +++ b/AltStore/Operations/FetchTrustedSourcesOperation.swift @@ -10,11 +10,7 @@ import Foundation private extension URL { - #if STAGING static let trustedSources = URL(string: "https://raw.githubusercontent.com/SideStore/SideStore/develop/trustedapps.json")! - #else - static let trustedSources = URL(string: "https://raw.githubusercontent.com/SideStore/SideStore/develop/trustedapps.json")! - #endif } extension FetchTrustedSourcesOperation diff --git a/AltStore/Operations/OperationError.swift b/AltStore/Operations/OperationError.swift index 7829502a..0398ce4d 100644 --- a/AltStore/Operations/OperationError.swift +++ b/AltStore/Operations/OperationError.swift @@ -163,4 +163,5 @@ extension MinimuxerError: LocalizedError { fileprivate func setArgument(name: String) -> String { return String(format: NSLocalizedString("Cannot set %@ on the device.", comment: ""), name) } + return error as! OperationError } diff --git a/AltStore/Operations/ResignAppOperation.swift b/AltStore/Operations/ResignAppOperation.swift index 59336d86..a353594f 100644 --- a/AltStore/Operations/ResignAppOperation.swift +++ b/AltStore/Operations/ResignAppOperation.swift @@ -8,6 +8,8 @@ import Foundation import Roxas +import SwiftUI +import ZIPFoundation import AltStoreCore import AltSign @@ -15,6 +17,9 @@ import AltSign @objc(ResignAppOperation) final class ResignAppOperation: ResultOperation { + static var skipResign: Bool = false + static var skipResignBinding: Binding { Binding(get: { skipResign }, set: { skipResign = $0 }) } + let context: InstallAppOperationContext init(context: InstallAppOperationContext) @@ -50,6 +55,23 @@ final class ResignAppOperation: ResultOperation let prepareAppBundleProgress = self.prepareAppBundle(for: app, profiles: profiles) { (result) in guard let appBundleURL = self.process(result) else { return } + if ResignAppOperation.skipResign { + print("⚠️ WARNING: Skipping resign. Unless you correctly resigned the IPA before installing it, things will not work! Also, this might crash SideStore. You have been warned!") + let ipaFile = self.context.temporaryDirectory.appendingPathComponent("App.ipa") + let archive = Archive(url: ipaFile, accessMode: .create)! + for case let fileURL as URL in FileManager.default.enumerator(at: appBundleURL, includingPropertiesForKeys: [])! { + let relative = fileURL.description.replacingOccurrences(of: appBundleURL.description, with: "").removingPercentEncoding! + try! archive.addEntry(with: "Payload/App.app\(relative)", fileURL: fileURL) + } + let destinationURL = InstalledApp.refreshedIPAURL(for: app) + try! FileManager.default.copyItem(at: ipaFile, to: destinationURL, shouldReplace: true) + + // Use appBundleURL since we need an app bundle, not .ipa. + let resignedApplication = ALTApplication(fileURL: appBundleURL)! + self.finish(.success(resignedApplication)) + return + } + print("Resigning App:", self.context.bundleIdentifier) // Resign app bundle diff --git a/AltStore/Protocols/Filterable.swift b/AltStore/Protocols/Filterable.swift new file mode 100644 index 00000000..b5051799 --- /dev/null +++ b/AltStore/Protocols/Filterable.swift @@ -0,0 +1,23 @@ +// +// Filterable.swift +// SideStore +// +// Created by Fabian Thies on 01.12.22. +// Copyright © 2022 SideStore. All rights reserved. +// + +import Foundation + +protocol Filterable { + func matches(_ searchText: String) -> Bool +} + +extension Collection where Element: Filterable { + func matches(_ searchText: String) -> Bool { + self.contains(where: { $0.matches(searchText) }) + } + + func items(matching searchText: String) -> [Element] { + self.filter { $0.matches(searchText) } + } +} diff --git a/AltStore/Protocols/NavigationTab.swift b/AltStore/Protocols/NavigationTab.swift new file mode 100644 index 00000000..ca4e83a9 --- /dev/null +++ b/AltStore/Protocols/NavigationTab.swift @@ -0,0 +1,22 @@ +// +// NavigationTab.swift +// SideStoreUI +// +// Created by Fabian Thies on 18.11.22. +// Copyright © 2022 Fabian Thies. All rights reserved. +// + +import Foundation +import SFSafeSymbols + +protocol NavigationTab: RawRepresentable, Identifiable, CaseIterable, Hashable where RawValue == Int { + static var defaultTab: Self { get } + var displaySymbol: SFSymbol { get } + var displayName: String { get } +} + +extension NavigationTab { + var id: Int { + self.rawValue + } +} diff --git a/AltStore/Protocols/ViewModel.swift b/AltStore/Protocols/ViewModel.swift new file mode 100644 index 00000000..35a69eeb --- /dev/null +++ b/AltStore/Protocols/ViewModel.swift @@ -0,0 +1,11 @@ +// +// ViewModel.swift +// SideStoreUI +// +// Created by Fabian Thies on 18.11.22. +// Copyright © 2022 Fabian Thies. All rights reserved. +// + +import SwiftUI + +protocol ViewModel: ObservableObject {} diff --git a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/100.png b/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/100.png deleted file mode 100644 index a7494326..00000000 Binary files a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/100.png and /dev/null differ diff --git a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/1024.png b/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/1024.png deleted file mode 100644 index 5e20d8e0..00000000 Binary files a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/1024.png and /dev/null differ diff --git a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/114.png b/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/114.png deleted file mode 100644 index e6736d01..00000000 Binary files a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/114.png and /dev/null differ diff --git a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/120.png b/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/120.png deleted file mode 100644 index de2f546f..00000000 Binary files a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/120.png and /dev/null differ diff --git a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/144.png b/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/144.png deleted file mode 100644 index 5b4d2b2d..00000000 Binary files a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/144.png and /dev/null differ diff --git a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/152.png b/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/152.png deleted file mode 100644 index b0722715..00000000 Binary files a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/152.png and /dev/null differ diff --git a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/167.png b/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/167.png deleted file mode 100644 index a6a34cb9..00000000 Binary files a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/167.png and /dev/null differ diff --git a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/180.png b/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/180.png deleted file mode 100644 index e14fee11..00000000 Binary files a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/180.png and /dev/null differ diff --git a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/20.png b/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/20.png deleted file mode 100644 index 7188b707..00000000 Binary files a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/20.png and /dev/null differ diff --git a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/29.png b/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/29.png deleted file mode 100644 index 6ef27d9f..00000000 Binary files a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/29.png and /dev/null differ diff --git a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/40.png b/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/40.png deleted file mode 100644 index f6477c38..00000000 Binary files a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/40.png and /dev/null differ diff --git a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/50.png b/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/50.png deleted file mode 100644 index 624a48a8..00000000 Binary files a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/50.png and /dev/null differ diff --git a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/57.png b/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/57.png deleted file mode 100644 index 4e9e6cfa..00000000 Binary files a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/57.png and /dev/null differ diff --git a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/58.png b/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/58.png deleted file mode 100644 index ae186abe..00000000 Binary files a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/58.png and /dev/null differ diff --git a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/60.png b/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/60.png deleted file mode 100644 index e2343134..00000000 Binary files a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/60.png and /dev/null differ diff --git a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/72.png b/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/72.png deleted file mode 100644 index 55c06905..00000000 Binary files a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/72.png and /dev/null differ diff --git a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/76.png b/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/76.png deleted file mode 100644 index e6294437..00000000 Binary files a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/76.png and /dev/null differ diff --git a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/80.png b/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/80.png deleted file mode 100644 index d4c829de..00000000 Binary files a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/80.png and /dev/null differ diff --git a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/87.png b/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/87.png deleted file mode 100644 index 5acfa839..00000000 Binary files a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/87.png and /dev/null differ diff --git a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 65b74d7e..00000000 --- a/AltStore/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1 +0,0 @@ -{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"}]} \ No newline at end of file diff --git a/AltStore/Resources/Assets.xcassets/Colors/AccentColor.colorset/Contents.json b/AltStore/Resources/Assets.xcassets/Colors/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..b39fa43c --- /dev/null +++ b/AltStore/Resources/Assets.xcassets/Colors/AccentColor.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFA", + "green" : "0x05", + "red" : "0xA4" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AltStore/Resources/Assets.xcassets/Riley.imageset/Contents.json b/AltStore/Resources/Assets.xcassets/Honeydew-image.imageset/Contents.json similarity index 77% rename from AltStore/Resources/Assets.xcassets/Riley.imageset/Contents.json rename to AltStore/Resources/Assets.xcassets/Honeydew-image.imageset/Contents.json index 6a3c352c..99a33a33 100644 --- a/AltStore/Resources/Assets.xcassets/Riley.imageset/Contents.json +++ b/AltStore/Resources/Assets.xcassets/Honeydew-image.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "riley.jpg", + "filename" : "icon-152.png", "idiom" : "universal" } ], diff --git a/AltStore/Resources/Assets.xcassets/Honeydew-image.imageset/icon-152.png b/AltStore/Resources/Assets.xcassets/Honeydew-image.imageset/icon-152.png new file mode 100644 index 00000000..cfda6543 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Honeydew-image.imageset/icon-152.png differ diff --git a/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/Contents.json b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/Contents.json new file mode 100644 index 00000000..5d0bd4d7 --- /dev/null +++ b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "filename" : "icon-40.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "icon-60.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "icon-58.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "icon-87.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "icon-80.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "icon-120.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "icon-120.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "icon-180.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "icon-20.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "icon-40.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "icon-29.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "icon-58.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "icon-40.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "icon-80.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "icon-76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "icon-152.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "icon-167.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "icon-1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-1024.png b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-1024.png new file mode 100644 index 00000000..5cdc3b3b Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-1024.png differ diff --git a/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-120.png b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-120.png new file mode 100644 index 00000000..995d92f2 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-120.png differ diff --git a/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-152.png b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-152.png new file mode 100644 index 00000000..cfda6543 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-152.png differ diff --git a/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-167.png b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-167.png new file mode 100644 index 00000000..6af92fad Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-167.png differ diff --git a/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-180.png b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-180.png new file mode 100644 index 00000000..747c0186 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-180.png differ diff --git a/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-20.png b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-20.png new file mode 100644 index 00000000..e666e9f7 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-20.png differ diff --git a/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-29.png b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-29.png new file mode 100644 index 00000000..5ad0e88b Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-29.png differ diff --git a/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-40.png b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-40.png new file mode 100644 index 00000000..b1090071 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-40.png differ diff --git a/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-58.png b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-58.png new file mode 100644 index 00000000..1b4eee2d Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-58.png differ diff --git a/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-60.png b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-60.png new file mode 100644 index 00000000..0fc8b79e Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-60.png differ diff --git a/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-76.png b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-76.png new file mode 100644 index 00000000..0bffba50 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-76.png differ diff --git a/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-80.png b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-80.png new file mode 100644 index 00000000..5b402fb2 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-80.png differ diff --git a/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-87.png b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-87.png new file mode 100644 index 00000000..e275037a Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Honeydew.appiconset/icon-87.png differ diff --git a/AltStore/Resources/Assets.xcassets/Shane.imageset/Contents.json b/AltStore/Resources/Assets.xcassets/Midnight-image.imageset/Contents.json similarity index 77% rename from AltStore/Resources/Assets.xcassets/Shane.imageset/Contents.json rename to AltStore/Resources/Assets.xcassets/Midnight-image.imageset/Contents.json index 4b20453d..99a33a33 100644 --- a/AltStore/Resources/Assets.xcassets/Shane.imageset/Contents.json +++ b/AltStore/Resources/Assets.xcassets/Midnight-image.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "shane.jpeg", + "filename" : "icon-152.png", "idiom" : "universal" } ], diff --git a/AltStore/Resources/Assets.xcassets/Midnight-image.imageset/icon-152.png b/AltStore/Resources/Assets.xcassets/Midnight-image.imageset/icon-152.png new file mode 100644 index 00000000..901be966 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Midnight-image.imageset/icon-152.png differ diff --git a/AltStore/Resources/Assets.xcassets/Midnight.appiconset/Contents.json b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/Contents.json new file mode 100644 index 00000000..5d0bd4d7 --- /dev/null +++ b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "filename" : "icon-40.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "icon-60.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "icon-58.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "icon-87.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "icon-80.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "icon-120.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "icon-120.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "icon-180.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "icon-20.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "icon-40.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "icon-29.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "icon-58.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "icon-40.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "icon-80.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "icon-76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "icon-152.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "icon-167.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "icon-1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-1024.png b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-1024.png new file mode 100644 index 00000000..2e8ea785 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-1024.png differ diff --git a/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-120.png b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-120.png new file mode 100644 index 00000000..6180d258 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-120.png differ diff --git a/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-152.png b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-152.png new file mode 100644 index 00000000..901be966 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-152.png differ diff --git a/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-167.png b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-167.png new file mode 100644 index 00000000..2eda7a55 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-167.png differ diff --git a/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-180.png b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-180.png new file mode 100644 index 00000000..9abc3042 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-180.png differ diff --git a/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-20.png b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-20.png new file mode 100644 index 00000000..dcf1ce76 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-20.png differ diff --git a/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-29.png b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-29.png new file mode 100644 index 00000000..70aa5e6d Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-29.png differ diff --git a/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-40.png b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-40.png new file mode 100644 index 00000000..a45bd88c Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-40.png differ diff --git a/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-58.png b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-58.png new file mode 100644 index 00000000..8daecb50 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-58.png differ diff --git a/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-60.png b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-60.png new file mode 100644 index 00000000..c129d510 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-60.png differ diff --git a/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-76.png b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-76.png new file mode 100644 index 00000000..3fdc87ed Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-76.png differ diff --git a/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-80.png b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-80.png new file mode 100644 index 00000000..74b3362b Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-80.png differ diff --git a/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-87.png b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-87.png new file mode 100644 index 00000000..233de0d2 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Midnight.appiconset/icon-87.png differ diff --git a/AltStore/Resources/Assets.xcassets/Neon-image.imageset/Contents.json b/AltStore/Resources/Assets.xcassets/Neon-image.imageset/Contents.json new file mode 100644 index 00000000..99a33a33 --- /dev/null +++ b/AltStore/Resources/Assets.xcassets/Neon-image.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon-152.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AltStore/Resources/Assets.xcassets/Neon-image.imageset/icon-152.png b/AltStore/Resources/Assets.xcassets/Neon-image.imageset/icon-152.png new file mode 100644 index 00000000..ac9798ce Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Neon-image.imageset/icon-152.png differ diff --git a/AltStore/Resources/Assets.xcassets/Neon.appiconset/Contents.json b/AltStore/Resources/Assets.xcassets/Neon.appiconset/Contents.json new file mode 100644 index 00000000..5d0bd4d7 --- /dev/null +++ b/AltStore/Resources/Assets.xcassets/Neon.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "filename" : "icon-40.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "icon-60.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "icon-58.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "icon-87.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "icon-80.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "icon-120.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "icon-120.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "icon-180.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "icon-20.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "icon-40.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "icon-29.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "icon-58.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "icon-40.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "icon-80.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "icon-76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "icon-152.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "icon-167.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "icon-1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-1024.png b/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-1024.png new file mode 100644 index 00000000..9a166f7c Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-1024.png differ diff --git a/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-120.png b/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-120.png new file mode 100644 index 00000000..d4f31d68 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-120.png differ diff --git a/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-152.png b/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-152.png new file mode 100644 index 00000000..ac9798ce Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-152.png differ diff --git a/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-167.png b/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-167.png new file mode 100644 index 00000000..96c31c53 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-167.png differ diff --git a/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-180.png b/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-180.png new file mode 100644 index 00000000..f9f55b1e Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-180.png differ diff --git a/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-20.png b/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-20.png new file mode 100644 index 00000000..31163399 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-20.png differ diff --git a/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-29.png b/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-29.png new file mode 100644 index 00000000..d8d61d2d Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-29.png differ diff --git a/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-40.png b/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-40.png new file mode 100644 index 00000000..ff01b325 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-40.png differ diff --git a/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-58.png b/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-58.png new file mode 100644 index 00000000..06563a9d Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-58.png differ diff --git a/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-60.png b/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-60.png new file mode 100644 index 00000000..b344678f Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-60.png differ diff --git a/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-76.png b/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-76.png new file mode 100644 index 00000000..d3547acc Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-76.png differ diff --git a/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-80.png b/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-80.png new file mode 100644 index 00000000..b6195cf7 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-80.png differ diff --git a/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-87.png b/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-87.png new file mode 100644 index 00000000..1b6ae021 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Neon.appiconset/icon-87.png differ diff --git a/AltStore/Resources/Assets.xcassets/Riley.imageset/riley.jpg b/AltStore/Resources/Assets.xcassets/Riley.imageset/riley.jpg deleted file mode 100644 index 1257d376..00000000 Binary files a/AltStore/Resources/Assets.xcassets/Riley.imageset/riley.jpg and /dev/null differ diff --git a/AltStore/Resources/Assets.xcassets/Shane.imageset/shane.jpeg b/AltStore/Resources/Assets.xcassets/Shane.imageset/shane.jpeg deleted file mode 100644 index 037f4061..00000000 Binary files a/AltStore/Resources/Assets.xcassets/Shane.imageset/shane.jpeg and /dev/null differ diff --git a/AltStore/Resources/Assets.xcassets/Sky-image.imageset/Contents.json b/AltStore/Resources/Assets.xcassets/Sky-image.imageset/Contents.json new file mode 100644 index 00000000..99a33a33 --- /dev/null +++ b/AltStore/Resources/Assets.xcassets/Sky-image.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon-152.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AltStore/Resources/Assets.xcassets/Sky-image.imageset/icon-152.png b/AltStore/Resources/Assets.xcassets/Sky-image.imageset/icon-152.png new file mode 100644 index 00000000..9fa45cc7 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Sky-image.imageset/icon-152.png differ diff --git a/AltStore/Resources/Assets.xcassets/Sky.appiconset/Contents.json b/AltStore/Resources/Assets.xcassets/Sky.appiconset/Contents.json new file mode 100644 index 00000000..8702438f --- /dev/null +++ b/AltStore/Resources/Assets.xcassets/Sky.appiconset/Contents.json @@ -0,0 +1,114 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "icon-20.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "icon-40.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "icon-29.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "icon-58.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "icon-40.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "icon-80.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "icon-76.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "icon-152.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "icon-167.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "icon-40.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "icon-60.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "icon-58.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "icon-87.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "icon-80.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "icon-120.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "icon-120.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "icon-180.png", + "scale" : "3x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "icon-1024.png", + "scale" : "1x" + } + ], + "author" : "Iconizer", + "version" : "2020.11.0" +} \ No newline at end of file diff --git a/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-1024.png b/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-1024.png new file mode 100644 index 00000000..2d42e6fc Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-1024.png differ diff --git a/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-120.png b/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-120.png new file mode 100644 index 00000000..42d5ea7e Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-120.png differ diff --git a/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-152.png b/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-152.png new file mode 100644 index 00000000..9fa45cc7 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-152.png differ diff --git a/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-167.png b/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-167.png new file mode 100644 index 00000000..2d57dd3a Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-167.png differ diff --git a/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-180.png b/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-180.png new file mode 100644 index 00000000..d207e191 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-180.png differ diff --git a/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-20.png b/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-20.png new file mode 100644 index 00000000..91ca6f98 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-20.png differ diff --git a/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-29.png b/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-29.png new file mode 100644 index 00000000..72c9e447 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-29.png differ diff --git a/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-40.png b/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-40.png new file mode 100644 index 00000000..706ee2c5 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-40.png differ diff --git a/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-58.png b/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-58.png new file mode 100644 index 00000000..6c7890bd Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-58.png differ diff --git a/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-60.png b/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-60.png new file mode 100644 index 00000000..05184984 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-60.png differ diff --git a/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-76.png b/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-76.png new file mode 100644 index 00000000..543c9737 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-76.png differ diff --git a/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-80.png b/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-80.png new file mode 100644 index 00000000..c50ef059 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-80.png differ diff --git a/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-87.png b/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-87.png new file mode 100644 index 00000000..5a03e7e9 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Sky.appiconset/icon-87.png differ diff --git a/AltStore/Resources/Assets.xcassets/Starburst-image.imageset/Contents.json b/AltStore/Resources/Assets.xcassets/Starburst-image.imageset/Contents.json new file mode 100644 index 00000000..99a33a33 --- /dev/null +++ b/AltStore/Resources/Assets.xcassets/Starburst-image.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon-152.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AltStore/Resources/Assets.xcassets/Starburst-image.imageset/icon-152.png b/AltStore/Resources/Assets.xcassets/Starburst-image.imageset/icon-152.png new file mode 100644 index 00000000..5838b18e Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Starburst-image.imageset/icon-152.png differ diff --git a/AltStore/Resources/Assets.xcassets/Starburst.appiconset/Contents.json b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/Contents.json new file mode 100644 index 00000000..5d0bd4d7 --- /dev/null +++ b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "filename" : "icon-40.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "icon-60.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "icon-58.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "icon-87.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "icon-80.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "icon-120.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "icon-120.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "icon-180.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "icon-20.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "icon-40.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "icon-29.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "icon-58.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "icon-40.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "icon-80.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "icon-76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "icon-152.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "icon-167.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "icon-1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-1024.png b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-1024.png new file mode 100644 index 00000000..6384484b Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-1024.png differ diff --git a/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-120.png b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-120.png new file mode 100644 index 00000000..d44baff0 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-120.png differ diff --git a/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-152.png b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-152.png new file mode 100644 index 00000000..5838b18e Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-152.png differ diff --git a/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-167.png b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-167.png new file mode 100644 index 00000000..22365a0d Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-167.png differ diff --git a/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-180.png b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-180.png new file mode 100644 index 00000000..cbd70b3a Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-180.png differ diff --git a/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-20.png b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-20.png new file mode 100644 index 00000000..3920571e Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-20.png differ diff --git a/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-29.png b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-29.png new file mode 100644 index 00000000..4075d7b7 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-29.png differ diff --git a/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-40.png b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-40.png new file mode 100644 index 00000000..a347b358 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-40.png differ diff --git a/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-58.png b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-58.png new file mode 100644 index 00000000..25b37ea1 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-58.png differ diff --git a/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-60.png b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-60.png new file mode 100644 index 00000000..ad829ac7 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-60.png differ diff --git a/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-76.png b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-76.png new file mode 100644 index 00000000..78d30b96 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-76.png differ diff --git a/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-80.png b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-80.png new file mode 100644 index 00000000..acf29473 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-80.png differ diff --git a/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-87.png b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-87.png new file mode 100644 index 00000000..9616e4b2 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Starburst.appiconset/icon-87.png differ diff --git a/AltStore/Resources/Assets.xcassets/Steel-image.imageset/Contents.json b/AltStore/Resources/Assets.xcassets/Steel-image.imageset/Contents.json new file mode 100644 index 00000000..99a33a33 --- /dev/null +++ b/AltStore/Resources/Assets.xcassets/Steel-image.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon-152.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AltStore/Resources/Assets.xcassets/Steel-image.imageset/icon-152.png b/AltStore/Resources/Assets.xcassets/Steel-image.imageset/icon-152.png new file mode 100644 index 00000000..b97b5ecb Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Steel-image.imageset/icon-152.png differ diff --git a/AltStore/Resources/Assets.xcassets/Steel.appiconset/Contents.json b/AltStore/Resources/Assets.xcassets/Steel.appiconset/Contents.json new file mode 100644 index 00000000..5d0bd4d7 --- /dev/null +++ b/AltStore/Resources/Assets.xcassets/Steel.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "filename" : "icon-40.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "icon-60.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "icon-58.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "icon-87.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "icon-80.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "icon-120.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "icon-120.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "icon-180.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "icon-20.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "icon-40.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "icon-29.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "icon-58.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "icon-40.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "icon-80.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "icon-76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "icon-152.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "icon-167.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "icon-1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-1024.png b/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-1024.png new file mode 100644 index 00000000..88a84b40 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-1024.png differ diff --git a/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-120.png b/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-120.png new file mode 100644 index 00000000..e97517db Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-120.png differ diff --git a/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-152.png b/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-152.png new file mode 100644 index 00000000..b97b5ecb Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-152.png differ diff --git a/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-167.png b/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-167.png new file mode 100644 index 00000000..3188f9f1 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-167.png differ diff --git a/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-180.png b/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-180.png new file mode 100644 index 00000000..beb79011 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-180.png differ diff --git a/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-20.png b/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-20.png new file mode 100644 index 00000000..5f4c4e24 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-20.png differ diff --git a/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-29.png b/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-29.png new file mode 100644 index 00000000..3c989f95 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-29.png differ diff --git a/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-40.png b/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-40.png new file mode 100644 index 00000000..d7006859 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-40.png differ diff --git a/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-58.png b/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-58.png new file mode 100644 index 00000000..36ea0037 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-58.png differ diff --git a/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-60.png b/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-60.png new file mode 100644 index 00000000..9bb2947f Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-60.png differ diff --git a/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-76.png b/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-76.png new file mode 100644 index 00000000..09f0f630 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-76.png differ diff --git a/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-80.png b/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-80.png new file mode 100644 index 00000000..1cd35f5e Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-80.png differ diff --git a/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-87.png b/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-87.png new file mode 100644 index 00000000..91b5b1c5 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Steel.appiconset/icon-87.png differ diff --git a/AltStore/Resources/Assets.xcassets/Storm-image.imageset/Contents.json b/AltStore/Resources/Assets.xcassets/Storm-image.imageset/Contents.json new file mode 100644 index 00000000..99a33a33 --- /dev/null +++ b/AltStore/Resources/Assets.xcassets/Storm-image.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon-152.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AltStore/Resources/Assets.xcassets/Storm-image.imageset/icon-152.png b/AltStore/Resources/Assets.xcassets/Storm-image.imageset/icon-152.png new file mode 100644 index 00000000..759533ae Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Storm-image.imageset/icon-152.png differ diff --git a/AltStore/Resources/Assets.xcassets/Storm.appiconset/Contents.json b/AltStore/Resources/Assets.xcassets/Storm.appiconset/Contents.json new file mode 100644 index 00000000..5d0bd4d7 --- /dev/null +++ b/AltStore/Resources/Assets.xcassets/Storm.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "filename" : "icon-40.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "icon-60.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "icon-58.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "icon-87.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "icon-80.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "icon-120.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "icon-120.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "icon-180.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "icon-20.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "icon-40.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "icon-29.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "icon-58.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "icon-40.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "icon-80.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "icon-76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "icon-152.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "icon-167.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "icon-1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-1024.png b/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-1024.png new file mode 100644 index 00000000..e6bdf0ba Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-1024.png differ diff --git a/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-120.png b/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-120.png new file mode 100644 index 00000000..66a2904b Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-120.png differ diff --git a/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-152.png b/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-152.png new file mode 100644 index 00000000..759533ae Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-152.png differ diff --git a/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-167.png b/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-167.png new file mode 100644 index 00000000..c583c451 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-167.png differ diff --git a/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-180.png b/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-180.png new file mode 100644 index 00000000..2c6fcd8e Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-180.png differ diff --git a/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-20.png b/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-20.png new file mode 100644 index 00000000..7dcea151 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-20.png differ diff --git a/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-29.png b/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-29.png new file mode 100644 index 00000000..50230436 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-29.png differ diff --git a/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-40.png b/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-40.png new file mode 100644 index 00000000..ee780861 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-40.png differ diff --git a/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-58.png b/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-58.png new file mode 100644 index 00000000..daa84e58 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-58.png differ diff --git a/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-60.png b/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-60.png new file mode 100644 index 00000000..2e511a93 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-60.png differ diff --git a/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-76.png b/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-76.png new file mode 100644 index 00000000..55779968 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-76.png differ diff --git a/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-80.png b/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-80.png new file mode 100644 index 00000000..233338b5 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-80.png differ diff --git a/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-87.png b/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-87.png new file mode 100644 index 00000000..d8e93cb9 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Storm.appiconset/icon-87.png differ diff --git a/AltStore/Resources/Assets.xcassets/Vista-image.imageset/Contents.json b/AltStore/Resources/Assets.xcassets/Vista-image.imageset/Contents.json new file mode 100644 index 00000000..99a33a33 --- /dev/null +++ b/AltStore/Resources/Assets.xcassets/Vista-image.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon-152.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AltStore/Resources/Assets.xcassets/Vista-image.imageset/icon-152.png b/AltStore/Resources/Assets.xcassets/Vista-image.imageset/icon-152.png new file mode 100644 index 00000000..e383c15c Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Vista-image.imageset/icon-152.png differ diff --git a/AltStore/Resources/Assets.xcassets/Vista.appiconset/Contents.json b/AltStore/Resources/Assets.xcassets/Vista.appiconset/Contents.json new file mode 100644 index 00000000..5d0bd4d7 --- /dev/null +++ b/AltStore/Resources/Assets.xcassets/Vista.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "filename" : "icon-40.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "icon-60.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "icon-58.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "icon-87.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "icon-80.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "icon-120.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "icon-120.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "icon-180.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "icon-20.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "icon-40.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "icon-29.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "icon-58.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "icon-40.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "icon-80.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "icon-76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "icon-152.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "icon-167.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "icon-1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-1024.png b/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-1024.png new file mode 100644 index 00000000..d8082191 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-1024.png differ diff --git a/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-120.png b/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-120.png new file mode 100644 index 00000000..d06cb76f Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-120.png differ diff --git a/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-152.png b/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-152.png new file mode 100644 index 00000000..e383c15c Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-152.png differ diff --git a/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-167.png b/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-167.png new file mode 100644 index 00000000..870989fd Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-167.png differ diff --git a/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-180.png b/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-180.png new file mode 100644 index 00000000..9826219a Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-180.png differ diff --git a/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-20.png b/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-20.png new file mode 100644 index 00000000..81b77084 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-20.png differ diff --git a/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-29.png b/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-29.png new file mode 100644 index 00000000..6d627790 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-29.png differ diff --git a/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-40.png b/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-40.png new file mode 100644 index 00000000..e6bb390e Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-40.png differ diff --git a/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-58.png b/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-58.png new file mode 100644 index 00000000..c6919396 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-58.png differ diff --git a/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-60.png b/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-60.png new file mode 100644 index 00000000..ca57d497 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-60.png differ diff --git a/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-76.png b/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-76.png new file mode 100644 index 00000000..5376e63b Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-76.png differ diff --git a/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-80.png b/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-80.png new file mode 100644 index 00000000..05cd2715 Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-80.png differ diff --git a/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-87.png b/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-87.png new file mode 100644 index 00000000..79a493fe Binary files /dev/null and b/AltStore/Resources/Assets.xcassets/Vista.appiconset/icon-87.png differ diff --git a/AltStore/Resources/en.lproj/Localizable.strings b/AltStore/Resources/en.lproj/Localizable.strings new file mode 100644 index 00000000..ff089400 --- /dev/null +++ b/AltStore/Resources/en.lproj/Localizable.strings @@ -0,0 +1,211 @@ +/* + Localizable.strings + AltStore + + Created by Fabian Thies on 22.12.22. + Copyright © 2022 SideStore. All rights reserved. +*/ + + +/* General Actions */ +"Action.done" = "Done"; +"Action.close" = "Close"; +"Action.enable" = "Enable"; +"Action.submit" = "Submit"; +"Action.cancel" = "Cancel"; +"Action.tryAgain" = "Try Again"; + +/* NewsView */ +"NewsView.title" = "News"; +"NewsView.Section.FromSources.title" = "From your Sources"; + +/* BrowseView */ +"BrowseView.title" = "Browse"; +"BrowseView.search" = "Search"; +"BrowseView.Section.PromotedCategories.title" = "Promoted Categories"; +"BrowseView.Section.PromotedCategories.showAll" = "Show all"; +"BrowseView.Section.AllApps.title" = "All Apps"; +"BrowseView.Hints.NoApps.title" = "You don't have any apps yet."; +"BrowseView.Hints.NoApps.text" = "Apps are provided by \"sources\". The specification for them is an open standard, so everyone can create their own source. To get you started, we have compiled a list of \"Trusted Sources\" which you can check out by tapping the button below."; +"BrowseView.Hints.NoApps.addSource" = "Add Source"; +"BrowseView.Actions.sources" = "Sources"; +"BrowseView.Categories.gamesAndEmulators" = "Games and\nEmulators"; + +/* AppRowView */ +"AppRowView.sideloaded" = "Sideloaded"; + +/* AppPillButton */ +"AppPillButton.free" = "Free"; +"AppPillButton.open" = "Open"; + +/* RootView */ +"RootView.news" = "News"; +"RootView.browse" = "Browse"; +"RootView.myApps" = "My Apps"; +"RootView.settings" = "Settings"; + +/* SettingsView */ +"SettingsView.ConnectedAppleID.name" = "Name"; +"SettingsView.ConnectedAppleID.eMail" = "E-Mail"; +"SettingsView.ConnectedAppleID.type" = "Type"; +"SettingsView.ConnectedAppleID.text" = "Connected Apple ID"; +"SettingsView.ConnectedAppleID.signOut" = "Sign Out"; +"SettingsView.ConnectedAppleID.Footer.p1" = "Your Apple ID is required to sign the apps you install with SideStore."; +"SettingsView.ConnectedAppleID.Footer.p2" = "Your credentials are only sent to Apple's servers and are not accessible by the SideStore Team. Once successfully logged in, the login details are stored securely on your device."; + +"SettingsView.connectAppleID" = "Connect your Apple ID"; +"SettingsView.backgroundRefresh" = "Background Refresh"; +"SettingsView.addToSiri" = "Add to Siri..."; +"SettingsView.refreshingApps" = "Refreshing Apps"; +"SettingsView.switchToUIKit" = "Switch to UIKit"; +"SettingsView.resetImageCache" = "Reset Image Cache"; +"SettingsView.debug" = "Debug"; +"SettingsView.swiftUIRedesign" = "SwiftUI Redesign"; +"SettingsView.credits" = "Credits"; +"SettingsView.title" = "Settings"; +"SettingsView.refreshingAppsFooter" = "Enable Background Refresh to automatically refresh apps in the background when connected to WiFi and with Wireguard active."; +"SettingsView.exportLogs" = "Export Logs"; +"SettingsView.debugLogging" = "Debug Logging"; + +/* ConnectAppleIDView */ +"ConnectAppleIDView.startWithSignIn" = "Sign in with your Apple ID to get started."; +"ConnectAppleIDView.signIn" = "Sign In"; +"ConnectAppleIDView.appleID" = "Apple ID"; +"ConnectAppleIDView.password" = "Password"; +"ConnectAppleIDView.whyDoWeNeedThis" = "Why do we need this?"; +"ConnectAppleIDView.footer" = "Your Apple ID is used to configure apps so they can be installed on this device. Your credentials will be stored securely in this device's Keychain and sent only to Apple for authentication."; +"ConnectAppleIDView.connectYourAppleID" = "Connect Your Apple ID"; +"ConnectAppleIDView.cancel" = "Cancel"; +"ConnectAppleIDView.failedToSignIn" = "Failed to Sign In"; + +/* SourcesView */ +"SourcesView.sourcesDescription" = "Sources control what apps are available to download through SideStore."; +"SourcesView.remove" = "Remove"; +"SourcesView.trustedSources" = "Trusted Sources"; +"SourcesView.reviewedText" = "SideStore has reviewed these sources to make sure they meet our safety standards."; +"SourcesView.sources" = "Sources"; +"SourcesView.done" = "Done"; + +/* AddSourceView */ +"AddSourceView.sourceURL" = "Source URL"; +"AddSourceView.sourceWarning" = "Please enter the source url here. Then, tap continue to validate and add the source in the next step."; +"AddSourceView.sourceWarningContinued" = "Be careful with unvalidated third-party sources! Make sure to only add sources that you trust."; +"AddSourceView.continue" = "Continue"; +"AddSourceView.title" = "Add Source"; + +/* ConfirmAddSourceView */ +"ConfirmAddSourceView.apps" = "Apps"; +"ConfirmAddSourceView.newsItems" = "News Items"; +"ConfirmAddSourceView.sourceContents" = "Source Contents"; +"ConfirmAddSourceView.sourceIdentifier" = "Source Identifier"; +"ConfirmAddSourceView.sourceURL" = "Source URL"; +"ConfirmAddSourceView.sourceInfo" = "Source Information"; +"ConfirmAddSourceView.addSource" = "Add Source"; + +/* AppPillButton */ +"AppPillButton.free" = "Free"; +"AppPillButton.open" = "Open"; + +/* AppAction */ +"AppAction.install" = "Install"; +"AppAction.open" = "Open"; +"AppAction.refresh" = "Refresh"; +"AppAction.activate" = "Activate"; +"AppAction.deactivate" = "Deactivate"; +"AppAction.remove" = "Remove"; +"AppAction.enableJIT" = "Activate JIT"; +"AppAction.backup" = "Backup"; +"AppAction.exportBackup" = "Export backup"; +"AppAction.restoreBackup" = "Restore backup"; +"AppAction.chooseCustomIcon" = "Customize icon"; +"AppAction.resetIcon" = "Reset icon"; + +/* AppDetailView*/ +"AppDetailView.Badge.official" = "Official App"; +"AppDetailView.Badge.trusted" = "From Trusted Source"; +"AppDetailView.noScreenshots" = "No screenshots available for this app."; +"AppDetailView.more" = "More..."; +"AppDetailView.whatsNew" = "What's New"; +"AppDetailView.WhatsNew.versionHistory" = "Version History"; +"AppDetailView.WhatsNew.showOnGithub" = "Show project on GitHub"; +"AppDetailView.reviews" = "Ratings & Reviews"; +"AppDetailView.Reviews.outOf" = "out of %d"; +"AppDetailView.Reviews.ratings" = "%d Ratings"; +"AppDetailView.Reviews.seeAll" = "See All"; +"AppDetailView.version" = "Version %@"; +"AppDetailView.noVersionInformation" = "No version information"; +"AppDetailView.noPermissions" = "The app requires no permissions."; +"AppDetailView.permissions" = "Permissions"; +"AppDetailView.information" = "Information"; +"AppDetailView.Information.source" = "Source"; +"AppDetailView.Information.developer" = "Developer"; +"AppDetailView.Information.size" = "Size"; +"AppDetailView.Information.latestVersion" = "Latest Version"; +"AppDetailView.Information.compatibility" = "Compatibility"; +"AppDetailView.Information.compatibilityCompatible" = "Unknown"; +"AppDetailView.Information.compatibilityUnknown" = "Unknown"; +"AppDetailView.Information.compatibilityAtLeast" = "Requires iOS %@ or higher"; +"AppDetailView.Information.compatibilityOrLower" = "Requires iOS %@ or lower"; + +/* AppPermissionGrid */ +"AppPermissionGrid.usageDescription" = "Usage Description"; + +/* MyAppsView */ +"MyAppsView.active" = "Active"; +"MyAppsView.sideloading" = "Sideloading in progress..."; +"MyAppsView.refreshAll" = "Refresh All"; +"MyAppsView.appIDsRemaining" = "App IDs Remaining"; +"MyAppsView.failedToRefresh" = "Failed to refresh"; +"MyAppsView.apps" = "apps"; /* Keep this lowercase */ +"MyAppsView.viewAppIDs" = "View App IDs"; +"MyAppsView.myApps" = "My Apps"; +"MyAppsView.Hints.NoUpdates.title" = "All Apps are Up To Date"; +"MyAppsView.Hints.NoUpdates.text" = "You will be notified once updates for your apps are available. The updates will then be shown here."; +"MyAppsView.Hints.NoUpdates.dismissForNow" = "Dismiss for now"; +"MyAppsView.Hints.NoUpdates.dontShowAgain" = "Don't show this again"; + +/* AppIDsView */ +"AppIDsView.title" = "App IDs"; +"AppIDsView.description" = "Each app and app extension installed with SideStore must register an App ID with Apple.\n\nApp IDs for paid developer accounts never expire, and there is no limit to how many you can create."; + +/* AppIconsView */ +"AppIconsView.title" = "App Icon"; + +/* DevModeView */ +"DevModeView.title" = "Developer Mode"; +"DevModeView.prompt" = "SideStore's Developer Mode gives access to a menu with some debugging actions commonly used by developers. **However, some of them can break SideStore if used in the wrong way.** + +You should only enable Developer Mode if you meet one of the following requirements: +- You are a SideStore developer or contributor +- You were asked to do this by a helper when getting support +- You were asked to do this when you reported a bug or helped a developer test a change + +**_We will not provide support if you break SideStore with Developer Mode._**"; +"DevModeView.password" = "Password"; +"DevModeView.incorrectPassword" = "Incorrect password."; +"DevModeView.read" = "Read the text!"; +"DevModeView.console" = "Console"; +"DevModeView.dataExplorer" = "Data File Explorer"; +"DevModeView.tmpExplorer" = "Temporary File Explorer"; +"DevModeView.skipResign" = "Skip Resign"; +"DevModeView.footer" = "Skip Resign should only be used when you have an IPA that you have self signed. Otherwise, it will break things, and might make SideStore crash (there is absolutely no error handling and everything is expected to work)."; +"DevModeView.minimuxer" = "minimuxer debug actions"; +"DevModeView.Minimuxer.dumpProfiles" = "Dump provisioning profiles to Documents directory"; +"DevModeView.Minimuxer.afcExplorer" = "AFC File Explorer (check footer for notes)"; +"DevModeView.Minimuxer.footer" = "Notes on AFC File Explorer: +- If nothing shows up, check minimuxer logs for error +- It is currently extremely very unoptimized and may be very slow; a new AFC client is created for every action +- It is currently limited to a maximum depth of 3 to ensure it doesn't take too long to iterate over everything when you open it +- Very buggy +- There are multiple unimplemented actions"; + +/* AsyncFallibleButton */ +"AsyncFallibleButton.error" = "An error occurred"; + +/* AdvancedSettingsView */ +"AdvancedSettingsView.title" = "Advanced Settings"; +"AdvancedSettingsView.anisette" = "Anisette Server"; +"AdvancedSettingsView.dangerZone" = "Danger Zone"; +"AdvancedSettingsView.dangerZoneInfo" = "If you disable \"Use preferred servers\" then SideStore will use the server you input into the \"Anisette URL\" box rather than one selected in \"Anisette Server\"."; +"AdvancedSettingsView.DangerZone.usePreferred" = "Use preferred servers"; +"AdvancedSettingsView.DangerZone.anisetteURL" = "Anisette URL"; diff --git a/AltStore/Resources/es-419.lproj/Localizable.strings b/AltStore/Resources/es-419.lproj/Localizable.strings new file mode 100644 index 00000000..67ddb55f --- /dev/null +++ b/AltStore/Resources/es-419.lproj/Localizable.strings @@ -0,0 +1,124 @@ +/* + Localizable.strings + AltStore + + Created by Joshua Laymon on 23.12.22. + Updated by Gabriel Morazán on 25.12.22. + Copyright © 2022 SideStore. All rights reserved. +*/ + + +/* NewsView */ +"NewsView.title" = "Noticias"; +"NewsView.Section.FromSources.title" = "De tus fuentes"; + + +/* BrowseView */ +"BrowseView.title" = "Navegar"; +"BrowseView.search" = "Buscar"; +"BrowseView.Section.PromotedCategories.title" = "Categorías promocionadas"; +"BrowseView.Section.PromotedCategories.showAll" = "Mostrar todo"; +"BrowseView.Section.AllApps.title" = "Todas las aplicaciones"; +"BrowseView.Actions.sources" = "Fuentes"; +"BrowseView.Categories.gamesAndEmulators" = "Juegos y\nemuladores"; + +/* RootView */ +"RootView.news" = "Noticias"; +"RootView.browse" = "Navegar"; +"RootView.myApps" = "Mis apps"; +"RootView.settings" = "Configuración"; + +/* SettingsView */ +"SettingsView.ConnectedAppleID.name" = "Nombre"; +"SettingsView.ConnectedAppleID.eMail" = "Correo electrónico"; +"SettingsView.ConnectedAppleID.type" = "Tipo"; +"SettingsView.ConnectedAppleID.text" = "Apple ID conectado"; +"SettingsView.ConnectedAppleID.signOut" = "Cerrar sesión"; +"SettingsView.ConnectedAppleID.Footer.p1" = "Se requiere de su Apple ID para firmar las aplicaciones que se instalan con SideStore."; +"SettingsView.ConnectedAppleID.Footer.p2" = "Sus datos son enviados solamente a Apple, SideStore Team no tiene acceso alguno a sus credenciales. Los detalles del inicio de sesión serán guardadas de forma segura en su dispositivo después de haber iniciado sesión exitosamente."; +"SettingsView.connectAppleID" = "Inicia sesión con un Apple ID"; +"SettingsView.backgroundRefresh" = "Refrescar en segundo plano"; +"SettingsView.addToSiri" = "Agregar a Siri..."; +"SettingsView.refreshingApps" = "Refrescando las aplicaciones"; +"SettingsView.switchToUIKit" = "Cambiar a UIKit"; +"SettingsView.resetImageCache" = "Borrar el caché de imagenes"; +"SettingsView.debug" = "Depurar"; +"SettingsView.swiftUIRedesign" = "Rediseño de SwiftUI"; +"SettingsView.credits" = "Créditos"; +"SettingsView.title" = "Configuración"; +"SettingsView.refreshingAppsFooter" = "Active el refresco en segundo plano para que las aplicaciones sean refrescadas automáticamente cuando estés conectado a una red Wi-Fi y tengas a WireGuard activado."; + +/* ConnectAppleIDView */ +"ConnectAppleIDView.appleID" = "Apple ID"; +"ConnectAppleIDView.startWithSignIn" = "Inicie sesión con su Apple ID para comenzar."; +"ConnectAppleIDView.signIn" = "Iniciar sesión"; +"ConnectAppleIDView.password" = "Contraseña"; +"ConnectAppleIDView.whyDoWeNeedThis" = "¿Por qué necesitamos esto?"; +"ConnectAppleIDView.footer" = "Su Apple ID es usado para configurar las aplicaciones que se instalarán en su dispositivo. Sus credenciales serán guardadas de forma segura en su dispositivo y seran enviados solamente a Apple para ser autenticados."; +"ConnectAppleIDView.connectYourAppleID" = "Conectar su Apple ID"; +"ConnectAppleIDView.cancel" = "Cancelar"; +"ConnectAppleIDView.failedToSignIn" = "No se pudo iniciar sesión"; + +/* SourcesView */ +"SourcesView.sourcesDescription" = "Las fuentes determinan que aplicaciones estarán disponibles a través del SideStore."; +"SourcesView.remove" = "Eliminar"; +"SourcesView.trustedSources" = "Fuentes confiables"; +"SourcesView.reviewedText" = "SideStore Team ha verificado que estas fuentes son seguras de usar."; +"SourcesView.sources" = "Fuentes"; +"SourcesView.done" = "Listo"; + +/* AddSourceView */ +"AddSourceView.sourceURL" = "URL de fuente"; +"AddSourceView.sourceWarning" = "Introduce el URL de una fuente aquí. Luego, toca \"Continuar\" para validar y agregar la fuente en el siguiente paso."; +"AddSourceView.sourceWarningContinued" = "¡Ten cuidado con las fuentes de terceros! Asegurate de solo agregar las fuentes en las que confíes."; +"AddSourceView.continue" = "Continuar"; +"AddSourceView.title" = "Agregar fuente"; + +/* ConfirmAddSourceView */ +"ConfirmAddSourceView.apps" = "Aplicaciones"; +"ConfirmAddSourceView.newsItems" = "Noticias"; +"ConfirmAddSourceView.sourceContents" = "Contenidos de la fuente"; +"ConfirmAddSourceView.sourceIdentifier" = "Identificador de la fuente"; +"ConfirmAddSourceView.sourceURL" = "URL de la fuente"; +"ConfirmAddSourceView.sourceInfo" = "Información sobre la fuente"; +"ConfirmAddSourceView.addSource" = "Agregar una fuente"; + +/* AppPillButton */ +"AppPillButton.free" = "Gratis"; +"AppPillButton.open" = "Abrir"; + +/* AppAction */ +"AppAction.install" = "Instalar"; +"AppAction.open" = "Abrir"; +"AppAction.refresh" = "Refrescar"; +"AppAction.activate" = "Activar"; +"AppAction.deactivate" = "Desactivar"; +"AppAction.remove" = "Eliminar"; +"AppAction.enableJIT" = "Activar JIT"; +"AppAction.backup" = "Respaldar"; +"AppAction.exportBackup" = "Exportar respaldo"; +"AppAction.restoreBackup" = "Restablecer respaldo"; +"AppAction.chooseCustomIcon" = "Personalizar icono"; +"AppAction.resetIcon" = "Reiniciar icono"; + +/* AppDetailView*/ +"AppDetailView.more" = "Más..."; +"AppDetailView.whatsNew" = "Novedades"; +"AppDetailView.version" = "Versión"; +"AppDetailView.noVersionInformation" = "Sin información de la versión"; +"AppDetailView.noPermissions" = "Esta aplicación no requiere de permisos."; +"AppDetailView.permissions" = "Los permisos"; + +/* AppPermissionGrid */ +"AppPermissionGrid.usageDescription" = "Descripciones de uso"; + +/* MyAppsView */ +"MyAppsView.active" = "Activo"; +"MyAppsView.sideloading" = "Instalación en progreso..."; +"MyAppsView.refreshAll" = "Refrescar todo"; +"MyAppsView.appIDsRemaining" = "IDs de aplicaciones restantes"; +"MyAppsView.noUpdatesAvailable" = "No hay actualizaciones disponibles"; +"MyAppsView.failedToRefresh" = "No se pudo refrescar"; +"MyAppsView.apps" = "aplicaciones"; /* Keep this lowercase */ +"MyAppsView.viewAppIDs" = "Ver IDs de las aplicaciones"; +"MyAppsView.myApps" = "Mis aplicaciones"; diff --git a/AltStore/Resources/fr.lproj/Localizable.strings b/AltStore/Resources/fr.lproj/Localizable.strings new file mode 100644 index 00000000..67e12768 --- /dev/null +++ b/AltStore/Resources/fr.lproj/Localizable.strings @@ -0,0 +1,112 @@ + + + +/* NewsView */ +"NewsView.title" = "Actualités"; +"BrowseView.search" = "Rechercher"; +"BrowseView.Actions.sources" = "Sources"; +"AppAction.chooseCustomIcon" = "Personaliser l'icone"; + +/* AppPermissionGrid */ +"AppPermissionGrid.usageDescription" = "Description d'usage"; +"MyAppsView.noUpdatesAvailable" = "Aucunes mises à jour disponibles"; + + +/* BrowseView */ +"BrowseView.title" = "Parcourir"; +"BrowseView.Section.PromotedCategories.showAll" = "Montrer tout"; +"BrowseView.Section.AllApps.title" = "Toutes les applications"; +"BrowseView.Section.PromotedCategories.title" = "Catégories promus"; +"ConnectAppleIDView.whyDoWeNeedThis" = "Pourquoi avons-nous besoin de cela ?"; + +/* RootView */ +"RootView.news" = "Actualités"; +"RootView.browse" = "Parcourir"; +"RootView.myApps" = "Mes applications"; +"RootView.settings" = "Réglages"; + +/* SettingsView */ +"SettingsView.ConnectedAppleID.name" = "Nom"; +"SettingsView.ConnectedAppleID.eMail" = "Adresse courriel"; +"SettingsView.ConnectedAppleID.type" = "Type"; +"SettingsView.ConnectedAppleID.text" = "Identifiant Apple connecté"; +"SettingsView.ConnectedAppleID.signOut" = "Déconnexion"; +"SettingsView.connectAppleID" = "Connecter votre identifiant Apple"; +"SettingsView.backgroundRefresh" = "Rafraîchissement en arrière plan"; +"SettingsView.switchToUIKit" = "Changer vers UIKit"; +"SettingsView.addToSiri" = "Ajouter à Siri..."; +"SettingsView.debug" = "Déboguer"; +"SettingsView.swiftUIRedesign" = "Refonte SwiftUI"; +"SettingsView.credits" = "Crédits"; +"SettingsView.title" = "Réglages"; +"SettingsView.refreshingAppsFooter" = "Active la rafraîchissement dans l'arrière plan pour que SideStore soit capable de rafraîchir vos applications dans l'arrière plan lorsque vous êtes connecté au Wi-Fi avec Wireguard activé."; + +/* ConnectAppleIDView */ +"ConnectAppleIDView.startWithSignIn" = "Connectez-vous avec votre identifiant Apple pour commencer."; +"ConnectAppleIDView.signIn" = "Connection"; +"ConnectAppleIDView.appleID" = "Identifiant Apple"; +"ConnectAppleIDView.password" = "Mot de passe"; +"ConnectAppleIDView.connectYourAppleID" = "Connecter votre identifiant Apple"; +"ConnectAppleIDView.cancel" = "Annuler"; +"ConnectAppleIDView.failedToSignIn" = "Tentative de connexion échouée"; + +/* SourcesView */ +"SourcesView.sourcesDescription" = "Les sources contiennent des applications qui peuvent être installés avec SideStore."; +"SourcesView.remove" = "Enlever"; +"SourcesView.trustedSources" = "Sources Fiables"; +"SourcesView.reviewedText" = "L'équipe de SideStore a inspecté ces sources pour vérifier s'ils répondes à nos normes de sécurité."; +"SourcesView.sources" = "Sources"; +"SourcesView.done" = "OK"; + +/* AddSourceView */ +"AddSourceView.sourceURL" = "URL du Source"; +"AddSourceView.continue" = "Continuer"; +"AddSourceView.title" = "Ajouter une source"; +"AppAction.exportBackup" = "Exporter la sauvegarde"; +"AppAction.restoreBackup" = "Restaurer la sauvegarde"; +"AppAction.resetIcon" = "Réinitialiser l'icone"; + +/* AppDetailView*/ +"AppDetailView.more" = "Plus..."; +"AppDetailView.whatsNew" = "Quoi de neuf"; +"AppDetailView.version" = "Version"; +"AppDetailView.noPermissions" = "Cette application nécessite aucune permissions."; +"AppDetailView.permissions" = "Permissions"; + +/* MyAppsView */ +"MyAppsView.active" = "Active"; +"MyAppsView.sideloading" = "Installation en cours..."; +"MyAppsView.refreshAll" = "Rafraîchir tout"; +"NewsView.Section.FromSources.title" = "De vos Sources"; +"ConnectAppleIDView.footer" = "Votre identifiant Apple est utilisé pour configurer les applications pour qu'il peuvent être installé sur votre appareil. Les informations de votre compte vont être gardés en sécurité sur votre appareil et sont envoyés uniquement à Apple pour t'authentiquer."; +"AppDetailView.noVersionInformation" = "Aucune information sur la version"; +"SettingsView.ConnectedAppleID.Footer.p1" = "Votre identifiant Apple est requis pour signer les applications que vous installez avec SideStore."; +"SettingsView.ConnectedAppleID.Footer.p2" = "Vos informations sont envoyés uniquement aux servers d'Apple et ne sont pas accessibles par l'équipe de SideStore. Les informations de votre compte vont être gardés uniquement sur votre appareil."; +"AddSourceView.sourceWarning" = "Entrez l'URL du source ici. En suite, appuyez \"Continuer\" pour valider le source et l'ajouter."; +"AddSourceView.sourceWarningContinued" = "Soyez prudent avec des sources non officiels ! Ajoutez seulement des sources que vous jugez fiables."; +"ConfirmAddSourceView.sourceURL" = "URL du source"; +"ConfirmAddSourceView.sourceInfo" = "Informations du source"; +"ConfirmAddSourceView.addSource" = "Ajouter une source"; +"AppPillButton.open" = "Ouvrir"; + +/* AppAction */ +"AppAction.install" = "Installer"; +"AppAction.open" = "Ouvrir"; +"AppAction.refresh" = "Rafraîchir"; +"AppAction.activate" = "Activer"; +"AppAction.deactivate" = "Désactiver"; +"AppAction.remove" = "Enlever"; +"AppAction.enableJIT" = "Activer JIT"; +"AppAction.backup" = "Sauvegarde"; +"MyAppsView.failedToRefresh" = "Rafraîchissement échoué"; +"MyAppsView.myApps" = "Mes Applications"; +"BrowseView.Categories.gamesAndEmulators" = "Jeux et\némulateurs"; +"MyAppsView.appIDsRemaining" = "Identifiants d'applications restantes"; +"MyAppsView.apps" = "apps"; /* Keep this lowercase */ +"MyAppsView.viewAppIDs" = "Voir les identifiants d'applications"; + +/* ConfirmAddSourceView */ +"ConfirmAddSourceView.apps" = "Applications"; + +/* AppPillButton */ +"AppPillButton.free" = "Gratuit"; diff --git a/AltStore/Resources/hi.lproj/Localizable.strings b/AltStore/Resources/hi.lproj/Localizable.strings new file mode 100644 index 00000000..e9e33727 --- /dev/null +++ b/AltStore/Resources/hi.lproj/Localizable.strings @@ -0,0 +1,128 @@ +/* + Localizable.strings + AltStore + + Created by Fabian Thies on 22.12.22. + + Modified by mindfreakdev on 26.12.22 + + Copyright © 2022 SideStore. All rights reserved. +*/ + + +/* NewsView */ +"NewsView.title" = "समाचार"; +"NewsView.Section.FromSources.title" = "अपने सूत्रों से"; + + +/* BrowseView */ +"BrowseView.title" = "ब्राउज़"; +"BrowseView.search" = "खोज"; +"BrowseView.Section.PromotedCategories.title" = "प्रचारित श्रेणियां"; +"BrowseView.Section.PromotedCategories.showAll" = "सब दिखाएं"; +"BrowseView.Section.AllApps.title" = "सभी एप्लीकेशन"; +"BrowseView.Actions.sources" = "सूत्रों का कहना है"; +"BrowseView.Categories.gamesAndEmulators" = "गेम्स और\nएम्युलेटर्स"; + +/* RootView */ +"RootView.news" = "समाचार"; +"RootView.browse" = "ब्राउज़"; +"RootView.myApps" = "मेरी एप्प्स"; +"RootView.settings" = "समायोजन"; + +/* SettingsView */ +"SettingsView.ConnectedAppleID.name" = "नाम"; +"SettingsView.ConnectedAppleID.eMail" = "ईमेल"; +"SettingsView.ConnectedAppleID.type" = "टाइप"; +"SettingsView.ConnectedAppleID.text" = "कनेक्टेड ऐप्पल आईडी"; +"SettingsView.ConnectedAppleID.signOut" = "प्रस्थान करें"; +"SettingsView.ConnectedAppleID.Footer.p1" = "आपके द्वारा SideStore के साथ इंस्टॉल किए गए ऐप्स पर हस्ताक्षर करने के लिए आपकी ऐप्पल आईडी आवश्यक है।"; +"SettingsView.ConnectedAppleID.Footer.p2" = "आपके क्रेडेंशियल्स केवल Apple के सर्वरों को भेजे जाते हैं और SideStore टीम द्वारा एक्सेस नहीं किए जा सकते हैं। एक बार सफलतापूर्वक लॉग इन करने के बाद, लॉगिन विवरण आपके डिवाइस पर सुरक्षित रूप से संग्रहीत हो जाते हैं।"; + +"SettingsView.connectAppleID" = "अपनी ऐप्पल आईडी कनेक्ट करें"; +"SettingsView.backgroundRefresh" = "बैकग्राउंड रिफ्रेश"; +"SettingsView.addToSiri" = "सिरी में जोड़ें..."; +"SettingsView.refreshingApps" = "ताज़ा करने वाले ऐप्स"; +"SettingsView.switchToUIKit" = "यूआईकिट पर स्विच करें"; +"SettingsView.resetImageCache" = "छवि कैश रीसेट करें"; +"SettingsView.debug" = "डिबग"; +"SettingsView.swiftUIRedesign" = "स्विफ्टयूआई रिडिजाइन"; +"SettingsView.credits" = "क्रेडिट"; +"SettingsView.title" = "समायोजन"; +"SettingsView.refreshingAppsFooter" = "वाई-फाई से कनेक्ट होने और वायरगार्ड सक्रिय होने पर बैकग्राउंड में ऐप्स को स्वचालित रूप से रीफ्रेश करने के लिए बैकग्राउंड रिफ्रेश सक्षम करें।"; + +/* ConnectAppleIDView */ +"ConnectAppleIDView.startWithSignIn" = "आरंभ करने के लिए अपने Apple ID से साइन इन करें।"; +"ConnectAppleIDView.signIn" = "साइन इन करें"; +"ConnectAppleIDView.appleID" = "ऐप्पल आईडी"; +"ConnectAppleIDView.password" = "कुंजिका"; +"ConnectAppleIDView.whyDoWeNeedThis" = "हमें इसकी ज़रूरत क्यों है?"; +"ConnectAppleIDView.footer" = "आपकी Apple ID का उपयोग ऐप्स को कॉन्फ़िगर करने के लिए किया जाता है ताकि उन्हें इस डिवाइस पर इंस्टॉल किया जा सके। आपके क्रेडेंशियल इस डिवाइस के कीचेन में सुरक्षित रूप से स्टोर किए जाएंगे और प्रमाणीकरण के लिए केवल Apple को भेजे जाएंगे।"; +"ConnectAppleIDView.connectYourAppleID" = "अपनी ऐप्पल आईडी कनेक्ट करें"; +"ConnectAppleIDView.cancel" = "रद्द करना"; +"ConnectAppleIDView.failedToSignIn" = "साइन इन करने में विफल"; + +/* SourcesView */ +"SourcesView.sourcesDescription" = "स्रोत नियंत्रित करते हैं कि कौन से ऐप्स SideStore के माध्यम से डाउनलोड करने के लिए उपलब्ध हैं।"; +"SourcesView.remove" = "हटाना"; +"SourcesView.trustedSources" = "विश्वसनीय स्रोत"; +"SourcesView.reviewedText" = "SideStore ने यह सुनिश्चित करने के लिए इन स्रोतों की समीक्षा की है कि वे हमारे सुरक्षा मानकों को पूरा करते हैं।"; +"SourcesView.sources" = "सूत्रों का कहना है"; +"SourcesView.done" = "पूर्ण"; + +/* AddSourceView */ +"AddSourceView.sourceURL" = "स्रोत यूआरएल"; +"AddSourceView.sourceWarning" = "कृपया यहां स्रोत url दर्ज करें। फिर, अगले चरण में स्रोत को सत्यापित करने और जोड़ने के लिए जारी रखें पर टैप करें।"; +"AddSourceView.sourceWarningContinued" = "अमान्य तृतीय पक्ष स्रोतों से सावधान रहें! केवल उन स्रोतों को जोड़ना सुनिश्चित करें जिन पर आप भरोसा करते हैं।"; +"AddSourceView.continue" = "जारी रखना"; +"AddSourceView.title" = "स्रोत जोड़ें"; + +/* ConfirmAddSourceView */ +"ConfirmAddSourceView.apps" = "ऐप्स"; +"ConfirmAddSourceView.newsItems" = "समाचार आइटम"; +"ConfirmAddSourceView.sourceContents" = "स्रोत सामग्री"; +"ConfirmAddSourceView.sourceIdentifier" = "स्रोत पहचानकर्ता"; +"ConfirmAddSourceView.sourceURL" = "स्रोत यूआरएल"; +"ConfirmAddSourceView.sourceInfo" = "स्रोत की जानकारी"; +"ConfirmAddSourceView.addSource" = "स्रोत जोड़ें"; + +/* AppPillButton */ +"AppPillButton.free" = "नि शुल्क"; +"AppPillButton.open" = "खुला"; + +/* AppAction */ +"AppAction.install" = "स्थापित करना"; +"AppAction.open" = "खुला"; +"AppAction.refresh" = "ताज़ा करना"; +"AppAction.activate" = "सक्रिय"; +"AppAction.deactivate" = "निष्क्रिय करें"; +"AppAction.remove" = "हटाना"; +"AppAction.enableJIT" = "सक्रिय JIT"; +"AppAction.backup" = "बैकअप"; +"AppAction.exportBackup" = "बैकअप निर्यात करें"; +"AppAction.restoreBackup" = "बैकअप बहाल"; +"AppAction.chooseCustomIcon" = "आइकन अनुकूलित करें"; +"AppAction.resetIcon" = "रीसेट आइकन"; + +/* AppDetailView*/ +"AppDetailView.more" = "अधिक..."; +"AppDetailView.whatsNew" = "नया क्या है"; +"AppDetailView.version" = "संस्करण"; +"AppDetailView.noVersionInformation" = "कोई संस्करण जानकारी नहीं"; +"AppDetailView.noPermissions" = "ऐप को किसी अनुमति की आवश्यकता नहीं है।"; +"AppDetailView.permissions" = "अनुमतियां"; + +/* AppPermissionGrid */ +"AppPermissionGrid.usageDescription" = "उपयोग विवरण"; + +/* MyAppsView */ +"MyAppsView.active" = "सक्रिय"; +"MyAppsView.sideloading" = "साइडलोडिंग प्रगति पर है..."; +"MyAppsView.refreshAll" = "सभी को रीफ्रेश करें"; +"MyAppsView.remainingAppID" = "शेष ऐप आईडी"; +"MyAppsView.appIDsRemaining" = "ऐप आईडी शेष"; +"MyAppsView.noUpdatesAvailable" = "कोई अपडेट उपलब्ध नहीं"; +"MyAppsView.failedToRefresh" = "रीफ़्रेश करने में विफल"; +"MyAppsView.apps" = "ऐप्स"; /* Keep this lowercase */ +"MyAppsView.viewAppIDs" = "ऐप आईडी देखें"; +"MyAppsView.myApps" = "मेरी एप्प्स"; diff --git a/AltStore/Resources/ko.lproj/Localizable.strings b/AltStore/Resources/ko.lproj/Localizable.strings new file mode 100644 index 00000000..138878e1 --- /dev/null +++ b/AltStore/Resources/ko.lproj/Localizable.strings @@ -0,0 +1,117 @@ + + +"AppAction.enableJIT" = "JIT 사용"; +"AppAction.backup" = "백업"; + + +/* BrowseView */ +"BrowseView.title" = "탐럼하기"; +"BrowseView.search" = "검색"; +"BrowseView.Section.PromotedCategories.title" = "추천된 앱 종류"; +"BrowseView.Section.PromotedCategories.showAll" = "모두 보기"; +"BrowseView.Section.AllApps.title" = "모두 보기"; +"BrowseView.Actions.sources" = "소스"; +"BrowseView.Categories.gamesAndEmulators" = "게임과\n에뮬레이터"; + +/* RootView */ +"RootView.news" = "뉴즈"; +"RootView.browse" = "탐험하기"; +"RootView.myApps" = "설치된 앱"; +"RootView.settings" = "설정"; +"SettingsView.ConnectedAppleID.eMail" = "현재 연동된 Apple ID의 이매일"; +"SettingsView.ConnectedAppleID.type" = "종류"; +"SettingsView.ConnectedAppleID.text" = "현재 연동된 Apple ID"; +"SettingsView.ConnectedAppleID.signOut" = "Apple ID에서 로그아웃하기"; +"ConfirmAddSourceView.addSource" = "소스추가"; + +/* AppPillButton */ +"AppPillButton.free" = "무료"; + +/* AppAction */ +"AppAction.install" = "설치"; +"AppAction.open" = "열기"; +"AppPillButton.open" = "열기"; +"AppAction.refresh" = "재검색"; +"AppAction.activate" = "활성화"; +"AppAction.deactivate" = "비활성화"; +"AppAction.remove" = "지우기"; +"ConnectAppleIDView.appleID" = "Apple ID 이메일"; +"SettingsView.connectAppleID" = "Apple ID와 연동하기"; +"SettingsView.backgroundRefresh" = "백그라운드 사인"; +"SettingsView.refreshingApps" = "앱 다시 사인 하는중"; +"SettingsView.addToSiri" = "시리에 추가하기..."; +"SettingsView.switchToUIKit" = "UIkit을 사용하기"; +"SettingsView.resetImageCache" = "사진 다시 연동하기"; +"SettingsView.debug" = "디버그"; +"SettingsView.swiftUIRedesign" = "SwiftUI 리디다인"; +"SettingsView.credits" = "크래딧"; +"SettingsView.title" = "설정"; +"ConnectAppleIDView.signIn" = "로그인"; +"ConnectAppleIDView.password" = "비밀번호"; +"ConnectAppleIDView.whyDoWeNeedThis" = "정보수집을 왜하지?"; +"ConnectAppleIDView.connectYourAppleID" = "Apple ID 연동하기"; +"ConnectAppleIDView.cancel" = "취소"; +"ConnectAppleIDView.failedToSignIn" = "로그인 실패"; +"SourcesView.remove" = "삭제"; + +/* SourcesView */ +"SourcesView.sourcesDescription" = "소스는 SideStore에서 설치 가능한 앱을 설정합니다."; +"SourcesView.trustedSources" = "검증소스"; +"SourcesView.reviewedText" = "이 소스는 SideStore에서 점검해 안전한 앱이 있습니다."; +"SourcesView.sources" = "소스"; +"SourcesView.done" = "완료"; + +/* AddSourceView */ +"AddSourceView.sourceURL" = "소스 URL"; +"AddSourceView.sourceWarningContinued" = "미검증 소스를 함부로 추가하지 마세요."; +"AddSourceView.sourceWarning" = "소스 URL을 입력하고, 작동 검증한 뒤 추가해주세요."; +"AddSourceView.continue" = "추가"; +"AddSourceView.title" = "소스 추고"; + +/* ConfirmAddSourceView */ +"ConfirmAddSourceView.apps" = "앱"; +"ConfirmAddSourceView.newsItems" = "뉴스"; +"ConfirmAddSourceView.sourceContents" = "소스 내용물"; +"ConfirmAddSourceView.sourceIdentifier" = "소스 식별"; +"ConfirmAddSourceView.sourceURL" = "소스 URL"; +"ConfirmAddSourceView.sourceInfo" = "소스 정보"; + +/* NewsView */ +"NewsView.title" = "뉴스"; +"NewsView.Section.FromSources.title" = "소스에서"; + +/* SettingsView */ +"SettingsView.ConnectedAppleID.name" = "현재 연동된 Apple ID의 이름"; +"SettingsView.ConnectedAppleID.Footer.p1" = "이 앱을 사용하려면, Apple ID에 로그인을 해서 앱을 인증을 할 수 있습니다."; +"AppAction.exportBackup" = "백업 내보내기"; +"ConnectAppleIDView.footer" = "당신의 Apple ID는 앱을 설정하여 기기에 설치가 가능하게 합니다. 당신의 개인정보는 디바이스의 Keychain에만 저장되며 애플외 전송되지 않습니다."; +"SettingsView.ConnectedAppleID.Footer.p2" = "당신의 개인정보는 애플 외에는 전송되지 않으며, 로그인이 완료된 후에 보안이 강하게 저장됨니다."; +"AppAction.restoreBackup" = "백업 받기"; +"AppAction.chooseCustomIcon" = "아이콘 바꾸기"; +"SettingsView.refreshingAppsFooter" = "백그라운드 앱 샤로고침 키면 와이파이와 Wireguard가켜진상태에 앱을 라프래쉬합니다."; +"AppAction.resetIcon" = "유저 아이콘 삭재"; + +/* ConnectAppleIDView */ +"ConnectAppleIDView.startWithSignIn" = "Apple ID에 로그인을 하여 시작합시다."; +"AppDetailView.whatsNew" = "무엇이 새로운가"; +"AppDetailView.version" = "버전"; +"AppDetailView.noVersionInformation" = "버전정보 없움"; +"AppDetailView.noPermissions" = "앱은 추가 기능이 필요 없다."; +"AppDetailView.permissions" = "추가 기능"; + +/* AppPermissionGrid */ +"AppPermissionGrid.usageDescription" = "사용량"; + +/* MyAppsView */ +"MyAppsView.active" = "활성화"; +"MyAppsView.noUpdatesAvailable" = "업데이트 없움"; +"MyAppsView.failedToRefresh" = "업대이트 실패"; + +/* AppDetailView*/ +"AppDetailView.more" = "더 보기..."; +"MyAppsView.refreshAll" = "모두 다시 검색"; +"MyAppsView.appIDsRemaining" = "사용 가능한 App ID 개수"; +"MyAppsView.apps" = "apps"; /* Keep this lowercase */ +"MyAppsView.viewAppIDs" = "사용한 App ID보기"; +"MyAppsView.myApps" = "나의 앱"; +"MyAppsView.sideloading" = "설치 중..."; diff --git a/AltStore/Resources/nl.lproj/Localizable.strings b/AltStore/Resources/nl.lproj/Localizable.strings new file mode 100644 index 00000000..2f3ccd12 --- /dev/null +++ b/AltStore/Resources/nl.lproj/Localizable.strings @@ -0,0 +1,128 @@ +/* + Localizable.strings + AltStore + + Created by Fabian Thies on 22.12.22. + + Modified by mindfreakdev on 25.12.22 + + Copyright © 2022 SideStore. All rights reserved. +*/ + + +/* NewsView */ +"NewsView.title" = "Nieuws"; +"NewsView.Section.FromSources.title" = "Van je bronnen"; + + +/* BrowseView */ +"BrowseView.title" = "Bladeren"; +"BrowseView.search" = "Zoekopdracht"; +"BrowseView.Section.PromotedCategories.title" = "Gepromoveerde categorieën"; +"BrowseView.Section.PromotedCategories.showAll" = "Toon alles"; +"BrowseView.Section.AllApps.title" = "Alle Apps"; +"BrowseView.Actions.sources" = "Bronnen"; +"BrowseView.Categories.gamesAndEmulators" = "Spellen en\nEmulators"; + +/* RootView */ +"RootView.news" = "Nieuws"; +"RootView.browse" = "Bladeren"; +"RootView.myApps" = "Mijn apps"; +"RootView.settings" = "Instellingen"; + +/* SettingsView */ +"SettingsView.ConnectedAppleID.name" = "Naam"; +"SettingsView.ConnectedAppleID.eMail" = "E-mailen"; +"SettingsView.ConnectedAppleID.type" = "Type"; +"SettingsView.ConnectedAppleID.text" = "Verbonden Apple ID"; +"SettingsView.ConnectedAppleID.signOut" = "Afmelden"; +"SettingsView.ConnectedAppleID.Footer.p1" = "Uw Apple ID is vereist om de apps te ondertekenen die u met SideStore installeert."; +"SettingsView.ConnectedAppleID.Footer.p2" = "Uw inloggegevens worden alleen naar de servers van Apple verzonden en zijn niet toegankelijk voor het SideStore-team. Nadat u succesvol bent ingelogd, worden de inloggegevens veilig op uw apparaat opgeslagen."; + +"SettingsView.connectAppleID" = "Koppel uw Apple ID"; +"SettingsView.backgroundRefresh" = "Achtergrond Vernieuwen"; +"SettingsView.addToSiri" = "Toevoegen aan Siri..."; +"SettingsView.refreshingApps" = "Verfrissende Apps"; +"SettingsView.switchToUIKit" = "Schakel over naar UIKit"; +"SettingsView.resetImageCache" = "Stel De Afbeeldingscache Opnieuw In"; +"SettingsView.debug" = "Debuggen"; +"SettingsView.swiftUIRedesign" = "SwiftUI Herontwerp"; +"SettingsView.credits" = "Credits"; +"SettingsView.title" = "Instellingen"; +"SettingsView.refreshingAppsFooter" = "Schakel Achtergrondvernieuwing in om apps op de achtergrond automatisch te vernieuwen wanneer ze verbonden zijn met wifi en Wireguard actief is."; + +/* ConnectAppleIDView */ +"ConnectAppleIDView.startWithSignIn" = "Aanmelden met uw Apple ID om aan de slag te gaan."; +"ConnectAppleIDView.signIn" = "Aanmelden"; +"ConnectAppleIDView.appleID" = "Apple ID"; +"ConnectAppleIDView.password" = "Wachtwoord"; +"ConnectAppleIDView.whyDoWeNeedThis" = "Waarom hebben we dit nodig?"; +"ConnectAppleIDView.footer" = "Uw Apple ID wordt gebruikt om apps te configureren zodat ze op dit apparaat kunnen worden geïnstalleerd. Uw inloggegevens worden veilig opgeslagen in de sleutelhanger van dit apparaat en alleen naar Apple verzonden voor authenticatie."; +"ConnectAppleIDView.connectYourAppleID" = "Verbind uw Apple ID"; +"ConnectAppleIDView.cancel" = "Annuleren"; +"ConnectAppleIDView.failedToSignIn" = "Aanmelden mislukt"; + +/* SourcesView */ +"SourcesView.sourcesDescription" = "Bronnen bepalen welke apps beschikbaar zijn om te downloaden via SideStore."; +"SourcesView.remove" = "Verwijderen"; +"SourcesView.trustedSources" = "Vertrouwde Bronnen"; +"SourcesView.reviewedText" = "SideStore heeft deze bronnen beoordeeld om er zeker van te zijn dat ze voldoen aan onze veiligheidsnormen."; +"SourcesView.sources" = "Bronnen"; +"SourcesView.done" = "Gedaan"; + +/* AddSourceView */ +"AddSourceView.sourceURL" = "Bron URL"; +"AddSourceView.sourceWarning" = "Voer hier de bron-URL in. Tik vervolgens op doorgaan om te valideren en voeg de bron toe in de volgende stap."; +"AddSourceView.sourceWarningContinued" = "Wees voorzichtig met niet-gevalideerde bronnen van derden! Zorg ervoor dat u alleen bronnen toevoegt die u vertrouwt."; +"AddSourceView.continue" = "Doorgaan"; +"AddSourceView.title" = "Bron Toevoegen"; + +/* ConfirmAddSourceView */ +"ConfirmAddSourceView.apps" = "Apps"; +"ConfirmAddSourceView.newsItems" = "Nieuwsberichten"; +"ConfirmAddSourceView.sourceContents" = "Bron Inhoud"; +"ConfirmAddSourceView.sourceIdentifier" = "Bron Identificatie"; +"ConfirmAddSourceView.sourceURL" = "Bron URL"; +"ConfirmAddSourceView.sourceInfo" = "Bron Informatie"; +"ConfirmAddSourceView.addSource" = "Bron Toevoegen"; + +/* AppPillButton */ +"AppPillButton.free" = "Vrij"; +"AppPillButton.open" = "Open"; + +/* AppAction */ +"AppAction.install" = "Installeren"; +"AppAction.open" = "Open"; +"AppAction.refresh" = "Vernieuwen"; +"AppAction.activate" = "Activeren"; +"AppAction.deactivate" = "Deactiveren"; +"AppAction.remove" = "Verwijderen"; +"AppAction.enableJIT" = "Activeer JIT"; +"AppAction.backup" = "Backup"; +"AppAction.exportBackup" = "Backup exporteren"; +"AppAction.restoreBackup" = "Backup terugzetten"; +"AppAction.chooseCustomIcon" = "Pictogram aanpassen"; +"AppAction.resetIcon" = "Pictogram resetten"; + +/* AppDetailView*/ +"AppDetailView.more" = "Meer..."; +"AppDetailView.whatsNew" = "Wat Is Er Nieuw"; +"AppDetailView.version" = "Versie"; +"AppDetailView.noVersionInformation" = "Geen versie informatie"; +"AppDetailView.noPermissions" = "De app vereist geen machtigingen."; +"AppDetailView.permissions" = "Rechten"; + +/* AppPermissionGrid */ +"AppPermissionGrid.usageDescription" = "Gebruik Beschrijving"; + +/* MyAppsView */ +"MyAppsView.active" = "Actief"; +"MyAppsView.sideloading" = "Bezig met sideloaden..."; +"MyAppsView.refreshAll" = "Ververs Alles"; +"MyAppsView.remainingAppID" = "Resterende App IDs"; +"MyAppsView.appIDsRemaining" = "App IDs Resterende"; +"MyAppsView.noUpdatesAvailable" = "Geen Updates Beschikbaar"; +"MyAppsView.failedToRefresh" = "Kan niet vernieuwen"; +"MyAppsView.apps" = "apps"; /* Keep this lowercase */ +"MyAppsView.viewAppIDs" = "App IDs Bekijken"; +"MyAppsView.myApps" = "Mijn Apps"; \ No newline at end of file diff --git a/AltStore/Resources/uk.lproj/Localizable.strings b/AltStore/Resources/uk.lproj/Localizable.strings new file mode 100644 index 00000000..f62fd681 --- /dev/null +++ b/AltStore/Resources/uk.lproj/Localizable.strings @@ -0,0 +1,128 @@ +/* + Localizable.strings + AltStore + + Created by Fabian Thies on 22.12.22. + + Modified by mindfreakdev on 25.12.22 + + Copyright © 2022 SideStore. All rights reserved. +*/ + + +/* NewsView */ +"NewsView.title" = "Новини"; +"NewsView.Section.FromSources.title" = "З ваших джерел"; + + +/* BrowseView */ +"BrowseView.title" = "переглядати"; +"BrowseView.search" = "Пошук"; +"BrowseView.Section.PromotedCategories.title" = "Розширені категорії"; +"BrowseView.Section.PromotedCategories.showAll" = "Показати все"; +"BrowseView.Section.AllApps.title" = "Усі додатки"; +"BrowseView.Actions.sources" = "Джерела"; +"BrowseView.Categories.gamesAndEmulators" = "Ігри та\nемулятори"; + +/* RootView */ +"RootView.news" = "Новини"; +"RootView.browse" = "переглядати"; +"RootView.myApps" = "Мої програми"; +"RootView.settings" = "Налаштування"; + +/* SettingsView */ +"SettingsView.ConnectedAppleID.name" = "Ім'я"; +"SettingsView.ConnectedAppleID.eMail" = "Електронна пошта"; +"SettingsView.ConnectedAppleID.type" = "Тип"; +"SettingsView.ConnectedAppleID.text" = "Підключений Apple ID"; +"SettingsView.ConnectedAppleID.signOut" = "Вийти з аккаунта"; +"SettingsView.ConnectedAppleID.Footer.p1" = "Ваш Apple ID потрібен для підпису програм, які ви встановлюєте за допомогою SideStore."; +"SettingsView.ConnectedAppleID.Footer.p2" = "Ваші облікові дані надсилаються лише на сервери Apple і недоступні для команди SideStore. Після успішного входу дані для входу надійно зберігаються на вашому пристрої."; + +"SettingsView.connectAppleID" = "Підключіть свій Apple ID"; +"SettingsView.backgroundRefresh" = "Фонове оновлення"; +"SettingsView.addToSiri" = "Додати до Siri..."; +"SettingsView.refreshingApps" = "Оновлення програм"; +"SettingsView.switchToUIKit" = "Перейдіть на UIKit"; +"SettingsView.resetImageCache" = "Скинути кеш зображень"; +"SettingsView.debug" = "Відлагоджувати"; +"SettingsView.swiftUIRedesign" = "Редизайн SwiftUI"; +"SettingsView.credits" = "Кредити"; +"SettingsView.title" = "Налаштування"; +"SettingsView.refreshingAppsFooter" = "Увімкніть Background Refresh, щоб автоматично оновлювати програми у фоновому режимі при підключенні до Wi-Fi і Wireguard."; + +/* ConnectAppleIDView */ +"ConnectAppleIDView.startWithSignIn" = "Щоб почати, увійдіть за допомогою свого Apple ID."; +"ConnectAppleIDView.signIn" = "Увійти"; +"ConnectAppleIDView.appleID" = "Apple ID"; +"ConnectAppleIDView.password" = "Пароль"; +"ConnectAppleIDView.whyDoWeNeedThis" = "Навіщо нам це?"; +"ConnectAppleIDView.footer" = "Ваш Apple ID використовується для налаштування додатків, щоб їх можна було встановити на цьому пристрої. Ваші облікові дані будуть безпечно зберігатися в Keychain цього пристрою та надсилатися лише в Apple для автентифікації."; +"ConnectAppleIDView.connectYourAppleID" = "Підключіть свій Apple ID"; +"ConnectAppleIDView.cancel" = "Скасувати"; +"ConnectAppleIDView.failedToSignIn" = "Не вдалося ввійти"; + +/* SourcesView */ +"SourcesView.sourcesDescription" = "Джерела контролюють, які програми доступні для завантаження через SideStore."; +"SourcesView.remove" = "видалити"; +"SourcesView.trustedSources" = "Надійні джерела"; +"SourcesView.reviewedText" = "SideStore перевірив ці джерела, щоб переконатися, що вони відповідають нашим стандартам безпеки."; +"SourcesView.sources" = "Джерела"; +"SourcesView.done" = "Готово"; + +/* AddSourceView */ +"AddSourceView.sourceURL" = "Вихідна URL-адреса"; +"AddSourceView.sourceWarning" = "Введіть URL-адресу джерела тут. Потім натисніть «Продовжити», щоб перевірити та додати джерело на наступному кроці."; +"AddSourceView.sourceWarningContinued" = "Будьте обережні з неперевіреними сторонніми джерелами! Додавайте лише ті джерела, яким довіряєте."; +"AddSourceView.continue" = "Продовжити"; +"AddSourceView.title" = "Додати джерело"; + +/* ConfirmAddSourceView */ +"ConfirmAddSourceView.apps" = "програми"; +"ConfirmAddSourceView.newsItems" = "Новини"; +"ConfirmAddSourceView.sourceContents" = "Зміст джерела"; +"ConfirmAddSourceView.sourceIdentifier" = "Ідентифікатор джерела"; +"ConfirmAddSourceView.sourceURL" = "Вихідна URL-адреса"; +"ConfirmAddSourceView.sourceInfo" = "Вихідна інформація"; +"ConfirmAddSourceView.addSource" = "Додати джерело"; + +/* AppPillButton */ +"AppPillButton.free" = "безкоштовно"; +"AppPillButton.open" = "ВІДЧИНЕНО"; + +/* AppAction */ +"AppAction.install" = "встановити"; +"AppAction.open" = "ВІДЧИНЕНО"; +"AppAction.refresh" = "Оновити"; +"AppAction.activate" = "активувати"; +"AppAction.deactivate" = "Дезактивувати"; +"AppAction.remove" = "видалити"; +"AppAction.enableJIT" = "Активуйте JIT"; +"AppAction.backup" = "Резервне копіювання"; +"AppAction.exportBackup" = "Експорт резервної копії"; +"AppAction.restoreBackup" = "Відновлення резервної копії"; +"AppAction.chooseCustomIcon" = "Налаштувати значок"; +"AppAction.resetIcon" = "Значок скидання"; + +/* AppDetailView*/ +"AppDetailView.more" = "більше..."; +"AppDetailView.whatsNew" = "Що нового"; +"AppDetailView.version" = "Версія"; +"AppDetailView.noVersionInformation" = "Немає інформації про версію"; +"AppDetailView.noPermissions" = "Програма не потребує дозволів."; +"AppDetailView.permissions" = "Дозволи"; + +/* AppPermissionGrid */ +"AppPermissionGrid.usageDescription" = "Опис використання"; + +/* MyAppsView */ +"MyAppsView.active" = "Активний"; +"MyAppsView.sideloading" = "Виконується стороннє завантаження..."; +"MyAppsView.refreshAll" = "Оновити все"; +"MyAppsView.remainingAppID" = "Решта ідентифікаторів програм"; +"MyAppsView.appIDsRemaining" = "Залишилося ідентифікаторів програм"; +"MyAppsView.noUpdatesAvailable" = "Немає оновлень"; +"MyAppsView.failedToRefresh" = "Не вдалося оновити"; +"MyAppsView.apps" = "програми"; /* Keep this lowercase */ +"MyAppsView.viewAppIDs" = "Перегляньте ідентифікатори програм"; +"MyAppsView.myApps" = "Мої програми"; \ No newline at end of file diff --git a/AltStore/View Components/AppIconView.swift b/AltStore/View Components/AppIconView.swift new file mode 100644 index 00000000..072bbf0d --- /dev/null +++ b/AltStore/View Components/AppIconView.swift @@ -0,0 +1,81 @@ +// +// SwiftUIView.swift +// SideStore +// +// Created by Fabian Thies on 18.11.22. +// Copyright © 2022 Fabian Thies. All rights reserved. +// + +import SwiftUI +import AsyncImage + +struct AppIconView: View { + @ObservedObject private var iO = Inject.observer + + @ObservedObject private var sideStoreIconData = AppIconsData.shared + + let iconUrl: URL? + var isSideStore: Bool + var size: CGFloat = 64 + var cornerRadius: CGFloat { + size * 0.234 + } + + var image: some View { + if isSideStore { + return AnyView( + Image(uiImage: UIImage(named: sideStoreIconData.selectedIconName! + "-image") ?? UIImage()) + .resizable() + .renderingMode(.original) + ) + } + if let iconUrl { + return AnyView( + AsyncImage(url: iconUrl) { image in + image + .resizable() + } placeholder: { + Color(UIColor.secondarySystemBackground) + } + ) + } + return AnyView(Color(UIColor.secondarySystemBackground)) + } + + var body: some View { + image + .frame(width: size, height: size) + .clipShape(RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)) + .enableInjection() + } +} + +extension AppIconView: Equatable { + /// Prevent re-rendering of the view if the parameters didn't change + static func == (lhs: AppIconView, rhs: AppIconView) -> Bool { + lhs.iconUrl == rhs.iconUrl && lhs.cornerRadius == rhs.cornerRadius + } +} + + +import AltStoreCore + +struct AppIconView_Previews: PreviewProvider { + + static let context = DatabaseManager.shared.viewContext + static let app = StoreApp.makeAltStoreApp(in: context) + + static var previews: some View { + HStack { + AppIconView(iconUrl: app.iconURL, isSideStore: true) + + VStack(alignment: .leading) { + Text(app.name) + .bold() + Text(app.developerName) + .font(.callout) + .foregroundColor(.secondary) + } + } + } +} diff --git a/AltStore/View Components/AppPillButton.swift b/AltStore/View Components/AppPillButton.swift new file mode 100644 index 00000000..b8ede832 --- /dev/null +++ b/AltStore/View Components/AppPillButton.swift @@ -0,0 +1,144 @@ +// +// AppPillButton.swift +// SideStore +// +// Created by Fabian Thies on 20.11.22. +// Copyright © 2022 Fabian Thies. All rights reserved. +// + +import SwiftUI +import AltStoreCore + +struct AppPillButton: View { + + @ObservedObject + var appManager = AppManager.shared.publisher + + let app: AppProtocol + var showRemainingDays = false + + var storeApp: StoreApp? { + (app as? StoreApp) ?? (app as? InstalledApp)?.storeApp + } + + var installedApp: InstalledApp? { + (app as? InstalledApp) ?? (app as? StoreApp)?.installedApp + } + + var progress: Progress? { + appManager.refreshProgress[app.bundleIdentifier] ?? appManager.installationProgress[app.bundleIdentifier] + } +// let progress = { +// let progress = Progress(totalUnitCount: 100) +// progress.completedUnitCount = 20 +// return progress +// }() + + var buttonText: String { +// guard progress == nil else { +// return "" +// } + + if let installedApp { + if self.showRemainingDays { + return DateFormatterHelper.string(forExpirationDate: installedApp.expirationDate) + } + + return L10n.AppPillButton.open + } + + return L10n.AppPillButton.free + } + + var body: some View { + SwiftUI.Button(action: handleButton) { + Text(buttonText.uppercased()) + .bold() + } + .buttonStyle(PillButtonStyle(tintColor: storeApp?.tintColor ?? .black, progress: progress)) + } + + func handleButton() { + if let installedApp { + if showRemainingDays { + self.refreshApp(installedApp) + } else { + self.openApp(installedApp) + } + } else if let storeApp { + self.installApp(storeApp) + } + } + + func openApp(_ installedApp: InstalledApp) { + UIApplication.shared.open(installedApp.openAppURL) + } + + func refreshApp(_ installedApp: InstalledApp) { + AppManager.shared.refresh([installedApp], presentingViewController: nil) + } + + func installApp(_ storeApp: StoreApp) { + let previousProgress = AppManager.shared.installationProgress(for: storeApp) + guard previousProgress == nil else { + previousProgress?.cancel() + return + } + + let _ = AppManager.shared.install(storeApp, presentingViewController: UIApplication.shared.keyWindow?.rootViewController) { result in + + switch result { + case let .success(installedApp): + print("Installed app: \(installedApp.bundleIdentifier)") + + case let .failure(error): + print("Failed to install app: \(error.localizedDescription)") + NotificationManager.shared.reportError(error: error) + AppManager.shared.installationProgress(for: storeApp)?.cancel() + } + } + } +} + +struct AppPillButton_Previews: PreviewProvider { + + static let context = DatabaseManager.shared.viewContext + static let app = StoreApp.makeAltStoreApp(in: context) + static let installedApp = InstalledApp.fetchAltStore(in: context) + + static var previews: some View { + VStack { + self.preview(for: app) + + self.preview(for: installedApp!) + + self.preview(for: installedApp!, showRemainingDays: true) + } + .padding() + } + + @ViewBuilder + static func preview(for app: AppProtocol, showRemainingDays: Bool = false) -> some View { + HintView(backgroundColor: Color(UIColor.secondarySystemBackground)) { + HStack { + AppIconView(iconUrl: self.app.iconURL, isSideStore: true) + + VStack(alignment: .leading) { + Text(app is StoreApp ? "Store App" : "Installed App") + .bold() + Text( + app is StoreApp ? + "Can be installed" : + showRemainingDays ? "Can be refreshed" : "Can be opened" + ) + .font(.callout) + .foregroundColor(.secondary) + } + + Spacer() + + AppPillButton(app: app, showRemainingDays: showRemainingDays) + } + } + } +} diff --git a/AltStore/View Components/AppRowView.swift b/AltStore/View Components/AppRowView.swift new file mode 100644 index 00000000..a4856900 --- /dev/null +++ b/AltStore/View Components/AppRowView.swift @@ -0,0 +1,55 @@ +// +// AppRowView.swift +// SideStore +// +// Created by Fabian Thies on 18.11.22. +// Copyright © 2022 Fabian Thies. All rights reserved. +// + +import SwiftUI +import AltStoreCore + +struct AppRowView: View { + let app: AppProtocol + + var storeApp: StoreApp? { + (app as? StoreApp) ?? (app as? InstalledApp)?.storeApp + } + + var showRemainingDays: Bool = false + + var body: some View { + HStack(alignment: .center, spacing: 12) { + AppIconView(iconUrl: storeApp?.iconURL, isSideStore: storeApp?.isSideStore ?? false) + + VStack(alignment: .leading, spacing: 2) { + Text(app.name) + .bold() + + Text(storeApp?.developerName ?? L10n.AppRowView.sideloaded) + .font(.callout) + .foregroundColor(.secondary) + + if false { + RatingStars(rating: 4) + .frame(height: 12) + .foregroundColor(.secondary) + } + } + .lineLimit(1) + + Spacer() + + AppPillButton(app: app, showRemainingDays: showRemainingDays) + } + .padding() + .tintedBackground(Color(storeApp?.tintColor ?? UIColor(Color.accentColor))) + .clipShape(RoundedRectangle(cornerRadius: 24, style: .circular)) + } +} + +//struct AppRowView_Previews: PreviewProvider { +// static var previews: some View { +// AppRowView() +// } +//} diff --git a/AltStore/View Components/AppScreenshot.swift b/AltStore/View Components/AppScreenshot.swift new file mode 100644 index 00000000..f7bc1abd --- /dev/null +++ b/AltStore/View Components/AppScreenshot.swift @@ -0,0 +1,55 @@ +// +// AppScreenshot.swift +// SideStore +// +// Created by Fabian Thies on 20.12.22. +// Copyright © 2022 SideStore. All rights reserved. +// + +import SwiftUI +import UIKit +import AsyncImage + +struct AppScreenshot: View { + let url: URL + var aspectRatio: CGFloat = 9/16 + + static let processor = Self.ScreenshotProcessor() + + var body: some View { + AsyncImage(url: self.url, processor: Self.processor) { image in + image + .resizable() + } placeholder: { + Rectangle() + .foregroundColor(.secondary) + } + .aspectRatio(self.aspectRatio, contentMode: .fit) + .cornerRadius(8) + } +} + +extension AppScreenshot { + class ScreenshotProcessor: ImageProcessor { + func process(image: UIImage) -> UIImage { + guard let cgImage = image.cgImage, image.size.width > image.size.height else { return image } + + let rotatedImage = UIImage(cgImage: cgImage, scale: image.scale, orientation: .right) + return rotatedImage + } + } +} + + +import AltStoreCore + +struct AppScreenshot_Previews: PreviewProvider { + + static let context = DatabaseManager.shared.viewContext + static let app = StoreApp.makeAltStoreApp(in: context) + + static var previews: some View { + AppScreenshot(url: app.screenshotURLs[0]) + .padding() + } +} diff --git a/AltStore/View Components/AsyncFallibleButton.swift b/AltStore/View Components/AsyncFallibleButton.swift new file mode 100644 index 00000000..24a91402 --- /dev/null +++ b/AltStore/View Components/AsyncFallibleButton.swift @@ -0,0 +1,118 @@ +// +// AsyncFallibleButton.swift +// SideStore +// +// Created by naturecodevoid on 2/18/23. +// Copyright © 2023 SideStore. All rights reserved. +// + +import SwiftUI + +private enum AsyncFallibleButtonState { + case none + case loading + case success + case error +} + +struct AsyncFallibleButton: View { + @ObservedObject private var iO = Inject.observer + + let action: () throws -> Void + let label: (_ execute: @escaping () -> Void) -> Label + + var afterFinish: (_ success: Bool) -> Void = { success in } // runs after the checkmark/X has disappeared + var wrapInButton = true + var secondsToDisplayResultIcon: Double = 3 + + @State private var state: AsyncFallibleButtonState = .none + @State private var showErrorAlert = false + @State private var errorAlertMessage = "" + + private var inside: some View { + HStack { + label(execute) + if state != .none { + if wrapInButton { + Spacer() + } + switch (state) { + case .loading: + ProgressView() + case .success: + Image(systemSymbol: .checkmark) + .foregroundColor(Color.green) + case .error: + Image(systemSymbol: .xmark) + .foregroundColor(Color.red) + default: + Image(systemSymbol: .questionmark) + .foregroundColor(Color.yellow) + } + } + } + } + + private var wrapped: some View { + if wrapInButton { + return AnyView(SwiftUI.Button(action: { + execute() + }) { + inside + }) + } else { + return AnyView(inside) + } + } + + var body: some View { + wrapped + .alert(isPresented: $showErrorAlert) { + Alert( + title: Text(L10n.AsyncFallibleButton.error), + message: Text(errorAlertMessage) + ) + } + .disabled(state != .none) + .animation(.default, value: state) + .enableInjection() + } + + func execute() { + if state != .none { return } + state = .loading + DispatchQueue.global().async { + do { + try action() + DispatchQueue.main.async { state = .success } + } catch { + DispatchQueue.main.async { + state = .error + errorAlertMessage = (error as? LocalizedError)?.failureReason ?? error.localizedDescription + showErrorAlert = true + } + } + + DispatchQueue.main.asyncAfter(deadline: .now() + secondsToDisplayResultIcon) { + let lastState = state + state = .none + afterFinish(lastState == .success) + } + } + } +} + +struct AsyncFallibleButton_Previews: PreviewProvider { + static var previews: some View { + AsyncFallibleButton(action: { + print("Start") + for index in 0...5000000 { + _ = index + index + } + throw NSError(domain: "TestError", code: -1) + //print("Finish") + }) { execute in + Text("Hello World") + } + } +} diff --git a/AltStore/View Components/FileExplorer.swift b/AltStore/View Components/FileExplorer.swift new file mode 100644 index 00000000..85ce78d6 --- /dev/null +++ b/AltStore/View Components/FileExplorer.swift @@ -0,0 +1,457 @@ +// +// FileExplorer.swift +// SideStore +// +// Created by naturecodevoid on 2/16/23. +// Copyright © 2023 SideStore. All rights reserved. +// + +import SwiftUI +import ZIPFoundation +import UniformTypeIdentifiers +import minimuxer + +extension Binding: Equatable { + public static func == (lhs: Binding, rhs: Binding) -> Bool { + return lhs.wrappedValue == rhs.wrappedValue + } +} + +private protocol FileExplorerBackend { + func delete(_ path: URL) throws + func zip(_ path: URL) throws + func insert(file: URL, to: URL) throws + func iterate(_ directory: URL) -> DirectoryEntry + func getQuickLookURL(_ path: URL) throws -> URL +} + +private class NormalFileExplorerBackend: FileExplorerBackend { + func delete(_ path: URL) throws { + try FileManager.default.removeItem(at: path) + } + + func zip(_ path: URL) throws { + let dest = FileManager.default.documentsDirectory.appendingPathComponent(path.pathComponents.last! + ".zip") + do { + try FileManager.default.removeItem(at: dest) + } catch {} + + try FileManager.default.zipItem(at: path, to: dest) + } + + func insert(file: URL, to: URL) throws { + try FileManager.default.copyItem(at: file, to: to.appendingPathComponent(file.pathComponents.last!), shouldReplace: true) + } + + private func _iterate(directory: URL, parent: URL) -> DirectoryEntry { + var directoryEntry = DirectoryEntry(path: directory, parent: parent, isFile: false) + if let contents = try? FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: []) { + for entry in contents { + if entry.hasDirectoryPath { + directoryEntry.children!.append(_iterate(directory: entry, parent: directory)) + } else { + directoryEntry.children!.append(DirectoryEntry(path: entry, parent: directory, isFile: true, size: { + guard let attributes = try? FileManager.default.attributesOfItem(atPath: entry.description.replacingOccurrences(of: "file://", with: "")) else { return nil } + return attributes[FileAttributeKey.size] as? Double + }())) + } + } + } + return directoryEntry + } + + func iterate(_ directory: URL) -> DirectoryEntry { + return _iterate(directory: directory, parent: directory) + } + + func getQuickLookURL(_ path: URL) throws -> URL { + path + } +} + +private class AfcFileExplorerBackend: FileExplorerBackend { + func delete(_ path: URL) throws { + try AfcFileManager.remove(path.description.replacingOccurrences(of: "file://", with: "").removingPercentEncoding!) + } + + func zip(_ path: URL) throws { + throw NSError(domain: "AFC currently doesn't support zipping a directory/file. however, it is possible (we should be able to copy the files outside of AFC and then zip the copied directory/file), it just hasn't been implemented", code: -1) + } + + func insert(file: URL, to: URL) throws { + let data = try Data(contentsOf: file) + let rustByteSlice = data.toRustByteSlice() + let to = to.appendingPathComponent(file.lastPathComponent).description.replacingOccurrences(of: "file://", with: "").removingPercentEncoding! + print("writing to \(to)") + try AfcFileManager.writeFile(to, rustByteSlice.forRust()) + } + + private func _addChildren(_ rustEntry: RustDirectoryEntryRef) -> DirectoryEntry { + var entry = DirectoryEntry( + path: URL(string: rustEntry.path().toString())!, + parent: URL(string: rustEntry.parent().toString())!, + isFile: rustEntry.isFile(), + size: rustEntry.size() != nil ? Double(rustEntry.size()!) : nil + ) + for child in rustEntry.children() { + entry.children!.append(_addChildren(child)) + } + return entry + } + + func iterate(_ directory: URL) -> DirectoryEntry { + var directoryEntry = DirectoryEntry(path: directory, parent: directory, isFile: false) + for child in AfcFileManager.contents() { + directoryEntry.children!.append(_addChildren(child)) + } + return directoryEntry + } + + func getQuickLookURL(_ path: URL) throws -> URL { + throw NSError(domain: "AFC currently doesn't support viewing a file. however, it is possible (we should be able to copy the file outside of AFC and then view the copied file), it just hasn't been implemented", code: -1) + } +} + +private struct DirectoryEntry: Identifiable { + var id = UUID() + + var path: URL + var parent: URL + + var isFile: Bool + var size: Double? + var children: [DirectoryEntry]? = [] + + var asString: String { + let str = path.description.replacingOccurrences(of: parent.description, with: "").removingPercentEncoding! + if str.count <= 0 { + return "/" + } + return str + } +} + +private enum FileExplorerAction { + case delete + case zip + case insert + case quickLook +} + +private struct File: View { + @ObservedObject private var iO = Inject.observer + + var item: DirectoryEntry + var backend: FileExplorerBackend + @Binding var explorerHidden: Bool + + @State var quickLookURL: URL? + @State var fileExplorerAction: FileExplorerAction? + @State var hidden = false + @State var isShowingFilePicker = false + @State var selectedFile: URL? + + var body: some View { + AsyncFallibleButton(action: { + switch (fileExplorerAction) { + case .delete: + print("deleting \(item.path.description)") + try backend.delete(item.path) + + case .zip: + print("zipping \(item.path.description)") + try backend.zip(item.path) + + case .insert: + print("inserting \(selectedFile!.description) to \(item.path.description)") + + try backend.insert(file: selectedFile!, to: item.path) + explorerHidden = true + explorerHidden = false + + case .quickLook: + print("viewing \(item.path.description)") + quickLookURL = try backend.getQuickLookURL(item.path) + + default: + print("unknown action for \(item.path.description): \(String(describing: fileExplorerAction))") + } + }, label: { execute in + HStack { + Text(item.asString) + if item.isFile { + Text(getFileSize(item.size)).foregroundColor(.secondary) + } + Spacer() + Menu { + if item.isFile { + SwiftUI.Button(action: { + fileExplorerAction = .quickLook + execute() + }) { + Label("View/Share", systemSymbol: .eye) + } + } else { + SwiftUI.Button(action: { + fileExplorerAction = .zip + execute() + }) { + Label("Save to ZIP file", systemSymbol: .squareAndArrowDown) + } + + SwiftUI.Button { + isShowingFilePicker = true + } label: { + Label("Insert file", systemSymbol: .plus) + } + } + + if item.asString != "/" { + SwiftUI.Button(action: { + fileExplorerAction = .delete + execute() + }) { + Label("Delete", systemSymbol: .trash) + } + } + } label: { + Image(systemSymbol: .ellipsis) + .frame(width: 20, height: 20) // Make it easier to tap + } + } + .onChange(of: $selectedFile) { file in + guard file.wrappedValue != nil else { return } + + fileExplorerAction = .insert + execute() + } + }, afterFinish: { success in + switch (fileExplorerAction) { + case .delete: + if success { hidden = true } + + case .zip: + UIApplication.shared.open(URL(string: "shareddocuments://" + FileManager.default.documentsDirectory.description.replacingOccurrences(of: "file://", with: ""))!, options: [:], completionHandler: nil) + + default: break + } + }, wrapInButton: false) + .quickLookPreview($quickLookURL) + .sheet(isPresented: $isShowingFilePicker) { + DocumentPicker(selectedUrl: $selectedFile, supportedTypes: allUTITypes().map({ $0.identifier })) + .ignoresSafeArea() + } + .isHidden($hidden) + .enableInjection() + } + + func getFileSize(_ bytes: Double?) -> String { + guard var bytes = bytes else { return "Unknown file size" } + + // https://stackoverflow.com/a/14919494 (ported to swift) + let thresh = 1024.0; + + if (bytes < thresh) { + return String(describing: bytes) + " B"; + } + + let units = ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + var u = -1; + + while (bytes >= thresh && u < units.count - 1) { + bytes /= thresh; + u += 1; + } + + return String(format: "%.2f", bytes) + " " + units[u]; + } +} + +struct FileExplorer: View { + @ObservedObject private var iO = Inject.observer + + private var url: URL? + private var backend: FileExplorerBackend + + private init(_ url: URL?, _ backend: FileExplorerBackend) { + self.url = url + self.backend = backend + } + + static func normal(url: URL?) -> FileExplorer { + FileExplorer(url, NormalFileExplorerBackend()) + } + + static func afc() -> FileExplorer { + FileExplorer(URL(string: "/")!, AfcFileExplorerBackend()) + } + + @State var hidden = false + + var body: some View { + List([backend.iterate(url!)], children: \.children) { item in + File(item: item, backend: backend, explorerHidden: $hidden) + } + .toolbar { + ToolbarItem { + SwiftUI.Button { + hidden = true + hidden = false + } label: { + Image(systemSymbol: .arrowClockwise) + } + } + } + .isHidden($hidden) + .enableInjection() + } +} + +struct FileExplorer_Previews: PreviewProvider { + static var previews: some View { + FileExplorer.normal(url: FileManager.default.altstoreSharedDirectory) + } +} + +// https://stackoverflow.com/a/72165424 +func allUTITypes() -> [UTType] { + let types: [UTType] = + [.item, + .content, + .compositeContent, + .diskImage, + .data, + .directory, + .resolvable, + .symbolicLink, + .executable, + .mountPoint, + .aliasFile, + .urlBookmarkData, + .url, + .fileURL, + .text, + .plainText, + .utf8PlainText, + .utf16ExternalPlainText, + .utf16PlainText, + .delimitedText, + .commaSeparatedText, + .tabSeparatedText, + .utf8TabSeparatedText, + .rtf, + .html, + .xml, + .yaml, + .sourceCode, + .assemblyLanguageSource, + .cSource, + .objectiveCSource, + .swiftSource, + .cPlusPlusSource, + .objectiveCPlusPlusSource, + .cHeader, + .cPlusPlusHeader] + + let types_1: [UTType] = + [.script, + .appleScript, + .osaScript, + .osaScriptBundle, + .javaScript, + .shellScript, + .perlScript, + .pythonScript, + .rubyScript, + .phpScript, + .json, + .propertyList, + .xmlPropertyList, + .binaryPropertyList, + .pdf, + .rtfd, + .flatRTFD, + .webArchive, + .image, + .jpeg, + .tiff, + .gif, + .png, + .icns, + .bmp, + .ico, + .rawImage, + .svg, + .livePhoto, + .heif, + .heic, + .webP, + .threeDContent, + .usd, + .usdz, + .realityFile, + .sceneKitScene, + .arReferenceObject, + .audiovisualContent] + + let types_2: [UTType] = + [.movie, + .video, + .audio, + .quickTimeMovie, + UTType("com.apple.quicktime-image"), + .mpeg, + .mpeg2Video, + .mpeg2TransportStream, + .mp3, + .mpeg4Movie, + .mpeg4Audio, + .appleProtectedMPEG4Audio, + .appleProtectedMPEG4Video, + .avi, + .aiff, + .wav, + .midi, + .playlist, + .m3uPlaylist, + .folder, + .volume, + .package, + .bundle, + .pluginBundle, + .spotlightImporter, + .quickLookGenerator, + .xpcService, + .framework, + .application, + .applicationBundle, + .applicationExtension, + .unixExecutable, + .exe, + .systemPreferencesPane, + .archive, + .gzip, + .bz2, + .zip, + .appleArchive, + .spreadsheet, + .presentation, + .database, + .message, + .contact, + .vCard, + .toDoItem, + .calendarEvent, + .emailMessage, + .internetLocation, + .internetShortcut, + .font, + .bookmark, + .pkcs12, + .x509Certificate, + .epub, + .log] + .compactMap({ $0 }) + + return types + types_1 + types_2 +} diff --git a/AltStore/View Components/HintView.swift b/AltStore/View Components/HintView.swift new file mode 100644 index 00000000..ad405c10 --- /dev/null +++ b/AltStore/View Components/HintView.swift @@ -0,0 +1,42 @@ +// +// HintView.swift +// SideStore +// +// Created by Fabian Thies on 15.01.23. +// Copyright © 2023 SideStore. All rights reserved. +// + +import SwiftUI + +struct HintView: View { + + var backgroundColor: Color = Color(.tertiarySystemBackground) + + @ViewBuilder + let content: () -> Content + + var body: some View { + VStack(alignment: .leading, spacing: 8) { + self.content() + } + .padding() + .background(self.backgroundColor) + .clipShape(RoundedRectangle(cornerRadius: 8)) + } +} + +struct HintView_Previews: PreviewProvider { + static var previews: some View { + ZStack { + Color(.secondarySystemBackground).edgesIgnoringSafeArea(.all) + + HintView { + Text("Hint Title") + .bold() + + Text("This hint view can be used to tell the user something about how SideStore works.") + .foregroundColor(.secondary) + } + } + } +} diff --git a/AltStore/View Components/ModalNavigationLink.swift b/AltStore/View Components/ModalNavigationLink.swift new file mode 100644 index 00000000..fce881da --- /dev/null +++ b/AltStore/View Components/ModalNavigationLink.swift @@ -0,0 +1,45 @@ +// +// ModalNavigationLink.swift +// SideStore +// +// Created by Fabian Thies on 03.02.23. +// Copyright © 2023 SideStore. All rights reserved. +// + +import SwiftUI + +struct ModalNavigationLink: View { + let modal: () -> Modal + let label: () -> Label + + @State var isPresentingModal: Bool = false + + init(@ViewBuilder modal: @escaping () -> Modal, @ViewBuilder label: @escaping () -> Label) { + self.modal = modal + self.label = label + } + + init(_ title: String, @ViewBuilder modal: @escaping () -> Modal) where Label == Text { + self.modal = modal + self.label = { Text(title) } + } + + var body: some View { + SwiftUI.Button { + self.isPresentingModal = true + } label: { + self.label() + } + .sheet(isPresented: self.$isPresentingModal) { + self.modal() + } + } +} + +struct ModalNavigationLink_Previews: PreviewProvider { + static var previews: some View { + ModalNavigationLink("Present Modal") { + Text("Modal") + } + } +} diff --git a/AltStore/View Components/ObservableScrollView.swift b/AltStore/View Components/ObservableScrollView.swift new file mode 100644 index 00000000..38fe9270 --- /dev/null +++ b/AltStore/View Components/ObservableScrollView.swift @@ -0,0 +1,47 @@ +// +// ObservableScrollView.swift +// SideStore +// +// Created by Fabian Thies on 20.11.22. +// Copyright © 2022 Fabian Thies. All rights reserved. +// + +import SwiftUI + +struct ObservableScrollView: View { + @Namespace var scrollViewNamespace + + @Binding var scrollOffset: CGFloat + + let content: (ScrollViewProxy) -> Content + + init(scrollOffset: Binding, @ViewBuilder content: @escaping (ScrollViewProxy) -> Content) { + self._scrollOffset = scrollOffset + self.content = content + } + + var body: some View { + ScrollView { + ScrollViewReader { proxy in + content(proxy) + .background(GeometryReader { geoReader in + let offset = -geoReader.frame(in: .named(scrollViewNamespace)).minY + Color.clear + .preference(key: ScrollViewOffsetPreferenceKey.self, value: offset) + }) + } + } + .coordinateSpace(name: scrollViewNamespace) + .onPreferenceChange(ScrollViewOffsetPreferenceKey.self) { value in + scrollOffset = value + } + } +} + +struct ScrollViewOffsetPreferenceKey: PreferenceKey { + static var defaultValue = CGFloat.zero + + static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { + value += nextValue() + } +} diff --git a/AltStore/View Components/RatingStars.swift b/AltStore/View Components/RatingStars.swift new file mode 100644 index 00000000..df39d116 --- /dev/null +++ b/AltStore/View Components/RatingStars.swift @@ -0,0 +1,31 @@ +// +// RatingStars.swift +// SideStore +// +// Created by Fabian Thies on 18.11.22. +// Copyright © 2022 Fabian Thies. All rights reserved. +// + +import SwiftUI +import SFSafeSymbols + +struct RatingStars: View { + + let rating: Int + + var body: some View { + HStack(spacing: 0) { + ForEach(0..<5) { i in + Image(systemSymbol: i < rating ? .starFill : .star) + .resizable() + .aspectRatio(contentMode: .fit) + } + } + } +} + +struct RatingStars_Previews: PreviewProvider { + static var previews: some View { + RatingStars(rating: 4) + } +} diff --git a/AltStore/View Components/RoundedTextField.swift b/AltStore/View Components/RoundedTextField.swift new file mode 100644 index 00000000..0bca15c1 --- /dev/null +++ b/AltStore/View Components/RoundedTextField.swift @@ -0,0 +1,52 @@ +// +// RoundedTextField.swift +// SideStore +// +// Created by Fabian Thies on 29.11.22. +// Copyright © 2022 SideStore. All rights reserved. +// + +import SwiftUI + +struct RoundedTextField: View { + + let title: String? + let placeholder: String + @Binding var text: String + let isSecure: Bool + + init(title: String?, placeholder: String, text: Binding, isSecure: Bool = false) { + self.title = title + self.placeholder = placeholder + self._text = text + self.isSecure = isSecure + } + + init(_ placeholder: String, text: Binding, isSecure: Bool = false) { + self.init(title: nil, placeholder: placeholder, text: text, isSecure: isSecure) + } + + var body: some View { + VStack(alignment: .leading, spacing: 8) { + if let title { + Text(title.uppercased()) + .font(.system(size: 12)) + .foregroundColor(.secondary) + .padding(.horizontal) + } + + HStack(alignment: .center) { + if isSecure { + SecureField(placeholder, text: $text) + } else { + TextField(placeholder, text: $text) + } + } + .padding() + .background( + RoundedRectangle(cornerRadius: 12) + .foregroundColor(Color(.secondarySystemBackground)) + ) + } + } +} diff --git a/AltStore/View Extensions/EnvironmentValues.swift b/AltStore/View Extensions/EnvironmentValues.swift new file mode 100644 index 00000000..3355bd3c --- /dev/null +++ b/AltStore/View Extensions/EnvironmentValues.swift @@ -0,0 +1,16 @@ +// +// EnvironmentValues.swift +// SideStore +// +// Created by Fabian Thies on 29.11.22. +// Copyright © 2022 SideStore. All rights reserved. +// + +import SwiftUI + +@available(iOS 14.0, *) +extension EnvironmentValues { + var dismiss: () -> Void { + { presentationMode.wrappedValue.dismiss() } + } +} diff --git a/AltStore/View Extensions/Modifiers.swift b/AltStore/View Extensions/Modifiers.swift new file mode 100644 index 00000000..dfae90ef --- /dev/null +++ b/AltStore/View Extensions/Modifiers.swift @@ -0,0 +1,35 @@ +// +// Modifiers.swift +// SideStore +// +// Created by Fabian Thies on 01.12.22. +// Copyright © 2022 SideStore. All rights reserved. +// + +import SwiftUI + +extension View { + + @ViewBuilder func `if`(_ condition: Bool, @ViewBuilder transform: (Self) -> Content) -> some View { + if condition { + transform(self) + } else { + self + } + } + + + @ViewBuilder func searchable(text: Binding, placeholder: String) -> some View { + if #available(iOS 15.0, *) { + self.searchable(text: text, prompt: Text(placeholder)) + } else { + self + } + } + + @ViewBuilder func tintedBackground(_ color: Color) -> some View { + self + .blurBackground(.systemUltraThinMaterial) + .background(color.opacity(0.4)) + } +} diff --git a/AltStore/View Extensions/Styles/FilledButtonStyle.swift b/AltStore/View Extensions/Styles/FilledButtonStyle.swift new file mode 100644 index 00000000..55879cba --- /dev/null +++ b/AltStore/View Extensions/Styles/FilledButtonStyle.swift @@ -0,0 +1,47 @@ +// +// FilledButtonStyle.swift +// SideStore +// +// Created by Fabian Thies on 29.11.22. +// Copyright © 2022 SideStore. All rights reserved. +// + +import SwiftUI + +struct FilledButtonStyle: ButtonStyle { + var isLoading: Bool = false + + func makeBody(configuration: Configuration) -> some View { + ZStack { + configuration.label + .opacity(isLoading ? 0 : 1) + + if isLoading { + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + } + } + .foregroundColor(.white) + .multilineTextAlignment(.center) + .frame(maxWidth: .infinity) + .padding() + .background( + RoundedRectangle(cornerRadius: 12) + .foregroundColor(.accentColor) + ) + .opacity(configuration.isPressed || isLoading ? 0.7 : 1) + .disabled(isLoading) + } +} + +struct FilledButtonStyle_Previews: PreviewProvider { + static var previews: some View { + SwiftUI.Button { + + } label: { + Label("Test Button", systemImage: "testtube.2") + .buttonStyle(FilledButtonStyle()) + } + + } +} diff --git a/AltStore/View Extensions/Styles/PillButtonProgressViewStyle.swift b/AltStore/View Extensions/Styles/PillButtonProgressViewStyle.swift new file mode 100644 index 00000000..7ea56fb2 --- /dev/null +++ b/AltStore/View Extensions/Styles/PillButtonProgressViewStyle.swift @@ -0,0 +1,28 @@ +// +// PillButtonProgressViewStyle.swift +// SideStore +// +// Created by Fabian Thies on 22.11.22. +// Copyright © 2022 Fabian Thies. All rights reserved. +// + +import SwiftUI + +struct PillButtonProgressViewStyle: ProgressViewStyle { + let tint: Color + + func makeBody(configuration: Configuration) -> some View { + ZStack(alignment: .leading) { + Capsule(style: .continuous) + .foregroundColor(tint.opacity(0.15)) + + GeometryReader { proxy in + Capsule(style: .continuous) +// .frame(width: proxy.size.width * (configuration.fractionCompleted ?? 0.0)) + .foregroundColor(tint) + .offset(x: -proxy.size.width * (1 - (configuration.fractionCompleted ?? 1))) + } + } + .animation(.easeInOut(duration: 0.2)) + } +} diff --git a/AltStore/View Extensions/Styles/PillButtonStyle.swift b/AltStore/View Extensions/Styles/PillButtonStyle.swift new file mode 100644 index 00000000..00197e85 --- /dev/null +++ b/AltStore/View Extensions/Styles/PillButtonStyle.swift @@ -0,0 +1,59 @@ +// +// PillButtonStyle.swift +// SideStore +// +// Created by Fabian Thies on 18.11.22. +// Copyright © 2022 Fabian Thies. All rights reserved. +// + +import SwiftUI + +struct PillButtonStyle: ButtonStyle { + + let tintColor: UIColor + var progress: Progress? + + func makeBody(configuration: Configuration) -> some View { + ZStack { + if progress == nil { + configuration.label + .opacity(configuration.isPressed ? 0.4 : 1.0) + } else { + ProgressView() + .progressViewStyle(DefaultProgressViewStyle()) + } + } + .frame(minWidth: 40) + .padding(.horizontal, 16) + .padding(.vertical, 6) + .background(background) + .foregroundColor(self.progress == nil ? .white : Color(tintColor)) + .clipShape(Capsule()) + } + + var background: some View { + ZStack { + if let progress { + Color(tintColor).opacity(0.15) + + ProgressView(progress) + .progressViewStyle(PillButtonProgressViewStyle(tint: Color(tintColor))) + } else { + Color(tintColor) + } + } + } +} + + +struct PillButtonStyle_Previews: PreviewProvider { + + static var previews: some View { + SwiftUI.Button { + + } label: { + Text("Label").bold() + } + .buttonStyle(PillButtonStyle(tintColor: Asset.accentColor.color)) + } +} diff --git a/AltStore/View Extensions/UIView Representables/ActivityView.swift b/AltStore/View Extensions/UIView Representables/ActivityView.swift new file mode 100644 index 00000000..b3bf4d39 --- /dev/null +++ b/AltStore/View Extensions/UIView Representables/ActivityView.swift @@ -0,0 +1,21 @@ +// +// ActivityView.swift +// SideStore +// +// Created by Fabian Thies on 19.05.23. +// Copyright © 2023 SideStore. All rights reserved. +// + +import SwiftUI +import UIKit + + +struct ActivityView: UIViewControllerRepresentable { + let items: [Any] + + func makeUIViewController(context: UIViewControllerRepresentableContext) -> UIActivityViewController { + return UIActivityViewController(activityItems: items, applicationActivities: nil) + } + + func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext) {} +} diff --git a/AltStore/View Extensions/UIView Representables/AppStoreProductView.swift b/AltStore/View Extensions/UIView Representables/AppStoreProductView.swift new file mode 100644 index 00000000..57b67359 --- /dev/null +++ b/AltStore/View Extensions/UIView Representables/AppStoreProductView.swift @@ -0,0 +1,87 @@ +// +// AppStoreProductView.swift +// SideStore +// +// Created by Fabian Thies on 25.02.23. +// Copyright © 2023 SideStore. All rights reserved. +// + +import SwiftUI +import StoreKit + + +struct AppStoreView: UIViewControllerRepresentable { + typealias UIViewControllerType = AppStoreProductViewController + + var isVisible: Binding + let itunesItemId: Int + + func makeUIViewController(context: Context) -> AppStoreProductViewController { + AppStoreProductViewController(isVisible: self.isVisible, itunesId: self.itunesItemId) + } + + func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { + if self.isVisible.wrappedValue { + uiViewController.presentStoreProduct() + } + } +} + + +class AppStoreProductViewController: UIViewController { + + private var isVisible: Binding + private let itunesId: Int + + init(isVisible: Binding, itunesId: Int) { + self.isVisible = isVisible + self.itunesId = itunesId + + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + } + + func presentStoreProduct() { + let storeProductViewController = SKStoreProductViewController() + storeProductViewController.delegate = self + + let parameters = [SKStoreProductParameterITunesItemIdentifier: self.itunesId] + storeProductViewController.loadProduct(withParameters: parameters) { (success, error) -> Void in + if let error = error { + print("Failed to load App Store product: \(error.localizedDescription)") + } + guard success else { + return + } + + self.present(storeProductViewController, animated: true, completion: nil) + } + } +} + +// MARK: - SKStoreProductViewControllerDelegate + +extension AppStoreProductViewController: SKStoreProductViewControllerDelegate { + + func productViewControllerDidFinish(_ viewController: SKStoreProductViewController) { + DispatchQueue.main.async { + self.isVisible.wrappedValue = false + } +// viewController.presentingViewController?.dismiss(animated: true, completion: nil) + } +} diff --git a/AltStore/View Extensions/UIView Representables/DocumentPicker.swift b/AltStore/View Extensions/UIView Representables/DocumentPicker.swift new file mode 100644 index 00000000..4b65491a --- /dev/null +++ b/AltStore/View Extensions/UIView Representables/DocumentPicker.swift @@ -0,0 +1,52 @@ +// +// DocumentPicker.swift +// SideStore +// +// Created by Fabian Thies on 20.12.22. +// Copyright © 2022 SideStore. All rights reserved. +// + +import UIKit +import SwiftUI + +struct DocumentPicker: UIViewControllerRepresentable { + internal class Coordinator: NSObject { + var parent: DocumentPicker + + init(_ parent: DocumentPicker) { + self.parent = parent + } + } + + @Binding var selectedUrl: URL? + let supportedTypes: [String] + + func makeCoordinator() -> Coordinator { + Coordinator(self) + } + + func makeUIViewController(context: Context) -> some UIViewController { + + let documentPickerViewController = UIDocumentPickerViewController(documentTypes: supportedTypes, in: .import) + documentPickerViewController.delegate = context.coordinator + return documentPickerViewController + } + + func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {} +} + +extension DocumentPicker.Coordinator: UIDocumentPickerDelegate { + + func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { + self.parent.selectedUrl = nil + } + + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + + guard let firstURL = urls.first else { + return + } + + self.parent.selectedUrl = firstURL + } +} diff --git a/AltStore/View Extensions/UIView Representables/FilePreviewView.swift b/AltStore/View Extensions/UIView Representables/FilePreviewView.swift new file mode 100644 index 00000000..0e618ae1 --- /dev/null +++ b/AltStore/View Extensions/UIView Representables/FilePreviewView.swift @@ -0,0 +1,48 @@ +// +// FilePreviewView.swift +// SideStore +// +// Created by Fabian Thies on 03.02.23. +// Copyright © 2023 SideStore. All rights reserved. +// + +import SwiftUI +import UIKit +import QuickLook + +struct FilePreviewView: UIViewControllerRepresentable { + let urls: [URL] + + func makeCoordinator() -> Coordinator { + Coordinator(urls: self.urls) + } + + func makeUIViewController(context: Context) -> some UIViewController { + let previewController = QLPreviewController() + previewController.dataSource = context.coordinator + return UINavigationController(rootViewController: previewController) + } + + func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { + context.coordinator.urls = self.urls + } +} + +extension FilePreviewView { + + class Coordinator: QLPreviewControllerDataSource { + var urls: [URL] + + init(urls: [URL]) { + self.urls = urls + } + + func numberOfPreviewItems(in controller: QLPreviewController) -> Int { + urls.count + } + + func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem { + urls[index] as QLPreviewItem + } + } +} diff --git a/AltStore/View Extensions/UIView Representables/MailComposeView.swift b/AltStore/View Extensions/UIView Representables/MailComposeView.swift new file mode 100644 index 00000000..a773befc --- /dev/null +++ b/AltStore/View Extensions/UIView Representables/MailComposeView.swift @@ -0,0 +1,71 @@ +// +// MailComposeView.swift +// SideStore +// +// Created by Fabian Thies on 04.02.23. +// Copyright © 2023 SideStore. All rights reserved. +// + +import SwiftUI +import MessageUI + +struct MailComposeView: UIViewControllerRepresentable { + typealias ActionHandler = () -> Void + typealias ErrorHandler = (Error) -> Void + + static var canSendMail: Bool { + MFMailComposeViewController.canSendMail() + } + + let recipients: [String] + let subject: String + var body: String? = nil + + var onMailSent: ActionHandler? = nil + var onError: ErrorHandler? = nil + + func makeCoordinator() -> Coordinator { + Coordinator(mailSentHandler: self.onMailSent, errorHandler: self.onError) + } + + func makeUIViewController(context: Context) -> some UIViewController { + let mailViewController = MFMailComposeViewController() + mailViewController.mailComposeDelegate = context.coordinator + mailViewController.setToRecipients(self.recipients) + mailViewController.setSubject(self.subject) + + if let body { + mailViewController.setMessageBody(body, isHTML: false) + } + + return mailViewController + } + + func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { + + } +} + +extension MailComposeView { + class Coordinator: NSObject, MFMailComposeViewControllerDelegate { + + let mailSentHandler: ActionHandler? + let errorHandler: ErrorHandler? + + init(mailSentHandler: ActionHandler?, errorHandler: ErrorHandler?) { + self.mailSentHandler = mailSentHandler + self.errorHandler = errorHandler + super.init() + } + + func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { + if result == .sent, let mailSentHandler { + mailSentHandler() + } else if result == .failed, let errorHandler, let error { + errorHandler(error) + } + + controller.dismiss(animated: true) + } + } +} diff --git a/AltStore/View Extensions/UIView Representables/SafariView.swift b/AltStore/View Extensions/UIView Representables/SafariView.swift new file mode 100644 index 00000000..8976acf9 --- /dev/null +++ b/AltStore/View Extensions/UIView Representables/SafariView.swift @@ -0,0 +1,20 @@ +// +// SafariView.swift +// SideStore +// +// Created by Fabian Thies on 18.11.22. +// Copyright © 2022 Fabian Thies. All rights reserved. +// + +import SwiftUI +import SafariServices + +struct SafariView: UIViewControllerRepresentable { + let url: URL + + func makeUIViewController(context: Context) -> some UIViewController { + SFSafariViewController(url: url) + } + + func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { } +} diff --git a/AltStore/View Extensions/UIView Representables/SiriShortcutSetupView.swift b/AltStore/View Extensions/UIView Representables/SiriShortcutSetupView.swift new file mode 100644 index 00000000..ccfc3e25 --- /dev/null +++ b/AltStore/View Extensions/UIView Representables/SiriShortcutSetupView.swift @@ -0,0 +1,52 @@ +// +// SiriShortcutSetupView.swift +// SideStore +// +// Created by Fabian Thies on 21.11.22. +// Copyright © 2022 Fabian Thies. All rights reserved. +// + +import SwiftUI + +import UIKit +import Intents +import IntentsUI + +struct SiriShortcutSetupView: UIViewControllerRepresentable { + + let shortcut: INShortcut + + func makeUIViewController(context: Context) -> some UIViewController { + let viewController = INUIAddVoiceShortcutViewController(shortcut: shortcut) + viewController.delegate = context.coordinator + viewController.modalPresentationStyle = .formSheet + return viewController + } + + func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { } + + func makeCoordinator() -> Coordinator { + Coordinator(shortcut: shortcut) + } + + class Coordinator: NSObject { + + let shortcut: INShortcut + + init(shortcut: INShortcut) { + self.shortcut = shortcut + } + } +} + +extension SiriShortcutSetupView.Coordinator: INUIAddVoiceShortcutViewControllerDelegate { + func addVoiceShortcutViewController(_ controller: INUIAddVoiceShortcutViewController, didFinishWith voiceShortcut: INVoiceShortcut?, error: Error?) { + + // TODO: Handle errors + controller.dismiss(animated: true) + } + + func addVoiceShortcutViewControllerDidCancel(_ controller: INUIAddVoiceShortcutViewController) { + controller.dismiss(animated: true) + } +} diff --git a/AltStore/View Extensions/UIView Representables/VisualEffectView.swift b/AltStore/View Extensions/UIView Representables/VisualEffectView.swift new file mode 100644 index 00000000..4543749d --- /dev/null +++ b/AltStore/View Extensions/UIView Representables/VisualEffectView.swift @@ -0,0 +1,28 @@ +// +// VisualEffectView.swift +// SideStore +// +// Created by Fabian Thies on 01.12.22. +// Copyright © 2022 SideStore. All rights reserved. +// + +import SwiftUI + +struct VisualEffectView: UIViewRepresentable { + let blurStyle: UIBlurEffect.Style + + func makeUIView(context: Context) -> some UIView { + UIVisualEffectView(effect: UIBlurEffect(style: blurStyle)) + } + + func updateUIView(_ uiView: UIViewType, context: Context) { } +} + + +extension View { + @ViewBuilder + func blurBackground(_ style: UIBlurEffect.Style) -> some View { + self + .background(VisualEffectView(blurStyle: style)) + } +} diff --git a/AltStore/Views/App Detail/AppDetailView.swift b/AltStore/Views/App Detail/AppDetailView.swift new file mode 100644 index 00000000..1f5d794f --- /dev/null +++ b/AltStore/Views/App Detail/AppDetailView.swift @@ -0,0 +1,454 @@ +// +// AppDetailView.swift +// SideStore +// +// Created by Fabian Thies on 18.11.22. +// Copyright © 2022 Fabian Thies. All rights reserved. +// + +import SwiftUI +import AsyncImage +import ExpandableText +import SFSafeSymbols +import AltStoreCore + +struct AppDetailView: View { + + let storeApp: StoreApp + + let byteCountFormatter: ByteCountFormatter = { + let formatter = ByteCountFormatter() + return formatter + }() + + @State var scrollOffset: CGFloat = .zero + let maxContentCornerRadius: CGFloat = 24 + let headerViewHeight: CGFloat = 140 + let permissionColumns = 4 + + var headerBlurRadius: CGFloat { + min(20, max(0, 20 - (scrollOffset / -150) * 20)) + } + var isHeaderViewVisible: Bool { + scrollOffset < headerViewHeight + 12 + } + var contentCornerRadius: CGFloat { + max(CGFloat.zero, min(maxContentCornerRadius, maxContentCornerRadius * (1 - self.scrollOffset / self.headerViewHeight))) + } + + var canRateApp: Bool { + self.storeApp.installedApp != nil + } + + + var body: some View { + ObservableScrollView(scrollOffset: $scrollOffset) { proxy in + LazyVStack { + headerView + .frame(height: headerViewHeight) + + contentView + } + } + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItemGroup(placement: .navigationBarTrailing) { + AppPillButton(app: storeApp) + .disabled(isHeaderViewVisible) + .offset(y: isHeaderViewVisible ? 12 : 0) + .opacity(isHeaderViewVisible ? 0 : 1) + .animation(.easeInOut(duration: 0.2), value: isHeaderViewVisible) + } + + ToolbarItemGroup(placement: .principal) { + HStack { + Spacer() + AppIconView(iconUrl: storeApp.iconURL, isSideStore: storeApp.isSideStore, size: 24) + Text(storeApp.name) + .bold() + Spacer() + } + .offset(y: isHeaderViewVisible ? 12 : 0) + .opacity(isHeaderViewVisible ? 0 : 1) + .animation(.easeInOut(duration: 0.2), value: isHeaderViewVisible) + } + } + } + + + var headerView: some View { + ZStack(alignment: .center) { + GeometryReader { proxy in + AppIconView(iconUrl: storeApp.iconURL, isSideStore: storeApp.isSideStore, size: proxy.frame(in: .global).width) + .blur(radius: headerBlurRadius) + .offset(y: min(0, scrollOffset)) + } + .padding() + + AppRowView(app: storeApp) + .padding(.horizontal) + } + } + + var contentView: some View { + VStack(alignment: .leading, spacing: 24) { + VStack(alignment: .leading, spacing: 32) { + if storeApp.isFromOfficialSource { + officialAppBadge + } else if storeApp.isFromTrustedSource { + trustedAppBadge + } + + if let subtitle = storeApp.subtitle { + VStack { + if #available(iOS 15.0, *) { + Image(systemSymbol: .quoteOpening) + .foregroundColor(.secondary.opacity(0.5)) + .imageScale(.large) + .transformEffect(CGAffineTransform(a: 1, b: 0, c: -0.3, d: 1, tx: 0, ty: 0)) + .frame(maxWidth: .infinity, alignment: .leading) + .offset(x: 30) + } + + Text(subtitle) + .bold() + .italic() + .multilineTextAlignment(.center) + .frame(maxWidth: .infinity) + + if #available(iOS 15.0, *) { + Image(systemSymbol: .quoteClosing) + .foregroundColor(.secondary.opacity(0.5)) + .imageScale(.large) + .transformEffect(CGAffineTransform(a: 1, b: 0, c: -0.3, d: 1, tx: 0, ty: 0)) + .frame(maxWidth: .infinity, alignment: .trailing) + .offset(x: -30) + } + } + .padding(.horizontal) + } + + if !storeApp.screenshotURLs.isEmpty { + // Equatable: Only reload the view if the screenshots change. + // This prevents unnecessary redraws on scroll. + AppScreenshotsScrollView(urls: storeApp.screenshotURLs) + .equatable() + } else { + VStack() { + Text(L10n.AppDetailView.noScreenshots) + .italic() + .foregroundColor(.secondary) + } + .frame(maxWidth: .infinity) + .padding(.horizontal) + } + + ExpandableText(text: storeApp.localizedDescription) + .lineLimit(6) + .expandButton(TextSet(text: L10n.AppDetailView.more, font: .callout, color: .accentColor)) + .padding(.horizontal) + } + + + VStack(spacing: 24) { + Divider() + + currentVersionView + + Divider() + + ratingsView + + Divider() + + permissionsView + + Divider() + + informationView + + if !(storeApp.isFromOfficialSource || storeApp.isFromTrustedSource) { + Divider() + + reportButton + } + } + .padding(.horizontal) + } + .padding(.vertical) + .background( + RoundedRectangle(cornerRadius: contentCornerRadius) + .foregroundColor(Color(UIColor.systemBackground)) + .shadow(radius: isHeaderViewVisible ? 12 : 0) + ) + } + + var officialAppBadge: some View { + HintView(backgroundColor: Color(UIColor.secondarySystemBackground)) { + HStack { + Spacer() + Image(systemSymbol: .checkmarkSealFill) + Text(L10n.AppDetailView.Badge.official) + Spacer() + } + .foregroundColor(.accentColor) + } + .padding(.horizontal) + } + + var trustedAppBadge: some View { + HintView(backgroundColor: Color(UIColor.secondarySystemBackground)) { + HStack { + Spacer() + Image(systemSymbol: .shieldLefthalfFill) + Text(L10n.AppDetailView.Badge.trusted) + Spacer() + } + .foregroundColor(.accentColor) + } + .padding(.horizontal) + } + + var currentVersionView: some View { + VStack(alignment: .leading, spacing: 8) { + VStack { + HStack(alignment: .firstTextBaseline) { + Text(L10n.AppDetailView.whatsNew) + .bold() + .font(.title3) + + Spacer() + + NavigationLink { + AppVersionHistoryView(storeApp: self.storeApp) + } label: { + Text(L10n.AppDetailView.WhatsNew.versionHistory) + } + } + + if let latestVersion = storeApp.latestVersion { + HStack { + Text(L10n.AppDetailView.version(latestVersion.version)) + Spacer() + Text(DateFormatterHelper.string(forRelativeDate: latestVersion.date)) + } + .font(.callout) + .foregroundColor(.secondary) + } + } + + if let versionDescription = storeApp.versionDescription { + ExpandableText(text: versionDescription) + .lineLimit(5) + .expandButton(TextSet(text: L10n.AppDetailView.more, font: .callout, color: .accentColor)) + } else { + Text(L10n.AppDetailView.noVersionInformation) + .foregroundColor(.secondary) + } + + + if true { + SwiftUI.Button { + UIApplication.shared.open(URL(string: "https://github.com/SideStore/SideStore")!) { _ in } + } label: { + HStack { + Text(L10n.AppDetailView.WhatsNew.showOnGithub) + Image(systemSymbol: .arrowUpForwardSquare) + } + } + } + } + } + + var ratingsView: some View { + VStack(alignment: .leading, spacing: 8) { + HStack(alignment: .firstTextBaseline) { + Text(L10n.AppDetailView.whatsNew) + .bold() + .font(.title3) + + Spacer() + + NavigationLink { + AppVersionHistoryView(storeApp: self.storeApp) + } label: { + Text(L10n.AppDetailView.Reviews.seeAll) + } + } + + HStack(spacing: 40) { + VStack { + Text("3.0") + .font(.system(size: 48, weight: .bold, design: .rounded)) + .opacity(0.8) + Text(L10n.AppDetailView.Reviews.outOf(5)) + .bold() + .font(.callout) + .foregroundColor(.secondary) + } + + VStack(alignment: .trailing) { + LazyVGrid(columns: [GridItem(.fixed(48), alignment: .trailing), GridItem(.flexible())], spacing: 2) { + ForEach(Array(1...5).reversed(), id: \.self) { rating in + HStack(spacing: 2) { + ForEach(0.. Bool { + lhs.urls == rhs.urls + } +} + +struct AppScreenshotsPreview_Previews: PreviewProvider { + + static let context = DatabaseManager.shared.viewContext + static let app = StoreApp.makeAltStoreApp(in: context) + + static var previews: some View { + Color.clear + .sheet(isPresented: .constant(true)) { + NavigationView { + AppScreenshotsPreview(urls: app.screenshotURLs) + } + } + } +} diff --git a/AltStore/Views/App Detail/AppScreenshotsScrollView.swift b/AltStore/Views/App Detail/AppScreenshotsScrollView.swift new file mode 100644 index 00000000..d3eeed2a --- /dev/null +++ b/AltStore/Views/App Detail/AppScreenshotsScrollView.swift @@ -0,0 +1,71 @@ +// +// AppScreenshotsScrollView.swift +// SideStore +// +// Created by Fabian Thies on 27.11.22. +// Copyright © 2022 SideStore. All rights reserved. +// + +import SwiftUI +import AsyncImage + + +/// Horizontal ScrollView with an asynchronously loaded image for each screenshot URL +/// +/// The struct inherits the `Equatable` protocol and implements the respective comparisation function to prevent the view from being constantly re-rendered when a `@State` change in the parent view occurs. +/// This way, the `AppScreenshotsScrollView` will only be reloaded when the parameters change. +struct AppScreenshotsScrollView: View { + let urls: [URL] + var aspectRatio: CGFloat = 9/16 + var height: CGFloat = 400 + + @State var selectedScreenshotIndex: Int? + + var body: some View { + ScrollView(.horizontal, showsIndicators: false) { + HStack { + ForEach(Array(urls.enumerated()), id: \.offset) { i, url in + SwiftUI.Button { + self.selectedScreenshotIndex = i + } label: { + AppScreenshot(url: url) + } + } + } + .padding(.horizontal) + } + .frame(height: height) + .shadow(radius: 12) + .sheet(item: self.$selectedScreenshotIndex) { index in + NavigationView { + AppScreenshotsPreview(urls: urls, aspectRatio: aspectRatio, initialIndex: index) + } + } + } +} + +extension AppScreenshotsScrollView: Equatable { + /// Prevent re-rendering of the view if the parameters didn't change + static func == (lhs: AppScreenshotsScrollView, rhs: AppScreenshotsScrollView) -> Bool { + lhs.urls == rhs.urls && lhs.aspectRatio == rhs.aspectRatio && lhs.height == rhs.height + } +} + +extension Int: Identifiable { + public var id: Int { + self + } +} + + +import AltStoreCore + +struct AppScreenshotsScrollView_Previews: PreviewProvider { + + static let context = DatabaseManager.shared.viewContext + static let app = StoreApp.makeAltStoreApp(in: context) + + static var previews: some View { + AppScreenshotsScrollView(urls: app.screenshotURLs) + } +} diff --git a/AltStore/Views/App Detail/AppVersionHistoryView.swift b/AltStore/Views/App Detail/AppVersionHistoryView.swift new file mode 100644 index 00000000..ac95e88a --- /dev/null +++ b/AltStore/Views/App Detail/AppVersionHistoryView.swift @@ -0,0 +1,55 @@ +// +// AppVersionHistoryView.swift +// SideStore +// +// Created by Fabian Thies on 28.01.23. +// Copyright © 2023 SideStore. All rights reserved. +// + +import SwiftUI +import AltStoreCore +import ExpandableText + +struct AppVersionHistoryView: View { + let storeApp: StoreApp + + var body: some View { + List { + ForEach(storeApp.versions.sorted(by: { $0.date < $1.date }), id: \.version) { version in + VStack(spacing: 8) { + HStack { + Text(version.version).bold() + Spacer() + Text(DateFormatterHelper.string(forRelativeDate: version.date)) + .foregroundColor(.secondary) + } + + if let versionDescription = version.localizedDescription { + ExpandableText(text: versionDescription) + .lineLimit(3) + .expandButton(TextSet(text: L10n.AppDetailView.more, font: .callout, color: .accentColor)) + .buttonStyle(.plain) + } else { + Text("No version desciption available") + .italic() + .foregroundColor(.secondary) + } + } + } + } + .listStyle(PlainListStyle()) + .navigationTitle("Version History") + } +} + +struct AppVersionHistoryView_Previews: PreviewProvider { + + static let context = DatabaseManager.shared.viewContext + static let app = StoreApp.makeAltStoreApp(in: context) + + static var previews: some View { + NavigationView { + AppVersionHistoryView(storeApp: app) + } + } +} diff --git a/AltStore/Views/App Detail/WriteAppReviewView.swift b/AltStore/Views/App Detail/WriteAppReviewView.swift new file mode 100644 index 00000000..b462defd --- /dev/null +++ b/AltStore/Views/App Detail/WriteAppReviewView.swift @@ -0,0 +1,102 @@ +// +// WriteAppReviewView.swift +// SideStore +// +// Created by Fabian Thies on 19.02.23. +// Copyright © 2023 SideStore. All rights reserved. +// + +import SwiftUI +import AltStoreCore + +struct WriteAppReviewView: View { + @Environment(\.dismiss) var dismiss + + let storeApp: StoreApp + + @State var currentRating = 0 + @State var reviewText = "" + + var canSendReview: Bool { + // Only allow the user to send the review if a rating has been set and + // the review text is either empty or doesn't contain only whitespaces. + self.currentRating > 0 && ( + self.reviewText.isEmpty || !self.reviewText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + ) + } + + var body: some View { + List { + // App Information + HStack { + AppIconView(iconUrl: storeApp.iconURL, isSideStore: storeApp.isSideStore, size: 50) + VStack(alignment: .leading) { + Text(storeApp.name) + .bold() + Text(storeApp.developerName) + .font(.callout) + .foregroundColor(.secondary) + } + } + + // Rating + Section { + HStack { + Spacer() + ForEach(1...5) { rating in + SwiftUI.Button { + self.currentRating = rating + } label: { + Image(systemSymbol: rating > self.currentRating ? .star : .starFill) + .resizable() + .aspectRatio(contentMode: .fit) + } + .buttonStyle(PlainButtonStyle()) + .frame(maxHeight: 40) + } + Spacer() + } + .foregroundColor(.yellow) + } header: { + Text("Rate the App") + } + + // Review + Section { + TextEditor(text: self.$reviewText) + .frame(minHeight: 100, maxHeight: 250) + } header: { + Text("Leave a Review (optional)") + } + } + .navigationTitle("Write a Review") + .toolbar { + ToolbarItem(placement: .cancellationAction) { + SwiftUI.Button("Cancel", action: self.dismiss) + } + + ToolbarItem(placement: .confirmationAction) { + SwiftUI.Button("Send", action: self.sendReview) + .disabled(!self.canSendReview) + } + } + } + + + private func sendReview() { + NotificationManager.shared.showNotification(title: "Feature not Implemented") + self.dismiss() + } +} + +struct WriteAppReviewView_Previews: PreviewProvider { + + static let context = DatabaseManager.shared.viewContext + static let app = StoreApp.makeAltStoreApp(in: context) + + static var previews: some View { + NavigationView { + WriteAppReviewView(storeApp: app) + } + } +} diff --git a/AltStore/Views/Browse/AddSourceView.swift b/AltStore/Views/Browse/AddSourceView.swift new file mode 100644 index 00000000..24ee83d9 --- /dev/null +++ b/AltStore/Views/Browse/AddSourceView.swift @@ -0,0 +1,56 @@ +// +// AddSourceView.swift +// SideStore +// +// Created by Fabian Thies on 20.11.22. +// Copyright © 2022 Fabian Thies. All rights reserved. +// + +import SwiftUI +import SFSafeSymbols + +struct AddSourceView: View { + + @State var sourceUrlText: String = "" + + var continueHandler: (_ urlText: String) -> () + + var body: some View { + List { + Section { + TextField("https://connect.altstore.ml", text: $sourceUrlText) + .keyboardType(.URL) + .autocapitalization(.none) + .autocorrectionDisabled() + } header: { + Text(L10n.AddSourceView.sourceURL) + } footer: { + VStack(alignment: .leading, spacing: 4) { + Text(L10n.AddSourceView.sourceWarning) + + HStack(alignment: .top) { + Image(systemSymbol: .exclamationmarkTriangleFill) + + Text(L10n.AddSourceView.sourceWarningContinued) + } + } + } + + SwiftUI.Button { + self.continueHandler(self.sourceUrlText) + } label: { + Text(L10n.AddSourceView.continue) + } + .disabled(URL(string: self.sourceUrlText)?.host == nil) + } + .listStyle(InsetGroupedListStyle()) + .navigationTitle(L10n.AddSourceView.title) + .navigationBarTitleDisplayMode(.inline) + } +} + +struct AddSourceView_Previews: PreviewProvider { + static var previews: some View { + AddSourceView(continueHandler: { _ in }) + } +} diff --git a/AltStore/Views/Browse/BrowseAppPreviewView.swift b/AltStore/Views/Browse/BrowseAppPreviewView.swift new file mode 100644 index 00000000..73f443da --- /dev/null +++ b/AltStore/Views/Browse/BrowseAppPreviewView.swift @@ -0,0 +1,42 @@ +// +// BrowseAppPreviewView.swift +// SideStore +// +// Created by Fabian Thies on 20.11.22. +// Copyright © 2022 Fabian Thies. All rights reserved. +// + +import SwiftUI +import AsyncImage +import AltStoreCore + +struct BrowseAppPreviewView: View { + let storeApp: StoreApp + + var body: some View { + VStack(spacing: 16) { + AppRowView(app: storeApp) + + if let subtitle = storeApp.subtitle { + Text(subtitle) + .multilineTextAlignment(.center) + } + + if !storeApp.screenshotURLs.isEmpty { + HStack { + ForEach(storeApp.screenshotURLs.prefix(2)) { url in + AppScreenshot(url: url) + } + } + .frame(height: 300) + .shadow(radius: 8) + } + } + } +} + +//struct BrowseAppPreviewView_Previews: PreviewProvider { +// static var previews: some View { +// BrowseAppPreviewView() +// } +//} diff --git a/AltStore/Views/Browse/BrowseView.swift b/AltStore/Views/Browse/BrowseView.swift new file mode 100644 index 00000000..d082deed --- /dev/null +++ b/AltStore/Views/Browse/BrowseView.swift @@ -0,0 +1,165 @@ +// +// BrowseView.swift +// SideStoreUI +// +// Created by Fabian Thies on 18.11.22. +// Copyright © 2022 Fabian Thies. All rights reserved. +// + +import SwiftUI +import SFSafeSymbols +import AltStoreCore + +struct BrowseView: View { + @SwiftUI.FetchRequest(sortDescriptors: [ + NSSortDescriptor(keyPath: \StoreApp.sourceIdentifier, ascending: true), + NSSortDescriptor(keyPath: \StoreApp.sortIndex, ascending: true), + NSSortDescriptor(keyPath: \StoreApp.name, ascending: true), + NSSortDescriptor(keyPath: \StoreApp.bundleIdentifier, ascending: true) + ], predicate: NSPredicate(format: "%K != %@", #keyPath(StoreApp.bundleIdentifier), StoreApp.altstoreAppID)) + var apps: FetchedResults + + var filteredApps: [StoreApp] { + apps.items(matching: self.searchText) + } + + @State + var selectedStoreApp: StoreApp? + + @State var searchText = "" + + @State var isShowingSourcesView = false + + var body: some View { + ScrollView { + VStack(alignment: .leading) { + if searchText.isEmpty { + VStack(alignment: .leading, spacing: 32) { + promotedCategoriesView + + Text(L10n.BrowseView.Section.AllApps.title) + .font(.title2) + .bold() + } + } + + if searchText.isEmpty, filteredApps.count == 0 { + HintView { + Text(L10n.BrowseView.Hints.NoApps.title) + .bold() + + Text(L10n.BrowseView.Hints.NoApps.text) + .font(.callout) + .foregroundColor(.secondary) + + SwiftUI.Button { + self.isShowingSourcesView = true + } label: { + Label(L10n.BrowseView.Hints.NoApps.addSource, systemSymbol: .plus) + } + .buttonStyle(FilledButtonStyle()) + .padding(.top, 8) + } + } else { + LazyVStack(spacing: 32) { + ForEach(filteredApps, id: \.bundleIdentifier) { app in + NavigationLink { + AppDetailView(storeApp: app) + } label: { + BrowseAppPreviewView(storeApp: app) + } + .buttonStyle(PlainButtonStyle()) + } + } + } + } + .padding() + .searchable(text: self.$searchText, placeholder: L10n.BrowseView.search) + } + .background(Color(UIColor.systemGroupedBackground).ignoresSafeArea()) + .navigationTitle(L10n.BrowseView.title) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + SwiftUI.Button { + self.isShowingSourcesView = true + } label: { + Text(L10n.BrowseView.Actions.sources) + } + .sheet(isPresented: self.$isShowingSourcesView) { + NavigationView { + SourcesView() + } + } + } + + ToolbarItem(placement: .navigationBarTrailing) { + SwiftUI.Button { + + } label: { + Image(systemSymbol: .lineHorizontal3DecreaseCircle) + .imageScale(.large) + } + + } + } + } + + var promotedCategoriesView: some View { + VStack { + HStack { + Text(L10n.BrowseView.Section.PromotedCategories.title) + .font(.title2) + .bold() + Spacer() + SwiftUI.Button(action: {}, label: { + Text(L10n.BrowseView.Section.PromotedCategories.showAll) + }) + .font(.callout) + } + + LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())]) { + PromotedCategoryView() + .shadow(color: .black.opacity(0.1), radius: 8, y: 5) + + PromotedCategoryView() + .shadow(color: .black.opacity(0.1), radius: 8, y: 5) + } + } + } +} + +struct BrowseView_Previews: PreviewProvider { + static var previews: some View { + NavigationView { + BrowseView() + } + } +} + + +struct PromotedCategoryView: View { + + var body: some View { + ZStack { + GeometryReader { proxy in + RadialGradient(colors: [ + Color(UIColor(hexString: "477E84")!), + Color(UIColor.secondarySystemBackground), + Color(UIColor.secondarySystemBackground), + Color(UIColor(hexString: "C38FF5")!) + ], center: .bottomLeading, startRadius: 0, endRadius: proxy.size.width) + } + + HStack { + Image(systemSymbol: .dpadRightFill) + Text(L10n.BrowseView.Categories.gamesAndEmulators) + .multilineTextAlignment(.leading) + } + .foregroundColor(.accentColor) + .padding() + } + .aspectRatio(21/9, contentMode: .fill) + .frame(maxWidth: .infinity) + .clipShape(RoundedRectangle(cornerRadius: 12)) + } +} diff --git a/AltStore/Views/Browse/ConfirmAddSourceView.swift b/AltStore/Views/Browse/ConfirmAddSourceView.swift new file mode 100644 index 00000000..e9959861 --- /dev/null +++ b/AltStore/Views/Browse/ConfirmAddSourceView.swift @@ -0,0 +1,105 @@ +// +// ConfirmAddSourceView.swift +// SideStore +// +// Created by Fabian Thies on 18.11.22. +// Copyright © 2022 Fabian Thies. All rights reserved. +// + +import SwiftUI +import SFSafeSymbols +import AltStoreCore + +struct ConfirmAddSourceView: View { + + let fetchedSource: FetchedSource + var source: Source { + fetchedSource.source + } + + var confirmationHandler: (_ source: FetchedSource) -> () + var cancellationHandler: () -> () + + var body: some View { + VStack(alignment: .leading) { + List { + Section { + VStack(alignment: .leading) { + Text("\(source.apps.count) \(L10n.ConfirmAddSourceView.apps)") + + Text(source.apps.map { $0.name }.joined(separator: ", ")) + .font(.callout) + .lineLimit(1) + .foregroundColor(.secondary) + } + + VStack() { + Text("\(source.newsItems.count) \(L10n.ConfirmAddSourceView.newsItems)") + } + } header: { + Text(L10n.ConfirmAddSourceView.sourceContents) + } + + Section { + VStack(alignment: .leading) { + Text(L10n.ConfirmAddSourceView.sourceIdentifier) + Text(source.identifier) + .font(.callout) + .foregroundColor(.secondary) + } + + VStack(alignment: .leading) { + Text(L10n.ConfirmAddSourceView.sourceURL) + Text(source.sourceURL.absoluteString) + .font(.callout) + .foregroundColor(.secondary) + } + } header: { + Text(L10n.ConfirmAddSourceView.sourceInfo) + } + } + .listStyle(InsetGroupedListStyle()) + + Spacer() + + SwiftUI.Button { + confirmationHandler(fetchedSource) + } label: { + Label(L10n.ConfirmAddSourceView.addSource, systemSymbol: .plus) + } + .buttonStyle(FilledButtonStyle()) + .padding() + } + .background(Color(UIColor.systemGroupedBackground).ignoresSafeArea()) + .toolbar { + ToolbarItemGroup(placement: .navigationBarTrailing) { + SwiftUI.Button { + + } label: { + Image(systemSymbol: .xmarkCircleFill) + .foregroundColor(.secondary) + } + + } + + ToolbarItemGroup(placement: .navigation) { + VStack(alignment: .leading) { + Text(source.name) + .font(.title3) + .bold() + + Text(source.identifier) + .font(.callout) + .foregroundColor(.secondary) + } + } + } + .navigationBarTitleDisplayMode(.inline) + } +} + +struct ConfirmAddSourceView_Previews: PreviewProvider { + static var previews: some View { + AddSourceView(continueHandler: { _ in }) + } +} diff --git a/AltStore/Views/Browse/SourcesView.swift b/AltStore/Views/Browse/SourcesView.swift new file mode 100644 index 00000000..a21f4d5c --- /dev/null +++ b/AltStore/Views/Browse/SourcesView.swift @@ -0,0 +1,299 @@ +// +// SourcesView.swift +// SideStore +// +// Created by Fabian Thies on 20.11.22. +// Copyright © 2022 Fabian Thies. All rights reserved. +// + +import SwiftUI +import SFSafeSymbols +import AltStoreCore +import CoreData + +struct SourcesView: View { + + @Environment(\.dismiss) + private var dismiss + + @Environment(\.managedObjectContext) + var managedObjectContext + + @SwiftUI.FetchRequest(sortDescriptors: [ + NSSortDescriptor(keyPath: \Source.name, ascending: true), + NSSortDescriptor(keyPath: \Source.sourceURL, ascending: true), + NSSortDescriptor(keyPath: \Source.identifier, ascending: true) + ]) + var installedSources: FetchedResults + + @State var trustedSources: [Source] = [] + @State private var isLoadingTrustedSources: Bool = false + @State private var sourcesFetchContext: NSManagedObjectContext? + + @State var isShowingAddSourceAlert = false + @State var sourceToConfirm: FetchedSource? + + var body: some View { + ScrollView { + LazyVStack(alignment: .leading, spacing: 24) { + // Installed Sources + LazyVStack(alignment: .leading, spacing: 12) { + Text(L10n.SourcesView.sourcesDescription) + .font(.callout) + .foregroundColor(.secondary) + + ForEach(installedSources, id: \.identifier) { source in + + VStack(alignment: .leading) { + Text(source.name) + .bold() + + Text(source.sourceURL.absoluteString) + .font(.callout) + .foregroundColor(.secondary) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding() + .tintedBackground(.accentColor) + .clipShape(RoundedRectangle(cornerRadius: 24, style: .circular)) + .if(source.identifier != Source.altStoreIdentifier) { view in + view.contextMenu(ContextMenu(menuItems: { + SwiftUI.Button { + self.removeSource(source) + } label: { + Label(L10n.SourcesView.remove, systemSymbol: .trash) + } + })) + } + } + } + + // Trusted Sources + LazyVStack(alignment: .leading, spacing: 16) { + HStack(spacing: 4) { + Text(L10n.SourcesView.trustedSources) + .font(.title3) + .bold() + + Image(systemSymbol: .shieldLefthalfFill) + .foregroundColor(.accentColor) + } + + Text(L10n.SourcesView.reviewedText) + .font(.callout) + .foregroundColor(.secondary) + + if self.isLoadingTrustedSources { + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + .frame(maxWidth: .infinity) + } else { + ForEach(self.trustedSources, id: \.sourceURL) { source in + + HStack { + VStack(alignment: .leading) { + Text(source.name) + .bold() + + Text(source.sourceURL.absoluteString) + .font(.callout) + .foregroundColor(.secondary) + } + + Spacer() + + if self.installedSources.contains(where: { $0.sourceURL == source.sourceURL }) { + Image(systemSymbol: .checkmarkCircle) + .foregroundColor(.accentColor) + } else { + SwiftUI.Button { + self.fetchSource(with: source.sourceURL.absoluteString) + } label: { + Text("ADD") + .bold() + } + .buttonStyle(PillButtonStyle(tintColor: Asset.accentColor.color, progress: nil)) + } + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding() + .tintedBackground(.accentColor) + .clipShape(RoundedRectangle(cornerRadius: 24, style: .circular)) + } + } + } + } + .padding() + } + .navigationTitle(L10n.SourcesView.sources) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + SwiftUI.Button { + self.isShowingAddSourceAlert = true + } label: { + Image(systemSymbol: .plus) + } + .sheet(isPresented: self.$isShowingAddSourceAlert) { + NavigationView { + AddSourceView(continueHandler: fetchSource(with:)) + } + } + .sheet(item: self.$sourceToConfirm) { source in + if #available(iOS 16.0, *) { + NavigationView { + ConfirmAddSourceView(fetchedSource: source, confirmationHandler: addSource(_:)) { + self.sourceToConfirm = nil + } + } + .presentationDetents([.medium]) + } + } + } + + ToolbarItem(placement: .navigationBarTrailing) { + SwiftUI.Button(action: self.dismiss) { + Text(L10n.SourcesView.done).bold() + } + } + } + .onAppear(perform: self.fetchTrustedSources) + } + + + func fetchSource(with urlText: String) { + self.isShowingAddSourceAlert = false + + guard let url = URL(string: urlText) else { + return + } + + let context = DatabaseManager.shared.persistentContainer.newBackgroundSavingViewContext() + + AppManager.shared.fetchSource(sourceURL: url, managedObjectContext: context) { result in + + switch result { + case let .success(source): + self.sourceToConfirm = FetchedSource(source: source, context: context) + case let .failure(error): + print(error) + } + } + } + + func addSource(_ source: FetchedSource) { + source.context.perform { + do { + try source.context.save() + } catch { + print(error) + NotificationManager.shared.reportError(error: error) + } + } + + self.sourceToConfirm = nil + } + + func removeSource(_ source: Source) { + DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in + let source = context.object(with: source.objectID) as! Source + context.delete(source) + + do { + try context.save() + } catch { + print(error) + } + } + } + + func fetchTrustedSources() { + self.isLoadingTrustedSources = true + + AppManager.shared.fetchTrustedSources { result in + + switch result { + case .success(let trustedSources): + // Cache trusted source IDs. + UserDefaults.shared.trustedSourceIDs = trustedSources.map { $0.identifier } + + // Don't show sources without a sourceURL. + let featuredSourceURLs = trustedSources.compactMap { $0.sourceURL } + + // This context is never saved, but keeps the managed sources alive. + let context = DatabaseManager.shared.persistentContainer.newBackgroundSavingViewContext() + self.sourcesFetchContext = context + + let dispatchGroup = DispatchGroup() + + var sourcesByURL = [URL: Source]() + var errors: [(error: Error, sourceURL: URL)] = [] + + for sourceURL in featuredSourceURLs { + dispatchGroup.enter() + + AppManager.shared.fetchSource(sourceURL: sourceURL, managedObjectContext: context) { result in + defer { + dispatchGroup.leave() + } + + // Serialize access to sourcesByURL. + context.performAndWait { + switch result + { + case .failure(let error): errors.append((error, sourceURL)) + case .success(let source): sourcesByURL[source.sourceURL] = source + } + } + } + } + + dispatchGroup.notify(queue: .main) { + if let (error, _) = errors.first { + NotificationManager.shared.reportError(error: error) + } + let sources = featuredSourceURLs.compactMap { sourcesByURL[$0] } + self.trustedSources = sources + + self.isLoadingTrustedSources = false + } + + case .failure(let error): + NotificationManager.shared.reportError(error: error) + self.isLoadingTrustedSources = false + } + } + } +} + +struct SourcesView_Previews: PreviewProvider { + static var previews: some View { + Color.clear + .sheet(isPresented: .constant(true)) { + NavigationView { + SourcesView() + } + } + } +} + + +extension Source: Identifiable { + public var id: String { + self.identifier + } +} + + +struct FetchedSource: Identifiable { + let source: Source + let context: NSManagedObjectContext + + var id: String { + source.identifier + } + + init(source: Source, context: NSManagedObjectContext) { + self.source = source + self.context = context + } +} diff --git a/AltStore/Views/My Apps/AppAction.swift b/AltStore/Views/My Apps/AppAction.swift new file mode 100644 index 00000000..91b15e04 --- /dev/null +++ b/AltStore/Views/My Apps/AppAction.swift @@ -0,0 +1,54 @@ +// +// AppAction.swift +// SideStore +// +// Created by Fabian Thies on 20.12.22. +// Copyright © 2022 SideStore. All rights reserved. +// + +import Foundation +import SFSafeSymbols + +enum AppAction: Int, CaseIterable { + case install, open, refresh + case activate, deactivate + case remove + case enableJIT + case backup, exportBackup, restoreBackup + case chooseCustomIcon, resetCustomIcon + + + var title: String { + switch self { + case .install: return L10n.AppAction.install + case .open: return L10n.AppAction.open + case .refresh: return L10n.AppAction.refresh + case .activate: return L10n.AppAction.activate + case .deactivate: return L10n.AppAction.deactivate + case .remove: return L10n.AppAction.remove + case .enableJIT: return L10n.AppAction.enableJIT + case .backup: return L10n.AppAction.backup + case .exportBackup: return L10n.AppAction.exportBackup + case .restoreBackup: return L10n.AppAction.restoreBackup + case .chooseCustomIcon: return L10n.AppAction.chooseCustomIcon + case .resetCustomIcon: return L10n.AppAction.resetIcon + } + } + + var symbol: SFSymbol { + switch self { + case .install: return .squareAndArrowDown + case .open: return .arrowUpForwardApp + case .refresh: return .arrowClockwise + case .activate: return .checkmarkCircle + case .deactivate: return .xmarkCircle + case .remove: return .trash + case .enableJIT: return .bolt + case .backup: return .docOnDoc + case .exportBackup: return .arrowUpDoc + case .restoreBackup: return .arrowDownDoc + case .chooseCustomIcon: return .photo + case .resetCustomIcon: return .arrowUturnLeft + } + } +} diff --git a/AltStore/Views/My Apps/AppIDsView.swift b/AltStore/Views/My Apps/AppIDsView.swift new file mode 100644 index 00000000..34b1711f --- /dev/null +++ b/AltStore/Views/My Apps/AppIDsView.swift @@ -0,0 +1,119 @@ +// +// AppIDsView.swift +// SideStore +// +// Created by Fabian Thies on 23.12.22. +// Copyright © 2022 SideStore. All rights reserved. +// + +import SwiftUI +import AltStoreCore + +struct AppIDsView: View { + + @Environment(\.dismiss) var dismiss + + @SwiftUI.FetchRequest(sortDescriptors: [ + NSSortDescriptor(keyPath: \AppID.name, ascending: true), + NSSortDescriptor(keyPath: \AppID.bundleIdentifier, ascending: true), + NSSortDescriptor(keyPath: \AppID.expirationDate, ascending: true) + ], predicate: NSPredicate(format: "%K == %@", #keyPath(AppID.team), DatabaseManager.shared.activeTeam() ?? Team())) + var appIDs: FetchedResults + + @State var isLoading: Bool = false + + + var body: some View { + ScrollView { + LazyVStack(alignment: .leading) { + Text(L10n.AppIDsView.description) + .foregroundColor(.secondary) + + ForEach(appIDs, id: \.identifier) { appId in + HStack { + VStack(alignment: .leading) { + Text(appId.name) + .bold() + + Text(appId.bundleIdentifier) + .font(.footnote) + .foregroundColor(.secondary) + } + + Spacer() + + if let expirationDate = appId.expirationDate { + VStack(spacing: 4) { + Text("Expires in") + .font(.caption) + .foregroundColor(.accentColor) + + SwiftUI.Button { + + } label: { + Text(DateFormatterHelper.string(forExpirationDate: expirationDate).uppercased()) + .bold() + } + .buttonStyle(PillButtonStyle(tintColor: .altPrimary)) + .disabled(true) + } + } + } + .padding() + .tintedBackground(.accentColor) + .clipShape(RoundedRectangle(cornerRadius: 24, style: .circular)) + } + } + .padding() + } + .navigationTitle(L10n.AppIDsView.title) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + if self.isLoading { + ProgressView() + .progressViewStyle(.circular) + } + } + + ToolbarItem(placement: .confirmationAction) { + SwiftUI.Button(L10n.Action.done, action: self.dismiss) + } + } + .onAppear(performAsync: self.updateAppIDs) + } + + + func updateAppIDs() async { + self.isLoading = true + defer { self.isLoading = false } + + await withCheckedContinuation { continuation in + AppManager.shared.fetchAppIDs { result in + do { + let (_, context) = try result.get() + try context.save() + } catch { + print(error) + NotificationManager.shared.reportError(error: error) + } + + continuation.resume() + } + } + } +} + +extension View { + + func onAppear(performAsync task: @escaping () async -> Void) -> some View { + self.onAppear(perform: { Task { await task() } }) + } +} + +struct AppIDsView_Previews: PreviewProvider { + static var previews: some View { + NavigationView { + AppIDsView() + } + } +} diff --git a/AltStore/Views/My Apps/MyAppsView.swift b/AltStore/Views/My Apps/MyAppsView.swift new file mode 100644 index 00000000..90603753 --- /dev/null +++ b/AltStore/Views/My Apps/MyAppsView.swift @@ -0,0 +1,437 @@ +// +// MyAppsView.swift +// SideStoreUI +// +// Created by Fabian Thies on 18.11.22. +// Copyright © 2022 Fabian Thies. All rights reserved. +// + +import SwiftUI +import SFSafeSymbols +import MobileCoreServices +import AltStoreCore + +struct MyAppsView: View { + + // TODO: Refactor + @SwiftUI.FetchRequest(sortDescriptors: [ + NSSortDescriptor(keyPath: \InstalledApp.storeApp?.latestVersion?.date, ascending: true), + NSSortDescriptor(keyPath: \InstalledApp.name, ascending: true) + ], predicate: NSPredicate(format: "%K == YES AND %K != nil AND %K != %K", + #keyPath(InstalledApp.isActive), #keyPath(InstalledApp.storeApp), + #keyPath(InstalledApp.version), #keyPath(InstalledApp.storeApp.latestVersion.version)) + ) + var updates: FetchedResults + + + @SwiftUI.FetchRequest(sortDescriptors: [ + NSSortDescriptor(keyPath: \InstalledApp.expirationDate, ascending: true), + NSSortDescriptor(keyPath: \InstalledApp.refreshedDate, ascending: false), + NSSortDescriptor(keyPath: \InstalledApp.name, ascending: true) + ], predicate: NSPredicate(format: "%K == YES", #keyPath(InstalledApp.isActive))) + var activeApps: FetchedResults + + @AppStorage("shouldShowAppUpdateHint") + var shouldShowAppUpdateHint: Bool = true + + @ObservedObject + var viewModel = MyAppsViewModel() + + // TODO: Refactor + @State var isRefreshingAllApps: Bool = false + @State var selectedSideloadingIpaURL: URL? + + var remainingAppIDs: Int { + guard let team = DatabaseManager.shared.activeTeam() else { + return 0 + } + + let maximumAppIDCount = 10 + return max(maximumAppIDCount - team.appIDs.count, 0) + } + + // TODO: Refactor + let sideloadFileTypes: [String] = { + if let types = UTTypeCreateAllIdentifiersForTag(kUTTagClassFilenameExtension, "ipa" as CFString, nil)?.takeRetainedValue() + { + return (types as NSArray).map { $0 as! String } + } + else + { + return ["com.apple.itunes.ipa"] // Declared by the system. + } + }() + + + var body: some View { + ScrollView { + LazyVStack(spacing: 16) { + if let progress = SideloadingManager.shared.progress { + VStack { + Text(L10n.MyAppsView.sideloading) + .padding() + + ProgressView(progress) + .progressViewStyle(LinearProgressViewStyle()) + } + .background(Color(UIColor.secondarySystemBackground)) + } + + if updates.isEmpty { + if shouldShowAppUpdateHint { + updatesSection + } + } + + HStack { + Text(L10n.MyAppsView.active) + .font(.title2) + .bold() + + Spacer() + + if !self.isRefreshingAllApps { + SwiftUI.Button(L10n.MyAppsView.refreshAll, action: self.refreshAllApps) + } else { + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + } + } + + ForEach(activeApps, id: \.bundleIdentifier) { app in + + if let storeApp = app.storeApp { + NavigationLink { + AppDetailView(storeApp: storeApp) + } label: { + self.rowView(for: app) + } + .buttonStyle(PlainButtonStyle()) + } else { + self.rowView(for: app) + } + } + + if let activeTeam = DatabaseManager.shared.activeTeam() { + VStack { + if activeTeam.type == .free { + Text("\(remainingAppIDs) \(L10n.MyAppsView.appIDsRemaining)") + .foregroundColor(.secondary) + } + + ModalNavigationLink(L10n.MyAppsView.viewAppIDs) { + NavigationView { + AppIDsView() + } + } + } + } + } + .padding(.horizontal) + } + .background(Color(UIColor.systemGroupedBackground).ignoresSafeArea()) + .navigationTitle(L10n.MyAppsView.myApps) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + ModalNavigationLink { + DocumentPicker(selectedUrl: $selectedSideloadingIpaURL, supportedTypes: sideloadFileTypes) + .ignoresSafeArea() + } label: { + Image(systemSymbol: .plus) + .imageScale(.large) + } + .onChange(of: self.selectedSideloadingIpaURL) { newValue in + guard let url = newValue else { + return + } + + self.sideloadApp(at: url) + } + } + } + } + + var updatesSection: some View { + HintView { + HStack(alignment: .center) { + Text(L10n.MyAppsView.Hints.NoUpdates.title) + .bold() + Spacer() + + Menu { + SwiftUI.Button { + self.dismissUpdatesHint(forever: false) + } label: { + Label(L10n.MyAppsView.Hints.NoUpdates.dismissForNow, systemSymbol: .zzz) + } + + SwiftUI.Button { + self.dismissUpdatesHint(forever: true) + } label: { + Label(L10n.MyAppsView.Hints.NoUpdates.dontShowAgain, systemSymbol: .xmark) + } + } label: { + Image(systemSymbol: .xmark) + } + .foregroundColor(.secondary) + } + + Text(L10n.MyAppsView.Hints.NoUpdates.text) + .font(.callout) + .foregroundColor(.secondary) + } + } + + @ViewBuilder + func rowView(for app: AppProtocol) -> some View { + AppRowView(app: app, showRemainingDays: true) + .contextMenu(ContextMenu(menuItems: { + ForEach(self.actions(for: app), id: \.self) { action in + SwiftUI.Button { + self.perform(action: action, for: app) + } label: { + Label(action.title, systemSymbol: action.symbol) + } + } + })) + } + + func refreshAllApps() { + let installedApps = InstalledApp.fetchAppsForRefreshingAll(in: DatabaseManager.shared.viewContext) + + self.isRefreshingAllApps = true + self.refresh(installedApps) { result in + self.isRefreshingAllApps = false + } + } + + func dismissUpdatesHint(forever: Bool) { + withAnimation { + self.shouldShowAppUpdateHint = false + } + } +} + + +extension MyAppsView { + // TODO: Convert to async? + func refresh(_ apps: [InstalledApp], completionHandler: @escaping ([String : Result]) -> Void) { + let group = AppManager.shared.refresh(apps, presentingViewController: nil, group: self.viewModel.refreshGroup) + + group.completionHandler = { results in + DispatchQueue.main.async { + let failures = results.compactMapValues { result -> Error? in + switch result { + case .failure(OperationError.cancelled): + return nil + case .failure(let error): + return error + case .success: + return nil + } + } + + guard !failures.isEmpty else { return } + + if let failure = failures.first, results.count == 1 { + NotificationManager.shared.reportError(error: failure.value) + } else { + // TODO: Localize + let title = "\(L10n.MyAppsView.failedToRefresh) \(failures.count) \(L10n.MyAppsView.apps)" + + let error = failures.first?.value as NSError? + let message = error?.localizedFailure ?? error?.localizedFailureReason ?? error?.localizedDescription + + NotificationManager.shared.showNotification(title: title, detailText: message) + } + + self.viewModel.refreshGroup = nil + completionHandler(results) + } + } + + self.viewModel.refreshGroup = group + } +} + +extension MyAppsView { + func actions(for app: AppProtocol) -> [AppAction] { + guard let installedApp = app as? InstalledApp else { + return [] + } + + guard installedApp.bundleIdentifier != StoreApp.altstoreAppID else { + return [.refresh] + } + + var actions: [AppAction] = [] + + if installedApp.isActive { + actions.append(.open) + actions.append(.refresh) + actions.append(.enableJIT) + } else { + actions.append(.activate) + } + + actions.append(.chooseCustomIcon) + if installedApp.hasAlternateIcon { + actions.append(.resetCustomIcon) + } + + if installedApp.isActive { + actions.append(.backup) + } else if let _ = UTTypeCopyDeclaration(installedApp.installedAppUTI as CFString)?.takeRetainedValue() as NSDictionary?, !UserDefaults.standard.isLegacyDeactivationSupported { + // Allow backing up inactive apps if they are still installed, + // but on an iOS version that no longer supports legacy deactivation. + // This handles edge case where you can't install more apps until you + // delete some, but can't activate inactive apps again to back them up first. + actions.append(.backup) + } + + if let backupDirectoryURL = FileManager.default.backupDirectoryURL(for: installedApp) { + + // TODO: Refactor + var backupExists = false + var outError: NSError? = nil + + let coordinator = NSFileCoordinator() + coordinator.coordinate(readingItemAt: backupDirectoryURL, options: [.withoutChanges], error: &outError) { (backupDirectoryURL) in + backupExists = FileManager.default.fileExists(atPath: backupDirectoryURL.path) + } + + if backupExists { + actions.append(.exportBackup) + + if installedApp.isActive { + actions.append(.restoreBackup) + } + } else if let error = outError { + print("Unable to check if backup exists:", error) + } + } + + if installedApp.isActive { + actions.append(.deactivate) + } + + if installedApp.bundleIdentifier != StoreApp.altstoreAppID { + actions.append(.remove) + } + + return actions + } + + func perform(action: AppAction, for app: AppProtocol) { + guard let installedApp = app as? InstalledApp else { + // Invalid state. + return + } + + switch action { + case .install: break + case .open: self.open(installedApp) + case .refresh: self.refresh(installedApp) + case .activate: self.activate(installedApp) + case .deactivate: self.deactivate(installedApp) + case .remove: self.remove(installedApp) + case .enableJIT: self.enableJIT(for: installedApp) + case .backup: self.backup(installedApp) + case .exportBackup: self.exportBackup(installedApp) + case .restoreBackup: self.restoreBackup(installedApp) + case .chooseCustomIcon: self.chooseIcon(for: installedApp) + case .resetCustomIcon: self.resetIcon(for: installedApp) + } + } + + + func open(_ app: InstalledApp) { + UIApplication.shared.open(app.openAppURL) { success in + guard !success else { return } + + NotificationManager.shared.reportError(error: OperationError.openAppFailed(name: app.name)) + } + } + + func refresh(_ app: InstalledApp) { + let previousProgress = AppManager.shared.refreshProgress(for: app) + guard previousProgress == nil else { + previousProgress?.cancel() + return + } + + self.refresh([app]) { (results) in + print("Finished refreshing with results:", results.map { ($0, $1.error?.localizedDescription ?? "success") }) + } + } + + func activate(_ app: InstalledApp) { + + } + + func deactivate(_ app: InstalledApp) { + + } + + func remove(_ app: InstalledApp) { + + } + + func enableJIT(for app: InstalledApp) { + AppManager.shared.enableJIT(for: app) { result in + switch result { + case .success: + break + case .failure(let error): + NotificationManager.shared.reportError(error: error) + } + } + } + + func backup(_ app: InstalledApp) { + + } + + func exportBackup(_ app: InstalledApp) { + + } + + func restoreBackup(_ app: InstalledApp) { + + } + + func chooseIcon(for app: InstalledApp) { + + } + + func resetIcon(for app: InstalledApp) { + + } + + func setIcon(for app: InstalledApp, to image: UIImage? = nil) { + + } + + func sideloadApp(at url: URL) { + SideloadingManager.shared.sideloadApp(at: url) { result in + switch result { + case .success: + print("App sideloaded successfully.") + case .failure(let error): + print("Failed to sideload app: \(error.localizedDescription)") + } + } + } +} + +struct MyAppsView_Previews: PreviewProvider { + + static let context = DatabaseManager.shared.viewContext + static let app = StoreApp.makeAltStoreApp(in: context) + + static var previews: some View { + NavigationView { + MyAppsView() + } + } +} diff --git a/AltStore/Views/My Apps/MyAppsViewModel.swift b/AltStore/Views/My Apps/MyAppsViewModel.swift new file mode 100644 index 00000000..f2811a15 --- /dev/null +++ b/AltStore/Views/My Apps/MyAppsViewModel.swift @@ -0,0 +1,16 @@ +// +// MyAppsViewModel.swift +// SideStore +// +// Created by Fabian Thies on 13.12.22. +// Copyright © 2022 SideStore. All rights reserved. +// + +import SwiftUI +import AltStoreCore + +class MyAppsViewModel: ViewModel { + + var refreshGroup: RefreshGroup? + +} diff --git a/AltStore/Views/News/NewsItemView.swift b/AltStore/Views/News/NewsItemView.swift new file mode 100644 index 00000000..2572599a --- /dev/null +++ b/AltStore/Views/News/NewsItemView.swift @@ -0,0 +1,129 @@ +// +// NewsItemView.swift +// SideStoreUI +// +// Created by Fabian Thies on 18.11.22. +// Copyright © 2022 Fabian Thies. All rights reserved. +// + +import SwiftUI +import AsyncImage +import AltStoreCore + +struct NewsItemView: View { + typealias TapHandler = (T) -> Void + + let newsItem: NewsItem + + private var newsSelectionHandler: TapHandler? = nil + private var appSelectionHandler: TapHandler? = nil + + init(newsItem: NewsItem) { + self.newsItem = newsItem + } + + var body: some View { + VStack(spacing: 12) { + newsContent + .onTapGesture { + newsSelectionHandler?(newsItem) + } + + if let connectedApp = newsItem.storeApp { + NavigationLink { + AppDetailView(storeApp: connectedApp) + } label: { + AppRowView(app: connectedApp) + } + .buttonStyle(PlainButtonStyle()) + + } + } + } + + var newsContent: some View { + VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading) { + Text(newsItem.title) + .font(.title2) + .bold() + .foregroundColor(.white) + + HStack(spacing: 0) { + if let sourceName = newsItem.source?.name { + Text(sourceName) + .italic() + } + + if let externalURL = newsItem.externalURL { + Text(" • ") + + HStack(spacing: 0) { + Image(systemSymbol: .link) + Text(externalURL.host ?? "") + .italic() + } + } + } + .font(.system(size: 14)) + .foregroundColor(.white.opacity(0.7)) + } + + Text(newsItem.caption) + .foregroundColor(.white.opacity(0.7)) + } + .padding(24) + + if let imageUrl = newsItem.imageURL { + AsyncImage(url: imageUrl) { image in + image + .resizable() + .aspectRatio(contentMode: .fit) + } placeholder: { + Color.secondary + .frame(maxWidth: .infinity, maxHeight: 100) + } + } + } + .frame( + maxWidth: .infinity, + alignment: .topLeading + ) + .background(Color(newsItem.tintColor)) + .clipShape(RoundedRectangle(cornerRadius: 24, style: .circular)) + } + + + func onNewsSelection(_ handler: @escaping TapHandler) -> Self { + var newSelf = self + newSelf.newsSelectionHandler = handler + return newSelf + } + + func onAppSelection(_ handler: @escaping TapHandler) -> Self { + var newSelf = self + newSelf.appSelectionHandler = handler + return newSelf + } +} + +extension URL: Identifiable { + public var id: String { + return self.absoluteString + } +} + +//struct NewsItemView_Previews: PreviewProvider { +// static var previews: some View { +// NewsItemView() +// } +//} + + +extension NewsItemView: Equatable { + /// Prevent re-rendering of the view if the parameters didn't change + static func == (lhs: NewsItemView, rhs: NewsItemView) -> Bool { + lhs.newsItem.identifier == rhs.newsItem.identifier + } +} diff --git a/AltStore/Views/News/NewsView.swift b/AltStore/Views/News/NewsView.swift new file mode 100644 index 00000000..1c070e59 --- /dev/null +++ b/AltStore/Views/News/NewsView.swift @@ -0,0 +1,97 @@ +// +// NewsView.swift +// SideStoreUI +// +// Created by Fabian Thies on 18.11.22. +// Copyright © 2022 Fabian Thies. All rights reserved. +// + +import SwiftUI +import AltStoreCore + +struct NewsView: View { + + @SwiftUI.FetchRequest(sortDescriptors: [ + NSSortDescriptor(keyPath: \NewsItem.date, ascending: false), + NSSortDescriptor(keyPath: \NewsItem.sortIndex, ascending: true), + NSSortDescriptor(keyPath: \NewsItem.sourceIdentifier, ascending: true) + ]) + var news: FetchedResults + + @State + var activeExternalUrl: URL? + + @State + var selectedStoreApp: StoreApp? + + var body: some View { + ScrollView { + self.announcementsCarousel + + VStack(alignment: .leading) { + Text(L10n.NewsView.Section.FromSources.title) + .font(.title2) + .bold() + + LazyVStack(spacing: 24) { + ForEach(news, id: \.objectID) { newsItem in + NewsItemView(newsItem: newsItem) + .onNewsSelection { newsItem in + self.activeExternalUrl = newsItem.externalURL + } + .frame( + maxWidth: .infinity, + alignment: .topLeading + ) + } + } + } + .padding() + } + .background(Color(UIColor.systemGroupedBackground).ignoresSafeArea()) + .navigationTitle(L10n.NewsView.title) + .sheet(item: self.$activeExternalUrl) { url in + SafariView(url: url) + .ignoresSafeArea() + } + .onAppear(perform: fetchNews) + } + + var announcementsCarousel: some View { + TabView { + ForEach(0..<5) { _ in + RoundedRectangle(cornerRadius: 10) + .foregroundColor(.secondary) + .shadow(radius: 5, y: 3) + .padding() + } + } + .tabViewStyle(PageTabViewStyle()) + .frame(maxWidth: .infinity) + .aspectRatio(16/9, contentMode: .fit) + } + + + func fetchNews() { + AppManager.shared.fetchSources { result in + do { + do { + let (_, context) = try result.get() + try context.save() + } catch let error as AppManager.FetchSourcesError { + try error.managedObjectContext?.save() + throw error + } + } catch { + print(error) + NotificationManager.shared.reportError(error: error) + } + } + } +} + +struct NewsView_Previews: PreviewProvider { + static var previews: some View { + NewsView() + } +} diff --git a/AltStore/Views/News/NewsViewModel.swift b/AltStore/Views/News/NewsViewModel.swift new file mode 100644 index 00000000..1bffd4e0 --- /dev/null +++ b/AltStore/Views/News/NewsViewModel.swift @@ -0,0 +1,22 @@ +// +// NewsViewModel.swift +// SideStoreUI +// +// Created by Fabian Thies on 18.11.22. +// Copyright © 2022 Fabian Thies. All rights reserved. +// + +import SwiftUI +import AltStoreCore + +class NewsViewModel: ViewModel { + + @SwiftUI.FetchRequest(sortDescriptors: [ + NSSortDescriptor(keyPath: \NewsItem.date, ascending: false), + NSSortDescriptor(keyPath: \NewsItem.sortIndex, ascending: true), + NSSortDescriptor(keyPath: \NewsItem.sourceIdentifier, ascending: true) + ]) + var news: FetchedResults + + init() {} +} diff --git a/AltStore/Views/Onboarding/AppIconsShowcase.swift b/AltStore/Views/Onboarding/AppIconsShowcase.swift new file mode 100644 index 00000000..7b78a4fc --- /dev/null +++ b/AltStore/Views/Onboarding/AppIconsShowcase.swift @@ -0,0 +1,89 @@ +// +// AppIconsShowcase.swift +// SideStore +// +// Created by Fabian Thies on 25.02.23. +// Copyright © 2023 SideStore. All rights reserved. +// + +import SwiftUI + +struct AppIconsShowcase: View { + + @State var animationProgress = 0.0 + @State var animation2Progress = 0.0 + + var body: some View { + VStack { + GeometryReader { proxy in + ZStack(alignment: .bottom) { + Image(uiImage: UIImage(named: "AppIcon")!) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: 0.2 * proxy.size.width) + .clipShape(RoundedRectangle(cornerRadius: 24, style: .circular)) + .offset(x: -0.3*proxy.size.width * self.animationProgress, y: -30) + .rotationEffect(.degrees(-20 * self.animationProgress)) + .shadow(radius: 8 * self.animationProgress) + + Image(uiImage: UIImage(named: "AppIcon")!) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: 0.25 * proxy.size.width) + .clipShape(RoundedRectangle(cornerRadius: 24, style: .circular)) + .offset(x: -0.15*proxy.size.width * self.animationProgress, y: -10) + .rotationEffect(.degrees(-10 * self.animationProgress)) + .shadow(radius: 12 * self.animationProgress) + + Image(uiImage: UIImage(named: "AppIcon")!) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: 0.2 * proxy.size.width) + .clipShape(RoundedRectangle(cornerRadius: 24, style: .circular)) + .offset(x: self.animationProgress*0.3*proxy.size.width, y: -30) + .rotationEffect(.degrees(self.animationProgress*20)) + .shadow(radius: 8 * self.animationProgress) + + Image(uiImage: UIImage(named: "AppIcon")!) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: 0.25 * proxy.size.width) + .clipShape(RoundedRectangle(cornerRadius: 24, style: .circular)) + .offset(x: self.animationProgress * 0.15*proxy.size.width, y: -10) + .rotationEffect(.degrees(self.animationProgress * 10)) + .shadow(radius: 12 * self.animationProgress) + + Image(uiImage: UIImage(named: "AppIcon")!) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: 0.3 * proxy.size.width) + .clipShape(RoundedRectangle(cornerRadius: 24, style: .circular)) + .shadow(radius: 16 * self.animationProgress + 8 * self.animation2Progress) + .scaleEffect(1.0 + 0.05 * self.animation2Progress) + } + .frame(maxWidth: proxy.size.width) + } + } + .onAppear { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + withAnimation(.spring()) { + self.animationProgress = 1.0 + self.animation2Progress = 1.0 + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { + withAnimation(.spring()) { + self.animation2Progress = 0.0 + } + } + } + } + } +} + +struct AppIconsShowcase_Previews: PreviewProvider { + static var previews: some View { + AppIconsShowcase() + .frame(height: 150) + } +} diff --git a/AltStore/Views/Onboarding/OnboardingStepView.swift b/AltStore/Views/Onboarding/OnboardingStepView.swift new file mode 100644 index 00000000..1cc15d30 --- /dev/null +++ b/AltStore/Views/Onboarding/OnboardingStepView.swift @@ -0,0 +1,86 @@ +// +// OnboardingStepView.swift +// SideStore +// +// Created by Fabian Thies on 25.02.23. +// Copyright © 2023 SideStore. All rights reserved. +// + +import SwiftUI + + +struct OnboardingStep { + + @ViewBuilder + var title: Title + + @ViewBuilder + var hero: Hero + + @ViewBuilder + var content: Content + + @ViewBuilder + var action: Action +} + + +struct OnboardingStepView: View { + + @ViewBuilder + var title: Title + + @ViewBuilder + var hero: Hero + + @ViewBuilder + var content: Content + + @ViewBuilder + var action: Action + + var body: some View { + VStack(spacing: 64) { + self.title + .font(.largeTitle.weight(.bold)) + .frame(maxWidth: .infinity, alignment: .leading) + + self.hero + .frame(height: 150) + + self.content + + Spacer() + + self.action + } + .frame(maxWidth: .infinity) + .padding() + } +} + +struct OnboardingStepView_Previews: PreviewProvider { + static var previews: some View { + OnboardingStepView(title: { + VStack(alignment: .leading) { + Text("Welcome to") + Text("SideStore") + .foregroundColor(.accentColor) + } + }, hero: { + AppIconsShowcase() + }, content: { + VStack(spacing: 16) { + Text("Before you can start sideloading apps, there is some setup to do.") + Text("The following setup will guide you through the steps one by one.") + Text("You will need a computer (Windows, macOS, Linux) and your Apple ID.") + } + }, action: { + SwiftUI.Button("Continue") { + + } + .buttonStyle(FilledButtonStyle()) + }) + } +} + diff --git a/AltStore/Views/Onboarding/OnboardingView.swift b/AltStore/Views/Onboarding/OnboardingView.swift new file mode 100644 index 00000000..53e5e799 --- /dev/null +++ b/AltStore/Views/Onboarding/OnboardingView.swift @@ -0,0 +1,443 @@ +// +// OnboardingView.swift +// SideStore +// +// Created by Fabian Thies on 25.02.23. +// Copyright © 2023 SideStore. All rights reserved. +// + +import SwiftUI +import CoreData +import AltStoreCore +import minimuxer +import Reachability +import UniformTypeIdentifiers + + +struct OnboardingView: View { + enum OnboardingStep: Int, CaseIterable { + case welcome, pairing, wireguard, wireguardConfig, addSources, finish + } + + @Environment(\.dismiss) var dismiss + + // Temporary workaround for UIKit compatibility + var onDismiss: (() -> Void)? = nil + + @State var currentStep: OnboardingStep = .wireguard //.welcome + @State private var pairingFileURL: URL? = nil + @State private var isWireGuardAppStorePageVisible: Bool = false + @State private var isDownloadingWireGuardProfile: Bool = false + @State private var wireGuardProfileFileURL: URL? = nil + @State private var reachabilityNotifier: Reachability? = nil + @State private var isWireGuardTunnelReachable: Bool = false + @State private var areTrustedSourcesEnabled: Bool = false + @State private var isLoadingTrustedSources: Bool = false + + let pairingFileTypes = UTType.types(tag: "plist", tagClass: UTTagClass.filenameExtension, conformingTo: nil) + UTType.types(tag: "mobiledevicepairing", tagClass: UTTagClass.filenameExtension, conformingTo: UTType.data) + [.xml] + + var body: some View { + TabView(selection: self.$currentStep) { + welcomeStep + .tag(OnboardingStep.welcome) + .highPriorityGesture(DragGesture()) + + pairingView + .tag(OnboardingStep.pairing) + .highPriorityGesture(DragGesture()) + + wireguardView + .tag(OnboardingStep.wireguard) + .highPriorityGesture(DragGesture()) + + wireguardConfigView + .tag(OnboardingStep.wireguardConfig) + .highPriorityGesture(DragGesture()) + + addSourcesView + .tag(OnboardingStep.addSources) + .highPriorityGesture(DragGesture()) + + finishView + .tag(OnboardingStep.finish) + .highPriorityGesture(DragGesture()) + + } + .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) + .edgesIgnoringSafeArea(.bottom) + .background(Color.accentColor.opacity(0.1).edgesIgnoringSafeArea(.all)) + .onChange(of: self.currentStep) { step in + switch step { + case .wireguardConfig: + self.startPingingWireGuardTunnel() + default: + self.stopPingingWireGuardTunnel() + } + } + } + + func showNextStep() { + withAnimation { + self.currentStep = OnboardingStep(rawValue: self.currentStep.rawValue + 1) ?? self.currentStep + } + } + + var welcomeStep: some View { + OnboardingStepView { + VStack(alignment: .leading) { + Text("Welcome to") + Text("SideStore") + .foregroundColor(.accentColor) + } + } hero: { + AppIconsShowcase() + } content: { + VStack(alignment: .leading, spacing: 16) { + Text("Before you can start sideloading apps, there is some setup to do.") + Text("The following setup will guide you through the steps one by one.") + Text("You will need a computer (Windows, macOS, Linux) and your Apple ID.") + } + } action: { + SwiftUI.Button("Continue") { + self.showNextStep() + } + .buttonStyle(FilledButtonStyle()) + } + } + + var pairingView: some View { + OnboardingStepView(title: { + VStack(alignment: .leading) { + Text("Pair your Device") + } + }, hero: { + Image(systemSymbol: .link) + .resizable() + .aspectRatio(contentMode: .fit) + .foregroundColor(.accentColor) + .shadow(color: .accentColor.opacity(0.8), radius: 12) + }, content: { + VStack(alignment: .leading, spacing: 16) { + Text("SideStore supports sideloading even on non-jailbroken devices.") + Text("For it to work, you have to generate a pairing file as described [here in our documentation](https://wiki.sidestore.io/guides/install#pairing-process).") + Text("Once you have the `.mobiledevicepairing`, import it using the button below.") + } + }, action: { + ModalNavigationLink("Select Pairing File") { + DocumentPicker(selectedUrl: self.$pairingFileURL, + supportedTypes: self.pairingFileTypes.map { $0.identifier }) + } + .buttonStyle(FilledButtonStyle()) + .onChange(of: self.pairingFileURL) { newValue in + guard let url = newValue else { + return + } + + self.importPairingFile(url: url) + } + }) + } + + var wireguardView: some View { + OnboardingStepView(title: { + VStack(alignment: .leading) { + Text("Download WireGuard") + } + }, hero: { + Image(systemSymbol: .icloudAndArrowDown) + .resizable() + .aspectRatio(contentMode: .fit) + .foregroundColor(.accentColor) + .shadow(color: .accentColor.opacity(0.8), radius: 12) + }, content: { + VStack(alignment: .leading, spacing: 16) { + Text("To sideload and sign app on-device without the need of a computer program like SideServer, a local WireGuard connection is required.") + Text("This connection is strictly local-only and does not connect to a server on the internet.") + Text("First, download WireGuard from the App Store (free).") + } + }, action: { + AppStoreView(isVisible: self.$isWireGuardAppStorePageVisible, itunesItemId: 1441195209) + .frame(width: .zero, height: .zero) + + VStack { + SwiftUI.Button("Show in App Store") { + self.isWireGuardAppStorePageVisible = true + } + .buttonStyle(FilledButtonStyle()) + + SwiftUI.Button("Continue") { + self.showNextStep() + } + .buttonStyle(FilledButtonStyle()) + } + }) + + } + + var wireguardConfigView: some View { + OnboardingStepView(title: { + VStack(alignment: .leading) { + Text("Enable the WireGuard Tunnel") + } + }, hero: { + Image(systemSymbol: .network) + .resizable() + .aspectRatio(contentMode: .fit) + .foregroundColor(.accentColor) + .shadow(color: .accentColor.opacity(0.8), radius: 12) + }, content: { + VStack(alignment: .leading, spacing: 16) { + Text("Once WireGuard is installed, a configuration file has to be installed in the WireGuard app.") + Text("Tap the button below and open the downloaded file in the WireGuard app.") + Text("Then, activate the VPN tunnel to continue.") + } + }, action: { + VStack { + SwiftUI.Button("Download and Install Configuration File") { + self.downloadWireGuardProfile() + } + .buttonStyle(FilledButtonStyle(isLoading: self.isDownloadingWireGuardProfile)) + .sheet(item: self.$wireGuardProfileFileURL) { fileURL in + ActivityView(items: [fileURL]) + } + + SwiftUI.Button(self.isWireGuardTunnelReachable ? "Continue" : "Waiting for connection...", + action: self.showNextStep) + .buttonStyle(FilledButtonStyle()) + .disabled(!self.isWireGuardTunnelReachable) + } + }) + } + + var addSourcesView: some View { + OnboardingStepView(title: { + VStack(alignment: .leading) { + Text("Add Sources") + } + }, hero: { + Image(systemSymbol: .booksVertical) + .resizable() + .aspectRatio(contentMode: .fit) + .foregroundColor(.accentColor) + .shadow(color: .accentColor.opacity(0.8), radius: 12) + }, content: { + VStack(alignment: .leading, spacing: 16) { + Text("All apps are provided through sources, which anyone can create and share with the world.") + Text("We have compiled a list of trusted sources for SideStore which you can enable to start sideloading your favorite apps.") + Text("By default, only the source containing SideStore itself is enabled.") + + Toggle("Enable Trusted Sources", isOn: $areTrustedSourcesEnabled) + } + }, action: { + SwiftUI.Button("Continue") { + self.setupTrustedSources() + } + .buttonStyle(FilledButtonStyle(isLoading: self.isLoadingTrustedSources)) + .disabled(self.isLoadingTrustedSources) + }) + } + + var finishView: some View { + OnboardingStepView(title: { + VStack(alignment: .leading) { + Text("Setup Completed") + } + }, hero: { + Image(systemSymbol: .checkmark) + .resizable() + .aspectRatio(contentMode: .fit) + .foregroundColor(.accentColor) + .shadow(color: .accentColor.opacity(0.8), radius: 12) + }, content: { + VStack(alignment: .leading, spacing: 16) { + Text("Congratulations, you did it! 🎉") + Text("You can start your sideloading journey.") + } + }, action: { + SwiftUI.Button("Let's Go") { + self.finishOnboarding() + } + .buttonStyle(FilledButtonStyle()) + }) + } +} + +extension OnboardingView { + func importPairingFile(url: URL) { + let isSecuredURL = url.startAccessingSecurityScopedResource() == true + + do { + // Read to a string + let data = try Data(contentsOf: url) + let pairing_string = String(bytes: data, encoding: .utf8) + if pairing_string == nil { + // TODO: Show error message + debugPrint("Unable to read pairing file") + // displayError("Unable to read pairing file") + } + + // Save to a file for next launch + let filename = "ALTPairingFile.mobiledevicepairing" + let fm = FileManager.default + let documentsPath = fm.documentsDirectory.appendingPathComponent("/\(filename)") + try pairing_string?.write(to: documentsPath, atomically: true, encoding: String.Encoding.utf8) + + // Start minimuxer now that we have a file + start_minimuxer_threads(pairing_string!) + + // Show the next onboarding step + self.showNextStep() + + } catch { + NotificationManager.shared.reportError(error: error) + } + + if (isSecuredURL) { + url.stopAccessingSecurityScopedResource() + } + } + + func start_minimuxer_threads(_ pairing_file: String) { + target_minimuxer_address() + let documentsDirectory = FileManager.default.documentsDirectory.absoluteString + do { + try start(pairing_file, documentsDirectory) + } catch { + try! FileManager.default.removeItem(at: FileManager.default.documentsDirectory.appendingPathComponent("\(pairingFileName)")) + NotificationManager.shared.reportError(error: error) + debugPrint("minimuxer failed to start, please restart SideStore.", error) +// displayError("minimuxer failed to start, please restart SideStore. \((error as? LocalizedError)?.failureReason ?? "UNKNOWN ERROR!!!!!! REPORT TO GITHUB ISSUES!")") + } + start_auto_mounter(documentsDirectory) + } +} + +extension OnboardingView { + func downloadWireGuardProfile() { + let profileDownloadUrl = "https://github.com/SideStore/SideStore/releases/download/0.3.1/SideStore.conf" + let destinationUrl = FileManager.default.temporaryDirectory.appendingPathComponent("SideStore.conf") + + self.isDownloadingWireGuardProfile = true + URLSession.shared.dataTask(with: URLRequest(url: URL(string: profileDownloadUrl)!)) { data, response, error in + + defer { self.isDownloadingWireGuardProfile = false } + + if let error { + NotificationManager.shared.reportError(error: error) + return + } + + guard let response = response as? HTTPURLResponse, 200..<300 ~= response.statusCode, let data else { + // TODO: Show error message + return + } + + do { + try data.write(to: destinationUrl) + self.wireGuardProfileFileURL = destinationUrl + } catch { + NotificationManager.shared.reportError(error: error) + return + } + }.resume() + } + + func startPingingWireGuardTunnel() { + do { + self.reachabilityNotifier = try Reachability(hostname: "10.7.0.1") + self.reachabilityNotifier?.whenReachable = { _ in + self.isWireGuardTunnelReachable = true + } + self.reachabilityNotifier?.whenUnreachable = { _ in + self.isWireGuardTunnelReachable = false + } + + try self.reachabilityNotifier?.startNotifier() + } catch { + // TODO: Show error message + debugPrint(error) + NotificationManager.shared.reportError(error: error) + } + } + + func stopPingingWireGuardTunnel() { + self.reachabilityNotifier?.stopNotifier() + } +} + +extension OnboardingView { + func setupTrustedSources() { + guard self.areTrustedSourcesEnabled else { + return self.showNextStep() + } + + self.isLoadingTrustedSources = true + + AppManager.shared.fetchTrustedSources { result in + + switch result { + case .success(let trustedSources): + // Cache trusted source IDs. + UserDefaults.shared.trustedSourceIDs = trustedSources.map { $0.identifier } + + // Don't show sources without a sourceURL. + let featuredSourceURLs = trustedSources.compactMap { $0.sourceURL } + + // This context is never saved, but keeps the managed sources alive. + let context = DatabaseManager.shared.persistentContainer.newBackgroundSavingViewContext() + + let dispatchGroup = DispatchGroup() + for sourceURL in featuredSourceURLs { + dispatchGroup.enter() + + AppManager.shared.fetchSource(sourceURL: sourceURL, managedObjectContext: context) { result in + dispatchGroup.leave() + } + } + + dispatchGroup.notify(queue: .main) { + self.isLoadingTrustedSources = false + + // Save the fetched trusted sources + do { + try context.save() + } catch { + NotificationManager.shared.reportError(error: error) + } + + self.showNextStep() + } + case .failure(let error): + NotificationManager.shared.reportError(error: error) + self.isLoadingTrustedSources = false + } + } + + } +} + +extension OnboardingView { + func finishOnboarding() { + // Set the onboarding complete flag + UserDefaults.standard.onboardingComplete = true + + if let onDismiss { + onDismiss() + } else { + self.dismiss() + } + } +} + + +struct OnboardingView_Previews: PreviewProvider { + static var previews: some View { + ForEach(OnboardingView.OnboardingStep.allCases, id: \.self) { step in + Color.red + .ignoresSafeArea() + .sheet(isPresented: .constant(true)) { + OnboardingView(currentStep: step) + } + } + } +} diff --git a/AltStore/Views/RootView.swift b/AltStore/Views/RootView.swift new file mode 100644 index 00000000..09127f6a --- /dev/null +++ b/AltStore/Views/RootView.swift @@ -0,0 +1,114 @@ +// +// RootView.swift +// SideStoreUI +// +// Created by Fabian Thies on 18.11.22. +// Copyright © 2022 Fabian Thies. All rights reserved. +// + +import SwiftUI +import SFSafeSymbols +@_exported import Inject + +struct RootView: View { + @State var selectedTab: Tab = .defaultTab + + var body: some View { + TabView(selection: self.$selectedTab) { + ForEach(Tab.allCases) { tab in + NavigationView { + content(for: tab) + } + .navigationViewStyle(StackNavigationViewStyle()) + .tag(tab) + .tabItem { + tab.label + } + } + } + .overlay(self.notificationsOverlay) + } + + @ViewBuilder + func content(for tab: Tab) -> some View { + switch tab { + case .news: + NewsView() + case .browse: + BrowseView() + case .myApps: + MyAppsView() + case .settings: + SettingsView() + } + } + + + @ObservedObject + var notificationManager = NotificationManager.shared + + var notificationsOverlay: some View { + VStack { + Spacer() + + ForEach(Array(notificationManager.notifications.values)) { notification in + VStack(alignment: .leading) { + Text(notification.title) + .bold() + + if let message = notification.message { + Text(message) + .font(.callout) + } + } + .padding() + .frame(maxWidth: .infinity) + .foregroundColor(.white) + .background(Color.accentColor) + .clipShape(RoundedRectangle(cornerRadius: 16)) + .shadow(radius: 15) + } + + Spacer() + .frame(height: 50) + } + .padding() + .animation(.easeInOut) + } +} + +extension RootView { + enum Tab: Int, NavigationTab { + case news, browse, myApps, settings + + static var defaultTab: RootView.Tab = .news + + var displaySymbol: SFSymbol { + switch self { + case .news: return .newspaper + case .browse: return .booksVertical + case .myApps: return .squareStack + case .settings: return .gearshape + } + } + + var displayName: String { + switch self { + case .news: return L10n.RootView.news + case .browse: return L10n.RootView.browse + case .myApps: return L10n.RootView.myApps + case .settings: return L10n.RootView.settings + } + } + + var label: some View { + Label(self.displayName, systemSymbol: self.displaySymbol) + } + } +} + +struct RootView_Previews: PreviewProvider { + static var previews: some View { + RootView() + } +} diff --git a/AltStore/Views/Settings/AdvancedSettingsView.swift b/AltStore/Views/Settings/AdvancedSettingsView.swift new file mode 100644 index 00000000..06853e07 --- /dev/null +++ b/AltStore/Views/Settings/AdvancedSettingsView.swift @@ -0,0 +1,76 @@ +// +// AdvancedSettingsView.swift +// SideStore +// +// Created by naturecodevoid on 2/19/23. +// Copyright © 2023 SideStore. All rights reserved. +// + +import SwiftUI + +private struct Server: Identifiable { + var id: String { value } + var display: String + var value: String +} + +struct AdvancedSettingsView: View { + @ObservedObject private var iO = Inject.observer + + private let anisetteServers = [ + Server(display: "SideStore", value: "http://ani.sidestore.io"), + Server(display: "Macley (US)", value: "http://us1.sternserv.tech"), + Server(display: "Macley (DE)", value: "http://de1.sternserv.tech"), + Server(display: "DrPudding", value: "https://sign.rheaa.xyz"), + Server(display: "jkcoxson (AltServer)", value: "http://jkcoxson.com:2095"), + Server(display: "jkcoxson (Provision)", value: "http://jkcoxson.com:2052"), + Server(display: "Sideloadly", value: "https://sideloadly.io/anisette/irGb3Quww8zrhgqnzmrx"), + Server(display: "Nick", value: "http://45.33.29.114"), + Server(display: "Jawshoeadan", value: "https://anisette.jawshoeadan.me"), + Server(display: "crystall1nedev", value: "https://anisette.crystall1ne.software/"), + ] + + @AppStorage("textServer") + var usePreferred: Bool = true + + @AppStorage("textInputAnisetteURL") + var anisetteURL: String = "" + + @AppStorage("customAnisetteURL") + var selectedAnisetteServer: String = "" + + var body: some View { + List { + Section { + Picker(L10n.AdvancedSettingsView.anisette, selection: $selectedAnisetteServer) { + ForEach(anisetteServers) { server in + Text(server.display) + } + } + } + + Section { + Toggle(L10n.AdvancedSettingsView.DangerZone.usePreferred, isOn: $usePreferred) + + HStack { + Text(L10n.AdvancedSettingsView.DangerZone.anisetteURL) + TextField("", text: $anisetteURL) + .autocapitalization(.none) + .autocorrectionDisabled(true) + } + } header: { + Text(L10n.AdvancedSettingsView.dangerZone) + } footer: { + Text(L10n.AdvancedSettingsView.dangerZoneInfo) + } + } + .navigationTitle(L10n.AdvancedSettingsView.title) + .enableInjection() + } +} + +struct AdvancedSettingsView_Previews: PreviewProvider { + static var previews: some View { + AdvancedSettingsView() + } +} diff --git a/AltStore/Views/Settings/AppIconsView.swift b/AltStore/Views/Settings/AppIconsView.swift new file mode 100644 index 00000000..65e54681 --- /dev/null +++ b/AltStore/Views/Settings/AppIconsView.swift @@ -0,0 +1,135 @@ +// +// AppIconsView.swift +// SideStore +// +// Created by naturecodevoid on 2/14/23. +// Copyright © 2023 SideStore. All rights reserved. +// + +import SwiftUI +import SFSafeSymbols + +struct Icon: Identifiable { + var id: String { assetName } + var displayName: String + let assetName: String +} + +private struct SpecialIcon { + let assetName: String + let suffix: String? + let forceIndex: Int? +} + +class AppIconsData: ObservableObject { + static let shared = AppIconsData() + + private static let specialIcons = [ + SpecialIcon(assetName: "Neon", suffix: "(Stable)", forceIndex: 0), + SpecialIcon(assetName: "Starburst", suffix: "(Beta)", forceIndex: 1), + SpecialIcon(assetName: "Steel", suffix: "(Nightly)", forceIndex: 2), + ] + + @Published var icons: [Icon] = [] + @Published var primaryIcon: Icon? + @Published var selectedIconName: String? + + private init() { + let bundleIcons = Bundle.main.object(forInfoDictionaryKey: "CFBundleIcons") as! [String: Any] + + let primaryIconData = bundleIcons["CFBundlePrimaryIcon"] as! [String: Any] + let primaryIconName = primaryIconData["CFBundleIconName"] as! String + primaryIcon = Icon(displayName: primaryIconName, assetName: primaryIconName) + icons.append(primaryIcon!) + + for (key, _) in bundleIcons["CFBundleAlternateIcons"] as! [String: Any] { + icons.append(Icon(displayName: key, assetName: key)) + } + + // sort alphabetically + icons.sort { $0.assetName < $1.assetName } + + for specialIcon in AppIconsData.specialIcons { + guard let icon = icons.enumerated().first(where: { $0.element.assetName == specialIcon.assetName }) else { continue } + + if let suffix = specialIcon.suffix { + icons[icon.offset].displayName += " " + suffix + } + + if let forceIndex = specialIcon.forceIndex { + let e = icons.remove(at: icon.offset) + icons.insert(e, at: forceIndex) + } + } + + if let alternateIconName = UIApplication.shared.alternateIconName { + selectedIconName = icons.first { $0.assetName == alternateIconName }?.assetName ?? primaryIcon!.assetName + } else { + selectedIconName = primaryIcon!.assetName + } + } +} + +struct AppIconsView: View { + @ObservedObject private var iO = Inject.observer + + @ObservedObject private var data = AppIconsData.shared + + private let artists = [ + "Chris (LitRitt)": ["Neon", "Starburst", "Steel", "Storm"], + "naturecodevoid": ["Honeydew", "Midnight", "Sky"], + "Swifticul": ["Vista"], + ] + + @State private var selectedIcon: String? = "" // this is just so the list row background changes when selecting a value, I couldn't get it to keep the selected icon name (for some reason it was always "", even when I set it to the selected icon asset name) + + private let size: CGFloat = 72 + private var cornerRadius: CGFloat { + size * 0.234 + } + + var body: some View { + List(data.icons, selection: $selectedIcon) { icon in + SwiftUI.Button(action: { + data.selectedIconName = icon.assetName + // Pass nil for primary icon + UIApplication.shared.setAlternateIconName(icon.assetName == data.primaryIcon!.assetName ? nil : icon.assetName, completionHandler: { error in + if let error = error { + print("error when setting alternate app icon to \(icon.assetName): \(error.localizedDescription)") + } else { + print("successfully changed app icon to \(icon.assetName)") + } + }) + }) { + HStack(spacing: 20) { + // if we don't have an additional image asset for each icon, it will have low resolution + Image(uiImage: UIImage(named: icon.assetName + "-image") ?? UIImage()) + .resizable() + .renderingMode(.original) + .cornerRadius(cornerRadius) + .frame(width: size, height: size) + VStack(alignment: .leading) { + Text(icon.displayName) + if let artist = artists.first(where: { $0.value.contains(icon.assetName) }) { + Text("By " + artist.key) + .foregroundColor(.gray) + } + } + Spacer() + if data.selectedIconName == icon.assetName { + Image(systemSymbol: .checkmark) + .foregroundColor(Color.blue) + } + } + }.foregroundColor(.primary) + } + .navigationTitle(L10n.AppIconsView.title) + .enableInjection() + } +} + +struct AppIconsView_Previews: PreviewProvider { + static var previews: some View { + AppIconsView() + } +} diff --git a/AltStore/Views/Settings/ConnectAppleIDView.swift b/AltStore/Views/Settings/ConnectAppleIDView.swift new file mode 100644 index 00000000..e53c8183 --- /dev/null +++ b/AltStore/Views/Settings/ConnectAppleIDView.swift @@ -0,0 +1,109 @@ +// +// ConnectAppleIDView.swift +// SideStore +// +// Created by Fabian Thies on 29.11.22. +// Copyright © 2022 SideStore. All rights reserved. +// + +import SwiftUI +import AltSign + +struct ConnectAppleIDView: View { + typealias AuthenticationHandler = (String, String, @escaping (Result<(ALTAccount, ALTAppleAPISession), Error>) -> Void) -> Void + typealias CompletionHandler = ((ALTAccount, ALTAppleAPISession, String)?) -> Void + + @Environment(\.dismiss) + private var dismiss + + var authenticationHandler: AuthenticationHandler? + var completionHandler: CompletionHandler? + + @State var email: String = "" + @State var password: String = "" + @State var isLoading: Bool = false + + var isFormValid: Bool { + !email.isEmpty && !password.isEmpty + } + + var body: some View { + ScrollView { + VStack(alignment: .leading, spacing: 32) { + Text(L10n.ConnectAppleIDView.startWithSignIn) + + VStack(spacing: 16) { + RoundedTextField(title: L10n.ConnectAppleIDView.appleID, placeholder: "user@sidestore.io", text: $email) + + RoundedTextField(title: L10n.ConnectAppleIDView.password, placeholder: "••••••", text: $password, isSecure: true) + } + + SwiftUI.Button(action: signIn) { + Text(L10n.ConnectAppleIDView.signIn) + .bold() + } + .buttonStyle(FilledButtonStyle(isLoading: isLoading)) + .disabled(!isFormValid) + + Spacer() + + VStack(alignment: .leading) { + Text(L10n.ConnectAppleIDView.whyDoWeNeedThis) + .bold() + + Text(L10n.ConnectAppleIDView.footer) + } + .padding() + .background( + RoundedRectangle(cornerRadius: 12) + .foregroundColor(Color(.secondarySystemBackground)) + ) + } + .padding(.horizontal) + } + .frame(maxWidth: .infinity) + .navigationTitle(L10n.ConnectAppleIDView.connectYourAppleID) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + SwiftUI.Button(action: self.cancel) { + Text(L10n.ConnectAppleIDView.cancel) + } + } + } + } + + + func signIn() { + self.isLoading = true + self.authenticationHandler?(email, password) { (result) in + defer { + self.isLoading = false + } + + switch result + { + case .failure(ALTAppleAPIError.requiresTwoFactorAuthentication): + // Ignore + break + + case .failure(let error as NSError): + let error = error.withLocalizedFailure(NSLocalizedString(L10n.ConnectAppleIDView.failedToSignIn, comment: "")) + print(error) + + case .success((let account, let session)): + self.completionHandler?((account, session, password)) + } + } + } + + func cancel() { + self.completionHandler?(nil) +// self.dismiss() + } +} + +struct ConnectAppleIDView_Previews: PreviewProvider { + static var previews: some View { + ConnectAppleIDView() + } +} diff --git a/AltStore/Views/Settings/DevModeView.swift b/AltStore/Views/Settings/DevModeView.swift new file mode 100644 index 00000000..0b074885 --- /dev/null +++ b/AltStore/Views/Settings/DevModeView.swift @@ -0,0 +1,190 @@ +// +// DevModeView.swift +// SideStore +// +// Created by naturecodevoid on 2/16/23. +// Copyright © 2023 SideStore. All rights reserved. +// + +import SwiftUI +import LocalConsole +import minimuxer + +// Yes, we know the password is right here. It's not supposed to be a secret, just something to hopefully prevent people breaking SideStore with dev mode and then complaining to us. +let DEV_MODE_PASSWORD = "devmode" + +struct DevModePrompt: View { + @Binding var isShowingDevModePrompt: Bool + @Binding var isShowingDevModeMenu: Bool + + @State var countdown = 0 + @State var isShowingPasswordAlert = false + @State var isShowingIncorrectPasswordAlert = false + @State var password = "" + + var button: some View { + SwiftUI.Button(action: { + if #available(iOS 16.0, *) { + isShowingPasswordAlert = true + } else { + // iOS 14 doesn't support .alert, so just go straight to dev mode without asking for a password + // iOS 15 also doesn't seem to support TextField in an alert (the text field was nonexistent) + enableDevMode() + } + }) { + Text(countdown <= 0 ? L10n.Action.enable + " " + L10n.DevModeView.title : L10n.DevModeView.read + " (\(countdown))") + .foregroundColor(.red) + } + .buttonStyle(FilledButtonStyle()) // TODO: set tintColor so text is more readable + .disabled(countdown > 0) + } + + @ViewBuilder + var text: some View { + if #available(iOS 15.0, *), + let string = try? AttributedString(markdown: L10n.DevModeView.prompt, options: AttributedString.MarkdownParsingOptions(interpretedSyntax: .inlineOnlyPreservingWhitespace)) { + Text(string) + } else { + Text(L10n.DevModeView.prompt) + } + } + + var view: some View { + ScrollView { + VStack { + text + .foregroundColor(.primary) + .padding(.bottom) + + button + } + .padding(.horizontal) + } + .frame(maxWidth: .infinity) + .navigationTitle(L10n.DevModeView.title) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + SwiftUI.Button(action: { isShowingDevModePrompt = false }) { + Text(L10n.Action.close) + } + } + } + .onAppear { + countdown = 20 + tickCountdown() + } + } + + var body: some View { + NavigationView { + if #available(iOS 15.0, *) { + view + .alert(L10n.DevModeView.password, isPresented: $isShowingPasswordAlert) { + TextField(L10n.DevModeView.password, text: $password) + .autocapitalization(.none) + .autocorrectionDisabled(true) + SwiftUI.Button(L10n.Action.submit, action: { + if password == DEV_MODE_PASSWORD { + enableDevMode() + } else { + isShowingIncorrectPasswordAlert = true + } + }) + } + .alert(L10n.DevModeView.incorrectPassword, isPresented: $isShowingIncorrectPasswordAlert) { + SwiftUI.Button(L10n.Action.tryAgain, action: { + isShowingIncorrectPasswordAlert = false + isShowingPasswordAlert = true + }) + SwiftUI.Button(L10n.Action.cancel, action: { + isShowingIncorrectPasswordAlert = false + isShowingDevModePrompt = false + }) + } + } else { + view + } + } + } + + func enableDevMode() { + UserDefaults.standard.isDevModeEnabled = true + isShowingDevModePrompt = false + isShowingDevModeMenu = true + } + + func tickCountdown() { + if countdown <= 0 { return } + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + countdown -= 1 + tickCountdown() + } + } +} + +struct DevModeMenu: View { + @ObservedObject private var iO = Inject.observer + + @AppStorage("isConsoleEnabled") + var isConsoleEnabled: Bool = false + + var body: some View { + List { + Section { + Toggle(L10n.DevModeView.console, isOn: self.$isConsoleEnabled) + .onChange(of: self.isConsoleEnabled) { value in + LCManager.shared.isVisible = value + } + + NavigationLink(L10n.DevModeView.dataExplorer) { + FileExplorer.normal(url: FileManager.default.altstoreSharedDirectory) + .navigationTitle(L10n.DevModeView.dataExplorer) + }.foregroundColor(.red) + + NavigationLink(L10n.DevModeView.tmpExplorer) { + FileExplorer.normal(url: FileManager.default.temporaryDirectory) + .navigationTitle(L10n.DevModeView.tmpExplorer) + }.foregroundColor(.red) + + Toggle(L10n.DevModeView.skipResign, isOn: ResignAppOperation.skipResignBinding) + .foregroundColor(.red) + } footer: { + Text(L10n.DevModeView.footer) + } + + Section { + AsyncFallibleButton(action: { + let dir = try dump_profiles(FileManager.default.documentsDirectory.absoluteString) + DispatchQueue.main.async { + UIApplication.shared.open(URL(string: "shareddocuments://" + dir.toString())!, options: [:], completionHandler: nil) + } + }) { execute in + Text(L10n.DevModeView.Minimuxer.dumpProfiles) + } + + NavigationLink(L10n.DevModeView.Minimuxer.afcExplorer) { + FileExplorer.afc() + .navigationTitle(L10n.DevModeView.Minimuxer.afcExplorer) + }.foregroundColor(.red) + } header: { + Text(L10n.DevModeView.minimuxer) + } footer: { + Text(L10n.DevModeView.Minimuxer.footer) + } + } + .navigationTitle(L10n.DevModeView.title) + .enableInjection() + } +} + +struct DevModeView_Previews: PreviewProvider { + static var previews: some View { + NavigationView { + List { + NavigationLink("DevModeMenu") { + DevModeMenu() + } + } + } + } +} diff --git a/AltStore/Views/Settings/ErrorLogView.swift b/AltStore/Views/Settings/ErrorLogView.swift new file mode 100644 index 00000000..6c3dc203 --- /dev/null +++ b/AltStore/Views/Settings/ErrorLogView.swift @@ -0,0 +1,169 @@ +// +// ErrorLogView.swift +// SideStore +// +// Created by Fabian Thies on 03.02.23. +// Copyright © 2023 SideStore. All rights reserved. +// + +import SwiftUI +import AltStoreCore +import ExpandableText + +struct ErrorLogView: View { + @Environment(\.dismiss) var dismiss + + @SwiftUI.FetchRequest(sortDescriptors: [ + NSSortDescriptor(keyPath: \LoggedError.date, ascending: false) + ]) + var loggedErrors: FetchedResults + + var groupedLoggedErrors: [Date: [LoggedError]] { + Dictionary(grouping: loggedErrors, by: { Calendar.current.startOfDay(for: $0.date) }) + } + + @State var currentFaqUrl: URL? + @State var isShowingMinimuxerLog: Bool = false + @State var isShowingDeleteConfirmation: Bool = false + + + var body: some View { + List { + ForEach(groupedLoggedErrors.keys.sorted(by: { $0 > $1 }), id: \.self) { date in + Section { + let errors = groupedLoggedErrors[date] ?? [] + ForEach(errors, id: \.date) { error in + VStack(spacing: 8) { + HStack(alignment: .top) { + Group { + if let storeApp = error.storeApp { + AppIconView(iconUrl: storeApp.iconURL, isSideStore: storeApp.isSideStore, size: 50) + } else { + ZStack { + RoundedRectangle(cornerRadius: 50*0.234, style: .continuous) + .foregroundColor(Color(UIColor.secondarySystemFill)) + + Image(systemSymbol: .exclamationmarkCircle) + .imageScale(.large) + .foregroundColor(.red) + } + .frame(width: 50, height: 50) + } + } + + VStack(alignment: .leading) { + Text(error.localizedFailure ?? "Operation Failed") + .bold() + + Group { + switch error.domain { + case AltServerErrorDomain: Text("SideServer Error \(error.code)") + case OperationError.domain: Text("SideStore Error \(error.code)") + default: Text(error.error.localizedErrorCode) + } + } + .font(.caption) + .foregroundColor(.secondary) + } + + Spacer() + + Text(DateFormatterHelper.timeString(for: error.date)) + .font(.caption) + .foregroundColor(.secondary) + } + + let nsError = error.error as NSError + let errorDescription = [nsError.localizedDescription, nsError.localizedRecoverySuggestion].compactMap { $0 }.joined(separator: "\n\n") + + Menu { + SwiftUI.Button { + UIPasteboard.general.string = errorDescription + } label: { + Label("Copy Error Message", systemSymbol: .docOnDoc) + } + + SwiftUI.Button { + UIPasteboard.general.string = error.error.localizedErrorCode + } label: { + Label("Copy Error Code", systemSymbol: .docOnDoc) + } + + SwiftUI.Button { + self.searchFAQ(for: error) + } label: { + Label("Search FAQ", systemSymbol: .magnifyingglass) + } + + } label: { + Text(errorDescription) + .multilineTextAlignment(.leading) + .foregroundColor(.primary) + } + } + } + } header: { + Text(DateFormatterHelper.string(for: date)) + } + } + } + .navigationBarTitle("Error Log") + .toolbar { + ToolbarItemGroup(placement: .navigationBarTrailing) { + ModalNavigationLink { + FilePreviewView(urls: [ + FileManager.default.documentsDirectory.appendingPathComponent("minimuxer.log") + ]) + .ignoresSafeArea() + } label: { + Image(systemSymbol: .ladybug) + } + + + SwiftUI.Button { + self.isShowingDeleteConfirmation = true + } label: { + Image(systemSymbol: .trash) + } + .actionSheet(isPresented: self.$isShowingDeleteConfirmation) { + ActionSheet( + title: Text("Are you sure you want to clear the error log?"), + buttons: [ + .destructive(Text("Clear Error Log"), action: self.clearLoggedErrors), + .cancel() + ] + ) + } + } + } + .sheet(item: self.$currentFaqUrl) { url in + SafariView(url: url) + } + } + + func searchFAQ(for error: LoggedError) { + let baseURL = URL(string: "https://faq.altstore.io/getting-started/troubleshooting-guide")! + var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false)! + + let query = [error.domain, "\(error.code)"].joined(separator: "+") + components.queryItems = [URLQueryItem(name: "q", value: query)] + + self.currentFaqUrl = components.url ?? baseURL + } + + func clearLoggedErrors() { + DatabaseManager.shared.purgeLoggedErrors { result in + if case let .failure(error) = result { + NotificationManager.shared.reportError(error: error) + } + } + } +} + +struct ErrorLogView_Previews: PreviewProvider { + static var previews: some View { + NavigationView { + ErrorLogView() + } + } +} diff --git a/AltStore/Views/Settings/LicensesView.swift b/AltStore/Views/Settings/LicensesView.swift new file mode 100644 index 00000000..55f17205 --- /dev/null +++ b/AltStore/Views/Settings/LicensesView.swift @@ -0,0 +1,112 @@ +// +// LicensesView.swift +// SideStore +// +// Created by Fabian Thies on 21.01.23. +// Copyright © 2023 SideStore. All rights reserved. +// + +import SwiftUI + +struct LicensesView: View { + + let licenses = """ + Jay Freeman (ldid) + Copyright (C) 2007-2012 Jay Freeman (saurik) + + libimobiledevice + © 2007-2015 by the contributors of libimobiledevice - All rights reserved. + + Gilles Vollant (minizip) + Copyright (C) 1998-2005 Gilles Vollant + + Kishikawa Katsumi (KeychainAccess) + Copyright (c) 2014 kishikawa katsumi + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Alexander Grebenyuk (Nuke) + Copyright (c) 2015-2019 Alexander Grebenyuk + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Craig Hockenberry (MarkdownAttributedString) + Copyright (c) 2020 The Iconfactory, Inc. https://iconfactory.com + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + The OpenSSL Project (OpenSSL) + Copyright (c) 1998-2019 The OpenSSL Project. All rights reserved. + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + 3. All advertising materials mentioning features or use of this software must display the following acknowledgment: "This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to endorse or promote products derived from this software without prior written permission. For written permission, please contact openssl-core@openssl.org. + 5. Products derived from this software may not be called "OpenSSL" nor may "OpenSSL" appear in their names without prior written permission of the OpenSSL Project. + 6. Redistributions of any form whatsoever must retain the following acknowledgment: + "This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (http://www.openssl.org/)" + THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + This product includes cryptographic software written by Eric Young (eay@cryptsoft.com). This product includes software written by Tim Hudson (tjh@cryptsoft.com). + + Eric Young (SSLeay) + /* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + All rights reserved. + This package is an SSL implementation written by Eric Young (eay@cryptsoft.com). + The implementation was written so as to conform with Netscapes SSL. This library is free for commercial and non-commercial use as long as the following conditions are aheared to. The following conditions apply to all code found in this distribution, be it the RC4, RSA, lhash, DES, etc., code; not just the SSL code. The SSL documentation included with this distribution is covered by the same copyright terms except that the holder is Tim Hudson (tjh@cryptsoft.com). + Copyright remains Eric Young's, and as such any Copyright notices in the code are not to be removed. If this package is used in a product, Eric Young should be given attribution as the author of the parts of the library used. This can be in the form of a textual message at program startup or in documentation (online or textual) provided with the package. + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the copyright notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. All advertising materials mentioning features or use of this software must display the following acknowledgement: + "This product includes cryptographic software written by Eric Young (eay@cryptsoft.com)" + The word 'cryptographic' can be left out if the rouines from the library being used are not cryptographic related :-). + 4. If you include any Windows specific code (or a derivative thereof) from the apps directory (application code) you must include an acknowledgement: + "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + The licence and distribution terms for any publically available version or derivative of this code cannot be changed. i.e. this code cannot simply be copied and put under another distribution licence [including the GNU Public Licence.] + + Toni Ronkko (dirent) + Copyright (c) 1998-2019 Toni Ronkko + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Microsoft Corporation (C++ REST SDK) + Copyright (c) Microsoft Corporation + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Kutuzov Viktor (mman-win32) + Copyright (c) Kutuzov Viktor + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + ICONS + + Settings by i cons from the Noun Project + """ + + + var body: some View { + ScrollView { + Text(licenses) + .padding() + } + .navigationTitle("Software Licenses") + } +} + + +struct LicensesView_Previews: PreviewProvider { + + static var previews: some View { + NavigationView { + LicensesView() + } + } +} diff --git a/AltStore/Views/Settings/RefreshAttemptsView.swift b/AltStore/Views/Settings/RefreshAttemptsView.swift new file mode 100644 index 00000000..24d8abfd --- /dev/null +++ b/AltStore/Views/Settings/RefreshAttemptsView.swift @@ -0,0 +1,90 @@ +// +// RefreshAttemptsView.swift +// SideStore +// +// Created by Fabian Thies on 04.02.23. +// Copyright © 2023 SideStore. All rights reserved. +// + +import SwiftUI +import AltStoreCore + +struct RefreshAttemptsView: View { + @SwiftUI.FetchRequest(sortDescriptors: [ + NSSortDescriptor(keyPath: \RefreshAttempt.date, ascending: false) + ]) + var refreshAttempts: FetchedResults + + var groupedRefreshAttempts: [Date: [RefreshAttempt]] { + Dictionary(grouping: refreshAttempts, by: { Calendar.current.startOfDay(for: $0.date) }) + } + + var body: some View { + List { + ForEach(groupedRefreshAttempts.keys.sorted(by: { $0 > $1 }), id: \.self) { date in + Section { + let attempts = groupedRefreshAttempts[date] ?? [] + ForEach(attempts, id: \.date) { attempt in + VStack(alignment: .leading, spacing: 8) { + HStack { + if attempt.isSuccess { + Text("Success") + .bold() + .foregroundColor(.green) + } else { + Text("Failure") + .bold() + .foregroundColor(.red) + } + + Spacer() + + Text(DateFormatterHelper.timeString(for: attempt.date)) + .font(.caption) + .foregroundColor(.secondary) + } + + if let description = attempt.errorDescription { + Text(description) + } + } + } + } header: { + Text(DateFormatterHelper.string(for: date)) + } + } + } + .background(self.listBackground) + .navigationTitle("Refresh Attempts") + } + + @ViewBuilder + var listBackground: some View { + if self.refreshAttempts.isEmpty { + VStack(spacing: 8) { + Spacer() + Text("No Refresh Attempts") + .font(.title) + + Text("The more you use SideStore, the more often iOS will allow it to refresh apps in the background.") + + Spacer() + } + .multilineTextAlignment(.center) + .foregroundColor(.secondary) + .padding() + } else { + Color.clear + } + } +} + + +struct RefreshAttemptsView_Previews: PreviewProvider { + + static var previews: some View { + NavigationView { + RefreshAttemptsView() + } + } +} diff --git a/AltStore/Views/Settings/SettingsView.swift b/AltStore/Views/Settings/SettingsView.swift new file mode 100644 index 00000000..94b12307 --- /dev/null +++ b/AltStore/Views/Settings/SettingsView.swift @@ -0,0 +1,374 @@ +// +// SettingsView.swift +// SideStoreUI +// +// Created by Fabian Thies on 18.11.22. +// Copyright © 2022 Fabian Thies. All rights reserved. +// + +import SwiftUI +import AsyncImage +import SFSafeSymbols +import LocalConsole +import AltStoreCore +import Intents +import minimuxer + +struct SettingsView: View { + @ObservedObject private var iO = Inject.observer + + var connectedAppleID: Team? { + DatabaseManager.shared.activeTeam() + } + + @SwiftUI.FetchRequest(sortDescriptors: [], predicate: NSPredicate(format: "%K == YES", #keyPath(Team.isActiveTeam))) + var connectedTeams: FetchedResults + + + @AppStorage("isBackgroundRefreshEnabled") + var isBackgroundRefreshEnabled: Bool = true + + @AppStorage("isDevModeEnabled") + var isDevModeEnabled: Bool = false + + @AppStorage("isDebugLoggingEnabled") + var isDebugLoggingEnabled: Bool = false + + @State var isShowingConnectAppleIDView = false + @State var isShowingResetPairingFileConfirmation = false + @State var isShowingDevModePrompt = false + @State var isShowingDevModeMenu = false + + @State var externalURLToShow: URL? + @State var quickLookURL: URL? + + let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown Version" + + var body: some View { + List { + Section { + if let connectedAppleID = connectedTeams.first { + HStack { + Text(L10n.SettingsView.ConnectedAppleID.name) + .foregroundColor(.secondary) + Spacer() + Text(connectedAppleID.name) + } + + HStack { + Text(L10n.SettingsView.ConnectedAppleID.eMail) + .foregroundColor(.secondary) + Spacer() + Text(connectedAppleID.account.appleID) + } + + HStack { + Text(L10n.SettingsView.ConnectedAppleID.type) + .foregroundColor(.secondary) + Spacer() + Text(connectedAppleID.type.localizedDescription) + } + } else { + SwiftUI.Button { + self.connectAppleID() + } label: { + Text(L10n.SettingsView.connectAppleID) + } + } + } header: { + if !connectedTeams.isEmpty { + HStack { + Text(L10n.SettingsView.ConnectedAppleID.text) + Spacer() + SwiftUI.Button { + self.disconnectAppleID() + } label: { + Text(L10n.SettingsView.ConnectedAppleID.signOut) + .font(.callout) + .bold() + } + } + } + } footer: { + VStack(alignment: .leading, spacing: 4) { + Text(L10n.SettingsView.ConnectedAppleID.Footer.p1) + + Text(L10n.SettingsView.ConnectedAppleID.Footer.p2) + } + } + + Section { + NavigationLink(L10n.AppIconsView.title) { + AppIconsView() + } + } + + Section { + Toggle(isOn: self.$isBackgroundRefreshEnabled, label: { + Text(L10n.SettingsView.backgroundRefresh) + }) + + ModalNavigationLink(L10n.SettingsView.addToSiri) { + if let shortcut = INShortcut(intent: INInteraction.refreshAllApps().intent) { + SiriShortcutSetupView(shortcut: shortcut) + } + } + } header: { + Text(L10n.SettingsView.refreshingApps) + } footer: { + Text(L10n.SettingsView.refreshingAppsFooter) + } + + Section { + SwiftUI.Button { + self.externalURLToShow = URL(string: "https://sidestore.io")! + } label: { + HStack { + Text("Developers") + .foregroundColor(.secondary) + Spacer() + Text("SideStore Team") + Image(systemSymbol: .chevronRight) + .foregroundColor(.secondary) + } + } + .foregroundColor(.primary) + + SwiftUI.Button { + self.externalURLToShow = URL(string: "https://fabian-thies.de")! + } label: { + HStack { + Text(L10n.SettingsView.swiftUIRedesign) + .foregroundColor(.secondary) + Spacer() + Text("fabianthdev") + Image(systemSymbol: .chevronRight) + .foregroundColor(.secondary) + } + } + .foregroundColor(.primary) + + NavigationLink { + LicensesView() + } label: { + Text("Licenses") + } + + } header: { + Text(L10n.SettingsView.credits) + } + + Section { + NavigationLink("Show Error Log") { + ErrorLogView() + } + + NavigationLink("Show Refresh Attempts") { + RefreshAttemptsView() + } + + NavigationLink(L10n.AdvancedSettingsView.title) { + AdvancedSettingsView() + } + + Toggle(L10n.SettingsView.debugLogging, isOn: self.$isDebugLoggingEnabled) + .onChange(of: self.isDebugLoggingEnabled) { value in + UserDefaults.shared.isDebugLoggingEnabled = value + set_debug(value) + } + + AsyncFallibleButton(action: self.exportLogs, label: { execute in Text(L10n.SettingsView.exportLogs) }) + + if MailComposeView.canSendMail { + ModalNavigationLink("Send Feedback") { + MailComposeView(recipients: ["support@sidestore.io"], + subject: "SideStore Beta \(appVersion) Feedback") { + NotificationManager.shared.showNotification(title: "Thank you for your feedback!") + } onError: { error in + NotificationManager.shared.reportError(error: error) + } + .ignoresSafeArea() + } + } + + SwiftUI.Button(L10n.SettingsView.switchToUIKit, action: self.switchToUIKit) + + SwiftUI.Button(L10n.SettingsView.resetImageCache, action: self.resetImageCache) + .foregroundColor(.red) + + SwiftUI.Button("Reset Pairing File") { + self.isShowingResetPairingFileConfirmation = true + } + .foregroundColor(.red) + .actionSheet(isPresented: self.$isShowingResetPairingFileConfirmation) { + ActionSheet(title: Text("Are you sure to reset the pairing file?"), message: Text("You can reset the pairing file when you cannot sideload apps or enable JIT. SideStore will close when the file has been deleted."), buttons: [ + .destructive(Text("Delete and Reset"), action: self.resetPairingFile), + .cancel() + ]) + } + + if isDevModeEnabled { + NavigationLink(L10n.DevModeView.title, isActive: self.$isShowingDevModeMenu) { + DevModeMenu() + }.foregroundColor(.red) + } else { + SwiftUI.Button(L10n.DevModeView.title) { + self.isShowingDevModePrompt = true + } + .foregroundColor(.red) + .sheet(isPresented: self.$isShowingDevModePrompt) { + DevModePrompt(isShowingDevModePrompt: self.$isShowingDevModePrompt, isShowingDevModeMenu: self.$isShowingDevModeMenu) + } + } + } header: { + Text(L10n.SettingsView.debug) + } + + + Section { + + } footer: { + Text("SideStore \(appVersion)") + .multilineTextAlignment(.center) + .frame(maxWidth: .infinity) + } + } + .listStyle(InsetGroupedListStyle()) + .navigationTitle(L10n.SettingsView.title) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + SwiftUI.Button { + + } label: { + Image(systemSymbol: .personCropCircle) + .imageScale(.large) + } + + } + } + .sheet(item: $externalURLToShow) { url in + SafariView(url: url) + } + .quickLookPreview($quickLookURL) + .enableInjection() + } + + +// var appleIDSection: some View { +// +// } + + + + func connectAppleID() { + guard let rootViewController = UIApplication.shared.keyWindow?.rootViewController else { + return + } + + AppManager.shared.authenticate(presentingViewController: rootViewController) { (result) in + DispatchQueue.main.async { + switch result + { + case .failure(OperationError.cancelled): + // Ignore + break + + case .failure(let error): + NotificationManager.shared.reportError(error: error) + + case .success: break + } + } + } + } + + func disconnectAppleID() { + DatabaseManager.shared.signOut { (error) in + DispatchQueue.main.async { + if let error = error + { + NotificationManager.shared.reportError(error: error) + } + } + } + } + + func switchToUIKit() { + let storyboard = UIStoryboard(name: "Main", bundle: .main) + let rootVC = storyboard.instantiateViewController(withIdentifier: "tabBarController") as! TabBarController + + UIApplication.shared.keyWindow?.rootViewController = rootVC + } + + func resetImageCache() { + do { + let url = try FileManager.default.url( + for: .cachesDirectory, + in: .userDomainMask, + appropriateFor: nil, + create: true) + try FileManager.default.removeItem(at: url.appendingPathComponent("com.zeu.cache", isDirectory: true)) + } catch let error { + fatalError("\(error)") + } + } + + func resetPairingFile() { + let filename = "ALTPairingFile.mobiledevicepairing" + let fileURL = FileManager.default.documentsDirectory.appendingPathComponent(filename) + + // Delete the pairing file if it exists + if FileManager.default.fileExists(atPath: fileURL.path) { + do { + try FileManager.default.removeItem(at: fileURL) + print("Pairing file deleted successfully.") + } catch { + print("Failed to delete pairing file:", error) + } + } + + // Close and exit SideStore + UIApplication.shared.perform(#selector(URLSessionTask.suspend)) + DispatchQueue.main.asyncAfter(deadline: .now().advanced(by: .milliseconds(500))) { + exit(0) + } + } + + func exportLogs() throws { + let path = FileManager.default.documentsDirectory.appendingPathComponent("sidestore.log") + var text = LCManager.shared.currentText + + // TODO: add more potentially sensitive info to this array + var remove = [String]() + if let connectedAppleID = connectedTeams.first { + remove.append(connectedAppleID.name) + remove.append(connectedAppleID.account.appleID) + remove.append(connectedAppleID.account.firstName) + remove.append(connectedAppleID.account.lastName) + remove.append(connectedAppleID.account.localizedName) + remove.append(connectedAppleID.account.identifier) + remove.append(connectedAppleID.identifier) + } + if let udid = fetch_udid() { + remove.append(udid.toString()) + } + + for toRemove in remove { + text = text.replacingOccurrences(of: toRemove, with: "[removed]") + } + + guard let data = text.data(using: .utf8) else { throw NSError(domain: "Failed to get data.", code: 2) } + try data.write(to: path) + quickLookURL = path + } +} + +struct SettingsView_Previews: PreviewProvider { + static var previews: some View { + NavigationView { + SettingsView() + } + } +} + + diff --git a/AltStoreCore/Extensions/UserDefaults+AltStore.swift b/AltStoreCore/Extensions/UserDefaults+AltStore.swift index 7a057a55..e996ad44 100644 --- a/AltStoreCore/Extensions/UserDefaults+AltStore.swift +++ b/AltStoreCore/Extensions/UserDefaults+AltStore.swift @@ -20,12 +20,17 @@ public extension UserDefaults }() @NSManaged var firstLaunch: Date? + @NSManaged var onboardingComplete: Bool @NSManaged var requiresAppGroupMigration: Bool @NSManaged var textServer: Bool @NSManaged var textInputAnisetteURL: String? @NSManaged var customAnisetteURL: String? @NSManaged var preferredServerID: String? + @NSManaged var isDevModeEnabled: Bool + @NSManaged var isConsoleEnabled: Bool + @NSManaged var isDebugLoggingEnabled: Bool + @NSManaged var isBackgroundRefreshEnabled: Bool @NSManaged var isDebugModeEnabled: Bool @NSManaged var presentedLaunchReminderNotification: Bool @@ -73,6 +78,10 @@ public extension UserDefaults let localServerSupportsRefreshing = !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios14) let defaults = [ + #keyPath(UserDefaults.isDevModeEnabled): false, + #keyPath(UserDefaults.isConsoleEnabled): false, + #keyPath(UserDefaults.isDebugLoggingEnabled): false, + #keyPath(UserDefaults.onboardingComplete): false, #keyPath(UserDefaults.isBackgroundRefreshEnabled): true, #keyPath(UserDefaults.isLegacyDeactivationSupported): isLegacyDeactivationSupported, #keyPath(UserDefaults.activeAppLimitIncludesExtensions): activeAppLimitIncludesExtensions, diff --git a/AltStoreCore/Model/AppPermission.swift b/AltStoreCore/Model/AppPermission.swift index 2763f95c..8169c804 100644 --- a/AltStoreCore/Model/AppPermission.swift +++ b/AltStoreCore/Model/AppPermission.swift @@ -7,6 +7,7 @@ // import CoreData +import SFSafeSymbols import UIKit public extension ALTAppPermissionType @@ -46,28 +47,51 @@ public extension ALTAppPermissionType } var icon: UIImage? { - switch self - { - case .photos: return UIImage(systemName: "photo.on.rectangle.angled") - case .camera: return UIImage(systemName: "camera.fill") - case .location: return UIImage(systemName: "location.fill") - case .contacts: return UIImage(systemName: "person.2.fill") - case .reminders: return UIImage(systemName: "checklist") - case .appleMusic: return UIImage(systemName: "music.note") - case .microphone: return UIImage(systemName: "mic.fill") - case .speechRecognition: return UIImage(systemName: "waveform.and.mic") - case .backgroundAudio: return UIImage(systemName: "speaker.fill") - case .backgroundFetch: return UIImage(systemName: "square.and.arrow.down") - case .bluetooth: return UIImage(systemName: "wave.3.right") - case .network: return UIImage(systemName: "network") - case .calendars: return UIImage(systemName: "calendar") - case .touchID: return UIImage(systemName: "touchid") - case .faceID: return UIImage(systemName: "faceid") - case .siri: return UIImage(systemName: "mic.and.signal.meter.fill") - case .motion: return UIImage(systemName: "figure.walk.motion") - default: + let symbol: SFSymbol? = { + switch self { + case .photos: return .photoOnRectangleAngled + case .camera: return .cameraFill + case .location: return .locationFill + case .contacts: return .person2Fill + case .reminders: + if #available(iOS 15.0, *) { + return .checklist + } + return .listBullet + case .appleMusic: return .musicNote + case .microphone: return .micFill + case .speechRecognition: + if #available(iOS 15.0, *) { + return .waveformAndMic + } + return .recordingtape + case .backgroundAudio: return .speakerFill + case .backgroundFetch: return .squareAndArrowDown + case .bluetooth: return .wave3Right + case .network: return .network + case .calendars: return .calendar + case .touchID: return .touchid + case .faceID: return .faceid + case .siri: + if #available(iOS 16.0, *) { + return .micAndSignalMeterFill + } + return .waveform + case .motion: + if #available(iOS 16.0, *) { + return .figureWalkMotion + } + return .figureWalk + default: + return nil + } + }() + + guard let symbol = symbol else { return nil } + + return UIImage(systemSymbol: symbol) } } diff --git a/AltStoreCore/Model/MergePolicy.swift b/AltStoreCore/Model/MergePolicy.swift index 08038988..7dee4903 100644 --- a/AltStoreCore/Model/MergePolicy.swift +++ b/AltStoreCore/Model/MergePolicy.swift @@ -51,9 +51,14 @@ open class MergePolicy: RSTRelationshipPreservingMergePolicy print("[ALTLog] Resolving AppVersion context-level conflict. Most likely due to migrating from pre-AppVersion model version.", primaryAppVersion) } + case is Source where conflict.conflictingObjects.count == 2: + // Use the first one + let second = conflict.conflictingObjects[1] + second.managedObjectContext?.delete(second) + default: // Unknown context-level conflict. - assertionFailure("MergePolicy is only intended to work with database-level conflicts.") + assertionFailure("MergePolicy is only intended to work with database-level conflicts. \(conflict.conflictingObjects.map { obj in obj.description }.joined(separator: ", "))") } } diff --git a/AltStoreCore/Model/StoreApp.swift b/AltStoreCore/Model/StoreApp.swift index 2e38b57d..a8531a0f 100644 --- a/AltStoreCore/Model/StoreApp.swift +++ b/AltStoreCore/Model/StoreApp.swift @@ -342,25 +342,31 @@ public extension StoreApp class func makeAltStoreApp(in context: NSManagedObjectContext) -> StoreApp { let app = StoreApp(context: context) + app.source = Source.makeAltStoreSource(in: context) app.name = "SideStore" app.bundleIdentifier = StoreApp.altstoreAppID app.developerName = "Side Team" app.localizedDescription = "SideStore is an alternative App Store." - app.iconURL = URL(string: "https://user-images.githubusercontent.com/705880/63392210-540c5980-c37b-11e9-968c-8742fc68ab2e.png")! - app.screenshotURLs = [] - app.sourceIdentifier = Source.altStoreIdentifier + app.iconURL = URL(string: "https://apps.sidestore.io/apps/sidestore/v0.1.1/icon.png")! + app.screenshotURLs = [ + URL(string: "https://apps.sidestore.io/apps/sidestore/v0.1.1/browse-dark.png")!, + URL(string: "https://apps.sidestore.io/apps/sidestore/v0.1.1/apps-dark.png")!, + URL(string: "https://apps.sidestore.io/apps/sidestore/v0.1.1/news-dark.png")!, + URL(string: "https://apps.sidestore.io/apps/sidestore/v0.1.1/browse-light.png")!, + URL(string: "https://apps.sidestore.io/apps/sidestore/v0.1.1/apps-light.png")!, + URL(string: "https://apps.sidestore.io/apps/sidestore/v0.1.1/news-light.png")!, + ] + app.tintColor = UIColor(named: "AccentColor") - let appVersion = AppVersion.makeAppVersion(version: "0.3.0", + let appVersion = AppVersion.makeAppVersion(version: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown", date: Date(), - downloadURL: URL(string: "http://rileytestut.com")!, + downloadURL: URL(string: "https://github.com/SideStore/SideStore/releases/download/0.1.1/SideStore.ipa")!, size: 0, appBundleID: app.bundleIdentifier, sourceID: Source.altStoreIdentifier, in: context) app.setVersions([appVersion]) - print("makeAltStoreApp StoreApp: \(String(describing: app))") - #if BETA app.isBeta = true #endif diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f71e494a..e2833175 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,11 +4,11 @@ Thank you for your interest in contributing to SideStore! SideStore is a communi There are many ways to contribute to SideStore, so if you aren't a developer, there are still many other ways you can help out: -- [Writing documentation](https://github.com/SideStore/SideStore-Docs) -- [Submitting detailed bug reports and suggesting new features](https://github.com/SideStore/SideStore/issues/new/choose) -- Helping out with support - - [Discord](https://discord.gg/RgpFBX3Q3k) - - [GitHub Discussions](https://github.com/SideStore/SideStore/discussions) +- [Writing documentation](https://github.com/SideStore/SideStore-Docs) +- [Submitting detailed bug reports and suggesting new features](https://github.com/SideStore/SideStore/issues/new/choose) +- Helping out with support + - [Discord](https://discord.gg/RgpFBX3Q3k) + - [GitHub Discussions](https://github.com/SideStore/SideStore/discussions) However, this guide will focus on the development side of things. For now, we will only have setup information here, but you can [join our Discord](https://discord.gg/RgpFBX3Q3k) if you need help after setup. @@ -17,19 +17,19 @@ after setup. This guide assumes you: -- are on a Mac -- have Xcode installed -- have basic command line knowledge (know how to run commands, cd into a directory) -- have basic Git knowledge ([GitHub Desktop](https://desktop.github.com) is a great tool for beginners, and greatly simplifies working with Git) -- have basic Swift/iOS development knowledge +- are on a Mac +- have Xcode installed +- have basic command line knowledge (know how to run commands, cd into a directory) +- have basic Git knowledge ([GitHub Desktop](https://desktop.github.com) is a great tool for beginners, and greatly simplifies working with Git) +- have basic Swift/iOS development knowledge ## Setup 1. Fork the SideStore repo on GitHub. 2. Clone the fork: `git clone https://github.com//SideStore.git --recurse-submodules` - If you are using GitHub Desktop, refer to - [this guide](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/adding-and-cloning-repositories/cloning-and-forking-repositories-from-github-desktop). + If you are using GitHub Desktop, refer to + [this guide](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/adding-and-cloning-repositories/cloning-and-forking-repositories-from-github-desktop). 3. Copy `CodeSigning.xcconfig.sample` to `CodeSigning.xcconfig` and fill in the values. 4. **(Development only)** Change the value for `ALTDeviceID` in the Info.plist to your device's UDID. Normally, SideServer embeds the device's UDID in SideStore's Info.plist during installation. When @@ -39,6 +39,10 @@ This guide assumes you: Next, make and test your changes. Then, commit and push your changes using git and make a pull request. +## Developer Mode + +If you are using the SwiftUI version of SideStore, there is a developer mode that has some useful development tools. You can access it at the bottom of Settings; the password is in `DevModeView.swift`. + ## Prebuilt binary information minimuxer and em_proxy use prebuilt static library binaries built by GitHub Actions to speed up builds and remove the need for Rust to be installed when working on SideStore. diff --git a/Dependencies/minimuxer.xcodeproj/project.pbxproj b/Dependencies/minimuxer.xcodeproj/project.pbxproj index 0eecb844..e8cb7f8f 100644 --- a/Dependencies/minimuxer.xcodeproj/project.pbxproj +++ b/Dependencies/minimuxer.xcodeproj/project.pbxproj @@ -18,6 +18,8 @@ filePatterns = "*/minimuxer.h"; fileType = pattern.proxy; inputFiles = ( + "./minimuxer/libminimuxer-ios.a", + "./minimuxer/libminimuxer-sim.a", ); isEditable = 0; name = "Cargo project build"; @@ -150,6 +152,8 @@ ./minimuxer/minimuxer.swift, ./minimuxer/SwiftBridgeCore.swift, "./minimuxer/minimuxer-Bridging-Header.h", + "./minimuxer/libminimuxer-ios.a", + "./minimuxer/libminimuxer-sim.a", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; diff --git a/swiftgen.yml b/swiftgen.yml new file mode 100644 index 00000000..8fffff13 --- /dev/null +++ b/swiftgen.yml @@ -0,0 +1,23 @@ +# colors: +# inputs: colors.xml +# outputs: +# templateName: swift5 +# output: Colors.swift +# +# fonts: +# inputs: Fonts +# outputs: +# templateName: swift5 +# output: Fonts.swift + +strings: + - inputs: AltStore/Resources/en.lproj/Localizable.strings + outputs: + templateName: structured-swift5 + output: AltStore/Generated/Localizations.swift + +xcassets: + inputs: AltStore/Resources/Assets.xcassets + outputs: + templateName: swift5 + output: AltStore/Generated/Assets.swift