2023-01-17 11:36:01 +01:00
|
|
|
import Account
|
|
|
|
import AppAccount
|
2023-01-09 18:52:53 +01:00
|
|
|
import AVFoundation
|
2022-12-24 14:55:04 +01:00
|
|
|
import DesignSystem
|
2023-01-17 11:36:01 +01:00
|
|
|
import Env
|
|
|
|
import KeychainSwift
|
|
|
|
import Network
|
2023-01-07 13:44:13 +01:00
|
|
|
import RevenueCat
|
2023-01-17 11:36:01 +01:00
|
|
|
import SwiftUI
|
|
|
|
import Timeline
|
2022-12-01 09:05:26 +01:00
|
|
|
|
|
|
|
@main
|
2023-01-17 11:36:01 +01:00
|
|
|
struct IceCubesApp: App {
|
2023-01-08 10:22:52 +01:00
|
|
|
@UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
|
2023-01-30 07:27:06 +01:00
|
|
|
|
2022-12-25 12:46:42 +01:00
|
|
|
@Environment(\.scenePhase) private var scenePhase
|
2023-01-30 07:27:06 +01:00
|
|
|
|
2023-01-08 10:22:52 +01:00
|
|
|
@StateObject private var appAccountsManager = AppAccountsManager.shared
|
2023-01-17 07:39:13 +01:00
|
|
|
@StateObject private var currentInstance = CurrentInstance.shared
|
|
|
|
@StateObject private var currentAccount = CurrentAccount.shared
|
|
|
|
@StateObject private var userPreferences = UserPreferences.shared
|
2022-12-25 12:46:42 +01:00
|
|
|
@StateObject private var watcher = StreamWatcher()
|
2022-12-22 10:53:36 +01:00
|
|
|
@StateObject private var quickLook = QuickLook()
|
2023-01-17 07:39:13 +01:00
|
|
|
@StateObject private var theme = Theme.shared
|
2023-01-17 13:02:05 +01:00
|
|
|
@StateObject private var sidebarRouterPath = RouterPath()
|
2023-01-30 07:27:06 +01:00
|
|
|
|
2023-01-04 12:55:09 +01:00
|
|
|
@State private var selectedTab: Tab = .timeline
|
|
|
|
@State private var selectSidebarItem: Tab? = .timeline
|
2022-12-24 11:50:05 +01:00
|
|
|
@State private var popToRootTab: Tab = .other
|
2023-01-17 13:02:05 +01:00
|
|
|
@State private var sideBarLoadedTabs: Set<Tab> = Set()
|
2023-01-30 07:27:06 +01:00
|
|
|
|
2023-01-24 09:19:53 +01:00
|
|
|
private let feedbackGenerator = UISelectionFeedbackGenerator()
|
2023-01-30 07:27:06 +01:00
|
|
|
|
2023-01-04 12:50:57 +01:00
|
|
|
private var availableTabs: [Tab] {
|
|
|
|
appAccountsManager.currentClient.isAuth ? Tab.loggedInTabs() : Tab.loggedOutTab()
|
|
|
|
}
|
2023-01-30 07:27:06 +01:00
|
|
|
|
2022-12-01 09:05:26 +01:00
|
|
|
var body: some Scene {
|
|
|
|
WindowGroup {
|
2023-01-04 12:50:57 +01:00
|
|
|
appView
|
2023-01-17 11:36:01 +01:00
|
|
|
.applyTheme(theme)
|
|
|
|
.onAppear {
|
|
|
|
setNewClientsInEnv(client: appAccountsManager.currentClient)
|
|
|
|
setupRevenueCat()
|
|
|
|
refreshPushSubs()
|
|
|
|
}
|
|
|
|
.environmentObject(appAccountsManager)
|
|
|
|
.environmentObject(appAccountsManager.currentClient)
|
|
|
|
.environmentObject(quickLook)
|
|
|
|
.environmentObject(currentAccount)
|
|
|
|
.environmentObject(currentInstance)
|
|
|
|
.environmentObject(userPreferences)
|
|
|
|
.environmentObject(theme)
|
|
|
|
.environmentObject(watcher)
|
|
|
|
.environmentObject(PushNotificationsService.shared)
|
2023-01-28 06:45:15 +01:00
|
|
|
.fullScreenCover(item: $quickLook.url, content: { url in
|
2023-01-17 11:36:01 +01:00
|
|
|
QuickLookPreview(selectedURL: url, urls: quickLook.urls)
|
|
|
|
.edgesIgnoringSafeArea(.bottom)
|
2023-01-28 06:45:15 +01:00
|
|
|
.background(TransparentBackground())
|
2023-01-17 11:36:01 +01:00
|
|
|
})
|
2022-12-01 09:05:26 +01:00
|
|
|
}
|
2023-01-17 13:02:05 +01:00
|
|
|
.commands {
|
2023-01-18 08:27:42 +01:00
|
|
|
appMenu
|
2023-01-17 13:02:05 +01:00
|
|
|
}
|
2023-01-08 19:49:49 +01:00
|
|
|
.onChange(of: scenePhase) { scenePhase in
|
2022-12-25 17:39:12 +01:00
|
|
|
handleScenePhase(scenePhase: scenePhase)
|
2023-01-08 19:49:49 +01:00
|
|
|
}
|
2022-12-29 10:39:34 +01:00
|
|
|
.onChange(of: appAccountsManager.currentClient) { newClient in
|
|
|
|
setNewClientsInEnv(client: newClient)
|
|
|
|
if newClient.isAuth {
|
2023-01-05 12:21:54 +01:00
|
|
|
watcher.watch(streams: [.user, .direct])
|
2022-12-29 10:39:34 +01:00
|
|
|
}
|
|
|
|
}
|
2022-12-01 09:05:26 +01:00
|
|
|
}
|
2023-01-30 07:27:06 +01:00
|
|
|
|
2023-01-04 12:50:57 +01:00
|
|
|
@ViewBuilder
|
|
|
|
private var appView: some View {
|
|
|
|
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
2023-01-16 14:40:23 +01:00
|
|
|
sidebarView
|
2023-01-04 12:50:57 +01:00
|
|
|
} else {
|
2023-01-05 21:40:15 +01:00
|
|
|
tabBarView
|
2023-01-04 12:50:57 +01:00
|
|
|
}
|
|
|
|
}
|
2023-01-30 07:27:06 +01:00
|
|
|
|
2023-01-05 12:21:54 +01:00
|
|
|
private func badgeFor(tab: Tab) -> Int {
|
|
|
|
if tab == .notifications && selectedTab != tab {
|
2023-01-09 18:52:53 +01:00
|
|
|
return watcher.unreadNotificationsCount + userPreferences.pushNotificationsCount
|
2023-01-05 12:21:54 +01:00
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
2023-01-30 07:27:06 +01:00
|
|
|
|
2023-01-16 14:40:23 +01:00
|
|
|
private var sidebarView: some View {
|
2023-01-16 19:51:05 +01:00
|
|
|
SideBarView(selectedTab: $selectedTab,
|
|
|
|
popToRootTab: $popToRootTab,
|
2023-01-17 13:02:05 +01:00
|
|
|
tabs: availableTabs,
|
|
|
|
routerPath: sidebarRouterPath) {
|
2023-01-29 16:45:58 +01:00
|
|
|
GeometryReader { proxy in
|
|
|
|
HStack(spacing: 0) {
|
|
|
|
ZStack {
|
|
|
|
if selectedTab == .profile {
|
|
|
|
ProfileTab(popToRootTab: $popToRootTab)
|
|
|
|
}
|
|
|
|
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 14:40:23 +01:00
|
|
|
}
|
2023-01-29 16:45:58 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if proxy.frame(in: .global).width > (.maxColumnWidth + .secondaryColumnWidth),
|
2023-01-30 19:14:43 +01:00
|
|
|
appAccountsManager.currentClient.isAuth
|
2023-01-30 07:27:06 +01:00
|
|
|
{
|
2023-01-29 16:45:58 +01:00
|
|
|
Divider().edgesIgnoringSafeArea(.all)
|
2023-01-29 17:59:04 +01:00
|
|
|
NotificationsTab(popToRootTab: $popToRootTab, lockedType: nil)
|
2023-01-29 17:37:15 +01:00
|
|
|
.environment(\.isSecondaryColumn, true)
|
2023-01-29 16:45:58 +01:00
|
|
|
.frame(maxWidth: 360)
|
2023-01-16 14:40:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-01-18 13:51:24 +01:00
|
|
|
}.onChange(of: $appAccountsManager.currentAccount.id) { _ in
|
|
|
|
sideBarLoadedTabs.removeAll()
|
2023-01-16 14:40:23 +01:00
|
|
|
}
|
|
|
|
}
|
2023-01-30 07:27:06 +01:00
|
|
|
|
2023-01-04 12:50:57 +01:00
|
|
|
private var tabBarView: some View {
|
|
|
|
TabView(selection: .init(get: {
|
|
|
|
selectedTab
|
|
|
|
}, set: { newTab in
|
|
|
|
if newTab == selectedTab {
|
|
|
|
/// Stupid hack to trigger onChange binding in tab views.
|
|
|
|
popToRootTab = .other
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
2023-01-04 12:55:09 +01:00
|
|
|
popToRootTab = selectedTab
|
2023-01-04 12:50:57 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
selectedTab = newTab
|
2023-01-24 09:19:53 +01:00
|
|
|
feedbackGenerator.selectionChanged()
|
2023-01-04 12:50:57 +01:00
|
|
|
})) {
|
|
|
|
ForEach(availableTabs) { tab in
|
|
|
|
tab.makeContentView(popToRootTab: $popToRootTab)
|
|
|
|
.tabItem {
|
|
|
|
tab.label
|
|
|
|
}
|
|
|
|
.tag(tab)
|
2023-01-05 12:21:54 +01:00
|
|
|
.badge(badgeFor(tab: tab))
|
2023-01-04 12:50:57 +01:00
|
|
|
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.50), for: .tabBar)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-01-30 07:27:06 +01:00
|
|
|
|
2022-12-25 17:39:12 +01:00
|
|
|
private func setNewClientsInEnv(client: Client) {
|
|
|
|
currentAccount.setClient(client: client)
|
2023-01-01 18:31:23 +01:00
|
|
|
currentInstance.setClient(client: client)
|
2023-01-09 19:47:54 +01:00
|
|
|
userPreferences.setClient(client: client)
|
2022-12-25 17:39:12 +01:00
|
|
|
watcher.setClient(client: client)
|
|
|
|
}
|
2023-01-30 07:27:06 +01:00
|
|
|
|
2022-12-25 17:39:12 +01:00
|
|
|
private func handleScenePhase(scenePhase: ScenePhase) {
|
|
|
|
switch scenePhase {
|
|
|
|
case .background:
|
|
|
|
watcher.stopWatching()
|
|
|
|
case .active:
|
2023-01-05 12:21:54 +01:00
|
|
|
watcher.watch(streams: [.user, .direct])
|
2023-01-09 18:52:53 +01:00
|
|
|
UIApplication.shared.applicationIconBadgeNumber = 0
|
2023-01-09 19:47:54 +01:00
|
|
|
Task {
|
|
|
|
await userPreferences.refreshServerPreferences()
|
|
|
|
}
|
2022-12-25 17:39:12 +01:00
|
|
|
case .inactive:
|
|
|
|
break
|
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2023-01-30 07:27:06 +01:00
|
|
|
|
2023-01-07 13:44:13 +01:00
|
|
|
private func setupRevenueCat() {
|
|
|
|
Purchases.logLevel = .error
|
|
|
|
Purchases.configure(withAPIKey: "appl_JXmiRckOzXXTsHKitQiicXCvMQi")
|
|
|
|
}
|
2023-01-30 07:27:06 +01:00
|
|
|
|
2023-01-08 10:22:52 +01:00
|
|
|
private func refreshPushSubs() {
|
2023-01-08 14:16:43 +01:00
|
|
|
PushNotificationsService.shared.requestPushNotifications()
|
2023-01-08 10:22:52 +01:00
|
|
|
}
|
2023-01-30 07:27:06 +01:00
|
|
|
|
2023-01-18 08:27:42 +01:00
|
|
|
@CommandsBuilder
|
|
|
|
private var appMenu: some Commands {
|
|
|
|
CommandGroup(replacing: .newItem) {
|
2023-01-26 06:40:33 +01:00
|
|
|
Button("menu.new-post") {
|
2023-01-25 06:28:16 +01:00
|
|
|
sidebarRouterPath.presentedSheet = .newStatusEditor(visibility: userPreferences.postVisibility)
|
2023-01-18 08:27:42 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
CommandGroup(replacing: .textFormatting) {
|
2023-01-26 06:40:33 +01:00
|
|
|
Menu("menu.font") {
|
|
|
|
Button("menu.font.bigger") {
|
2023-01-18 08:27:42 +01:00
|
|
|
if userPreferences.fontSizeScale < 1.5 {
|
|
|
|
userPreferences.fontSizeScale += 0.1
|
|
|
|
}
|
|
|
|
}
|
2023-01-26 06:40:33 +01:00
|
|
|
Button("menu.font.smaller") {
|
2023-01-18 08:27:42 +01:00
|
|
|
if userPreferences.fontSizeScale > 0.5 {
|
|
|
|
userPreferences.fontSizeScale -= 0.1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-01-08 10:22:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
class AppDelegate: NSObject, UIApplicationDelegate {
|
2023-01-20 21:58:57 +01:00
|
|
|
let themeObserver = ThemeObserverViewController(nibName: nil, bundle: nil)
|
2023-01-30 07:27:06 +01:00
|
|
|
|
2023-01-17 11:36:01 +01:00
|
|
|
func application(_: UIApplication,
|
|
|
|
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool
|
|
|
|
{
|
2023-01-09 18:52:53 +01:00
|
|
|
try? AVAudioSession.sharedInstance().setCategory(.ambient, options: .mixWithOthers)
|
2023-01-08 10:22:52 +01:00
|
|
|
return true
|
|
|
|
}
|
2023-01-30 07:27:06 +01:00
|
|
|
|
2023-01-17 11:36:01 +01:00
|
|
|
func application(_: UIApplication,
|
|
|
|
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data)
|
|
|
|
{
|
2023-01-08 14:16:43 +01:00
|
|
|
PushNotificationsService.shared.pushToken = deviceToken
|
2023-01-26 13:21:35 +01:00
|
|
|
Task {
|
|
|
|
PushNotificationsService.shared.setAccounts(accounts: AppAccountsManager.shared.pushAccounts)
|
|
|
|
await PushNotificationsService.shared.updateSubscriptions(forceCreate: false)
|
|
|
|
}
|
2023-01-08 10:22:52 +01:00
|
|
|
}
|
2023-01-30 07:27:06 +01:00
|
|
|
|
2023-01-17 11:36:01 +01:00
|
|
|
func application(_: UIApplication, didFailToRegisterForRemoteNotificationsWithError _: Error) {}
|
2023-01-30 07:27:06 +01:00
|
|
|
|
2023-01-25 13:02:28 +01:00
|
|
|
func application(_: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options _: UIScene.ConnectionOptions) -> UISceneConfiguration {
|
2023-01-23 18:43:48 +01:00
|
|
|
let configuration = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
|
|
|
|
if connectingSceneSession.role == .windowApplication {
|
|
|
|
configuration.delegateClass = SceneDelegate.self
|
|
|
|
}
|
|
|
|
return configuration
|
|
|
|
}
|
2022-12-01 09:05:26 +01:00
|
|
|
}
|
2023-01-20 21:58:57 +01:00
|
|
|
|
|
|
|
class ThemeObserverViewController: UIViewController {
|
|
|
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
|
|
|
super.traitCollectionDidChange(previousTraitCollection)
|
2023-01-30 07:27:06 +01:00
|
|
|
|
2023-01-20 21:58:57 +01:00
|
|
|
print(traitCollection.userInterfaceStyle.rawValue)
|
|
|
|
}
|
|
|
|
}
|