Full Xcode 16 supports + iOS 18 support (#2100)

* Compile on iOS 18

* Fix more warnings

* Tweak build settings

* Migrate to Swift Tests

* better tests

* Fix

* Fix tests

* More TabView cleanup

Bump to iOS 18 only + remove custom sidebar

* Revert "More TabView cleanup"

This reverts commit e051437fcb.

* Tabbar fix + bump to iOS 18

* Remove popToRoot

* Cleanup scrollToTop

* Support both TapBar

* Better TabView support

* Better TabView support

* Cleanup

* Disable TabView animations

* Remove id in ForEach

* Remove external init for StatusRowView

* Cleanup

* More Swift 6 concurrency

* Swift 6 mode

* Fixes

* Full Swift 6 packages support

* For now compile env in Swift 5 mode

* Fix archive

* More fix to Archive

* Address `dispatch_assert_queue_fail` (#2161)

See https://twitter.com/dimillian/status/1823089444397724003?s=61&t=SC3rvyJQWn1NQqAgMVrT0Q

* Bump Env to Swift 6

* Fix push notification

* Remove unecessary workaround

* Cleanup

* Move to @Entry

* Fix TabView on Catalyst

* Fix build

* Fix build 2

* fix warning

* Fix icons for iOS 18

---------

Co-authored-by: NachoSoto <NachoSoto@users.noreply.github.com>
This commit is contained in:
Thomas Ricouard 2024-09-10 06:53:19 +02:00 committed by GitHub
parent 4cce9d6333
commit 6297a428a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
174 changed files with 1824 additions and 839 deletions

View File

@ -13,7 +13,7 @@ import Models
import Network
// Sample code was sending this from a thread to another, let asume @Sendable for this
extension NSExtensionContext: @unchecked Sendable {}
extension NSExtensionContext: @unchecked @retroactive Sendable {}
final class ActionRequestHandler: NSObject, NSExtensionRequestHandling, Sendable {
enum Error: Swift.Error {

View File

@ -1211,7 +1211,7 @@
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2,7";
};
name = Debug;
@ -1246,7 +1246,7 @@
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2,7";
VALIDATE_PRODUCT = YES;
};
@ -1269,7 +1269,7 @@
INFOPLIST_FILE = IceCubesAppWidgetsExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = IceCubesAppWidgetsExtension;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 17.4;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -1283,7 +1283,7 @@
SUPPORTS_MACCATALYST = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@ -1305,7 +1305,7 @@
INFOPLIST_FILE = IceCubesAppWidgetsExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = IceCubesAppWidgetsExtension;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 17.4;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -1318,7 +1318,7 @@
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
@ -1353,7 +1353,7 @@
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@ -1387,7 +1387,7 @@
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
@ -1456,6 +1456,7 @@
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = "";
};
name = Debug;
};
@ -1515,6 +1516,7 @@
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = "";
};
name = Release;
};
@ -1569,8 +1571,19 @@
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0;
SWIFT_UPCOMING_FEATURE_CONCISE_MAGIC_FILE = YES;
SWIFT_UPCOMING_FEATURE_DEPRECATE_APPLICATION_MAIN = YES;
SWIFT_UPCOMING_FEATURE_DISABLE_OUTWARD_ACTOR_ISOLATION = YES;
SWIFT_UPCOMING_FEATURE_FORWARD_TRAILING_CLOSURES = YES;
SWIFT_UPCOMING_FEATURE_GLOBAL_CONCURRENCY = YES;
SWIFT_UPCOMING_FEATURE_IMPLICIT_OPEN_EXISTENTIALS = YES;
SWIFT_UPCOMING_FEATURE_IMPORT_OBJC_FORWARD_DECLS = YES;
SWIFT_UPCOMING_FEATURE_INFER_SENDABLE_FROM_CAPTURES = YES;
SWIFT_UPCOMING_FEATURE_ISOLATED_DEFAULT_VALUES = YES;
SWIFT_UPCOMING_FEATURE_REGION_BASED_ISOLATION = YES;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2,7";
_EXPERIMENTAL_SWIFT_EXPLICIT_MODULES = NO;
};
name = Debug;
};
@ -1625,8 +1638,19 @@
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0;
SWIFT_UPCOMING_FEATURE_CONCISE_MAGIC_FILE = YES;
SWIFT_UPCOMING_FEATURE_DEPRECATE_APPLICATION_MAIN = YES;
SWIFT_UPCOMING_FEATURE_DISABLE_OUTWARD_ACTOR_ISOLATION = YES;
SWIFT_UPCOMING_FEATURE_FORWARD_TRAILING_CLOSURES = YES;
SWIFT_UPCOMING_FEATURE_GLOBAL_CONCURRENCY = YES;
SWIFT_UPCOMING_FEATURE_IMPLICIT_OPEN_EXISTENTIALS = YES;
SWIFT_UPCOMING_FEATURE_IMPORT_OBJC_FORWARD_DECLS = YES;
SWIFT_UPCOMING_FEATURE_INFER_SENDABLE_FROM_CAPTURES = YES;
SWIFT_UPCOMING_FEATURE_ISOLATED_DEFAULT_VALUES = YES;
SWIFT_UPCOMING_FEATURE_REGION_BASED_ISOLATION = YES;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2,7";
_EXPERIMENTAL_SWIFT_EXPLICIT_MODULES = NO;
};
name = Release;
};
@ -1660,7 +1684,7 @@
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@ -1695,7 +1719,7 @@
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};

