2022-12-28 09:37:16 +01:00
|
|
|
//
|
|
|
|
// https://mczachurski.dev
|
|
|
|
// Copyright © 2022 Marcin Czachurski and the repository contributors.
|
2023-03-28 10:35:38 +02:00
|
|
|
// Licensed under the Apache License 2.0.
|
2022-12-28 09:37:16 +01:00
|
|
|
//
|
|
|
|
|
|
|
|
import SwiftUI
|
2023-01-24 20:38:21 +01:00
|
|
|
import Nuke
|
|
|
|
import NukeUI
|
2022-12-28 09:37:16 +01:00
|
|
|
|
|
|
|
@main
|
2023-01-03 14:09:22 +01:00
|
|
|
struct VernissageApp: App {
|
2022-12-31 16:31:05 +01:00
|
|
|
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
|
|
|
|
|
|
|
let coreDataHandler = CoreDataHandler.shared
|
2023-02-03 15:16:30 +01:00
|
|
|
|
2023-01-23 18:01:27 +01:00
|
|
|
@StateObject var applicationState = ApplicationState.shared
|
2023-02-03 15:16:30 +01:00
|
|
|
@StateObject var client = Client.shared
|
|
|
|
@StateObject var routerPath = RouterPath()
|
2023-02-14 07:32:00 +01:00
|
|
|
@StateObject var tipsStore = TipsStore()
|
2023-04-01 12:10:59 +02:00
|
|
|
|
2022-12-31 16:31:05 +01:00
|
|
|
@State var applicationViewMode: ApplicationViewMode = .loading
|
2023-01-12 18:34:48 +01:00
|
|
|
@State var tintColor = ApplicationState.shared.tintColor.color()
|
2023-01-13 13:37:01 +01:00
|
|
|
@State var theme = ApplicationState.shared.theme.colorScheme()
|
2023-04-01 12:10:59 +02:00
|
|
|
|
2023-02-08 20:18:03 +01:00
|
|
|
let timer = Timer.publish(every: 120, on: .main, in: .common).autoconnect()
|
2023-02-03 15:16:30 +01:00
|
|
|
|
2022-12-28 09:37:16 +01:00
|
|
|
var body: some Scene {
|
|
|
|
WindowGroup {
|
2023-01-23 18:01:27 +01:00
|
|
|
NavigationStack(path: $routerPath.path) {
|
2022-12-31 16:31:05 +01:00
|
|
|
switch applicationViewMode {
|
|
|
|
case .loading:
|
2023-01-06 13:44:02 +01:00
|
|
|
LoadingView()
|
2023-01-23 18:01:27 +01:00
|
|
|
.withAppRouteur()
|
|
|
|
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
|
2022-12-31 16:31:05 +01:00
|
|
|
case .signIn:
|
2023-03-29 17:06:41 +02:00
|
|
|
SignInView { accountModel in
|
|
|
|
self.setApplicationState(accountModel: accountModel)
|
2022-12-31 16:31:05 +01:00
|
|
|
}
|
2023-01-23 18:01:27 +01:00
|
|
|
.withAppRouteur()
|
|
|
|
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
|
2022-12-31 16:31:05 +01:00
|
|
|
case .mainView:
|
2023-01-23 18:01:27 +01:00
|
|
|
MainView()
|
|
|
|
.withAppRouteur()
|
|
|
|
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
|
2023-02-13 21:10:07 +01:00
|
|
|
.withOverlayDestinations(overlayDestinations: $routerPath.presentedOverlay)
|
2022-12-31 16:31:05 +01:00
|
|
|
}
|
|
|
|
}
|
2023-01-23 18:01:27 +01:00
|
|
|
.environment(\.managedObjectContext, coreDataHandler.container.viewContext)
|
|
|
|
.environmentObject(applicationState)
|
2023-02-03 15:16:30 +01:00
|
|
|
.environmentObject(client)
|
2023-01-23 18:01:27 +01:00
|
|
|
.environmentObject(routerPath)
|
2023-02-14 07:32:00 +01:00
|
|
|
.environmentObject(tipsStore)
|
2023-01-12 18:34:48 +01:00
|
|
|
.tint(self.tintColor)
|
2023-01-13 13:37:01 +01:00
|
|
|
.preferredColorScheme(self.theme)
|
2023-01-12 18:34:48 +01:00
|
|
|
.task {
|
2023-03-29 09:35:01 +02:00
|
|
|
await self.onApplicationStart()
|
2022-12-29 17:27:15 +01:00
|
|
|
}
|
|
|
|
.navigationViewStyle(.stack)
|
2023-01-06 13:05:21 +01:00
|
|
|
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
|
2023-02-28 08:44:23 +01:00
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
|
|
|
|
Task {
|
|
|
|
// Refresh indicator of new photos when application is become active.
|
|
|
|
await self.calculateNewPhotosInBackground()
|
|
|
|
}
|
2023-02-08 20:18:03 +01:00
|
|
|
}
|
2023-01-06 13:05:21 +01:00
|
|
|
}
|
2023-04-01 12:10:59 +02:00
|
|
|
.onReceive(timer) { _ in
|
2023-02-08 20:18:03 +01:00
|
|
|
Task {
|
2023-02-23 17:12:35 +01:00
|
|
|
// Refresh indicator of new photos each two minutes (when application is in the foreground)..
|
2023-02-22 14:33:03 +01:00
|
|
|
await self.calculateNewPhotosInBackground()
|
2023-02-08 20:18:03 +01:00
|
|
|
}
|
|
|
|
}
|
2023-01-23 18:01:27 +01:00
|
|
|
.onChange(of: applicationState.theme) { newValue in
|
|
|
|
self.theme = newValue.colorScheme()
|
|
|
|
}
|
|
|
|
.onChange(of: applicationState.tintColor) { newValue in
|
|
|
|
self.tintColor = newValue.color()
|
|
|
|
}
|
2023-02-23 08:09:02 +01:00
|
|
|
.onChange(of: applicationState.account) { newValue in
|
|
|
|
if newValue == nil {
|
|
|
|
self.applicationViewMode = .signIn
|
|
|
|
}
|
|
|
|
}
|
2023-03-11 18:30:33 +01:00
|
|
|
.onChange(of: applicationState.showStatusId) { newValue in
|
|
|
|
if let statusId = newValue {
|
|
|
|
self.routerPath.navigate(to: .status(id: statusId))
|
|
|
|
self.applicationState.showStatusId = nil
|
|
|
|
}
|
|
|
|
}
|
2022-12-28 09:37:16 +01:00
|
|
|
}
|
|
|
|
}
|
2023-04-01 12:10:59 +02:00
|
|
|
|
2023-03-29 17:06:41 +02:00
|
|
|
@MainActor
|
2023-03-29 09:35:01 +02:00
|
|
|
private func onApplicationStart() async {
|
|
|
|
UIPageControl.appearance().currentPageIndicatorTintColor = UIColor.white.withAlphaComponent(0.7)
|
|
|
|
UIPageControl.appearance().pageIndicatorTintColor = UIColor.white.withAlphaComponent(0.4)
|
2023-04-01 12:10:59 +02:00
|
|
|
|
2023-03-29 09:35:01 +02:00
|
|
|
// Set custom configurations for Nuke image/data loaders.
|
|
|
|
self.setImagePipelines()
|
|
|
|
|
|
|
|
// Load user preferences from database.
|
|
|
|
self.loadUserPreferences()
|
2023-04-01 12:10:59 +02:00
|
|
|
|
2023-03-29 09:35:01 +02:00
|
|
|
// Refresh other access tokens.
|
|
|
|
await self.refreshAccessTokens()
|
2023-04-01 12:10:59 +02:00
|
|
|
|
2023-03-29 17:06:41 +02:00
|
|
|
// When user doesn't exists then we have to open sign in view.
|
|
|
|
guard let currentAccount = AccountDataHandler.shared.getCurrentAccountData() else {
|
|
|
|
self.applicationViewMode = .signIn
|
|
|
|
return
|
|
|
|
}
|
2023-04-01 12:10:59 +02:00
|
|
|
|
2023-03-29 17:06:41 +02:00
|
|
|
// Create model based on core data entity.
|
|
|
|
let accountModel = AccountModel(accountData: currentAccount)
|
|
|
|
|
2023-03-29 09:35:01 +02:00
|
|
|
// Verify access token correctness.
|
|
|
|
let authorizationSession = AuthorizationSession()
|
2023-03-29 17:06:41 +02:00
|
|
|
await AuthorizationService.shared.verifyAccount(session: authorizationSession, accountModel: accountModel) { signedInAccountModel in
|
|
|
|
guard let signedInAccountModel else {
|
2023-03-29 09:35:01 +02:00
|
|
|
self.applicationViewMode = .signIn
|
|
|
|
return
|
|
|
|
}
|
2023-04-01 12:10:59 +02:00
|
|
|
|
2023-03-29 17:06:41 +02:00
|
|
|
self.setApplicationState(accountModel: signedInAccountModel, checkNewPhotos: true)
|
2023-03-29 09:35:01 +02:00
|
|
|
}
|
|
|
|
}
|
2023-02-20 16:41:18 +01:00
|
|
|
|
2023-03-29 17:06:41 +02:00
|
|
|
private func setApplicationState(accountModel: AccountModel, checkNewPhotos: Bool = false) {
|
2023-02-20 16:41:18 +01:00
|
|
|
Task { @MainActor in
|
2023-02-22 13:26:35 +01:00
|
|
|
let instance = try? await self.client.instances.instance(url: accountModel.serverUrl)
|
|
|
|
|
|
|
|
// Refresh client state.
|
2023-02-20 16:41:18 +01:00
|
|
|
self.client.setAccount(account: accountModel)
|
2023-04-01 12:10:59 +02:00
|
|
|
|
2023-02-22 13:26:35 +01:00
|
|
|
// Refresh application state.
|
|
|
|
self.applicationState.changeApplicationState(accountModel: accountModel,
|
|
|
|
instance: instance,
|
2023-03-29 17:06:41 +02:00
|
|
|
lastSeenStatusId: accountModel.lastSeenStatusId)
|
2023-04-01 12:10:59 +02:00
|
|
|
|
2023-02-22 13:26:35 +01:00
|
|
|
// Change view displayed by application.
|
2023-02-20 16:41:18 +01:00
|
|
|
self.applicationViewMode = .mainView
|
2023-04-01 12:10:59 +02:00
|
|
|
|
2023-02-20 16:41:18 +01:00
|
|
|
// Check amount of newly added photos.
|
|
|
|
if checkNewPhotos {
|
2023-02-22 14:33:03 +01:00
|
|
|
await self.calculateNewPhotosInBackground()
|
2023-02-20 16:41:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-04-01 12:10:59 +02:00
|
|
|
|
2023-01-24 20:38:21 +01:00
|
|
|
private func loadUserPreferences() {
|
2023-03-15 14:27:59 +01:00
|
|
|
let defaultSettings = ApplicationSettingsHandler.shared.get()
|
2023-04-01 12:10:59 +02:00
|
|
|
|
2023-01-24 20:38:21 +01:00
|
|
|
if let tintColor = TintColor(rawValue: Int(defaultSettings.tintColor)) {
|
|
|
|
self.applicationState.tintColor = tintColor
|
|
|
|
self.tintColor = tintColor.color()
|
|
|
|
}
|
2023-04-01 12:10:59 +02:00
|
|
|
|
2023-01-24 20:38:21 +01:00
|
|
|
if let theme = Theme(rawValue: Int(defaultSettings.theme)) {
|
|
|
|
self.applicationState.theme = theme
|
|
|
|
self.theme = theme.colorScheme()
|
|
|
|
}
|
|
|
|
|
|
|
|
if let avatarShape = AvatarShape(rawValue: Int(defaultSettings.avatarShape)) {
|
|
|
|
self.applicationState.avatarShape = avatarShape
|
|
|
|
}
|
2023-04-01 12:10:59 +02:00
|
|
|
|
2023-03-18 18:40:07 +01:00
|
|
|
self.applicationState.activeIcon = defaultSettings.activeIcon
|
2023-03-08 17:43:05 +01:00
|
|
|
self.applicationState.showSensitive = defaultSettings.showSensitive
|
|
|
|
self.applicationState.showPhotoDescription = defaultSettings.showPhotoDescription
|
2023-01-24 20:38:21 +01:00
|
|
|
}
|
2023-04-01 12:10:59 +02:00
|
|
|
|
2023-01-24 20:38:21 +01:00
|
|
|
private func setImagePipelines() {
|
|
|
|
let pipeline = ImagePipeline {
|
|
|
|
$0.dataLoader = DataLoader(configuration: {
|
|
|
|
// Disable disk caching built into URLSession
|
|
|
|
let conf = DataLoader.defaultConfiguration
|
|
|
|
conf.urlCache = nil
|
|
|
|
return conf
|
|
|
|
}())
|
2023-04-01 12:10:59 +02:00
|
|
|
|
2023-01-24 20:38:21 +01:00
|
|
|
$0.imageCache = ImageCache.shared
|
2023-04-01 12:10:59 +02:00
|
|
|
if let dataCache = try? DataCache(name: AppConstants.imagePipelineCacheName) {
|
|
|
|
$0.dataCache = dataCache
|
|
|
|
}
|
2023-01-24 20:38:21 +01:00
|
|
|
}
|
2023-04-01 12:10:59 +02:00
|
|
|
|
2023-01-24 20:38:21 +01:00
|
|
|
ImagePipeline.shared = pipeline
|
|
|
|
}
|
2023-04-01 12:10:59 +02:00
|
|
|
|
2023-02-06 14:50:28 +01:00
|
|
|
private func refreshAccessTokens() async {
|
2023-03-15 14:27:59 +01:00
|
|
|
let defaultSettings = ApplicationSettingsHandler.shared.get()
|
2023-04-01 12:10:59 +02:00
|
|
|
|
2023-02-06 14:50:28 +01:00
|
|
|
// Run refreshing access tokens once per day.
|
|
|
|
guard let refreshTokenDate = Calendar.current.date(byAdding: .day, value: 1, to: defaultSettings.lastRefreshTokens), refreshTokenDate < Date.now else {
|
|
|
|
return
|
|
|
|
}
|
2023-04-01 12:10:59 +02:00
|
|
|
|
2023-02-06 14:50:28 +01:00
|
|
|
// Refresh access tokens.
|
|
|
|
await AuthorizationService.shared.refreshAccessTokens()
|
2023-04-01 12:10:59 +02:00
|
|
|
|
2023-02-06 14:50:28 +01:00
|
|
|
// Update time when refresh tokens has been updated.
|
|
|
|
defaultSettings.lastRefreshTokens = Date.now
|
|
|
|
CoreDataHandler.shared.save()
|
|
|
|
}
|
2023-04-01 12:10:59 +02:00
|
|
|
|
2023-02-22 14:33:03 +01:00
|
|
|
private func calculateNewPhotosInBackground() async {
|
2023-02-08 20:18:03 +01:00
|
|
|
if let account = self.applicationState.account {
|
|
|
|
self.applicationState.amountOfNewStatuses = await HomeTimelineService.shared.amountOfNewStatuses(for: account)
|
|
|
|
}
|
|
|
|
}
|
2022-12-31 16:31:05 +01:00
|
|
|
}
|