Add icons + icon selector

This commit is contained in:
Thomas Ricouard 2022-12-04 09:50:25 +01:00
parent df2d383b8a
commit 846184ae58
16 changed files with 222 additions and 17 deletions

View file

@ -21,6 +21,7 @@
9FBFE63D292A715500C250E9 /* IceCubesApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBFE63C292A715500C250E9 /* IceCubesApp.swift */; }; 9FBFE63D292A715500C250E9 /* IceCubesApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBFE63C292A715500C250E9 /* IceCubesApp.swift */; };
9FBFE641292A715600C250E9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9FBFE640292A715600C250E9 /* Assets.xcassets */; }; 9FBFE641292A715600C250E9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9FBFE640292A715600C250E9 /* Assets.xcassets */; };
9FBFE64E292A72BD00C250E9 /* Network in Frameworks */ = {isa = PBXBuildFile; productRef = 9FBFE64D292A72BD00C250E9 /* Network */; }; 9FBFE64E292A72BD00C250E9 /* Network in Frameworks */ = {isa = PBXBuildFile; productRef = 9FBFE64D292A72BD00C250E9 /* Network */; };
9FE151A6293C90F900E9683D /* IconSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FE151A5293C90F900E9683D /* IconSelectorView.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
@ -40,6 +41,7 @@
9FBFE63C292A715500C250E9 /* IceCubesApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IceCubesApp.swift; sourceTree = "<group>"; }; 9FBFE63C292A715500C250E9 /* IceCubesApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IceCubesApp.swift; sourceTree = "<group>"; };
9FBFE640292A715600C250E9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 9FBFE640292A715600C250E9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
9FBFE642292A715600C250E9 /* IceCubesApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = IceCubesApp.entitlements; sourceTree = "<group>"; }; 9FBFE642292A715600C250E9 /* IceCubesApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = IceCubesApp.entitlements; sourceTree = "<group>"; };
9FE151A5293C90F900E9683D /* IconSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconSelectorView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -82,8 +84,8 @@
9FAE4AC9293783A200772766 /* Tabs */ = { 9FAE4AC9293783A200772766 /* Tabs */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
9FE151A4293C90EA00E9683D /* Settings */,
9F398AB229360A4C00A889F2 /* TimelineTab.swift */, 9F398AB229360A4C00A889F2 /* TimelineTab.swift */,
9FAE4ACA293783B000772766 /* SettingsTab.swift */,
); );
path = Tabs; path = Tabs;
sourceTree = "<group>"; sourceTree = "<group>";
@ -137,6 +139,15 @@
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
9FE151A4293C90EA00E9683D /* Settings */ = {
isa = PBXGroup;
children = (
9FAE4ACA293783B000772766 /* SettingsTab.swift */,
9FE151A5293C90F900E9683D /* IconSelectorView.swift */,
);
path = Settings;
sourceTree = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
@ -218,6 +229,7 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
9FE151A6293C90F900E9683D /* IconSelectorView.swift in Sources */,
9FAE4ACB293783B000772766 /* SettingsTab.swift in Sources */, 9FAE4ACB293783B000772766 /* SettingsTab.swift in Sources */,
9FAE4AD32937A0C600772766 /* AppAccountsManager.swift in Sources */, 9FAE4AD32937A0C600772766 /* AppAccountsManager.swift in Sources */,
9F398AB329360A4C00A889F2 /* TimelineTab.swift in Sources */, 9F398AB329360A4C00A889F2 /* TimelineTab.swift in Sources */,
@ -342,6 +354,7 @@
9FBFE649292A715600C250E9 /* Debug */ = { 9FBFE649292A715600C250E9 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "AppIconAlternate1 AppIconAlternate2 AppIconAlternate3 AppIconAlternate4";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = IceCubesApp/IceCubesApp.entitlements; CODE_SIGN_ENTITLEMENTS = IceCubesApp/IceCubesApp.entitlements;
@ -381,6 +394,7 @@
9FBFE64A292A715600C250E9 /* Release */ = { 9FBFE64A292A715600C250E9 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "AppIconAlternate1 AppIconAlternate2 AppIconAlternate3 AppIconAlternate4";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = IceCubesApp/IceCubesApp.entitlements; CODE_SIGN_ENTITLEMENTS = IceCubesApp/IceCubesApp.entitlements;

View file

@ -0,0 +1,54 @@
import SwiftUI
struct IconSelectorView: View {
enum Icon: String, CaseIterable, Identifiable {
var id: String {
self.rawValue
}
case primary = "AppIcon"
case alternate1 = "AppIconAlternate1"
case alternate2 = "AppIconAlternate2"
case alternate3 = "AppIconAlternate3"
case alternate4 = "AppIconAlternate4"
}
@State private var currentIcon = UIApplication.shared.alternateIconName ?? Icon.primary.rawValue
private let columns = [GridItem(.adaptive(minimum: 125, maximum: 1024))]
var body: some View {
ScrollView {
VStack(alignment: .leading) {
LazyVGrid(columns: columns, spacing: 6) {
ForEach(Icon.allCases) { icon in
Button {
currentIcon = icon.rawValue
if icon.rawValue == Icon.primary.rawValue {
UIApplication.shared.setAlternateIconName(nil)
} else {
UIApplication.shared.setAlternateIconName(icon.rawValue)
}
} label: {
ZStack(alignment: .bottomTrailing) {
Image(uiImage: .init(named: icon.rawValue)!)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(minHeight: 125, maxHeight: 1024)
.cornerRadius(6)
.shadow(radius: 3)
if icon.rawValue == currentIcon {
Image(systemName: "checkmark.seal.fill")
.padding(4)
.tint(.green)
}
}
}
}
}
}
.padding(6)
.navigationTitle("Icons")
}
}
}

View file

@ -12,27 +12,15 @@ struct SettingsTabs: View {
@State private var signInInProgress = false @State private var signInInProgress = false
@State private var accountData: Account? @State private var accountData: Account?
@State private var instanceData: Instance?
@State private var signInServer = IceCubesApp.defaultServer @State private var signInServer = IceCubesApp.defaultServer
var body: some View { var body: some View {
NavigationStack { NavigationStack {
Form { Form {
Section("Account") { appSection
if let accountData { accountSection
VStack(alignment: .leading) { instanceSection
Text(appAccountsManager.currentAccount.server)
.font(.headline)
Text(accountData.displayName)
Text(accountData.username)
.font(.footnote)
.foregroundColor(.gray)
}
signOutButton
} else {
TextField("Mastodon server", text: $signInServer)
signInButton
}
}
} }
.onOpenURL(perform: { url in .onOpenURL(perform: { url in
Task { Task {
@ -46,11 +34,61 @@ struct SettingsTabs: View {
if appAccountsManager.currentAccount.oauthToken != nil { if appAccountsManager.currentAccount.oauthToken != nil {
signInInProgress = true signInInProgress = true
await refreshAccountInfo() await refreshAccountInfo()
await refreshInstanceInfo()
signInInProgress = false signInInProgress = false
} }
} }
} }
private var accountSection: some View {
Section("Account") {
if let accountData {
VStack(alignment: .leading) {
Text(appAccountsManager.currentAccount.server)
.font(.headline)
Text(accountData.displayName)
Text(accountData.username)
.font(.footnote)
.foregroundColor(.gray)
}
signOutButton
} else {
TextField("Mastodon server", text: $signInServer)
signInButton
}
}
}
@ViewBuilder
private var instanceSection: some View {
if let instanceData {
Section("Instance info") {
LabeledContent("Name", value: instanceData.title)
Text(instanceData.shortDescription)
LabeledContent("Email", value: instanceData.email)
LabeledContent("Version", value: instanceData.version)
LabeledContent("Users", value: "\(instanceData.stats.userCount)")
LabeledContent("Status", value: "\(instanceData.stats.statusCount)")
LabeledContent("Domains", value: "\(instanceData.stats.domainCount)")
}
}
}
private var appSection: some View {
Section("App") {
NavigationLink(destination: IconSelectorView()) {
Label {
Text("Icon selector")
} icon: {
Image(uiImage: .init(named: UIApplication.shared.alternateIconName ?? "AppIcon")!)
.resizable()
.frame(width: 25, height: 25)
.cornerRadius(4)
}
}
}
}
private var signInButton: some View { private var signInButton: some View {
Button { Button {
signInInProgress = true signInInProgress = true
@ -68,6 +106,7 @@ struct SettingsTabs: View {
private var signOutButton: some View { private var signOutButton: some View {
Button { Button {
instanceData = nil
accountData = nil accountData = nil
appAccountsManager.delete(account: appAccountsManager.currentAccount) appAccountsManager.delete(account: appAccountsManager.currentAccount)
} label: { } label: {
@ -91,6 +130,7 @@ struct SettingsTabs: View {
let oauthToken = try await client.continueOauthFlow(url: url) let oauthToken = try await client.continueOauthFlow(url: url)
appAccountsManager.add(account: AppAccount(server: client.server, oauthToken: oauthToken)) appAccountsManager.add(account: AppAccount(server: client.server, oauthToken: oauthToken))
await refreshAccountInfo() await refreshAccountInfo()
await refreshInstanceInfo()
signInInProgress = false signInInProgress = false
} catch { } catch {
signInInProgress = false signInInProgress = false
@ -100,4 +140,8 @@ struct SettingsTabs: View {
private func refreshAccountInfo() async { private func refreshAccountInfo() async {
accountData = try? await client.get(endpoint: Accounts.verifyCredentials) accountData = try? await client.get(endpoint: Accounts.verifyCredentials)
} }
private func refreshInstanceInfo() async {
instanceData = try? await client.get(endpoint: Instances.instance)
}
} }

View file

@ -1,6 +1,7 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "icon.png",
"idiom" : "universal", "idiom" : "universal",
"platform" : "ios", "platform" : "ios",
"size" : "1024x1024" "size" : "1024x1024"

Binary file not shown.

After

Width:  |  Height:  |  Size: 986 KiB

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,016 KiB

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View file

@ -0,0 +1,18 @@
import Foundation
public struct Instance: Codable {
public struct Stats: Codable {
public let userCount: Int
public let statusCount: Int
public let domainCount: Int
}
public let title: String
public let shortDescription: String
public let email: String
public let version: String
public let stats: Stats
public let languages: [String]
public let registrations: Bool
public let thumbnail: URL?
}

View file

@ -0,0 +1,16 @@
import Foundation
public enum Instances: Endpoint {
case instance
public func path() -> String {
switch self {
case .instance:
return "instance"
}
}
public func queryItems() -> [URLQueryItem]? {
nil
}
}

View file

@ -1,2 +1,4 @@
# IceCubesApp # IceCubesApp
A SwiftUI Mastodon client A SwiftUI Mastodon client
![Icon](IceCubesApp/Resources/Assets.xcassets/AppIcon.appiconset/icon.png?)