View File

@ -56,8 +56,12 @@
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<RemoteRunnable
runnableDebuggingMode = "1"
BundleIdentifier = "com.thomasricouard.IceCubesApp"
RemotePath = "/Users/dimillian/Library/Developer/CoreSimulator/Devices/8EF923D0-4CF1-49B6-B287-5F05AD5440C1/data/Containers/Bundle/Application/C447A1D1-9BC9-49C9-8FA5-130E8403972F/Ice Cubes.app">
</RemoteRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9FBFE638292A715500C250E9"
@ -65,7 +69,7 @@
BlueprintName = "IceCubesApp"
ReferencedContainer = "container:IceCubesApp.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</MacroExpansion>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"

View File

@ -19,9 +19,9 @@ extension View {
navigationDestination(for: RouterDestination.self) { destination in
switch destination {
case let .accountDetail(id):
AccountDetailView(accountId: id, scrollToTopSignal: .constant(0))
AccountDetailView(accountId: id)
case let .accountDetailWithAccount(account):
AccountDetailView(account: account, scrollToTopSignal: .constant(0))
AccountDetailView(account: account)
case let .accountSettingsWithAccount(account, appAccount):
AccountSettingsView(account: account, appAccount: appAccount)
case let .accountMediaGridView(account, initialMedia):
@ -38,19 +38,16 @@ extension View {
TimelineView(timeline: .constant(.hashtag(tag: tag, accountId: accountId)),
pinnedFilters: .constant([]),
selectedTagGroup: .constant(nil),
scrollToTopSignal: .constant(0),
canFilterTimeline: false)
case let .list(list):
TimelineView(timeline: .constant(.list(list: list)),
pinnedFilters: .constant([]),
selectedTagGroup: .constant(nil),
scrollToTopSignal: .constant(0),
canFilterTimeline: false)
case let .linkTimeline(url, title):
TimelineView(timeline: .constant(.link(url: url, title: title)),
pinnedFilters: .constant([]),
selectedTagGroup: .constant(nil),
scrollToTopSignal: .constant(0),
canFilterTimeline: false)
case let .following(id):
AccountsListView(mode: .following(accountId: id))
@ -66,7 +63,6 @@ extension View {
TimelineView(timeline: .constant(.trending),
pinnedFilters: .constant([]),
selectedTagGroup: .constant(nil),
scrollToTopSignal: .constant(0),
canFilterTimeline: false)
case let .trendingLinks(cards):
TrendingLinksListView(cards: cards)
@ -76,8 +72,7 @@ extension View {
NotificationsRequestsListView()
case let .notificationForAccount(accountId):
NotificationsListView(lockedType: nil,
lockedAccountId: accountId,
scrollToTopSignal: .constant(0))
lockedAccountId: accountId)
case .blockedAccounts:
AccountsListView(mode: .blocked)
case .mutedAccounts:
@ -135,7 +130,7 @@ extension View {
StatusEditHistoryView(statusId: status)
.withEnvironments()
case .settings:
SettingsTabs(popToRootTab: .constant(.settings), isModal: true)
SettingsTabs(isModal: true)
.withEnvironments()
.preferredColorScheme(Theme.shared.selectedScheme == .dark ? .dark : .light)
case .accountPushNotficationsSettings:
@ -236,7 +231,7 @@ struct ActivityView: UIViewControllerRepresentable {
func updateUIViewController(_: UIActivityViewController, context _: UIViewControllerRepresentableContext<ActivityView>) {}
}
extension URL: Identifiable {
extension URL: @retroactive Identifiable {
public var id: String {
absoluteString
}

View File

@ -21,10 +21,9 @@ struct AppView: View {
@Environment(\.openWindow) var openWindow
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
@Binding var selectedTab: Tab
@Binding var selectedTab: AppTab
@Binding var appRouterPath: RouterPath
@State var popToRootTab: Tab = .other
@State var iosTabs = iOSTabs.shared
@State var sidebarTabs = SidebarTabs.shared
@ -40,45 +39,27 @@ struct AppView: View {
#endif
}
var availableTabs: [Tab] {
var availableTabs: [AppTab] {
guard appAccountsManager.currentClient.isAuth else {
return Tab.loggedOutTab()
return AppTab.loggedOutTab()
}
if UIDevice.current.userInterfaceIdiom == .phone || horizontalSizeClass == .compact {
return iosTabs.tabs
} else if UIDevice.current.userInterfaceIdiom == .vision {
return Tab.visionOSTab()
return AppTab.visionOSTab()
}
return sidebarTabs.tabs.map { $0.tab }
}
@ViewBuilder
var tabBarView: some View {
TabView(selection: .init(get: {
selectedTab
}, set: { newTab in
if newTab == .post {
#if os(visionOS)
openWindow(value: WindowDestinationEditor.newStatusEditor(visibility: userPreferences.postVisibility))
#else
appRouterPath.presentedSheet = .newStatusEditor(visibility: userPreferences.postVisibility)
#endif
return
}
if newTab == selectedTab {
/// Stupid hack to trigger onChange binding in tab views.
popToRootTab = .other
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
popToRootTab = selectedTab
}
}
HapticManager.shared.fireHaptic(.tabSelection)
SoundEffectManager.shared.playSound(.tabSelection)
selectedTab = newTab
updateTab(with: newTab)
})) {
ForEach(availableTabs) { tab in
tab.makeContentView(selectedTab: $selectedTab, popToRootTab: $popToRootTab)
tab.makeContentView(selectedTab: $selectedTab)
.tabItem {
if userPreferences.showiPhoneTabLabel {
tab.label
@ -95,8 +76,24 @@ struct AppView: View {
.id(appAccountsManager.currentClient.id)
.withSheetDestinations(sheetDestinations: $appRouterPath.presentedSheet)
}
private func updateTab(with newTab: AppTab) {
if newTab == .post {
#if os(visionOS)
openWindow(value: WindowDestinationEditor.newStatusEditor(visibility: userPreferences.postVisibility))
#else
appRouterPath.presentedSheet = .newStatusEditor(visibility: userPreferences.postVisibility)
#endif
return
}
HapticManager.shared.fireHaptic(.tabSelection)
SoundEffectManager.shared.playSound(.tabSelection)
private func badgeFor(tab: Tab) -> Int {
selectedTab = newTab
}
private func badgeFor(tab: AppTab) -> Int {
if tab == .notifications, selectedTab != tab,
let token = appAccountsManager.currentAccount.oauthToken
{
@ -108,25 +105,17 @@ struct AppView: View {
#if !os(visionOS)
var sidebarView: some View {
SideBarView(selectedTab: $selectedTab,
popToRootTab: $popToRootTab,
tabs: availableTabs)
{
HStack(spacing: 0) {
TabView(selection: $selectedTab) {
ForEach(availableTabs) { tab in
tab
.makeContentView(selectedTab: $selectedTab, popToRootTab: $popToRootTab)
.tabItem {
tab.label
}
.tag(tab)
if #available(iOS 18.0, *) {
baseTabView
.tabViewStyle(.sidebarAdaptable)
.introspect(.tabView, on: .iOS(.v17, .v18)) { (tabview: UITabBarController) in
tabview.sidebar.isHidden = true
}
}
.id(availableTabs.count) /// Resets the TabView state when the number of tabs changes to avoid navigation bar issues and prevent crashes
.introspect(.tabView, on: .iOS(.v17, .v18)) { (tabview: UITabBarController) in
tabview.tabBar.isHidden = horizontalSizeClass == .regular
tabview.customizableViewControllers = []
tabview.moreNavigationController.isNavigationBarHidden = true
} else {
baseTabView
}
if horizontalSizeClass == .regular,
appAccountsManager.currentClient.isAuth,
@ -140,10 +129,30 @@ struct AppView: View {
.environment(appRouterPath)
}
#endif
private var baseTabView: some View {
TabView(selection: $selectedTab) {
ForEach(availableTabs) { tab in
tab
.makeContentView(selectedTab: $selectedTab)
.toolbar(horizontalSizeClass == .regular ? .hidden : .visible, for: .tabBar)
.tabItem {
tab.label
}
.tag(tab)
}
}
.id(availableTabs.count) /// Resets the TabView state when the number of tabs changes to avoid navigation bar issues and prevent crashes
.introspect(.tabView, on: .iOS(.v17, .v18)) { (tabview: UITabBarController) in
tabview.tabBar.isHidden = horizontalSizeClass == .regular
tabview.customizableViewControllers = []
tabview.moreNavigationController.isNavigationBarHidden = true
}
}
var notificationsSecondaryColumn: some View {
NotificationsTab(selectedTab: .constant(.notifications),
popToRootTab: $popToRootTab, lockedType: nil)
NotificationsTab(selectedTab: .constant(.notifications)
, lockedType: nil)
.environment(\.isSecondaryColumn, true)
.frame(maxWidth: .secondaryColumnWidth)
.id(appAccountsManager.currentAccount.id)

View File

@ -29,7 +29,7 @@ struct IceCubesApp: App {
@State var quickLook = QuickLook.shared
@State var theme = Theme.shared
@State var selectedTab: Tab = .timeline
@State var selectedTab: AppTab = .timeline
@State var appRouterPath = RouterPath()
@State var isSupporter: Bool = false

View File

@ -18,14 +18,13 @@ struct SideBarView<Content: View>: View {
@Environment(UserPreferences.self) private var userPreferences
@Environment(RouterPath.self) private var routerPath
@Binding var selectedTab: Tab
@Binding var popToRootTab: Tab
var tabs: [Tab]
@Binding var selectedTab: AppTab
var tabs: [AppTab]
@ViewBuilder var content: () -> Content
@State private var sidebarTabs = SidebarTabs.shared
private func badgeFor(tab: Tab) -> Int {
private func badgeFor(tab: AppTab) -> Int {
if tab == .notifications, selectedTab != tab,
let token = appAccounts.currentAccount.oauthToken
{
@ -34,7 +33,7 @@ struct SideBarView<Content: View>: View {
return 0
}
private func makeIconForTab(tab: Tab) -> some View {
private func makeIconForTab(tab: AppTab) -> some View {
HStack {
ZStack(alignment: .topTrailing) {
SideBarIcon(systemIconName: tab.iconName,
@ -89,7 +88,7 @@ struct SideBarView<Content: View>: View {
.offset(x: 2, y: -2)
}
.buttonStyle(.borderedProminent)
.help(Tab.post.title)
.help(AppTab.post.title)
}
private func makeAccountButton(account: AppAccount, showBadge: Bool) -> some View {
@ -139,7 +138,7 @@ struct SideBarView<Content: View>: View {
if let accountName {
"tab.profile-account-\(accountName)"
} else {
Tab.profile.title
AppTab.profile.title
}
}
@ -149,13 +148,6 @@ struct SideBarView<Content: View>: View {
Button {
// ensure keyboard is always dismissed when selecting a tab
hideKeyboard()
if tab == selectedTab {
popToRootTab = .other
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
popToRootTab = tab
}
}
selectedTab = tab
SoundEffectManager.shared.playSound(.tabSelection)
if tab == .notifications {

View File

@ -13,12 +13,10 @@ struct ExploreTab: View {
@Environment(CurrentAccount.self) private var currentAccount
@Environment(Client.self) private var client
@State private var routerPath = RouterPath()
@State private var scrollToTopSignal: Int = 0
@Binding var popToRootTab: Tab
var body: some View {
NavigationStack(path: $routerPath.path) {
ExploreView(scrollToTopSignal: $scrollToTopSignal)
ExploreView()
.withAppRouter()
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.30), for: .navigationBar)
@ -28,15 +26,6 @@ struct ExploreTab: View {
}
.withSafariRouter()
.environment(routerPath)
.onChange(of: $popToRootTab.wrappedValue) { _, newValue in
if newValue == .explore {
if routerPath.path.isEmpty {
scrollToTopSignal += 1
} else {
routerPath.path = []
}
}
}
.onChange(of: client.id) {
routerPath.path = []
}

View File

@ -15,12 +15,10 @@ struct MessagesTab: View {
@Environment(CurrentAccount.self) private var currentAccount
@Environment(AppAccountsManager.self) private var appAccount
@State private var routerPath = RouterPath()
@State private var scrollToTopSignal: Int = 0
@Binding var popToRootTab: Tab
var body: some View {
NavigationStack(path: $routerPath.path) {
ConversationsListView(scrollToTopSignal: $scrollToTopSignal)
ConversationsListView()
.withAppRouter()
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
.toolbar {
@ -29,15 +27,6 @@ struct MessagesTab: View {
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.30), for: .navigationBar)
.id(client.id)
}
.onChange(of: $popToRootTab.wrappedValue) { _, newValue in
if newValue == .messages {
if routerPath.path.isEmpty {
scrollToTopSignal += 1
} else {
routerPath.path = []
}
}
}
.onChange(of: client.id) {
routerPath.path = []
}

View File

@ -20,16 +20,14 @@ struct NotificationsTab: View {
@Environment(UserPreferences.self) private var userPreferences
@Environment(PushNotificationsService.self) private var pushNotificationsService
@State private var routerPath = RouterPath()
@State private var scrollToTopSignal: Int = 0
@Binding var selectedTab: Tab
@Binding var popToRootTab: Tab
@Binding var selectedTab: AppTab
let lockedType: Models.Notification.NotificationType?
var body: some View {
NavigationStack(path: $routerPath.path) {
NotificationsListView(lockedType: lockedType, scrollToTopSignal: $scrollToTopSignal)
NotificationsListView(lockedType: lockedType)
.withAppRouter()
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
.toolbar {
@ -51,15 +49,6 @@ struct NotificationsTab: View {
}
.withSafariRouter()
.environment(routerPath)
.onChange(of: $popToRootTab.wrappedValue) { _, newValue in
if newValue == .notifications {
if routerPath.path.isEmpty {
scrollToTopSignal += 1
} else {
routerPath.path = []
}
}
}
.onChange(of: selectedTab) { _, _ in
clearNotifications()
}
@ -92,10 +81,12 @@ struct NotificationsTab: View {
private func clearNotifications() {
if selectedTab == .notifications || isSecondaryColumn {
if let token = appAccount.currentAccount.oauthToken {
if let token = appAccount.currentAccount.oauthToken, userPreferences.notificationsCount[token] ?? 0 > 0 {
userPreferences.notificationsCount[token] = 0
}
watcher.unreadNotificationsCount = 0
if watcher.unreadNotificationsCount > 0 {
watcher.unreadNotificationsCount = 0
}
}
}
}

View File

@ -14,32 +14,21 @@ struct ProfileTab: View {
@Environment(Client.self) private var client
@Environment(CurrentAccount.self) private var currentAccount
@State private var routerPath = RouterPath()
@State private var scrollToTopSignal: Int = 0
@Binding var popToRootTab: Tab
var body: some View {
NavigationStack(path: $routerPath.path) {
if let account = currentAccount.account {
AccountDetailView(account: account, scrollToTopSignal: $scrollToTopSignal)
AccountDetailView(account: account)
.withAppRouter()
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.30), for: .navigationBar)
.id(account.id)
} else {
AccountDetailView(account: .placeholder(), scrollToTopSignal: $scrollToTopSignal)
AccountDetailView(account: .placeholder())
.redacted(reason: .placeholder)
.allowsHitTesting(false)
}
}
.onChange(of: $popToRootTab.wrappedValue) { _, newValue in
if newValue == .profile {
if routerPath.path.isEmpty {
scrollToTopSignal += 1
} else {
routerPath.path = []
}
}
}
.onChange(of: client.id) {
routerPath.path = []
}

View File

@ -152,13 +152,13 @@ struct AboutView: View {
private func fetchAccounts() async {
await withThrowingTaskGroup(of: Void.self) { group in
group.addTask {
let viewModel = try await fetchAccountViewModel(account: "dimillian@mastodon.social")
let viewModel = try await fetchAccountViewModel(client, account: "dimillian@mastodon.social")
await MainActor.run {
dimillianAccount = viewModel
}
}
group.addTask {
let viewModel = try await fetchAccountViewModel(account: "icecubesapp@mastodon.online")
let viewModel = try await fetchAccountViewModel(client, account: "icecubesapp@mastodon.online")
await MainActor.run {
iceCubesAccount = viewModel
}
@ -166,7 +166,7 @@ struct AboutView: View {
}
}
private func fetchAccountViewModel(account: String) async throws -> AccountsListRowViewModel {
private func fetchAccountViewModel(_ client: Client, account: String) async throws -> AccountsListRowViewModel {
let dimillianAccount: Account = try await client.get(endpoint: Accounts.lookup(name: account))
let rel: [Relationship] = try await client.get(endpoint: Accounts.relationships(ids: [dimillianAccount.id]))
return .init(account: dimillianAccount, relationShip: rel.first)

View File

@ -92,6 +92,7 @@ struct AddAccountView: View {
}
.onAppear {
isInstanceURLFieldFocused = true
let instanceName = instanceName
Task {
let instances = await instanceSocialClient.fetchInstances(keyword: instanceName)
withAnimation {
@ -102,6 +103,8 @@ struct AddAccountView: View {
}
.onChange(of: instanceName) {
searchingTask.cancel()
let instanceName = instanceName
let instanceSocialClient = instanceSocialClient
searchingTask = Task {
try? await Task.sleep(for: .seconds(0.1))
guard !Task.isCancelled else { return }
@ -127,7 +130,7 @@ struct AddAccountView: View {
let instance: Instance = try await instanceDetailClient.get(endpoint: Instances.instance)
withAnimation {
self.instance = instance
instanceName = sanitizedName // clean up the text box, principally to chop off the username if present so it's clear that you might not wind up siging in as the thing in the box
self.instanceName = sanitizedName // clean up the text box, principally to chop off the username if present so it's clear that you might not wind up siging in as the thing in the box
}
instanceFetchError = nil
} else {

View File

@ -33,6 +33,10 @@ struct IconSelectorView: View {
var appIconName: String {
return "AppIconAlternate\(rawValue)"
}
var previewImageName: String {
return "AppIconAlternate\(rawValue)-image"
}
}
struct IconSelector: Identifiable {
@ -98,7 +102,7 @@ struct IconSelectorView: View {
}
} label: {
ZStack(alignment: .bottomTrailing) {
Image(uiImage: .init(named: icon.appIconName) ?? .init())
Image(icon.previewImageName)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(minHeight: 125, maxHeight: 1024)

View File

@ -28,8 +28,6 @@ struct SettingsTabs: View {
@State private var cachedRemoved = false
@State private var timelineCache = TimelineCache()
@Binding var popToRootTab: Tab
let isModal: Bool
@State private var startingPoint: SettingsStartingPoint? = nil
@ -103,11 +101,6 @@ struct SettingsTabs: View {
}
.withSafariRouter()
.environment(routerPath)
.onChange(of: $popToRootTab.wrappedValue) { _, newValue in
if newValue == .notifications {
routerPath.path = []
}
}
}
private var accountsSection: some View {
@ -265,7 +258,7 @@ struct SettingsTabs: View {
Text("settings.app.icon")
} icon: {
let icon = IconSelectorView.Icon(string: UIApplication.shared.alternateIconName ?? "AppIcon")
if let image: UIImage = .init(named: icon.appIconName) {
if let image: UIImage = .init(named: icon.previewImageName) {
Image(uiImage: image)
.resizable()
.frame(width: 25, height: 25)

View File

@ -14,35 +14,35 @@ struct TabbarEntriesSettingsView: View {
Form {
Section {
Picker("settings.tabs.first-tab", selection: $tabs.firstTab) {
ForEach(Tab.allCases) { tab in
ForEach(AppTab.allCases) { tab in
if tab == tabs.firstTab || !tabs.tabs.contains(tab) {
tab.label.tag(tab)
}
}
}
Picker("settings.tabs.second-tab", selection: $tabs.secondTab) {
ForEach(Tab.allCases) { tab in
ForEach(AppTab.allCases) { tab in
if tab == tabs.secondTab || !tabs.tabs.contains(tab) {
tab.label.tag(tab)
}
}
}
Picker("settings.tabs.third-tab", selection: $tabs.thirdTab) {
ForEach(Tab.allCases) { tab in
ForEach(AppTab.allCases) { tab in
if tab == tabs.thirdTab || !tabs.tabs.contains(tab) {
tab.label.tag(tab)
}
}
}
Picker("settings.tabs.fourth-tab", selection: $tabs.fourthTab) {
ForEach(Tab.allCases) { tab in
ForEach(AppTab.allCases) { tab in
if tab == tabs.fourthTab || !tabs.tabs.contains(tab) {
tab.label.tag(tab)
}
}
}
Picker("settings.tabs.fifth-tab", selection: $tabs.fifthTab) {
ForEach(Tab.allCases) { tab in
ForEach(AppTab.allCases) { tab in
if tab == tabs.fifthTab || !tabs.tabs.contains(tab) {
tab.label.tag(tab)
}

View File

@ -7,7 +7,7 @@ import StatusKit
import SwiftUI
@MainActor
enum Tab: Int, Identifiable, Hashable, CaseIterable, Codable {
enum AppTab: Int, Identifiable, Hashable, CaseIterable, Codable {
case timeline, notifications, mentions, explore, messages, settings, other
case trending, federated, local
case profile
@ -22,37 +22,37 @@ enum Tab: Int, Identifiable, Hashable, CaseIterable, Codable {
rawValue
}
static func loggedOutTab() -> [Tab] {
static func loggedOutTab() -> [AppTab] {
[.timeline, .settings]
}
static func visionOSTab() -> [Tab] {
static func visionOSTab() -> [AppTab] {
[.profile, .timeline, .notifications, .mentions, .explore, .post, .settings]
}
@ViewBuilder
func makeContentView(selectedTab: Binding<Tab>, popToRootTab: Binding<Tab>) -> some View {
func makeContentView(selectedTab: Binding<AppTab>) -> some View {
switch self {
case .timeline:
TimelineTab(popToRootTab: popToRootTab)
TimelineTab()
case .trending:
TimelineTab(popToRootTab: popToRootTab, timeline: .trending)
TimelineTab(timeline: .trending)
case .local:
TimelineTab(popToRootTab: popToRootTab, timeline: .local)
TimelineTab(timeline: .local)
case .federated:
TimelineTab(popToRootTab: popToRootTab, timeline: .federated)
TimelineTab(timeline: .federated)
case .notifications:
NotificationsTab(selectedTab: selectedTab, popToRootTab: popToRootTab, lockedType: nil)
NotificationsTab(selectedTab: selectedTab, lockedType: nil)
case .mentions:
NotificationsTab(selectedTab: selectedTab, popToRootTab: popToRootTab, lockedType: .mention)
NotificationsTab(selectedTab: selectedTab, lockedType: .mention)
case .explore:
ExploreTab(popToRootTab: popToRootTab)
ExploreTab()
case .messages:
MessagesTab(popToRootTab: popToRootTab)
MessagesTab()
case .settings:
SettingsTabs(popToRootTab: popToRootTab, isModal: false)
SettingsTabs(isModal: false)
case .profile:
ProfileTab(popToRootTab: popToRootTab)
ProfileTab()
case .bookmarks:
NavigationTab {
AccountStatusesListView(mode: .bookmarks)
@ -168,7 +168,7 @@ enum Tab: Int, Identifiable, Hashable, CaseIterable, Codable {
@Observable
class SidebarTabs {
struct SidedebarTab: Hashable, Codable {
let tab: Tab
let tab: AppTab
var enabled: Bool
}
@ -202,7 +202,7 @@ class SidebarTabs {
}
}
func isEnabled(_ tab: Tab) -> Bool {
func isEnabled(_ tab: AppTab) -> Bool {
tabs.first(where: { $0.tab.id == tab.id })?.enabled == true
}
@ -219,45 +219,45 @@ class iOSTabs {
}
class Storage {
@AppStorage(TabEntries.first.rawValue) var firstTab = Tab.timeline
@AppStorage(TabEntries.second.rawValue) var secondTab = Tab.notifications
@AppStorage(TabEntries.third.rawValue) var thirdTab = Tab.explore
@AppStorage(TabEntries.fourth.rawValue) var fourthTab = Tab.links
@AppStorage(TabEntries.fifth.rawValue) var fifthTab = Tab.profile
@AppStorage(TabEntries.first.rawValue) var firstTab = AppTab.timeline
@AppStorage(TabEntries.second.rawValue) var secondTab = AppTab.notifications
@AppStorage(TabEntries.third.rawValue) var thirdTab = AppTab.explore
@AppStorage(TabEntries.fourth.rawValue) var fourthTab = AppTab.links
@AppStorage(TabEntries.fifth.rawValue) var fifthTab = AppTab.profile
}
private let storage = Storage()
public static let shared = iOSTabs()
var tabs: [Tab] {
var tabs: [AppTab] {
[firstTab, secondTab, thirdTab, fourthTab, fifthTab]
}
var firstTab: Tab {
var firstTab: AppTab {
didSet {
storage.firstTab = firstTab
}
}
var secondTab: Tab {
var secondTab: AppTab {
didSet {
storage.secondTab = secondTab
}
}
var thirdTab: Tab {
var thirdTab: AppTab {
didSet {
storage.thirdTab = thirdTab
}
}
var fourthTab: Tab {
var fourthTab: AppTab {
didSet {
storage.fourthTab = fourthTab
}
}
var fifthTab: Tab {
var fifthTab: AppTab {
didSet {
storage.fifthTab = fifthTab
}

View File

@ -77,6 +77,7 @@ struct AddRemoteTimelineView: View {
.onAppear {
isInstanceURLFieldFocused = true
let client = InstanceSocialClient()
let instanceName = instanceName
Task {
instances = await client.fetchInstances(keyword: instanceName)
}

View File

@ -18,12 +18,10 @@ struct TimelineTab: View {
@Environment(UserPreferences.self) private var preferences
@Environment(Client.self) private var client
@State private var routerPath = RouterPath()
@Binding var popToRootTab: Tab
@State private var didAppear: Bool = false
@State private var timeline: TimelineFilter = .home
@State private var selectedTagGroup: TagGroup?
@State private var scrollToTopSignal: Int = 0
@Query(sort: \LocalTimeline.creationDate, order: .reverse) var localTimelines: [LocalTimeline]
@Query(sort: \TagGroup.creationDate, order: .reverse) var tagGroups: [TagGroup]
@ -33,9 +31,8 @@ struct TimelineTab: View {
private let canFilterTimeline: Bool
init(popToRootTab: Binding<Tab>, timeline: TimelineFilter? = nil) {
init(timeline: TimelineFilter? = nil) {
canFilterTimeline = timeline == nil
_popToRootTab = popToRootTab
_timeline = .init(initialValue: timeline ?? .home)
}
@ -44,7 +41,6 @@ struct TimelineTab: View {
TimelineView(timeline: $timeline,
pinnedFilters: $pinnedFilters,
selectedTagGroup: $selectedTagGroup,
scrollToTopSignal: $scrollToTopSignal,
canFilterTimeline: canFilterTimeline)
.withAppRouter()
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
@ -77,15 +73,6 @@ struct TimelineTab: View {
.onChange(of: currentAccount.account?.id) {
resetTimelineFilter()
}
.onChange(of: $popToRootTab.wrappedValue) { _, newValue in
if newValue == .timeline {
if routerPath.path.isEmpty {
scrollToTopSignal += 1
} else {
routerPath.path = []
}
}
}
.onChange(of: client.id) {
routerPath.path = []
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "1024.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "1024.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "AppIconAlternate10.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icon15.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icon16.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icon17.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icon18.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "AppIconAlternate15.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "Alternate43-fs8.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "Alternate44-fs8.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "Alternate45-fs8.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "AppIconAlternate2.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "Alternate46-fs8.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "Alternate47-fs8.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icon23.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icon24.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "AppIconAlternate21.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icon22.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "AppIconAlternate20.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icecubes_comic_01.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icecubes_comic_02.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icecubes_comic_03.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icon6.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "blue.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "purple_alt.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "purple.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "stripe.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "blue_alt2.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "purple_alt2.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "stripe_alt.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "Alternate-48-fs8.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icon7.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "Alternate-49-fs8.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "e2a44f35b779f021.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "4c26b880d454e2ec.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "63245c61df096188.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icon38.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icon39.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Some files were not shown because too many files have changed in this diff Show More