IceCubesApp/IceCubesApp/App/IceCubesApp.swift

331 lines
11 KiB
Swift
Raw Normal View History

2023-01-17 10:36:01 +00:00
import Account
import AppAccount
2023-01-09 17:52:53 +00:00
import AVFoundation
2022-12-24 13:55:04 +00:00
import DesignSystem
2023-01-17 10:36:01 +00:00
import Env
import KeychainSwift
import Network
2023-01-07 12:44:13 +00:00
import RevenueCat
import Status
2023-01-17 10:36:01 +00:00
import SwiftUI
import Timeline
2023-10-16 17:08:59 +00:00
import MediaUI
2022-12-01 08:05:26 +00:00
@main
2023-01-17 10:36:01 +00:00
struct IceCubesApp: App {
2023-01-08 09:22:52 +00:00
@UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
2023-01-30 06:27:06 +00:00
@Environment(\.scenePhase) private var scenePhase
2023-01-30 06:27:06 +00:00
@State private var appAccountsManager = AppAccountsManager.shared
@State private var currentInstance = CurrentInstance.shared
@State private var currentAccount = CurrentAccount.shared
2023-09-19 07:18:20 +00:00
@State private var userPreferences = UserPreferences.shared
@State private var pushNotificationsService = PushNotificationsService.shared
@State private var watcher = StreamWatcher()
@State private var quickLook = QuickLook()
2023-09-18 19:03:52 +00:00
@State private var theme = Theme.shared
@State private var sidebarRouterPath = RouterPath()
2023-01-30 06:27:06 +00:00
2023-01-04 11:55:09 +00:00
@State private var selectedTab: Tab = .timeline
2022-12-24 10:50:05 +00:00
@State private var popToRootTab: Tab = .other
@State private var sideBarLoadedTabs: Set<Tab> = Set()
@State private var isSupporter: Bool = false
2023-01-30 06:27:06 +00:00
private var availableTabs: [Tab] {
appAccountsManager.currentClient.isAuth ? Tab.loggedInTabs() : Tab.loggedOutTab()
}
2023-01-30 06:27:06 +00:00
2022-12-01 08:05:26 +00:00
var body: some Scene {
2023-10-23 17:12:25 +00:00
appScene
otherScenes
}
private var appScene: some Scene {
2022-12-01 08:05:26 +00:00
WindowGroup {
appView
2023-01-17 10:36:01 +00:00
.applyTheme(theme)
.onAppear {
setNewClientsInEnv(client: appAccountsManager.currentClient)
setupRevenueCat()
refreshPushSubs()
}
.environment(appAccountsManager)
.environment(appAccountsManager.currentClient)
.environment(quickLook)
.environment(currentAccount)
.environment(currentInstance)
2023-09-19 07:18:20 +00:00
.environment(userPreferences)
2023-09-18 19:03:52 +00:00
.environment(theme)
.environment(watcher)
.environment(pushNotificationsService)
.environment(\.isSupporter, isSupporter)
2023-10-16 17:08:59 +00:00
.sheet(item: $quickLook.selectedMediaAttachment) { selectedMediaAttachment in
MediaUIView(selectedAttachment: selectedMediaAttachment,
attachments: quickLook.mediaAttachments)
.presentationBackground(.ultraThinMaterial)
.presentationCornerRadius(16)
2023-10-23 17:12:25 +00:00
.withEnvironments()
2023-10-16 17:08:59 +00:00
}
.onChange(of: pushNotificationsService.handledNotification) { _, newValue in
if newValue != nil {
2023-02-14 11:17:27 +00:00
pushNotificationsService.handledNotification = nil
if appAccountsManager.currentAccount.oauthToken?.accessToken != newValue?.account.token.accessToken,
2023-02-14 11:17:27 +00:00
let account = appAccountsManager.availableAccounts.first(where:
{ $0.oauthToken?.accessToken == newValue?.account.token.accessToken })
2023-02-18 06:26:48 +00:00
{
2023-02-14 11:17:27 +00:00
appAccountsManager.currentAccount = account
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
selectedTab = .notifications
pushNotificationsService.handledNotification = newValue
2023-02-14 11:17:27 +00:00
}
} else {
selectedTab = .notifications
}
}
}
2023-09-22 07:31:35 +00:00
.withModelContainer()
2022-12-01 08:05:26 +00:00
}
.commands {
2023-01-18 07:27:42 +00:00
appMenu
}
.onChange(of: scenePhase) { _, newValue in
handleScenePhase(scenePhase: newValue)
}
.onChange(of: appAccountsManager.currentClient) { _, newValue in
setNewClientsInEnv(client: newValue)
if newValue.isAuth {
watcher.watch(streams: [.user, .direct])
2022-12-29 09:39:34 +00:00
}
}
2023-10-23 17:12:25 +00:00
2022-12-01 08:05:26 +00:00
}
2023-01-30 06:27:06 +00:00
@ViewBuilder
private var appView: some View {
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
2023-01-16 13:40:23 +00:00
sidebarView
} else {
tabBarView
}
}
2023-01-30 06:27:06 +00:00
private func badgeFor(tab: Tab) -> Int {
2023-09-16 12:15:03 +00:00
if tab == .notifications, selectedTab != tab,
2023-02-21 06:23:42 +00:00
let token = appAccountsManager.currentAccount.oauthToken
{
2023-09-19 06:44:11 +00:00
return watcher.unreadNotificationsCount + (userPreferences.notificationsCount[token] ?? 0)
}
return 0
}
2023-01-30 06:27:06 +00:00
2023-01-16 13:40:23 +00:00
private var sidebarView: some View {
2023-01-16 18:51:05 +00:00
SideBarView(selectedTab: $selectedTab,
popToRootTab: $popToRootTab,
tabs: availableTabs)
2023-03-13 12:38:28 +00:00
{
2023-09-22 20:41:06 +00:00
HStack(spacing: 0) {
ZStack {
if selectedTab == .profile {
ProfileTab(popToRootTab: $popToRootTab)
}
2023-09-22 20:41:06 +00:00
ForEach(availableTabs) { tab in
if tab == selectedTab || sideBarLoadedTabs.contains(tab) {
tab
.makeContentView(popToRootTab: $popToRootTab)
.opacity(tab == selectedTab ? 1 : 0)
.transition(.opacity)
.id("\(tab)\(appAccountsManager.currentAccount.id)")
.onAppear {
sideBarLoadedTabs.insert(tab)
}
} else {
EmptyView()
}
2023-01-16 13:40:23 +00:00
}
}
2023-09-22 20:41:06 +00:00
if appAccountsManager.currentClient.isAuth,
userPreferences.showiPadSecondaryColumn
{
Divider().edgesIgnoringSafeArea(.all)
notificationsSecondaryColumn
}
2023-01-16 13:40:23 +00:00
}
}.onChange(of: $appAccountsManager.currentAccount.id) {
sideBarLoadedTabs.removeAll()
2023-01-16 13:40:23 +00:00
}
.environment(sidebarRouterPath)
2023-01-16 13:40:23 +00:00
}
2023-02-18 06:26:48 +00:00
private var notificationsSecondaryColumn: some View {
NotificationsTab(popToRootTab: $popToRootTab, lockedType: nil)
.environment(\.isSecondaryColumn, true)
.frame(maxWidth: .secondaryColumnWidth)
.id(appAccountsManager.currentAccount.id)
}
2023-01-30 06:27:06 +00:00
private var tabBarView: some View {
TabView(selection: .init(get: {
selectedTab
}, set: { newTab in
2023-03-04 08:30:27 +00:00
if newTab == selectedTab {
/// Stupid hack to trigger onChange binding in tab views.
popToRootTab = .other
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
popToRootTab = selectedTab
}
2023-03-04 08:30:27 +00:00
}
2023-03-13 12:38:28 +00:00
HapticManager.shared.fireHaptic(of: .tabSelection)
SoundEffectManager.shared.playSound(of: .tabSelection)
2023-03-13 12:38:28 +00:00
2023-03-04 08:30:27 +00:00
selectedTab = newTab
2023-03-13 12:38:28 +00:00
2023-03-04 08:30:27 +00:00
DispatchQueue.main.async {
if selectedTab == .notifications,
2023-02-21 06:23:42 +00:00
let token = appAccountsManager.currentAccount.oauthToken
{
2023-09-19 06:44:11 +00:00
userPreferences.notificationsCount[token] = 0
watcher.unreadNotificationsCount = 0
}
}
2023-03-13 12:38:28 +00:00
})) {
ForEach(availableTabs) { tab in
tab.makeContentView(popToRootTab: $popToRootTab)
.tabItem {
if userPreferences.showiPhoneTabLabel {
tab.label
} else {
2023-09-18 18:14:26 +00:00
Image(systemName: tab.iconName)
}
}
.tag(tab)
.badge(badgeFor(tab: tab))
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.50), for: .tabBar)
}
}
.id(appAccountsManager.currentClient.id)
}
2023-10-23 17:12:25 +00:00
private var otherScenes: some Scene {
WindowGroup(for: WindowDestination.self) { destination in
Group {
switch destination.wrappedValue {
case let .newStatusEditor(visibility):
StatusEditorView(mode: .new(visibility: visibility))
case let .mediaViewer(attachments, selectedAttachment):
MediaUIView(selectedAttachment: selectedAttachment,
attachments: attachments)
case .none:
EmptyView()
}
}
.withEnvironments()
.withModelContainer()
.applyTheme(theme)
}
.defaultSize(width: 600, height: 800)
.windowResizability(.automatic)
}
2023-01-30 06:27:06 +00:00
2022-12-25 16:39:12 +00:00
private func setNewClientsInEnv(client: Client) {
currentAccount.setClient(client: client)
2023-01-01 17:31:23 +00:00
currentInstance.setClient(client: client)
2023-01-09 18:47:54 +00:00
userPreferences.setClient(client: client)
2023-02-03 18:44:55 +00:00
Task {
await currentInstance.fetchCurrentInstance()
watcher.setClient(client: client, instanceStreamingURL: currentInstance.instance?.urls?.streamingApi)
watcher.watch(streams: [.user, .direct])
}
2022-12-25 16:39:12 +00:00
}
2023-01-30 06:27:06 +00:00
2022-12-25 16:39:12 +00:00
private func handleScenePhase(scenePhase: ScenePhase) {
switch scenePhase {
case .background:
2023-02-06 16:53:47 +00:00
watcher.stopWatching()
2022-12-25 16:39:12 +00:00
case .active:
watcher.watch(streams: [.user, .direct])
UNUserNotificationCenter.current().setBadgeCount(0)
userPreferences.reloadNotificationsCount(tokens: appAccountsManager.availableAccounts.compactMap(\.oauthToken))
2023-01-09 18:47:54 +00:00
Task {
await userPreferences.refreshServerPreferences()
}
2022-12-25 16:39:12 +00:00
default:
break
}
}
2023-01-30 06:27:06 +00:00
2023-01-07 12:44:13 +00:00
private func setupRevenueCat() {
Purchases.logLevel = .error
Purchases.configure(withAPIKey: "appl_JXmiRckOzXXTsHKitQiicXCvMQi")
Purchases.shared.getCustomerInfo { info, _ in
2023-03-01 20:14:26 +00:00
if info?.entitlements["Supporter"]?.isActive == true {
isSupporter = true
}
}
2023-01-07 12:44:13 +00:00
}
2023-01-30 06:27:06 +00:00
2023-01-08 09:22:52 +00:00
private func refreshPushSubs() {
2023-01-08 13:16:43 +00:00
PushNotificationsService.shared.requestPushNotifications()
2023-01-08 09:22:52 +00:00
}
2023-01-30 06:27:06 +00:00
2023-01-18 07:27:42 +00:00
@CommandsBuilder
private var appMenu: some Commands {
CommandGroup(replacing: .newItem) {
2023-01-26 05:40:33 +00:00
Button("menu.new-post") {
sidebarRouterPath.presentedSheet = .newStatusEditor(visibility: userPreferences.postVisibility)
2023-01-18 07:27:42 +00:00
}
}
CommandGroup(replacing: .textFormatting) {
2023-01-26 05:40:33 +00:00
Menu("menu.font") {
Button("menu.font.bigger") {
if theme.fontSizeScale < 1.5 {
theme.fontSizeScale += 0.1
2023-01-18 07:27:42 +00:00
}
}
2023-01-26 05:40:33 +00:00
Button("menu.font.smaller") {
if theme.fontSizeScale > 0.5 {
theme.fontSizeScale -= 0.1
2023-01-18 07:27:42 +00:00
}
}
}
}
}
2023-01-08 09:22:52 +00:00
}
class AppDelegate: NSObject, UIApplicationDelegate {
2023-01-17 10:36:01 +00:00
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool
{
2023-03-06 13:10:39 +00:00
try? AVAudioSession.sharedInstance().setCategory(.ambient)
PushNotificationsService.shared.setAccounts(accounts: AppAccountsManager.shared.pushAccounts)
2023-01-08 09:22:52 +00:00
return true
}
2023-01-30 06:27:06 +00:00
2023-01-17 10:36:01 +00:00
func application(_: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data)
{
2023-01-08 13:16:43 +00:00
PushNotificationsService.shared.pushToken = deviceToken
Task {
PushNotificationsService.shared.setAccounts(accounts: AppAccountsManager.shared.pushAccounts)
await PushNotificationsService.shared.updateSubscriptions(forceCreate: false)
}
2023-01-08 09:22:52 +00:00
}
2023-01-30 06:27:06 +00:00
2023-01-17 10:36:01 +00:00
func application(_: UIApplication, didFailToRegisterForRemoteNotificationsWithError _: Error) {}
func application(_: UIApplication, didReceiveRemoteNotification _: [AnyHashable: Any]) async -> UIBackgroundFetchResult {
UserPreferences.shared.reloadNotificationsCount(tokens: AppAccountsManager.shared.availableAccounts.compactMap(\.oauthToken))
2023-09-19 06:44:11 +00:00
return .noData
}
2023-01-30 06:27:06 +00:00
func application(_: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options _: UIScene.ConnectionOptions) -> UISceneConfiguration {
let configuration = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
if connectingSceneSession.role == .windowApplication {
configuration.delegateClass = SceneDelegate.self
}
return configuration
}
2022-12-01 08:05:26 +00:00
}