[ExpFeat] Alternate App Icons (#259)

commit 34ed9726034adbb439515ccb3d576311603bfe2b
Author: Chris Rittenhouse <dev@litritt.com>
Date:   Thu Aug 3 15:31:47 2023 -0400

    Removes `experimentalFeatures` property from `Settings`

commit 4e31d22d56d68439a340707d2fdbac3763df1e3a
Author: Chris Rittenhouse <dev@litritt.com>
Date:   Thu Aug 3 15:17:47 2023 -0400

    Reorder icons

commit 7509eaa29946b622ce0ad920a740f0212db5e771
Author: Chris Rittenhouse <dev@litritt.com>
Date:   Thu Aug 3 15:17:24 2023 -0400

    Moves icon changing code to `AlternateAppIcons`

commit 84821ef99ded74c066563041618aec3c4acb30d7
Author: Chris Rittenhouse <dev@litritt.com>
Date:   Thu Aug 3 14:33:15 2023 -0400

    Fixes icon authors

commit 0b821830f09b434fa375c37a2504c21ee5f90bdb
Author: Chris Rittenhouse <dev@litritt.com>
Date:   Fri Jul 21 01:44:00 2023 -0400

    Adds comments for functional alternate app icon code

commit e8dd6165619bc143f13dde51e5160af2695ef17e
Author: Chris Rittenhouse <dev@litritt.com>
Date:   Fri Jul 21 01:30:03 2023 -0400

    Adds implementation for changing app icon

commit 1e07483d18d1e865804ae34d6011844b63cb33cc
Author: Chris Rittenhouse <dev@litritt.com>
Date:   Fri Jul 21 01:28:24 2023 -0400

    Adds Alternate App Icon experimental feature and options

commit 46ed2ce3ae9593d4cc95f29d6caa09f33f2ce62d
Author: Chris Rittenhouse <dev@litritt.com>
Date:   Fri Jul 21 01:26:51 2023 -0400

    Adds extension to handle retrieving app icon images

commit 9dddb5f98f4523e7877f97c66a5ae95e6513cc09
Author: Chris Rittenhouse <dev@litritt.com>
Date:   Fri Jul 21 01:24:19 2023 -0400

    Adds experimental features as a property of `Settings`

commit d74038fd418a0e6fcd45942cb67e722904fc3e24
Author: Chris Rittenhouse <dev@litritt.com>
Date:   Fri Jul 21 01:23:02 2023 -0400

    Adds alternate icon assets

Co-authored-by: Chris Rittenhouse <dev@litritt.com>
This commit is contained in:
Riley Testut 2023-08-10 19:44:33 -05:00
parent 061f5abd3e
commit f3534e4415
13 changed files with 235 additions and 0 deletions

View File

@ -34,6 +34,8 @@
/* Begin PBXBuildFile section */
1FA4ABA79AB72914FE414A61 /* libPods-Delta.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DC866E433B3BA9AE18ABA1EC /* libPods-Delta.a */; };
87343D7B985519A5890A61C6 /* libPods-DeltaPreviews.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7E3E5A45AB20C8A87754453B /* libPods-DeltaPreviews.a */; };
AC1AE30A2A69BD3A00956EB9 /* AlternateAppIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC1AE3092A69BD3A00956EB9 /* AlternateAppIcons.swift */; };
AC1AE30C2A6A068F00956EB9 /* Bundle+AppIconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC1AE30B2A6A068F00956EB9 /* Bundle+AppIconImage.swift */; };
AC1C991029F8B8C30020E6E4 /* ToastNotificationOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC1C990F29F8B8C30020E6E4 /* ToastNotificationOptions.swift */; };
AC1C992729F9F1CF0020E6E4 /* GameViewController+ExperimentalToasts.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC1C992629F9F1CF0020E6E4 /* GameViewController+ExperimentalToasts.swift */; };
ACF7E30D29F73D03000FE071 /* GameScreenshots.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACF7E30C29F73D03000FE071 /* GameScreenshots.swift */; };
@ -272,6 +274,8 @@
8ECE6641DE30D01EA30FE7F6 /* Pods-DeltaPreviews.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DeltaPreviews.release.xcconfig"; path = "Pods/Target Support Files/Pods-DeltaPreviews/Pods-DeltaPreviews.release.xcconfig"; sourceTree = "<group>"; };
A01281C7023C0041B25963BE /* Pods-DeltaPreviews.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DeltaPreviews.debug.xcconfig"; path = "Pods/Target Support Files/Pods-DeltaPreviews/Pods-DeltaPreviews.debug.xcconfig"; sourceTree = "<group>"; };
A19FF50F55441BC2B2248241 /* Pods-Delta.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Delta.release.xcconfig"; path = "Pods/Target Support Files/Pods-Delta/Pods-Delta.release.xcconfig"; sourceTree = "<group>"; };
AC1AE3092A69BD3A00956EB9 /* AlternateAppIcons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlternateAppIcons.swift; sourceTree = "<group>"; };
AC1AE30B2A6A068F00956EB9 /* Bundle+AppIconImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+AppIconImage.swift"; sourceTree = "<group>"; };
AC1C990F29F8B8C30020E6E4 /* ToastNotificationOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastNotificationOptions.swift; sourceTree = "<group>"; };
AC1C992629F9F1CF0020E6E4 /* GameViewController+ExperimentalToasts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GameViewController+ExperimentalToasts.swift"; sourceTree = "<group>"; };
ACF7E30C29F73D03000FE071 /* GameScreenshots.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameScreenshots.swift; sourceTree = "<group>"; };
@ -561,6 +565,7 @@
AC1C992629F9F1CF0020E6E4 /* GameViewController+ExperimentalToasts.swift */,
D5CDCCC32A85765900E22131 /* OSLog+Delta.swift */,
D5CDCCEA2A8593FC00E22131 /* UserDefaults+Delta.swift */,
AC1AE30B2A6A068F00956EB9 /* Bundle+AppIconImage.swift */,
);
path = Extensions;
sourceTree = "<group>";
@ -1120,6 +1125,7 @@
ACF7E30C29F73D03000FE071 /* GameScreenshots.swift */,
AC1C990F29F8B8C30020E6E4 /* ToastNotificationOptions.swift */,
D5147EC72A817B4A00D6CD64 /* ReviewSaveStatesOptions.swift */,
AC1AE3092A69BD3A00956EB9 /* AlternateAppIcons.swift */,
);
path = Features;
sourceTree = "<group>";
@ -1636,8 +1642,10 @@
BF95E2791E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift in Sources */,
D524F4A3273DE9C000D500B2 /* ProcessInfo+JIT.swift in Sources */,
BFDCA1E9244F7E1000B8FBDB /* Delta5ToDelta6.xcmappingmodel in Sources */,
AC1AE30A2A69BD3A00956EB9 /* AlternateAppIcons.swift in Sources */,
D5A817B329DF6C6C00904AFE /* ExperimentalFeatures.swift in Sources */,
BFD097211D3A01B8005A44C2 /* SaveStatesViewController.swift in Sources */,
AC1AE30C2A6A068F00956EB9 /* Bundle+AppIconImage.swift in Sources */,
BF6EE5EB1F7C5F8F0051AD6C /* GameControllerInputMapping.swift in Sources */,
BF8A333421A484A000A42FD4 /* BadgedTableViewCell.swift in Sources */,
D5F82FB82981D3AC00B229AF /* LegacySearchBar.swift in Sources */,
@ -1919,6 +1927,7 @@
baseConfigurationReference = C786AF1D2DDB6223BE2063CC /* Pods-Delta.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CLANG_ENABLE_MODULES = YES;
CLANG_MODULES_AUTOLINK = NO;
CODE_SIGN_IDENTITY = "iPhone Developer";
@ -1952,6 +1961,7 @@
baseConfigurationReference = A19FF50F55441BC2B2248241 /* Pods-Delta.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CLANG_ENABLE_MODULES = YES;
CLANG_MODULES_AUTOLINK = NO;
CODE_SIGN_IDENTITY = "iPhone Developer";

View File

@ -41,6 +41,11 @@ struct ExperimentalFeatures: FeatureContainer
options: ReviewSaveStatesOptions())
var reviewSaveStates
@Feature(name: "Alternate App Icon",
description: "Change the app icon.",
options: AlternateAppIconOptions())
var alternateAppIcons
private init()
{
self.prepareFeatures()

View File

@ -0,0 +1,124 @@
//
// AlternateAppIcons.swift
// Delta
//
// Created by Chris Rittenhouse on 5/2/23.
// Copyright © 2023 LitRitt. All rights reserved.
//
import SwiftUI
import DeltaFeatures
enum AppIcon: String, CaseIterable, CustomStringConvertible, Identifiable
{
case normal = "Default"
case gba4ios = "GBA4iOS"
case inverted = "Inverted"
case pixelated = "Pixelated"
case skin = "Controller Skin"
var description: String {
return self.rawValue
}
var id: String {
return self.rawValue
}
var author: String {
switch self
{
case .normal: return "Caroline Moore"
case .gba4ios: return "Paul Thorsen"
case .inverted, .skin, .pixelated: return "LitRitt"
}
}
var assetName: String {
switch self
{
case .normal: return "AppIcon"
case .gba4ios: return "IconGBA4iOS"
case .inverted: return "IconInverted"
case .pixelated: return "IconPixelated"
case .skin: return "IconSkin"
}
}
}
extension AppIcon: Equatable
{
static func == (lhs: AppIcon, rhs: AppIcon) -> Bool
{
return lhs.description == rhs.description
}
}
extension AppIcon: LocalizedOptionValue
{
var localizedDescription: Text {
Text(self.description)
}
}
struct AlternateAppIconOptions
{
@Option(name: "Alternate App Icon",
description: "Choose from alternate app icons created by the community.",
detailView: { value in
List {
ForEach(AppIcon.allCases) { icon in
HStack {
if icon == value.wrappedValue
{
Text("")
}
icon.localizedDescription
Text("- by \(icon.author)")
.font(.system(size: 15))
.foregroundColor(.gray)
Spacer()
Image(uiImage: Bundle.appIcon(for: icon) ?? UIImage())
.cornerRadius(13)
}
.contentShape(Rectangle())
.onTapGesture {
value.wrappedValue = icon
}
}
}
.onChange(of: value.wrappedValue) { _ in
updateAppIcon()
}
.displayInline()
})
var icon: AppIcon = .normal
}
extension AlternateAppIconOptions
{
static func updateAppIcon()
{
// Get current icon
let currentIcon = UIApplication.shared.alternateIconName
// Apply chosen icon if feature is enabled
if ExperimentalFeatures.shared.alternateAppIcons.isEnabled
{
let icon = ExperimentalFeatures.shared.alternateAppIcons.icon
// Only apply new icon if it's not already the current icon
switch icon
{
case .normal: if currentIcon != nil { UIApplication.shared.setAlternateIconName(nil) } // Default app icon
default: if currentIcon != icon.assetName { UIApplication.shared.setAlternateIconName(icon.assetName) } // Alternate app icon
}
}
else
{
// Remove alternate icons if feature is disabled
if currentIcon != nil { UIApplication.shared.setAlternateIconName(nil) }
}
}
}

View File

@ -0,0 +1,34 @@
//
// Bundle+AppIconImage.swift
// Delta
//
// Created by Chris Rittenhouse on 7/20/23.
// Copyright © 2023 LitRitt. All rights reserved.
//
import UIKit
extension Bundle
{
static func appIcon(for icon: AppIcon = .normal) -> UIImage? {
guard let appIcons = Bundle.main.infoDictionary?["CFBundleIcons"] as? [String: Any] else { return nil }
switch icon
{
case .normal:
guard let primaryAppIcon = appIcons["CFBundlePrimaryIcon"] as? [String: Any],
let appIconFiles = primaryAppIcon["CFBundleIconFiles"] as? [String],
let appIcon = appIconFiles.first else { return nil }
return UIImage(named:appIcon)
default:
guard let alternateAppIcons = appIcons["CFBundleAlternateIcons"] as? [String: Any],
let alternateAppIcon = alternateAppIcons[icon.assetName] as? [String: Any],
let appIconFiles = alternateAppIcon["CFBundleIconFiles"] as? [String],
let appIcon = appIconFiles.first else { return nil }
return UIImage(named:appIcon)
}
}
}

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,14 @@
{
"images" : [
{
"filename" : "GBA4iOS.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

View File

@ -0,0 +1,14 @@
{
"images" : [
{
"filename" : "Inverted.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

View File

@ -0,0 +1,14 @@
{
"images" : [
{
"filename" : "Pixelated.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

View File

@ -0,0 +1,14 @@
{
"images" : [
{
"filename" : "Skin.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 KiB