Add indicator to notifications icon
This commit is contained in:
parent
06af34d4c8
commit
02c62b86d9
|
@ -27,7 +27,8 @@ import Foundation
|
|||
public let url: URL?
|
||||
public let username: String
|
||||
public let lastSeenStatusId: String?
|
||||
|
||||
public let lastSeenNotificationId: String?
|
||||
|
||||
public var avatarData: Data?
|
||||
|
||||
public init(id: String,
|
||||
|
@ -50,6 +51,7 @@ import Foundation
|
|||
url: URL?,
|
||||
username: String,
|
||||
lastSeenStatusId: String?,
|
||||
lastSeenNotificationId: String?,
|
||||
avatarData: Data? = nil) {
|
||||
self.id = id
|
||||
self.accessToken = accessToken
|
||||
|
@ -72,6 +74,7 @@ import Foundation
|
|||
self.username = username
|
||||
self.lastSeenStatusId = lastSeenStatusId
|
||||
self.avatarData = avatarData
|
||||
self.lastSeenNotificationId = lastSeenNotificationId
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,24 +10,62 @@ import ClientKit
|
|||
|
||||
@Model final public class AccountData {
|
||||
@Attribute(.unique) public var id: String
|
||||
|
||||
/// Access token to the server API.
|
||||
public var accessToken: String?
|
||||
|
||||
/// Refresh token which can be used to download new access token.
|
||||
public var refreshToken: String?
|
||||
|
||||
/// Full user name (user name with server address).
|
||||
public var acct: String
|
||||
|
||||
/// URL to user avatar.
|
||||
public var avatar: URL?
|
||||
public var avatarData: Data?
|
||||
|
||||
/// Avatar downloaded from server (visible mainly in top navigation bar).
|
||||
@Attribute(.externalStorage) public var avatarData: Data?
|
||||
|
||||
/// Id of OAuth client.
|
||||
public var clientId: String
|
||||
|
||||
/// Secret of OAutch client.
|
||||
public var clientSecret: String
|
||||
|
||||
/// Vapid key of OAuth client.
|
||||
public var clientVapidKey: String
|
||||
|
||||
/// Date of creating user.
|
||||
public var createdAt: String
|
||||
|
||||
/// Human readable user name.
|
||||
public var displayName: String?
|
||||
|
||||
/// Number of followers.
|
||||
public var followersCount: Int32
|
||||
|
||||
/// Number of following users.
|
||||
public var followingCount: Int32
|
||||
|
||||
/// URL to header image visible on user profile.
|
||||
public var header: URL?
|
||||
|
||||
/// User profile is locked.
|
||||
public var locked: Bool
|
||||
|
||||
/// Description on user profile.
|
||||
public var note: String?
|
||||
|
||||
/// Address to server.
|
||||
public var serverUrl: URL
|
||||
|
||||
/// NUmber of statuses added by the user.
|
||||
public var statusesCount: Int32
|
||||
|
||||
/// Url to user profile.
|
||||
public var url: URL?
|
||||
|
||||
/// User name (without server address).
|
||||
public var username: String
|
||||
|
||||
/// Last status seen on home timeline by the user.
|
||||
|
@ -39,10 +77,12 @@ import ClientKit
|
|||
/// JSON string with last objects loaded into home timeline.
|
||||
public var timelineCache: String?
|
||||
|
||||
/// Last notification seen by the user.
|
||||
public var lastSeenNotificationId: String?
|
||||
|
||||
@Relationship(deleteRule: .cascade, inverse: \ViewedStatus.pixelfedAccount) public var viewedStatuses: [ViewedStatus]
|
||||
@Relationship(deleteRule: .cascade, inverse: \AccountRelationship.pixelfedAccount) public var accountRelationships: [AccountRelationship]
|
||||
|
||||
|
||||
|
||||
init(
|
||||
accessToken: String? = nil,
|
||||
refreshToken: String? = nil,
|
||||
|
@ -119,6 +159,7 @@ extension AccountData {
|
|||
url: self.url,
|
||||
username: self.username,
|
||||
lastSeenStatusId: self.lastSeenStatusId,
|
||||
lastSeenNotificationId: self.lastSeenNotificationId,
|
||||
avatarData: self.avatarData)
|
||||
return accountModel
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import Foundation
|
||||
import SwiftData
|
||||
import PixelfedKit
|
||||
import EnvironmentKit
|
||||
|
||||
class AccountDataHandler {
|
||||
public static let shared = AccountDataHandler()
|
||||
|
@ -63,13 +64,18 @@ class AccountDataHandler {
|
|||
}
|
||||
}
|
||||
|
||||
func update(lastSeenStatusId: String?, lastLoadedStatusId: String?, statuses: [Status]? = nil, accountId: String, modelContext: ModelContext) throws {
|
||||
func update(lastSeenStatusId: String?, lastLoadedStatusId: String?, statuses: [Status]? = nil, applicationState: ApplicationState, modelContext: ModelContext) throws {
|
||||
guard let accountId = applicationState.account?.id else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let accountDataFromDb = self.getAccountData(accountId: accountId, modelContext: modelContext) else {
|
||||
return
|
||||
}
|
||||
|
||||
if (accountDataFromDb.lastSeenStatusId ?? "0") < (lastSeenStatusId ?? "0") {
|
||||
accountDataFromDb.lastSeenStatusId = lastSeenStatusId
|
||||
applicationState.lastSeenStatusId = lastSeenStatusId
|
||||
}
|
||||
|
||||
if (accountDataFromDb.lastLoadedStatusId ?? "0") < (lastLoadedStatusId ?? "0") {
|
||||
|
@ -82,4 +88,21 @@ class AccountDataHandler {
|
|||
|
||||
try modelContext.save()
|
||||
}
|
||||
|
||||
func update(lastSeenNotificationId: String?, applicationState: ApplicationState, modelContext: ModelContext) throws {
|
||||
guard let accountId = applicationState.account?.id else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let accountDataFromDb = self.getAccountData(accountId: accountId, modelContext: modelContext) else {
|
||||
return
|
||||
}
|
||||
|
||||
if (accountDataFromDb.lastSeenNotificationId ?? "0") < (lastSeenNotificationId ?? "0") {
|
||||
accountDataFromDb.lastSeenNotificationId = lastSeenNotificationId
|
||||
applicationState.lastSeenNotificationId = lastSeenNotificationId
|
||||
}
|
||||
|
||||
try modelContext.save()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,9 +35,15 @@ import ClientKit
|
|||
/// Each URL in a status will be assumed to be exactly this many characters.
|
||||
public private(set) var statusCharactersReservedPerUrl = defaults.statusCharactersReservedPerUrl
|
||||
|
||||
/// Last notification seen by the user.
|
||||
public var lastSeenNotificationId: String?
|
||||
|
||||
/// Information about new notifications.
|
||||
public var newNotificationsHasBeenAdded = false
|
||||
|
||||
/// Last status seen by the user.
|
||||
public var lastSeenStatusId: String?
|
||||
|
||||
|
||||
/// Amount of new statuses which are not displayed yet to the user.
|
||||
public var amountOfNewStatuses = 0
|
||||
|
||||
|
@ -113,10 +119,12 @@ import ClientKit
|
|||
/// Hide statuses without ALT text.
|
||||
public var hideStatusesWithoutAlt = false
|
||||
|
||||
public func changeApplicationState(accountModel: AccountModel, instance: Instance?, lastSeenStatusId: String?) {
|
||||
public func changeApplicationState(accountModel: AccountModel, instance: Instance?, lastSeenStatusId: String?, lastSeenNotificationId: String?) {
|
||||
self.account = accountModel
|
||||
self.lastSeenNotificationId = lastSeenNotificationId
|
||||
self.lastSeenStatusId = lastSeenStatusId
|
||||
self.amountOfNewStatuses = 0
|
||||
self.newNotificationsHasBeenAdded = false
|
||||
|
||||
if let statusesConfiguration = instance?.configuration?.statuses {
|
||||
self.statusMaxCharacters = statusesConfiguration.maxCharacters
|
||||
|
@ -132,7 +140,9 @@ import ClientKit
|
|||
public func clearApplicationState() {
|
||||
self.account = nil
|
||||
self.lastSeenStatusId = nil
|
||||
self.lastSeenNotificationId = nil
|
||||
self.amountOfNewStatuses = 0
|
||||
self.newNotificationsHasBeenAdded = false
|
||||
|
||||
self.statusMaxCharacters = ApplicationState.defaults.statusMaxCharacters
|
||||
self.statusMaxMediaAttachments = ApplicationState.defaults.statusMaxMediaAttachments
|
||||
|
|
|
@ -77,7 +77,6 @@
|
|||
F8705A7B29FF872F00DA818A /* QRCodeGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8705A7A29FF872F00DA818A /* QRCodeGenerator.swift */; };
|
||||
F8705A7E29FF880600DA818A /* FileFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8705A7D29FF880600DA818A /* FileFetcher.swift */; };
|
||||
F870EE5229F1645C00A2D43B /* MainNavigationOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F870EE5129F1645C00A2D43B /* MainNavigationOptions.swift */; };
|
||||
F871F21D29EF0D7000A351EF /* NavigationMenuItemDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = F871F21C29EF0D7000A351EF /* NavigationMenuItemDetails.swift */; };
|
||||
F8742FC429990AFB00E9642B /* ClientError.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8742FC329990AFB00E9642B /* ClientError.swift */; };
|
||||
F8764187298ABB520057D362 /* ViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8764186298ABB520057D362 /* ViewState.swift */; };
|
||||
F876418D298AE5020057D362 /* PaginableStatusesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F876418C298AE5020057D362 /* PaginableStatusesView.swift */; };
|
||||
|
@ -159,6 +158,7 @@
|
|||
F8D8E0CF2ACC23B300AA1374 /* ViewedStatusHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8D8E0CE2ACC23B300AA1374 /* ViewedStatusHandler.swift */; };
|
||||
F8D8E0D02ACC23B300AA1374 /* ViewedStatusHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8D8E0CE2ACC23B300AA1374 /* ViewedStatusHandler.swift */; };
|
||||
F8D8E0D12ACC23B300AA1374 /* ViewedStatusHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8D8E0CE2ACC23B300AA1374 /* ViewedStatusHandler.swift */; };
|
||||
F8DE749F2AE4F7B500ACD188 /* NotificationsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8DE749E2AE4F7B500ACD188 /* NotificationsService.swift */; };
|
||||
F8DF38E429DD68820047F1AA /* ViewOffsetKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8DF38E329DD68820047F1AA /* ViewOffsetKey.swift */; };
|
||||
F8DF38E629DDB98A0047F1AA /* SocialsSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8DF38E529DDB98A0047F1AA /* SocialsSectionView.swift */; };
|
||||
F8E36E462AB8745300769C55 /* Sizable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E36E452AB8745300769C55 /* Sizable.swift */; };
|
||||
|
@ -274,7 +274,6 @@
|
|||
F8705A7A29FF872F00DA818A /* QRCodeGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeGenerator.swift; sourceTree = "<group>"; };
|
||||
F8705A7D29FF880600DA818A /* FileFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileFetcher.swift; sourceTree = "<group>"; };
|
||||
F870EE5129F1645C00A2D43B /* MainNavigationOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainNavigationOptions.swift; sourceTree = "<group>"; };
|
||||
F871F21C29EF0D7000A351EF /* NavigationMenuItemDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationMenuItemDetails.swift; sourceTree = "<group>"; };
|
||||
F8742FC329990AFB00E9642B /* ClientError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientError.swift; sourceTree = "<group>"; };
|
||||
F8764186298ABB520057D362 /* ViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewState.swift; sourceTree = "<group>"; };
|
||||
F876418C298AE5020057D362 /* PaginableStatusesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginableStatusesView.swift; sourceTree = "<group>"; };
|
||||
|
@ -341,6 +340,7 @@
|
|||
F8D5444229D4066C002225D6 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
F8D8E0CA2ACC237000AA1374 /* ViewedStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewedStatus.swift; sourceTree = "<group>"; };
|
||||
F8D8E0CE2ACC23B300AA1374 /* ViewedStatusHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewedStatusHandler.swift; sourceTree = "<group>"; };
|
||||
F8DE749E2AE4F7B500ACD188 /* NotificationsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsService.swift; sourceTree = "<group>"; };
|
||||
F8DF38E329DD68820047F1AA /* ViewOffsetKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewOffsetKey.swift; sourceTree = "<group>"; };
|
||||
F8DF38E529DDB98A0047F1AA /* SocialsSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialsSectionView.swift; sourceTree = "<group>"; };
|
||||
F8E36E452AB8745300769C55 /* Sizable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sizable.swift; sourceTree = "<group>"; };
|
||||
|
@ -479,7 +479,6 @@
|
|||
F85D4DFD29B78C8400345267 /* HashtagModel.swift */,
|
||||
F89F57AF29D1C11200001EE3 /* RelationshipModel.swift */,
|
||||
F8DF38E329DD68820047F1AA /* ViewOffsetKey.swift */,
|
||||
F871F21C29EF0D7000A351EF /* NavigationMenuItemDetails.swift */,
|
||||
F8624D3C29F2D3AC00204986 /* SelectedMenuItemDetails.swift */,
|
||||
F8E36E452AB8745300769C55 /* Sizable.swift */,
|
||||
F8B758DD2AB9DD85000C8068 /* ColumnData.swift */,
|
||||
|
@ -773,6 +772,7 @@
|
|||
F878842129A4A4E3003CFAD2 /* AppMetadataService.swift */,
|
||||
F86BC9E829EBBB66009415EC /* ImageSaver.swift */,
|
||||
F8D0E5232AE2A88A0061C561 /* HomeTimelineService.swift */,
|
||||
F8DE749E2AE4F7B500ACD188 /* NotificationsService.swift */,
|
||||
);
|
||||
path = Services;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1084,7 +1084,6 @@
|
|||
F87AEB972986D16D00434FB6 /* AuthorisationError.swift in Sources */,
|
||||
F8742FC429990AFB00E9642B /* ClientError.swift in Sources */,
|
||||
F883402029B62AE900C3E096 /* SearchView.swift in Sources */,
|
||||
F871F21D29EF0D7000A351EF /* NavigationMenuItemDetails.swift in Sources */,
|
||||
F8E36E462AB8745300769C55 /* Sizable.swift in Sources */,
|
||||
F85DBF8F296732E20069BF89 /* AccountsView.swift in Sources */,
|
||||
F805DCF129DBEF83006A1FD9 /* ReportView.swift in Sources */,
|
||||
|
@ -1109,6 +1108,7 @@
|
|||
F8A93D7E2965FD89001D8331 /* UserProfileView.swift in Sources */,
|
||||
F88C246E295C37B80006098B /* MainView.swift in Sources */,
|
||||
F8AFF7C429B25EF40087D083 /* ImagesGrid.swift in Sources */,
|
||||
F8DE749F2AE4F7B500ACD188 /* NotificationsService.swift in Sources */,
|
||||
F8AFF7C129B259150087D083 /* HashtagsView.swift in Sources */,
|
||||
F8DF38E429DD68820047F1AA /* ViewOffsetKey.swift in Sources */,
|
||||
F89A46DE296EABA20062125F /* StatusPlaceholderView.swift in Sources */,
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the Apache License 2.0.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
@Observable class NavigationMenuItemDetails: Identifiable {
|
||||
var title: LocalizedStringKey
|
||||
var image: String
|
||||
|
||||
var viewMode: MainView.ViewMode {
|
||||
didSet {
|
||||
self.title = viewMode.title
|
||||
self.image = viewMode.image
|
||||
}
|
||||
}
|
||||
|
||||
init(viewMode: MainView.ViewMode) {
|
||||
self.viewMode = viewMode
|
||||
self.title = viewMode.title
|
||||
self.image = viewMode.image
|
||||
}
|
||||
}
|
|
@ -6,11 +6,12 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
class SelectedMenuItemDetails: NavigationMenuItemDetails {
|
||||
class SelectedMenuItemDetails: Identifiable {
|
||||
public let position: Int
|
||||
|
||||
public var viewMode: MainView.ViewMode
|
||||
|
||||
init(position: Int, viewMode: MainView.ViewMode) {
|
||||
self.position = position
|
||||
super.init(viewMode: viewMode)
|
||||
self.viewMode = viewMode
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,23 +105,7 @@ public class HomeTimelineService {
|
|||
|
||||
return visibleStatuses
|
||||
}
|
||||
|
||||
public func update(lastSeenStatusId: String?, lastLoadedStatusId: String?, statuses: [Status]? = nil, applicationState: ApplicationState, modelContext: ModelContext) throws {
|
||||
guard let accountId = applicationState.account?.id else {
|
||||
return
|
||||
}
|
||||
|
||||
try AccountDataHandler.shared.update(lastSeenStatusId: lastSeenStatusId,
|
||||
lastLoadedStatusId: lastLoadedStatusId,
|
||||
statuses: statuses,
|
||||
accountId: accountId,
|
||||
modelContext: modelContext)
|
||||
|
||||
if (applicationState.lastSeenStatusId ?? "0") < (lastSeenStatusId ?? "0") {
|
||||
applicationState.lastSeenStatusId = lastSeenStatusId
|
||||
}
|
||||
}
|
||||
|
||||
private func hasBeenAlreadyOnTimeline(accountId: String, status: Status, modelContext: ModelContext) -> Bool {
|
||||
return ViewedStatusHandler.shared.hasBeenAlreadyOnTimeline(accountId: accountId, status: status, modelContext: modelContext)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the Apache License 2.0.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftData
|
||||
import PixelfedKit
|
||||
import ClientKit
|
||||
import ServicesKit
|
||||
import Nuke
|
||||
import OSLog
|
||||
import EnvironmentKit
|
||||
import Semaphore
|
||||
|
||||
/// Service responsible for managing notifications.
|
||||
@MainActor
|
||||
public class NotificationsService {
|
||||
public static let shared = NotificationsService()
|
||||
private init() { }
|
||||
|
||||
private let semaphore = AsyncSemaphore(value: 1)
|
||||
|
||||
public func newNotificationsHasBeenAdded(for account: AccountModel, modelContext: ModelContext) async -> Bool {
|
||||
await semaphore.wait()
|
||||
defer { semaphore.signal() }
|
||||
|
||||
guard let accessToken = account.accessToken else {
|
||||
return false
|
||||
}
|
||||
|
||||
// Get maximimum downloaded stauts id.
|
||||
guard let lastSeenNotificationId = self.getLastSeenNotificationId(accountId: account.id, modelContext: modelContext) else {
|
||||
return false
|
||||
}
|
||||
|
||||
let client = PixelfedClient(baseURL: account.serverUrl).getAuthenticated(token: accessToken)
|
||||
|
||||
do {
|
||||
let linkableNotifications = try await client.notifications(minId: lastSeenNotificationId, limit: 5)
|
||||
return linkableNotifications.data.first(where: { $0.id != lastSeenNotificationId }) != nil
|
||||
} catch {
|
||||
ErrorService.shared.handle(error, message: "notifications.error.loadingNotificationsFailed")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private func getLastSeenNotificationId(accountId: String, modelContext: ModelContext) -> String? {
|
||||
let accountData = AccountDataHandler.shared.getAccountData(accountId: accountId, modelContext: modelContext)
|
||||
return accountData?.lastSeenNotificationId
|
||||
}
|
||||
}
|
|
@ -64,8 +64,8 @@ struct VernissageApp: App {
|
|||
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
|
||||
Task {
|
||||
// Refresh indicator of new photos when application is become active.
|
||||
await self.calculateNewPhotosInBackground()
|
||||
// Refresh indicator of new photos and new statuses when application is become active.
|
||||
_ = await (self.calculateNewPhotosInBackground(), self.calculateNewNotificationsInBackground())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,8 +74,8 @@ struct VernissageApp: App {
|
|||
}
|
||||
.onReceive(timer) { _ in
|
||||
Task {
|
||||
// Refresh indicator of new photos each two minutes (when application is in the foreground)..
|
||||
await self.calculateNewPhotosInBackground()
|
||||
// Refresh indicator of new photos and new notifications each two minutes (when application is in the foreground)..
|
||||
_ = await (self.calculateNewPhotosInBackground(), self.calculateNewNotificationsInBackground())
|
||||
}
|
||||
}
|
||||
.onChange(of: applicationState.theme) { oldValue, newValue in
|
||||
|
@ -155,14 +155,15 @@ struct VernissageApp: App {
|
|||
// Refresh application state.
|
||||
self.applicationState.changeApplicationState(accountModel: accountModel,
|
||||
instance: instance,
|
||||
lastSeenStatusId: accountModel.lastSeenStatusId)
|
||||
lastSeenStatusId: accountModel.lastSeenStatusId,
|
||||
lastSeenNotificationId: accountModel.lastSeenNotificationId)
|
||||
|
||||
// Change view displayed by application.
|
||||
self.applicationViewMode = .mainView
|
||||
|
||||
// Check amount of newly added photos.
|
||||
if checkNewPhotos {
|
||||
await self.calculateNewPhotosInBackground()
|
||||
_ = await (self.calculateNewPhotosInBackground(), self.calculateNewNotificationsInBackground())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -211,7 +212,7 @@ struct VernissageApp: App {
|
|||
}
|
||||
|
||||
private func calculateNewPhotosInBackground() async {
|
||||
let modelContext = self.modelContainer.mainContext
|
||||
let modelContext = self.modelContainer.mainContext
|
||||
|
||||
if let account = self.applicationState.account {
|
||||
self.applicationState.amountOfNewStatuses = await HomeTimelineService.shared.amountOfNewStatuses(
|
||||
|
@ -222,4 +223,12 @@ struct VernissageApp: App {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func calculateNewNotificationsInBackground() async {
|
||||
let modelContext = self.modelContainer.mainContext
|
||||
|
||||
if let account = self.applicationState.account {
|
||||
self.applicationState.newNotificationsHasBeenAdded = await NotificationsService.shared.newNotificationsHasBeenAdded(for: account, modelContext: modelContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ extension View {
|
|||
|
||||
@MainActor
|
||||
private struct NavigationMenuButtons: ViewModifier {
|
||||
@Environment(ApplicationState.self) var applicationState
|
||||
@Environment(RouterPath.self) var routerPath
|
||||
@Environment(\.modelContext) private var modelContext
|
||||
|
||||
|
@ -28,13 +29,13 @@ private struct NavigationMenuButtons: ViewModifier {
|
|||
private let onViewModeIconTap: (MainView.ViewMode) -> Void
|
||||
private let imageFontSize = 20.0
|
||||
|
||||
private let customMenuItems = [
|
||||
NavigationMenuItemDetails(viewMode: .home),
|
||||
NavigationMenuItemDetails(viewMode: .local),
|
||||
NavigationMenuItemDetails(viewMode: .federated),
|
||||
NavigationMenuItemDetails(viewMode: .search),
|
||||
NavigationMenuItemDetails(viewMode: .profile),
|
||||
NavigationMenuItemDetails(viewMode: .notifications)
|
||||
private let customMenuItems: [MainView.ViewMode] = [
|
||||
.home,
|
||||
.local,
|
||||
.federated,
|
||||
.search,
|
||||
.profile,
|
||||
.notifications
|
||||
]
|
||||
|
||||
@State private var displayedCustomMenuItems = [
|
||||
|
@ -168,7 +169,7 @@ private struct NavigationMenuButtons: ViewModifier {
|
|||
Button {
|
||||
self.onViewModeIconTap(displayedCustomMenuItem.viewMode)
|
||||
} label: {
|
||||
Image(systemName: displayedCustomMenuItem.image)
|
||||
displayedCustomMenuItem.viewMode.getImage(applicationState: applicationState)
|
||||
.font(.system(size: self.imageFontSize))
|
||||
.foregroundColor(.mainTextColor.opacity(0.75))
|
||||
.padding(.vertical, 10)
|
||||
|
@ -183,24 +184,28 @@ private struct NavigationMenuButtons: ViewModifier {
|
|||
ForEach(self.customMenuItems) { item in
|
||||
Button {
|
||||
withAnimation {
|
||||
displayedCustomMenuItem.viewMode = item.viewMode
|
||||
displayedCustomMenuItem.viewMode = item
|
||||
}
|
||||
|
||||
// Saving in core data.
|
||||
// Saving in database.
|
||||
switch displayedCustomMenuItem.position {
|
||||
case 1:
|
||||
ApplicationSettingsHandler.shared.set(customNavigationMenuItem1: item.viewMode.rawValue, modelContext: modelContext)
|
||||
ApplicationSettingsHandler.shared.set(customNavigationMenuItem1: item.rawValue, modelContext: modelContext)
|
||||
case 2:
|
||||
ApplicationSettingsHandler.shared.set(customNavigationMenuItem2: item.viewMode.rawValue, modelContext: modelContext)
|
||||
ApplicationSettingsHandler.shared.set(customNavigationMenuItem2: item.rawValue, modelContext: modelContext)
|
||||
case 3:
|
||||
ApplicationSettingsHandler.shared.set(customNavigationMenuItem3: item.viewMode.rawValue, modelContext: modelContext)
|
||||
ApplicationSettingsHandler.shared.set(customNavigationMenuItem3: item.rawValue, modelContext: modelContext)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
self.hiddenMenuItems = self.displayedCustomMenuItems.map({ $0.viewMode })
|
||||
} label: {
|
||||
Label(item.title, systemImage: item.image)
|
||||
Label {
|
||||
Text(item.title, comment: "Menu item")
|
||||
} icon: {
|
||||
item.getImage(applicationState: applicationState)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -217,10 +222,8 @@ private struct NavigationMenuButtons: ViewModifier {
|
|||
|
||||
private func setCustomMenuItem(position: Int, viewMode: MainView.ViewMode) {
|
||||
if let displayedCustomMenuItem = self.displayedCustomMenuItems.first(where: { $0.position == position }),
|
||||
let customMenuItem = self.customMenuItems.first(where: { $0.viewMode == viewMode }) {
|
||||
displayedCustomMenuItem.title = customMenuItem.title
|
||||
displayedCustomMenuItem.viewMode = customMenuItem.viewMode
|
||||
displayedCustomMenuItem.image = customMenuItem.image
|
||||
let customMenuItem = self.customMenuItems.first(where: { $0 == viewMode }) {
|
||||
displayedCustomMenuItem.viewMode = customMenuItem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -197,10 +197,10 @@ struct HomeTimelineView: View {
|
|||
modelContext: modelContext)
|
||||
|
||||
// Remeber first status returned by API in user context (when it's newer then remembered).
|
||||
try HomeTimelineService.shared.update(lastSeenStatusId: nil,
|
||||
lastLoadedStatusId: statuses.first?.id,
|
||||
applicationState: self.applicationState,
|
||||
modelContext: modelContext)
|
||||
try AccountDataHandler.shared.update(lastSeenStatusId: nil,
|
||||
lastLoadedStatusId: statuses.first?.id,
|
||||
applicationState: self.applicationState,
|
||||
modelContext: modelContext)
|
||||
|
||||
// Append statuses to viewed.
|
||||
try ViewedStatusHandler.shared.append(contentsOf: statuses, accountId: accountId, modelContext: modelContext)
|
||||
|
@ -274,11 +274,11 @@ struct HomeTimelineView: View {
|
|||
modelContext: modelContext)
|
||||
|
||||
// Remeber first status returned by API in user context (when it's newer then remembered).
|
||||
try HomeTimelineService.shared.update(lastSeenStatusId: self.statusViewModels.first?.id,
|
||||
lastLoadedStatusId: statuses.first?.id,
|
||||
statuses: statuses,
|
||||
applicationState: self.applicationState,
|
||||
modelContext: modelContext)
|
||||
try AccountDataHandler.shared.update(lastSeenStatusId: self.statusViewModels.first?.id,
|
||||
lastLoadedStatusId: statuses.first?.id,
|
||||
statuses: statuses,
|
||||
applicationState: self.applicationState,
|
||||
modelContext: modelContext)
|
||||
|
||||
// Append statuses to viewed.
|
||||
try ViewedStatusHandler.shared.append(contentsOf: statuses, accountId: accountId, modelContext: modelContext)
|
||||
|
|
|
@ -33,7 +33,7 @@ struct MainView: View {
|
|||
|
||||
@Query(sort: \AccountData.acct, order: .forward) var dbAccounts: [AccountData]
|
||||
|
||||
public enum ViewMode: Int {
|
||||
public enum ViewMode: Int, Identifiable {
|
||||
case home = 1
|
||||
case local = 2
|
||||
case federated = 3
|
||||
|
@ -44,6 +44,10 @@ struct MainView: View {
|
|||
case trendingTags = 8
|
||||
case trendingAccounts = 9
|
||||
|
||||
var id: Self {
|
||||
return self
|
||||
}
|
||||
|
||||
public var title: LocalizedStringKey {
|
||||
switch self {
|
||||
case .home:
|
||||
|
@ -67,26 +71,36 @@ struct MainView: View {
|
|||
}
|
||||
}
|
||||
|
||||
public var image: String {
|
||||
@ViewBuilder
|
||||
public func getImage(applicationState: ApplicationState) -> some View {
|
||||
switch self {
|
||||
case .home:
|
||||
return "house"
|
||||
Image(systemName: "house")
|
||||
case .trendingPhotos:
|
||||
return "photo.stack"
|
||||
Image(systemName: "photo.stack")
|
||||
case .trendingTags:
|
||||
return "tag"
|
||||
Image(systemName: "tag")
|
||||
case .trendingAccounts:
|
||||
return "person.3"
|
||||
Image(systemName: "person.3")
|
||||
case .local:
|
||||
return "building"
|
||||
Image(systemName: "building")
|
||||
case .federated:
|
||||
return "globe.europe.africa"
|
||||
Image(systemName: "globe.europe.africa")
|
||||
case .profile:
|
||||
return "person.crop.circle"
|
||||
Image(systemName: "person.crop.circle")
|
||||
case .notifications:
|
||||
return "bell.badge"
|
||||
if applicationState.menuPosition == .top {
|
||||
applicationState.newNotificationsHasBeenAdded ? Image(systemName: "bell.badge") : Image(systemName: "bell")
|
||||
} else {
|
||||
applicationState.newNotificationsHasBeenAdded
|
||||
? AnyView(
|
||||
Image(systemName: "bell.badge")
|
||||
.symbolRenderingMode(.palette)
|
||||
.foregroundStyle(applicationState.tintColor.color().opacity(0.75), Color.mainTextColor.opacity(0.75)))
|
||||
: AnyView(Image(systemName: "bell"))
|
||||
}
|
||||
case .search:
|
||||
return "magnifyingglass"
|
||||
Image(systemName: "magnifyingglass")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -309,7 +323,8 @@ struct MainView: View {
|
|||
// Refresh application state.
|
||||
self.applicationState.changeApplicationState(accountModel: signedInAccountModel,
|
||||
instance: instance,
|
||||
lastSeenStatusId: signedInAccountModel.lastSeenStatusId)
|
||||
lastSeenStatusId: signedInAccountModel.lastSeenStatusId,
|
||||
lastSeenNotificationId: signedInAccountModel.lastSeenNotificationId)
|
||||
|
||||
// Set account as default (application will open this account after restart).
|
||||
ApplicationSettingsHandler.shared.set(accountId: signedInAccountModel.id, modelContext: modelContext)
|
||||
|
|
|
@ -15,6 +15,7 @@ import WidgetsKit
|
|||
struct NotificationsView: View {
|
||||
@Environment(ApplicationState.self) var applicationState
|
||||
@Environment(Client.self) var client
|
||||
@Environment(\.modelContext) private var modelContext
|
||||
|
||||
@State var accountId: String
|
||||
@State private var notifications: [PixelfedKit.Notification] = []
|
||||
|
@ -76,7 +77,7 @@ struct NotificationsView: View {
|
|||
.listStyle(PlainListStyle())
|
||||
.refreshable {
|
||||
HapticService.shared.fireHaptic(of: .dataRefresh(intensity: 0.3))
|
||||
await self.loadNewNotifications()
|
||||
await self.refreshNotifications()
|
||||
HapticService.shared.fireHaptic(of: .dataRefresh(intensity: 0.7))
|
||||
}
|
||||
}
|
||||
|
@ -95,6 +96,9 @@ struct NotificationsView: View {
|
|||
withAnimation {
|
||||
self.state = .loaded
|
||||
}
|
||||
|
||||
try AccountDataHandler.shared.update(lastSeenNotificationId: linkable.data.first?.id, applicationState: self.applicationState, modelContext: modelContext)
|
||||
self.applicationState.newNotificationsHasBeenAdded = false
|
||||
}
|
||||
} catch {
|
||||
if !Task.isCancelled {
|
||||
|
@ -122,7 +126,7 @@ struct NotificationsView: View {
|
|||
}
|
||||
}
|
||||
|
||||
private func loadNewNotifications() async {
|
||||
private func refreshNotifications() async {
|
||||
do {
|
||||
if let linkable = try await self.client.notifications?.notifications(minId: self.minId, limit: self.defaultPageSize) {
|
||||
if let first = linkable.data.first, self.notifications.contains(where: { notification in notification.id == first.id }) {
|
||||
|
@ -130,6 +134,9 @@ struct NotificationsView: View {
|
|||
return
|
||||
}
|
||||
|
||||
try AccountDataHandler.shared.update(lastSeenNotificationId: linkable.data.first?.id, applicationState: self.applicationState, modelContext: modelContext)
|
||||
self.applicationState.newNotificationsHasBeenAdded = false
|
||||
|
||||
self.minId = linkable.link?.minId
|
||||
self.notifications.insert(contentsOf: linkable.data, at: 0)
|
||||
}
|
||||
|
|
|
@ -198,10 +198,10 @@ struct StatusesView: View {
|
|||
|
||||
if self.listType == .home {
|
||||
// Remeber first status returned by API in user context (when it's newer then remembered).
|
||||
try HomeTimelineService.shared.update(lastSeenStatusId: nil,
|
||||
lastLoadedStatusId: statuses.first?.id,
|
||||
applicationState: self.applicationState,
|
||||
modelContext: modelContext)
|
||||
try AccountDataHandler.shared.update(lastSeenStatusId: nil,
|
||||
lastLoadedStatusId: statuses.first?.id,
|
||||
applicationState: self.applicationState,
|
||||
modelContext: modelContext)
|
||||
|
||||
// Append statuses to viewed.
|
||||
try ViewedStatusHandler.shared.append(contentsOf: statuses, accountId: accountId, modelContext: modelContext)
|
||||
|
@ -276,10 +276,10 @@ struct StatusesView: View {
|
|||
|
||||
if self.listType == .home {
|
||||
// Remeber first status returned by API in user context (when it's newer then remembered).
|
||||
try HomeTimelineService.shared.update(lastSeenStatusId: self.statusViewModels.first?.id,
|
||||
lastLoadedStatusId: statuses.first?.id,
|
||||
applicationState: self.applicationState,
|
||||
modelContext: modelContext)
|
||||
try AccountDataHandler.shared.update(lastSeenStatusId: self.statusViewModels.first?.id,
|
||||
lastLoadedStatusId: statuses.first?.id,
|
||||
applicationState: self.applicationState,
|
||||
modelContext: modelContext)
|
||||
|
||||
// Append statuses to viewed.
|
||||
try ViewedStatusHandler.shared.append(contentsOf: statuses, accountId: accountId, modelContext: modelContext)
|
||||
|
|
|
@ -6,8 +6,11 @@
|
|||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import EnvironmentKit
|
||||
|
||||
struct MainNavigationOptions: View {
|
||||
@Environment(ApplicationState.self) var applicationState
|
||||
|
||||
let onViewModeIconTap: (MainView.ViewMode) -> Void
|
||||
|
||||
@Binding var hiddenMenuItems: [MainView.ViewMode]
|
||||
|
@ -24,7 +27,7 @@ struct MainNavigationOptions: View {
|
|||
} label: {
|
||||
HStack {
|
||||
Text(MainView.ViewMode.home.title)
|
||||
Image(systemName: MainView.ViewMode.home.image)
|
||||
MainView.ViewMode.home.getImage(applicationState: applicationState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +38,7 @@ struct MainNavigationOptions: View {
|
|||
} label: {
|
||||
HStack {
|
||||
Text(MainView.ViewMode.local.title)
|
||||
Image(systemName: MainView.ViewMode.local.image)
|
||||
MainView.ViewMode.local.getImage(applicationState: applicationState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +49,7 @@ struct MainNavigationOptions: View {
|
|||
} label: {
|
||||
HStack {
|
||||
Text(MainView.ViewMode.federated.title)
|
||||
Image(systemName: MainView.ViewMode.federated.image)
|
||||
MainView.ViewMode.federated.getImage(applicationState: applicationState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +60,7 @@ struct MainNavigationOptions: View {
|
|||
} label: {
|
||||
HStack {
|
||||
Text(MainView.ViewMode.search.title)
|
||||
Image(systemName: MainView.ViewMode.search.image)
|
||||
MainView.ViewMode.search.getImage(applicationState: applicationState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -70,7 +73,7 @@ struct MainNavigationOptions: View {
|
|||
} label: {
|
||||
HStack {
|
||||
Text(MainView.ViewMode.trendingPhotos.title)
|
||||
Image(systemName: MainView.ViewMode.trendingPhotos.image)
|
||||
MainView.ViewMode.trendingPhotos.getImage(applicationState: applicationState)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,7 +82,7 @@ struct MainNavigationOptions: View {
|
|||
} label: {
|
||||
HStack {
|
||||
Text(MainView.ViewMode.trendingTags.title)
|
||||
Image(systemName: MainView.ViewMode.trendingTags.image)
|
||||
MainView.ViewMode.trendingTags.getImage(applicationState: applicationState)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,7 +91,7 @@ struct MainNavigationOptions: View {
|
|||
} label: {
|
||||
HStack {
|
||||
Text(MainView.ViewMode.trendingAccounts.title)
|
||||
Image(systemName: MainView.ViewMode.trendingAccounts.image)
|
||||
MainView.ViewMode.trendingAccounts.getImage(applicationState: applicationState)
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
|
@ -106,7 +109,7 @@ struct MainNavigationOptions: View {
|
|||
} label: {
|
||||
HStack {
|
||||
Text(MainView.ViewMode.profile.title)
|
||||
Image(systemName: MainView.ViewMode.profile.image)
|
||||
MainView.ViewMode.profile.getImage(applicationState: applicationState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -117,7 +120,7 @@ struct MainNavigationOptions: View {
|
|||
} label: {
|
||||
HStack {
|
||||
Text(MainView.ViewMode.notifications.title)
|
||||
Image(systemName: MainView.ViewMode.notifications.image)
|
||||
MainView.ViewMode.notifications.getImage(applicationState: applicationState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,8 @@ class ShareViewController: UIViewController {
|
|||
// Set application state (with default instance settings).
|
||||
applicationState.changeApplicationState(accountModel: accountModel,
|
||||
instance: nil,
|
||||
lastSeenStatusId: accountModel.lastSeenStatusId)
|
||||
lastSeenStatusId: accountModel.lastSeenStatusId,
|
||||
lastSeenNotificationId: accountModel.lastSeenNotificationId)
|
||||
|
||||
// Update application settings from database.
|
||||
ApplicationSettingsHandler.shared.update(applicationState: applicationState, modelContext: modelContext)
|
||||
|
|
Loading…
Reference in New Issue