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