Fix CoreData and not clickable "Show NSFW" button on tabs.
This commit is contained in:
parent
973dc21cf9
commit
292883c45c
|
@ -63,8 +63,8 @@ class AccountDataHandler {
|
|||
}
|
||||
}
|
||||
|
||||
func createAccountDataEntity() -> AccountData {
|
||||
let context = CoreDataHandler.shared.container.viewContext
|
||||
func createAccountDataEntity(viewContext: NSManagedObjectContext? = nil) -> AccountData {
|
||||
let context = viewContext ?? CoreDataHandler.shared.container.viewContext
|
||||
return AccountData(context: context)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,15 +5,16 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
class ApplicationSettingsHandler {
|
||||
public static let shared = ApplicationSettingsHandler()
|
||||
private init() { }
|
||||
|
||||
func get() -> ApplicationSettings {
|
||||
func get(viewContext: NSManagedObjectContext? = nil) -> ApplicationSettings {
|
||||
var settingsList: [ApplicationSettings] = []
|
||||
|
||||
let context = CoreDataHandler.shared.container.viewContext
|
||||
let context = viewContext ?? CoreDataHandler.shared.container.viewContext
|
||||
let fetchRequest = ApplicationSettings.fetchRequest()
|
||||
do {
|
||||
settingsList = try context.fetch(fetchRequest)
|
||||
|
@ -24,11 +25,11 @@ class ApplicationSettingsHandler {
|
|||
if let settings = settingsList.first {
|
||||
return settings
|
||||
} else {
|
||||
let settings = self.createApplicationSettingsEntity()
|
||||
let settings = self.createApplicationSettingsEntity(viewContext: context)
|
||||
settings.avatarShape = Int32(AvatarShape.circle.rawValue)
|
||||
settings.theme = Int32(Theme.system.rawValue)
|
||||
settings.tintColor = Int32(TintColor.accentColor2.rawValue)
|
||||
CoreDataHandler.shared.save()
|
||||
CoreDataHandler.shared.save(viewContext: context)
|
||||
|
||||
return settings
|
||||
}
|
||||
|
@ -106,8 +107,8 @@ class ApplicationSettingsHandler {
|
|||
CoreDataHandler.shared.save()
|
||||
}
|
||||
|
||||
private func createApplicationSettingsEntity() -> ApplicationSettings {
|
||||
let context = CoreDataHandler.shared.container.viewContext
|
||||
private func createApplicationSettingsEntity(viewContext: NSManagedObjectContext? = nil) -> ApplicationSettings {
|
||||
let context = viewContext ?? CoreDataHandler.shared.container.viewContext
|
||||
return ApplicationSettings(context: context)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,22 +72,24 @@ public class CoreDataHandler {
|
|||
self.container.newBackgroundContext()
|
||||
}
|
||||
|
||||
public func save() {
|
||||
let context = self.container.viewContext
|
||||
public func save(viewContext: NSManagedObjectContext? = nil) {
|
||||
let context = viewContext ?? CoreDataHandler.shared.container.viewContext
|
||||
if context.hasChanges {
|
||||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
// Replace this implementation with code to handle the error appropriately.
|
||||
// fatalError() causes the application to generate a crash log and terminate.
|
||||
// You should not use this function in a shipping application, although it may be useful during development.
|
||||
context.performAndWait {
|
||||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
// Replace this implementation with code to handle the error appropriately.
|
||||
// fatalError() causes the application to generate a crash log and terminate.
|
||||
// You should not use this function in a shipping application, although it may be useful during development.
|
||||
|
||||
#if DEBUG
|
||||
let nserror = error as NSError
|
||||
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
|
||||
#else
|
||||
CoreDataError.shared.handle(error, message: "An error occurred while writing the data.")
|
||||
#endif
|
||||
#if DEBUG
|
||||
let nserror = error as NSError
|
||||
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
|
||||
#else
|
||||
CoreDataError.shared.handle(error, message: "An error occurred while writing the data.")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -203,6 +203,7 @@
|
|||
F8CC95CE2970761D00C9C2AC /* TintColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CC95CD2970761D00C9C2AC /* TintColor.swift */; };
|
||||
F8CEEDF829ABADDD00DBED66 /* UIImage+Size.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CEEDF729ABADDD00DBED66 /* UIImage+Size.swift */; };
|
||||
F8CEEDFA29ABAFD200DBED66 /* ImageFileTranseferable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CEEDF929ABAFD200DBED66 /* ImageFileTranseferable.swift */; };
|
||||
F8D5444329D4066C002225D6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8D5444229D4066C002225D6 /* AppDelegate.swift */; };
|
||||
F8E6D03329CDD52500416CCA /* EditProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E6D03229CDD52500416CCA /* EditProfileView.swift */; };
|
||||
F8E6D03529CE161B00416CCA /* UIImage+Jpeg.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E6D03429CE161B00416CCA /* UIImage+Jpeg.swift */; };
|
||||
F8E9391F29C0BCFA002BB3B8 /* ImageContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E9391E29C0BCFA002BB3B8 /* ImageContextMenu.swift */; };
|
||||
|
@ -424,6 +425,7 @@
|
|||
F8CC95CD2970761D00C9C2AC /* TintColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TintColor.swift; sourceTree = "<group>"; };
|
||||
F8CEEDF729ABADDD00DBED66 /* UIImage+Size.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Size.swift"; sourceTree = "<group>"; };
|
||||
F8CEEDF929ABAFD200DBED66 /* ImageFileTranseferable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageFileTranseferable.swift; sourceTree = "<group>"; };
|
||||
F8D5444229D4066C002225D6 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
F8E6D03229CDD52500416CCA /* EditProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileView.swift; sourceTree = "<group>"; };
|
||||
F8E6D03429CE161B00416CCA /* UIImage+Jpeg.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Jpeg.swift"; sourceTree = "<group>"; };
|
||||
F8E9391E29C0BCFA002BB3B8 /* ImageContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageContextMenu.swift; sourceTree = "<group>"; };
|
||||
|
@ -808,6 +810,7 @@
|
|||
F8341F94295C63FE009C8EE6 /* Extensions */,
|
||||
F8341F93295C63E2009C8EE6 /* Views */,
|
||||
F88C246B295C37B80006098B /* VernissageApp.swift */,
|
||||
F8D5444229D4066C002225D6 /* AppDelegate.swift */,
|
||||
F88E4D55297EAD6E0057491A /* AppRouteur.swift */,
|
||||
F86A4300299A97F500DF7645 /* ProductIdentifiers.swift */,
|
||||
F866F6A929605AFA002E8F88 /* SceneDelegate.swift */,
|
||||
|
@ -1232,6 +1235,7 @@
|
|||
F873F14C29BDB67300DE72D1 /* UIImage+Rounded.swift in Sources */,
|
||||
F8864CEF29ACE90B0020C534 /* UIFont+Font.swift in Sources */,
|
||||
F8CEEDFA29ABAFD200DBED66 /* ImageFileTranseferable.swift in Sources */,
|
||||
F8D5444329D4066C002225D6 /* AppDelegate.swift in Sources */,
|
||||
F802884F297AEED5000BDD51 /* DatabaseError.swift in Sources */,
|
||||
F86A4307299AA5E900DF7645 /* ThanksView.swift in Sources */,
|
||||
F89B5CC229D01BF700549F2F /* InstanceView.swift in Sources */,
|
||||
|
@ -1304,7 +1308,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 86;
|
||||
CURRENT_PROJECT_VERSION = 87;
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = VernissageWidget/Info.plist;
|
||||
|
@ -1332,7 +1336,7 @@
|
|||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 86;
|
||||
CURRENT_PROJECT_VERSION = 87;
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = VernissageWidget/Info.plist;
|
||||
|
@ -1480,7 +1484,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 86;
|
||||
CURRENT_PROJECT_VERSION = 87;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
@ -1520,7 +1524,7 @@
|
|||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 86;
|
||||
CURRENT_PROJECT_VERSION = 87;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the Apache License 2.0.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
func application(_ application: UIApplication,
|
||||
configurationForConnecting connectingSceneSession: UISceneSession,
|
||||
options: UIScene.ConnectionOptions
|
||||
) -> UISceneConfiguration {
|
||||
let sceneConfig: UISceneConfiguration = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
|
||||
sceneConfig.delegateClass = SceneDelegate.self
|
||||
return sceneConfig
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import Foundation
|
||||
import PixelfedKit
|
||||
import CoreData
|
||||
import AuthenticationServices
|
||||
|
||||
/// Srvice responsible for login user into the Pixelfed account.
|
||||
|
@ -75,7 +76,8 @@ public class AuthorizationService {
|
|||
let account = try await authenticatedClient.verifyCredentials()
|
||||
|
||||
// Get/create account object in database.
|
||||
let accountData = self.getAccountData(account: account)
|
||||
let backgroundContext = CoreDataHandler.shared.newBackgroundContext()
|
||||
let accountData = self.getAccountData(account: account, backgroundContext: backgroundContext)
|
||||
|
||||
accountData.id = account.id
|
||||
accountData.username = account.username
|
||||
|
@ -113,13 +115,13 @@ public class AuthorizationService {
|
|||
}
|
||||
|
||||
// Set newly created account as current (only when we create a first account).
|
||||
let defaultSettings = ApplicationSettingsHandler.shared.get()
|
||||
let defaultSettings = ApplicationSettingsHandler.shared.get(viewContext: backgroundContext)
|
||||
if defaultSettings.currentAccount == nil {
|
||||
defaultSettings.currentAccount = accountData.id
|
||||
}
|
||||
|
||||
// Save account/settings data in database.
|
||||
CoreDataHandler.shared.save()
|
||||
CoreDataHandler.shared.save(viewContext: backgroundContext)
|
||||
|
||||
// Return account data.
|
||||
result(accountData)
|
||||
|
@ -204,7 +206,8 @@ public class AuthorizationService {
|
|||
accessToken: String,
|
||||
refreshToken: String?
|
||||
) async {
|
||||
guard let dbAccount = AccountDataHandler.shared.getAccountData(accountId: accountId) else {
|
||||
let backgroundContext = CoreDataHandler.shared.newBackgroundContext()
|
||||
guard let dbAccount = AccountDataHandler.shared.getAccountData(accountId: accountId, viewContext: backgroundContext) else {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -237,14 +240,14 @@ public class AuthorizationService {
|
|||
}
|
||||
|
||||
// Save account data in database and in application state.
|
||||
CoreDataHandler.shared.save()
|
||||
CoreDataHandler.shared.save(viewContext: backgroundContext)
|
||||
}
|
||||
|
||||
private func getAccountData(account: Account) -> AccountData {
|
||||
if let accountFromDb = AccountDataHandler.shared.getAccountData(accountId: account.id) {
|
||||
private func getAccountData(account: Account, backgroundContext: NSManagedObjectContext) -> AccountData {
|
||||
if let accountFromDb = AccountDataHandler.shared.getAccountData(accountId: account.id, viewContext: backgroundContext) {
|
||||
return accountFromDb
|
||||
}
|
||||
|
||||
return AccountDataHandler.shared.createAccountDataEntity()
|
||||
return AccountDataHandler.shared.createAccountDataEntity(viewContext: backgroundContext)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ public class HomeTimelineService {
|
|||
let newStatuses = try await self.load(for: account, on: backgroundContext, maxId: oldestStatus.id)
|
||||
|
||||
// Save data into database.
|
||||
try backgroundContext.save()
|
||||
CoreDataHandler.shared.save(viewContext: backgroundContext)
|
||||
|
||||
// Return amount of newly downloaded statuses.
|
||||
return newStatuses.count
|
||||
|
@ -43,7 +43,7 @@ public class HomeTimelineService {
|
|||
let lastSeenStatusId = try await self.refresh(for: account, on: backgroundContext)
|
||||
|
||||
// Save data into database.
|
||||
try backgroundContext.save()
|
||||
CoreDataHandler.shared.save(viewContext: backgroundContext)
|
||||
|
||||
// Return id of last seen status.
|
||||
return lastSeenStatusId
|
||||
|
@ -59,7 +59,9 @@ public class HomeTimelineService {
|
|||
}
|
||||
|
||||
accountDataFromDb.lastSeenStatusId = lastSeenStatusId
|
||||
try backgroundContext.save()
|
||||
|
||||
// Save data into database.
|
||||
CoreDataHandler.shared.save(viewContext: backgroundContext)
|
||||
}
|
||||
|
||||
public func update(status statusData: StatusData, basedOn status: Status, for account: AccountModel) async throws -> StatusData? {
|
||||
|
@ -68,7 +70,9 @@ public class HomeTimelineService {
|
|||
|
||||
// Update status data in database.
|
||||
self.copy(from: status, to: statusData, on: backgroundContext)
|
||||
try backgroundContext.save()
|
||||
|
||||
// Save data into database.
|
||||
CoreDataHandler.shared.save(viewContext: backgroundContext)
|
||||
|
||||
return statusData
|
||||
}
|
||||
|
@ -80,6 +84,7 @@ public class HomeTimelineService {
|
|||
attachment.metaImageHeight = Int32(imageHeight)
|
||||
self.setExifProperties(in: attachment, from: imageData)
|
||||
|
||||
// Save data into database.
|
||||
CoreDataHandler.shared.save()
|
||||
}
|
||||
|
||||
|
|
|
@ -54,29 +54,7 @@ struct VernissageApp: App {
|
|||
.tint(self.tintColor)
|
||||
.preferredColorScheme(self.theme)
|
||||
.task {
|
||||
UIPageControl.appearance().currentPageIndicatorTintColor = UIColor.white.withAlphaComponent(0.7)
|
||||
UIPageControl.appearance().pageIndicatorTintColor = UIColor.white.withAlphaComponent(0.4)
|
||||
|
||||
// Set custom configurations for Nuke image/data loaders.
|
||||
self.setImagePipelines()
|
||||
|
||||
// Load user preferences from database.
|
||||
self.loadUserPreferences()
|
||||
|
||||
// Refresh other access tokens.
|
||||
await self.refreshAccessTokens()
|
||||
|
||||
// Verify access token correctness.
|
||||
let authorizationSession = AuthorizationSession()
|
||||
let currentAccount = AccountDataHandler.shared.getCurrentAccountData()
|
||||
await AuthorizationService.shared.verifyAccount(session: authorizationSession, currentAccount: currentAccount) { accountData in
|
||||
guard let accountData = accountData else {
|
||||
self.applicationViewMode = .signIn
|
||||
return
|
||||
}
|
||||
|
||||
self.setApplicationState(accountData: accountData, checkNewPhotos: true)
|
||||
}
|
||||
await self.onApplicationStart()
|
||||
}
|
||||
.navigationViewStyle(.stack)
|
||||
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
|
||||
|
@ -112,6 +90,32 @@ struct VernissageApp: App {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func onApplicationStart() async {
|
||||
UIPageControl.appearance().currentPageIndicatorTintColor = UIColor.white.withAlphaComponent(0.7)
|
||||
UIPageControl.appearance().pageIndicatorTintColor = UIColor.white.withAlphaComponent(0.4)
|
||||
|
||||
// Set custom configurations for Nuke image/data loaders.
|
||||
self.setImagePipelines()
|
||||
|
||||
// Load user preferences from database.
|
||||
self.loadUserPreferences()
|
||||
|
||||
// Refresh other access tokens.
|
||||
await self.refreshAccessTokens()
|
||||
|
||||
// Verify access token correctness.
|
||||
let authorizationSession = AuthorizationSession()
|
||||
let currentAccount = AccountDataHandler.shared.getCurrentAccountData()
|
||||
await AuthorizationService.shared.verifyAccount(session: authorizationSession, currentAccount: currentAccount) { accountData in
|
||||
guard let accountData = accountData else {
|
||||
self.applicationViewMode = .signIn
|
||||
return
|
||||
}
|
||||
|
||||
self.setApplicationState(accountData: accountData, checkNewPhotos: true)
|
||||
}
|
||||
}
|
||||
|
||||
private func setApplicationState(accountData: AccountData, checkNewPhotos: Bool = false) {
|
||||
Task { @MainActor in
|
||||
|
@ -196,14 +200,3 @@ struct VernissageApp: App {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
func application(_ application: UIApplication,
|
||||
configurationForConnecting connectingSceneSession: UISceneSession,
|
||||
options: UIScene.ConnectionOptions
|
||||
) -> UISceneConfiguration {
|
||||
let sceneConfig: UISceneConfiguration = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
|
||||
sceneConfig.delegateClass = SceneDelegate.self
|
||||
return sceneConfig
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,32 +40,7 @@ struct AccountsPhotoView: View {
|
|||
if self.accounts.isEmpty {
|
||||
NoDataView(imageSystemName: "person.3.sequence", text: "trendingAccounts.title.noAccounts")
|
||||
} else {
|
||||
List {
|
||||
ForEach(self.accounts, id: \.id) { account in
|
||||
Section {
|
||||
ImagesGrid(gridType: .account(accountId: account.id,
|
||||
accountDisplayName: account.displayNameWithoutEmojis,
|
||||
accountUserName: account.acct))
|
||||
} header: {
|
||||
HStack {
|
||||
UsernameRow(
|
||||
accountId: account.id,
|
||||
accountAvatar: account.avatar,
|
||||
accountDisplayName: account.displayNameWithoutEmojis,
|
||||
accountUsername: account.acct)
|
||||
Spacer()
|
||||
}
|
||||
.textCase(.none)
|
||||
.listRowInsets(EdgeInsets())
|
||||
.padding(.vertical, 12)
|
||||
.onTapGesture {
|
||||
self.routerPath.navigate(to: .userProfile(accountId: account.id,
|
||||
accountDisplayName: account.displayNameWithoutEmojis,
|
||||
accountUserName: account.acct))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.list()
|
||||
}
|
||||
case .error(let error):
|
||||
ErrorView(error: error) {
|
||||
|
@ -78,6 +53,36 @@ struct AccountsPhotoView: View {
|
|||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func list() -> some View {
|
||||
List {
|
||||
ForEach(self.accounts, id: \.id) { account in
|
||||
Section {
|
||||
ImagesGrid(gridType: .account(accountId: account.id,
|
||||
accountDisplayName: account.displayNameWithoutEmojis,
|
||||
accountUserName: account.acct))
|
||||
} header: {
|
||||
HStack {
|
||||
UsernameRow(
|
||||
accountId: account.id,
|
||||
accountAvatar: account.avatar,
|
||||
accountDisplayName: account.displayNameWithoutEmojis,
|
||||
accountUsername: account.acct)
|
||||
Spacer()
|
||||
}
|
||||
.textCase(.none)
|
||||
.listRowInsets(EdgeInsets())
|
||||
.padding(.vertical, 12)
|
||||
.onTapGesture {
|
||||
self.routerPath.navigate(to: .userProfile(accountId: account.id,
|
||||
accountDisplayName: account.displayNameWithoutEmojis,
|
||||
accountUserName: account.acct))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func loadData() async {
|
||||
do {
|
||||
self.accounts = try await self.loadAccounts()
|
||||
|
|
|
@ -46,34 +46,7 @@ struct AccountsView: View {
|
|||
if self.accounts.isEmpty {
|
||||
NoDataView(imageSystemName: "person.3.sequence", text: "accounts.title.noAccounts")
|
||||
} else {
|
||||
List {
|
||||
ForEach(accounts, id: \.id) { account in
|
||||
NavigationLink(value: RouteurDestinations.userProfile(
|
||||
accountId: account.id,
|
||||
accountDisplayName: account.displayNameWithoutEmojis,
|
||||
accountUserName: account.acct)
|
||||
) {
|
||||
UsernameRow(accountId: account.id,
|
||||
accountAvatar: account.avatar,
|
||||
accountDisplayName: account.displayNameWithoutEmojis,
|
||||
accountUsername: account.acct)
|
||||
}
|
||||
}
|
||||
|
||||
if allItemsLoaded == false {
|
||||
HStack {
|
||||
Spacer()
|
||||
LoadingIndicator()
|
||||
.task {
|
||||
self.downloadedPage = self.downloadedPage + 1
|
||||
await self.loadData(page: self.downloadedPage)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.listRowSeparator(.hidden)
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
self.list()
|
||||
}
|
||||
case .error(let error):
|
||||
ErrorView(error: error) {
|
||||
|
@ -88,6 +61,38 @@ struct AccountsView: View {
|
|||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func list() -> some View {
|
||||
List {
|
||||
ForEach(accounts, id: \.id) { account in
|
||||
NavigationLink(value: RouteurDestinations.userProfile(
|
||||
accountId: account.id,
|
||||
accountDisplayName: account.displayNameWithoutEmojis,
|
||||
accountUserName: account.acct)
|
||||
) {
|
||||
UsernameRow(accountId: account.id,
|
||||
accountAvatar: account.avatar,
|
||||
accountDisplayName: account.displayNameWithoutEmojis,
|
||||
accountUsername: account.acct)
|
||||
}
|
||||
}
|
||||
|
||||
if allItemsLoaded == false {
|
||||
HStack {
|
||||
Spacer()
|
||||
LoadingIndicator()
|
||||
.task {
|
||||
self.downloadedPage = self.downloadedPage + 1
|
||||
await self.loadData(page: self.downloadedPage)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.listRowSeparator(.hidden)
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
}
|
||||
|
||||
private func loadData(page: Int) async {
|
||||
do {
|
||||
try await self.loadAccounts(page: page)
|
||||
|
|
|
@ -30,6 +30,12 @@ struct EditProfileView: View {
|
|||
private let websiteMaxLength = 120
|
||||
|
||||
var body: some View {
|
||||
self.mainBody()
|
||||
.navigationTitle("editProfile.navigationBar.title")
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func mainBody() -> some View {
|
||||
switch state {
|
||||
case .loading:
|
||||
LoadingIndicator()
|
||||
|
@ -175,7 +181,6 @@ struct EditProfileView: View {
|
|||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
}
|
||||
.navigationTitle("editProfile.navigationBar.title")
|
||||
.onAppear {
|
||||
self.displayName = account.displayName ?? String.empty()
|
||||
self.website = account.website ?? String.empty()
|
||||
|
|
|
@ -40,25 +40,7 @@ struct HashtagsView: View {
|
|||
if self.tags.isEmpty {
|
||||
NoDataView(imageSystemName: "person.3.sequence", text: "trendingTags.title.noTags")
|
||||
} else {
|
||||
List {
|
||||
ForEach(self.tags, id: \.id) { tag in
|
||||
Section {
|
||||
ImagesGrid(gridType: .hashtag(name: tag.hashtag))
|
||||
} header: {
|
||||
HStack {
|
||||
Text(tag.name).font(.headline)
|
||||
Spacer()
|
||||
if let total = tag.total {
|
||||
Text(String(format: NSLocalizedString("trendingTags.title.amountOfPosts", comment: "Amount of posts"), total))
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
self.routerPath.navigate(to: .tag(hashTag: tag.hashtag))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.list()
|
||||
}
|
||||
case .error(let error):
|
||||
ErrorView(error: error) {
|
||||
|
@ -71,6 +53,29 @@ struct HashtagsView: View {
|
|||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func list() -> some View {
|
||||
List {
|
||||
ForEach(self.tags, id: \.id) { tag in
|
||||
Section {
|
||||
ImagesGrid(gridType: .hashtag(name: tag.hashtag))
|
||||
} header: {
|
||||
HStack {
|
||||
Text(tag.name).font(.headline)
|
||||
Spacer()
|
||||
if let total = tag.total {
|
||||
Text(String(format: NSLocalizedString("trendingTags.title.amountOfPosts", comment: "Amount of posts"), total))
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
self.routerPath.navigate(to: .tag(hashTag: tag.hashtag))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func loadData() async {
|
||||
do {
|
||||
self.tags = try await self.loadTags()
|
||||
|
|
|
@ -17,6 +17,12 @@ struct InstanceView: View {
|
|||
@State private var instance: Instance?
|
||||
|
||||
var body: some View {
|
||||
self.mainBody()
|
||||
.navigationTitle("instance.navigationBar.title")
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func mainBody() -> some View {
|
||||
switch state {
|
||||
case .loading:
|
||||
LoadingIndicator()
|
||||
|
@ -101,7 +107,6 @@ struct InstanceView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("instance.navigationBar.title")
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
|
|
|
@ -38,29 +38,7 @@ struct NotificationsView: View {
|
|||
if self.notifications.isEmpty {
|
||||
NoDataView(imageSystemName: "bell", text: "notifications.title.noNotifications")
|
||||
} else {
|
||||
List {
|
||||
ForEach(notifications, id: \.id) { notification in
|
||||
NotificationRowView(notification: notification)
|
||||
}
|
||||
|
||||
if allItemsLoaded == false {
|
||||
HStack {
|
||||
Spacer()
|
||||
LoadingIndicator()
|
||||
.task {
|
||||
await self.loadMoreNotifications()
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.listRowSeparator(.hidden)
|
||||
}
|
||||
}
|
||||
.listStyle(PlainListStyle())
|
||||
.refreshable {
|
||||
HapticService.shared.fireHaptic(of: .dataRefresh(intensity: 0.3))
|
||||
await self.loadNewNotifications()
|
||||
HapticService.shared.fireHaptic(of: .dataRefresh(intensity: 0.7))
|
||||
}
|
||||
self.list()
|
||||
}
|
||||
case .error(let error):
|
||||
ErrorView(error: error) {
|
||||
|
@ -71,6 +49,33 @@ struct NotificationsView: View {
|
|||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func list() -> some View {
|
||||
List {
|
||||
ForEach(notifications, id: \.id) { notification in
|
||||
NotificationRowView(notification: notification)
|
||||
}
|
||||
|
||||
if allItemsLoaded == false {
|
||||
HStack {
|
||||
Spacer()
|
||||
LoadingIndicator()
|
||||
.task {
|
||||
await self.loadMoreNotifications()
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.listRowSeparator(.hidden)
|
||||
}
|
||||
}
|
||||
.listStyle(PlainListStyle())
|
||||
.refreshable {
|
||||
HapticService.shared.fireHaptic(of: .dataRefresh(intensity: 0.3))
|
||||
await self.loadNewNotifications()
|
||||
HapticService.shared.fireHaptic(of: .dataRefresh(intensity: 0.7))
|
||||
}
|
||||
}
|
||||
|
||||
func loadNotifications() async {
|
||||
do {
|
||||
if let linkable = try await self.client.notifications?.notifications(maxId: maxId, minId: minId, limit: 5) {
|
||||
|
|
|
@ -43,37 +43,7 @@ struct PaginableStatusesView: View {
|
|||
if self.statusViewModels.isEmpty {
|
||||
NoDataView(imageSystemName: "photo.on.rectangle.angled", text: "statuses.title.noPhotos")
|
||||
} else {
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .center) {
|
||||
ForEach(self.statusViewModels, id: \.id) { item in
|
||||
NavigationLink(value: RouteurDestinations.status(
|
||||
id: item.id,
|
||||
blurhash: item.mediaAttachments.first?.blurhash,
|
||||
highestImageUrl: item.mediaAttachments.getHighestImage()?.url,
|
||||
metaImageWidth: item.getImageWidth(),
|
||||
metaImageHeight: item.getImageHeight())
|
||||
) {
|
||||
ImageRowAsync(statusViewModel: item)
|
||||
}
|
||||
.buttonStyle(EmptyButtonStyle())
|
||||
}
|
||||
|
||||
if allItemsLoaded == false {
|
||||
HStack {
|
||||
Spacer()
|
||||
LoadingIndicator()
|
||||
.task {
|
||||
do {
|
||||
try await self.loadMoreStatuses()
|
||||
} catch {
|
||||
ErrorService.shared.handle(error, message: "statuses.error.loadingStatusesFailed", showToastr: !Task.isCancelled)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.list()
|
||||
}
|
||||
case .error(let error):
|
||||
ErrorView(error: error) {
|
||||
|
@ -87,6 +57,32 @@ struct PaginableStatusesView: View {
|
|||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func list() -> some View {
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .center) {
|
||||
ForEach(self.statusViewModels, id: \.id) { item in
|
||||
ImageRowAsync(statusViewModel: item)
|
||||
}
|
||||
|
||||
if allItemsLoaded == false {
|
||||
HStack {
|
||||
Spacer()
|
||||
LoadingIndicator()
|
||||
.task {
|
||||
do {
|
||||
try await self.loadMoreStatuses()
|
||||
} catch {
|
||||
ErrorService.shared.handle(error, message: "statuses.error.loadingStatusesFailed", showToastr: !Task.isCancelled)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func loadData() async {
|
||||
do {
|
||||
try await self.loadStatuses()
|
||||
|
|
|
@ -51,81 +51,8 @@ struct StatusView: View {
|
|||
}
|
||||
case .loaded:
|
||||
if let statusViewModel = self.statusViewModel {
|
||||
ScrollView {
|
||||
VStack (alignment: .leading) {
|
||||
ImagesCarousel(attachments: statusViewModel.mediaAttachments,
|
||||
selectedAttachment: $selectedAttachmentModel,
|
||||
exifCamera: $exifCamera,
|
||||
exifExposure: $exifExposure,
|
||||
exifCreatedDate: $exifCreatedDate,
|
||||
exifLens: $exifLens,
|
||||
description: $description)
|
||||
.onTapGesture {
|
||||
withoutAnimation {
|
||||
self.tappedAttachmentModel = self.selectedAttachmentModel
|
||||
}
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
self.reblogInformation()
|
||||
|
||||
UsernameRow(accountId: statusViewModel.account.id,
|
||||
accountAvatar: statusViewModel.account.avatar,
|
||||
accountDisplayName: statusViewModel.account.displayNameWithoutEmojis,
|
||||
accountUsername: statusViewModel.account.acct)
|
||||
.onTapGesture {
|
||||
self.routerPath.navigate(to: .userProfile(accountId: statusViewModel.account.id,
|
||||
accountDisplayName: statusViewModel.account.displayNameWithoutEmojis,
|
||||
accountUserName: statusViewModel.account.acct))
|
||||
}
|
||||
|
||||
MarkdownFormattedText(statusViewModel.content.asMarkdown)
|
||||
.font(.callout)
|
||||
.environment(\.openURL, OpenURLAction { url in
|
||||
routerPath.handle(url: url)
|
||||
})
|
||||
|
||||
VStack (alignment: .leading) {
|
||||
if let name = statusViewModel.place?.name, let country = statusViewModel.place?.country {
|
||||
LabelIcon(iconName: "mappin.and.ellipse", value: "\(name), \(country)")
|
||||
}
|
||||
|
||||
LabelIcon(iconName: "camera", value: self.exifCamera)
|
||||
LabelIcon(iconName: "camera.aperture", value: self.exifLens)
|
||||
LabelIcon(iconName: "timelapse", value: self.exifExposure)
|
||||
LabelIcon(iconName: "calendar", value: self.exifCreatedDate?.toDate(.isoDateTimeSec)?.formatted())
|
||||
|
||||
if self.applicationState.showPhotoDescription {
|
||||
LabelIcon(iconName: "eye.trianglebadge.exclamationmark", value: self.description)
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 2)
|
||||
.foregroundColor(.lightGrayColor)
|
||||
|
||||
HStack {
|
||||
Text("status.title.uploaded", comment: "Uploaded")
|
||||
Text(statusViewModel.createdAt.toRelative(.isoDateTimeMilliSec))
|
||||
.padding(.horizontal, -4)
|
||||
if let applicationName = statusViewModel.application?.name {
|
||||
Text(String(format: NSLocalizedString("status.title.via", comment: "via"), applicationName))
|
||||
}
|
||||
}
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.font(.footnote)
|
||||
|
||||
InteractionRow(statusModel: statusViewModel) {
|
||||
self.dismiss()
|
||||
}
|
||||
.foregroundColor(.accentColor)
|
||||
.padding(8)
|
||||
}
|
||||
.padding(8)
|
||||
|
||||
CommentsSectionView(statusId: statusViewModel.id)
|
||||
}
|
||||
}
|
||||
self.statusView(statusViewModel: statusViewModel)
|
||||
}
|
||||
|
||||
case .error(let error):
|
||||
ErrorView(error: error) {
|
||||
self.state = .loading
|
||||
|
@ -135,7 +62,85 @@ struct StatusView: View {
|
|||
}
|
||||
}
|
||||
|
||||
@ViewBuilder func reblogInformation() -> some View {
|
||||
@ViewBuilder
|
||||
private func statusView(statusViewModel: StatusModel) -> some View {
|
||||
ScrollView {
|
||||
VStack (alignment: .leading) {
|
||||
ImagesCarousel(attachments: statusViewModel.mediaAttachments,
|
||||
selectedAttachment: $selectedAttachmentModel,
|
||||
exifCamera: $exifCamera,
|
||||
exifExposure: $exifExposure,
|
||||
exifCreatedDate: $exifCreatedDate,
|
||||
exifLens: $exifLens,
|
||||
description: $description)
|
||||
.onTapGesture {
|
||||
withoutAnimation {
|
||||
self.tappedAttachmentModel = self.selectedAttachmentModel
|
||||
}
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
self.reblogInformation()
|
||||
|
||||
UsernameRow(accountId: statusViewModel.account.id,
|
||||
accountAvatar: statusViewModel.account.avatar,
|
||||
accountDisplayName: statusViewModel.account.displayNameWithoutEmojis,
|
||||
accountUsername: statusViewModel.account.acct)
|
||||
.onTapGesture {
|
||||
self.routerPath.navigate(to: .userProfile(accountId: statusViewModel.account.id,
|
||||
accountDisplayName: statusViewModel.account.displayNameWithoutEmojis,
|
||||
accountUserName: statusViewModel.account.acct))
|
||||
}
|
||||
|
||||
MarkdownFormattedText(statusViewModel.content.asMarkdown)
|
||||
.font(.callout)
|
||||
.environment(\.openURL, OpenURLAction { url in
|
||||
routerPath.handle(url: url)
|
||||
})
|
||||
|
||||
VStack (alignment: .leading) {
|
||||
if let name = statusViewModel.place?.name, let country = statusViewModel.place?.country {
|
||||
LabelIcon(iconName: "mappin.and.ellipse", value: "\(name), \(country)")
|
||||
}
|
||||
|
||||
LabelIcon(iconName: "camera", value: self.exifCamera)
|
||||
LabelIcon(iconName: "camera.aperture", value: self.exifLens)
|
||||
LabelIcon(iconName: "timelapse", value: self.exifExposure)
|
||||
LabelIcon(iconName: "calendar", value: self.exifCreatedDate?.toDate(.isoDateTimeSec)?.formatted())
|
||||
|
||||
if self.applicationState.showPhotoDescription {
|
||||
LabelIcon(iconName: "eye.trianglebadge.exclamationmark", value: self.description)
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 2)
|
||||
.foregroundColor(.lightGrayColor)
|
||||
|
||||
HStack {
|
||||
Text("status.title.uploaded", comment: "Uploaded")
|
||||
Text(statusViewModel.createdAt.toRelative(.isoDateTimeMilliSec))
|
||||
.padding(.horizontal, -4)
|
||||
if let applicationName = statusViewModel.application?.name {
|
||||
Text(String(format: NSLocalizedString("status.title.via", comment: "via"), applicationName))
|
||||
}
|
||||
}
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.font(.footnote)
|
||||
|
||||
InteractionRow(statusModel: statusViewModel) {
|
||||
self.dismiss()
|
||||
}
|
||||
.foregroundColor(.accentColor)
|
||||
.padding(8)
|
||||
}
|
||||
.padding(8)
|
||||
|
||||
CommentsSectionView(statusId: statusViewModel.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func reblogInformation() -> some View {
|
||||
if let reblogStatus = self.statusViewModel?.reblogStatus {
|
||||
HStack(alignment: .center, spacing: 4) {
|
||||
UserAvatar(accountAvatar: reblogStatus.account.avatar, size: .mini)
|
||||
|
|
|
@ -52,46 +52,7 @@ struct StatusesView: View {
|
|||
if self.statusViewModels.isEmpty {
|
||||
NoDataView(imageSystemName: "photo.on.rectangle.angled", text: "statuses.title.noPhotos")
|
||||
} else {
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .center) {
|
||||
ForEach(self.statusViewModels, id: \.id) { item in
|
||||
NavigationLink(value: RouteurDestinations.status(
|
||||
id: item.id,
|
||||
blurhash: item.mediaAttachments.first?.blurhash,
|
||||
highestImageUrl: item.mediaAttachments.getHighestImage()?.url,
|
||||
metaImageWidth: item.getImageWidth(),
|
||||
metaImageHeight: item.getImageHeight())
|
||||
) {
|
||||
ImageRowAsync(statusViewModel: item)
|
||||
}
|
||||
.buttonStyle(EmptyButtonStyle())
|
||||
}
|
||||
|
||||
if allItemsLoaded == false {
|
||||
HStack {
|
||||
Spacer()
|
||||
LoadingIndicator()
|
||||
.task {
|
||||
do {
|
||||
try await self.loadMoreStatuses()
|
||||
} catch {
|
||||
ErrorService.shared.handle(error, message: "statuses.error.loadingStatusesFailed", showToastr: !Task.isCancelled)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.refreshable {
|
||||
do {
|
||||
HapticService.shared.fireHaptic(of: .dataRefresh(intensity: 0.3))
|
||||
try await self.loadTopStatuses()
|
||||
HapticService.shared.fireHaptic(of: .dataRefresh(intensity: 0.7))
|
||||
} catch {
|
||||
ErrorService.shared.handle(error, message: "statuses.error.loadingStatusesFailed", showToastr: !Task.isCancelled)
|
||||
}
|
||||
}
|
||||
self.list()
|
||||
}
|
||||
case .error(let error):
|
||||
ErrorView(error: error) {
|
||||
|
@ -102,6 +63,41 @@ struct StatusesView: View {
|
|||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func list() -> some View {
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .center) {
|
||||
ForEach(self.statusViewModels, id: \.id) { item in
|
||||
ImageRowAsync(statusViewModel: item)
|
||||
}
|
||||
|
||||
if allItemsLoaded == false {
|
||||
HStack {
|
||||
Spacer()
|
||||
LoadingIndicator()
|
||||
.task {
|
||||
do {
|
||||
try await self.loadMoreStatuses()
|
||||
} catch {
|
||||
ErrorService.shared.handle(error, message: "statuses.error.loadingStatusesFailed", showToastr: !Task.isCancelled)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.refreshable {
|
||||
do {
|
||||
HapticService.shared.fireHaptic(of: .dataRefresh(intensity: 0.3))
|
||||
try await self.loadTopStatuses()
|
||||
HapticService.shared.fireHaptic(of: .dataRefresh(intensity: 0.7))
|
||||
} catch {
|
||||
ErrorService.shared.handle(error, message: "statuses.error.loadingStatusesFailed", showToastr: !Task.isCancelled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func loadData() async {
|
||||
do {
|
||||
try await self.loadStatuses()
|
||||
|
|
|
@ -68,16 +68,7 @@ struct TrendStatusesView: View {
|
|||
} else {
|
||||
LazyVStack(alignment: .center) {
|
||||
ForEach(self.statusViewModels, id: \.id) { item in
|
||||
NavigationLink(value: RouteurDestinations.status(
|
||||
id: item.id,
|
||||
blurhash: item.mediaAttachments.first?.blurhash,
|
||||
highestImageUrl: item.mediaAttachments.getHighestImage()?.url,
|
||||
metaImageWidth: item.getImageWidth(),
|
||||
metaImageHeight: item.getImageHeight())
|
||||
) {
|
||||
ImageRowAsync(statusViewModel: item)
|
||||
}
|
||||
.buttonStyle(EmptyButtonStyle())
|
||||
ImageRowAsync(statusViewModel: item)
|
||||
}
|
||||
}
|
||||
.refreshable {
|
||||
|
|
|
@ -22,16 +22,7 @@ struct UserProfileStatusesView: View {
|
|||
LazyVStack(alignment: .center) {
|
||||
if firstLoadFinished == true {
|
||||
ForEach(self.statusViewModels, id: \.id) { item in
|
||||
NavigationLink(value: RouteurDestinations.status(
|
||||
id: item.id,
|
||||
blurhash: item.mediaAttachments.first?.blurhash,
|
||||
highestImageUrl: item.mediaAttachments.getHighestImage()?.url,
|
||||
metaImageWidth: item.getImageWidth(),
|
||||
metaImageHeight: item.getImageHeight())
|
||||
) {
|
||||
ImageRowAsync(statusViewModel: item)
|
||||
}
|
||||
.buttonStyle(EmptyButtonStyle())
|
||||
ImageRowAsync(statusViewModel: item)
|
||||
}
|
||||
|
||||
if allItemsLoaded == false && firstLoadFinished == true {
|
||||
|
|
|
@ -46,19 +46,7 @@ struct UserProfileView: View {
|
|||
}
|
||||
case .loaded:
|
||||
if let account = self.account {
|
||||
ScrollView {
|
||||
UserProfileHeaderView(account: account, relationship: relationship)
|
||||
.id(self.viewId)
|
||||
UserProfileStatusesView(accountId: account.id)
|
||||
}
|
||||
.onAppear {
|
||||
if let updatedProfile = self.applicationState.updatedProfile {
|
||||
self.account = nil
|
||||
self.account = updatedProfile
|
||||
self.applicationState.updatedProfile = nil
|
||||
self.viewId = UUID().uuidString
|
||||
}
|
||||
}
|
||||
self.accountView(account: account)
|
||||
}
|
||||
case .error(let error):
|
||||
ErrorView(error: error) {
|
||||
|
@ -69,6 +57,22 @@ struct UserProfileView: View {
|
|||
}
|
||||
}
|
||||
|
||||
private func accountView(account: Account) -> some View {
|
||||
ScrollView {
|
||||
UserProfileHeaderView(account: account, relationship: relationship)
|
||||
.id(self.viewId)
|
||||
UserProfileStatusesView(accountId: account.id)
|
||||
}
|
||||
.onAppear {
|
||||
if let updatedProfile = self.applicationState.updatedProfile {
|
||||
self.account = nil
|
||||
self.account = updatedProfile
|
||||
self.applicationState.updatedProfile = nil
|
||||
self.viewId = UUID().uuidString
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func loadData() async {
|
||||
do {
|
||||
if self.accountId.isEmpty {
|
||||
|
|
|
@ -6,24 +6,28 @@
|
|||
|
||||
import SwiftUI
|
||||
|
||||
struct ContentWarning<Content: View>: View {
|
||||
private let blurhash: String?
|
||||
struct ContentWarning<Content: View, Blurred: View>: View {
|
||||
private let spoilerText: String?
|
||||
private let content: Content
|
||||
private let content: () -> Content
|
||||
private let blurred: () -> Blurred
|
||||
|
||||
@State private var showSensitive = false
|
||||
|
||||
init(blurhash: String?, spoilerText: String?, @ViewBuilder content: () -> Content) {
|
||||
self.blurhash = blurhash
|
||||
init(spoilerText: String?,
|
||||
@ViewBuilder content: @escaping () -> Content,
|
||||
@ViewBuilder blurred: @escaping () -> Blurred) {
|
||||
|
||||
self.spoilerText = spoilerText
|
||||
self.content = content()
|
||||
self.content = content
|
||||
self.blurred = blurred
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if self.showSensitive {
|
||||
ZStack {
|
||||
content
|
||||
content()
|
||||
.transition(.opacity)
|
||||
|
||||
VStack(alignment: .trailing) {
|
||||
HStack(alignment: .top) {
|
||||
Spacer()
|
||||
|
@ -35,7 +39,8 @@ struct ContentWarning<Content: View>: View {
|
|||
Image(systemName: "eye.slash")
|
||||
.font(.title2)
|
||||
.shadow(color: Color.systemBackground, radius: 0.3)
|
||||
}.padding()
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
|
@ -43,7 +48,8 @@ struct ContentWarning<Content: View>: View {
|
|||
}
|
||||
} else {
|
||||
ZStack {
|
||||
BlurredImage(blurhash: blurhash)
|
||||
self.blurred()
|
||||
|
||||
VStack(alignment: .center) {
|
||||
Spacer()
|
||||
Image(systemName: "eye.slash.fill")
|
||||
|
@ -75,11 +81,3 @@ struct ContentWarning<Content: View>: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentWarning_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContentWarning(blurhash: nil, spoilerText: "Spoiler") {
|
||||
Image(systemName: "people")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,6 +66,9 @@ struct ImageRowAsync: View {
|
|||
.tag(attachment.id)
|
||||
}
|
||||
}
|
||||
.onFirstAppear {
|
||||
self.selected = statusViewModel.mediaAttachments.first?.id ?? String.empty()
|
||||
}
|
||||
.onChange(of: selected, perform: { attachmentId in
|
||||
if let attachment = self.statusViewModel.mediaAttachments.first(where: { item in item.id == attachmentId }) {
|
||||
if let size = ImageSizeService.shared.get(for: attachment.url) {
|
||||
|
|
|
@ -37,14 +37,19 @@ struct ImageRowItem: View {
|
|||
ZStack {
|
||||
if self.status.sensitive && !self.applicationState.showSensitive {
|
||||
ZStack {
|
||||
ContentWarning(blurhash: attachmentData.blurhash, spoilerText: self.status.spoilerText) {
|
||||
ContentWarning(spoilerText: self.status.spoilerText) {
|
||||
self.imageView(uiImage: uiImage)
|
||||
|
||||
|
||||
if showThumbImage {
|
||||
FavouriteTouch {
|
||||
self.showThumbImage = false
|
||||
}
|
||||
}
|
||||
} blurred: {
|
||||
BlurredImage(blurhash: attachmentData.blurhash)
|
||||
.onTapGesture{
|
||||
self.navigateToStatus()
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
|
|
|
@ -32,8 +32,13 @@ struct ImageRowItemAsync: View {
|
|||
if let image = state.image {
|
||||
if self.statusViewModel.sensitive && !self.applicationState.showSensitive {
|
||||
ZStack {
|
||||
ContentWarning(blurhash: attachment.blurhash, spoilerText: self.statusViewModel.spoilerText) {
|
||||
ContentWarning(spoilerText: self.statusViewModel.spoilerText) {
|
||||
self.imageView(image: image)
|
||||
} blurred: {
|
||||
BlurredImage(blurhash: attachment.blurhash)
|
||||
.onTapGesture {
|
||||
self.navigateToStatus()
|
||||
}
|
||||
}
|
||||
|
||||
if showThumbImage {
|
||||
|
@ -42,14 +47,15 @@ struct ImageRowItemAsync: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.opacity(self.opacity)
|
||||
.onAppear {
|
||||
withAnimation {
|
||||
self.opacity = 1.0
|
||||
}
|
||||
|
||||
if let uiImage = state.imageResponse?.image {
|
||||
self.recalculateSizeOfDownloadedImage(uiImage: uiImage)
|
||||
}
|
||||
|
||||
withAnimation {
|
||||
self.opacity = 1.0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ZStack {
|
||||
|
@ -61,14 +67,15 @@ struct ImageRowItemAsync: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.opacity(self.opacity)
|
||||
.onAppear {
|
||||
withAnimation {
|
||||
self.opacity = 1.0
|
||||
}
|
||||
|
||||
if let uiImage = state.imageResponse?.image {
|
||||
self.recalculateSizeOfDownloadedImage(uiImage: uiImage)
|
||||
}
|
||||
|
||||
withAnimation {
|
||||
self.opacity = 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if state.error != nil {
|
||||
|
@ -87,6 +94,9 @@ struct ImageRowItemAsync: View {
|
|||
} else {
|
||||
VStack(alignment: .center) {
|
||||
BlurredImage(blurhash: attachment.blurhash)
|
||||
.onTapGesture {
|
||||
self.navigateToStatus()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +108,6 @@ struct ImageRowItemAsync: View {
|
|||
image
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.opacity(self.opacity)
|
||||
.onTapGesture(count: 2) {
|
||||
Task {
|
||||
try? await self.client.statuses?.favourite(statusId: self.statusViewModel.id)
|
||||
|
@ -108,17 +117,21 @@ struct ImageRowItemAsync: View {
|
|||
HapticService.shared.fireHaptic(of: .buttonPress)
|
||||
}
|
||||
.onTapGesture {
|
||||
self.routerPath.navigate(to: .status(
|
||||
id: statusViewModel.id,
|
||||
blurhash: statusViewModel.mediaAttachments.first?.blurhash,
|
||||
highestImageUrl: statusViewModel.mediaAttachments.getHighestImage()?.url,
|
||||
metaImageWidth: statusViewModel.getImageWidth(),
|
||||
metaImageHeight: statusViewModel.getImageHeight()
|
||||
))
|
||||
self.navigateToStatus()
|
||||
}
|
||||
.imageContextMenu(client: self.client, statusModel: self.statusViewModel)
|
||||
}
|
||||
|
||||
private func navigateToStatus() {
|
||||
self.routerPath.navigate(to: .status(
|
||||
id: statusViewModel.id,
|
||||
blurhash: statusViewModel.mediaAttachments.first?.blurhash,
|
||||
highestImageUrl: statusViewModel.mediaAttachments.getHighestImage()?.url,
|
||||
metaImageWidth: statusViewModel.getImageWidth(),
|
||||
metaImageHeight: statusViewModel.getImageHeight()
|
||||
))
|
||||
}
|
||||
|
||||
private func recalculateSizeOfDownloadedImage(uiImage: UIImage) {
|
||||
let size = ImageSizeService.shared.calculate(for: attachment.url,
|
||||
width: uiImage.size.width,
|
||||
|
|
Loading…
Reference in New Issue