WIP activity view, french, optimizations

This commit is contained in:
Lumaa 2024-01-31 16:47:29 +01:00
parent c58c35fa37
commit 0073cfa4d1
10 changed files with 823 additions and 4 deletions

View File

@ -41,6 +41,10 @@
B9BED51A2B5D662D00C9B715 /* ShareSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9BED5192B5D662D00C9B715 /* ShareSheetController.swift */; };
B9CC45B82B40A2D6001E4FA5 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9CC45B72B40A2D6001E4FA5 /* AboutView.swift */; };
B9CFC43B2B4F08C9004CFCB7 /* LaunchStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B9CFC43A2B4F08C9004CFCB7 /* LaunchStoryboard.storyboard */; };
B9D9C6C12B6A56E000C26A41 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9D9C6C02B6A56E000C26A41 /* Notification.swift */; };
B9D9C6C32B6A576C00C26A41 /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9D9C6C22B6A576C00C26A41 /* NotificationsView.swift */; };
B9D9C6C52B6A587700C26A41 /* NotificationRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9D9C6C42B6A587700C26A41 /* NotificationRow.swift */; };
B9D9C6C72B6A590F00C26A41 /* ProfilePicture.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9D9C6C62B6A590F00C26A41 /* ProfilePicture.swift */; };
B9EBE8562B47256900FB594D /* PostAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9EBE8552B47256900FB594D /* PostAttachment.swift */; };
B9EBE8582B474FD600FB594D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9EBE8572B474FD600FB594D /* AppDelegate.swift */; };
B9F8FA162B5D3AC30044DAB4 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9F8FA152B5D3AC30044DAB4 /* SafariView.swift */; };
@ -130,6 +134,10 @@
B9CC45B72B40A2D6001E4FA5 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
B9CC45B92B40AA1E001E4FA5 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
B9CFC43A2B4F08C9004CFCB7 /* LaunchStoryboard.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchStoryboard.storyboard; sourceTree = "<group>"; };
B9D9C6C02B6A56E000C26A41 /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = "<group>"; };
B9D9C6C22B6A576C00C26A41 /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = "<group>"; };
B9D9C6C42B6A587700C26A41 /* NotificationRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRow.swift; sourceTree = "<group>"; };
B9D9C6C62B6A590F00C26A41 /* ProfilePicture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePicture.swift; sourceTree = "<group>"; };
B9EBE8552B47256900FB594D /* PostAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostAttachment.swift; sourceTree = "<group>"; };
B9EBE8572B474FD600FB594D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
B9F8FA152B5D3AC30044DAB4 /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = "<group>"; };
@ -220,6 +228,14 @@
path = Post;
sourceTree = "<group>";
};
B9D9C6BF2B6A56D500C26A41 /* Notifications */ = {
isa = PBXGroup;
children = (
B9D9C6C02B6A56E000C26A41 /* Notification.swift */,
);
path = Notifications;
sourceTree = "<group>";
};
B9FB944E2B2DEECE00D81C07 = {
isa = PBXGroup;
children = (
@ -270,6 +286,7 @@
B9FB946C2B2DF3A600D81C07 /* Data */ = {
isa = PBXGroup;
children = (
B9D9C6BF2B6A56D500C26A41 /* Notifications */,
B93BCC3F2B5E38E5008EEA19 /* Content */,
B9FB94BD2B2F038D00D81C07 /* Accounts */,
B9FB946F2B2DF3CD00D81C07 /* Navigator.swift */,
@ -296,6 +313,7 @@
B98BC7462B46CE6300595441 /* PostDetailsView.swift */,
B93B677B2B433A6E000892E9 /* PostingView.swift */,
B9F8FA152B5D3AC30044DAB4 /* SafariView.swift */,
B9D9C6C22B6A576C00C26A41 /* NotificationsView.swift */,
);
path = Views;
sourceTree = "<group>";
@ -312,6 +330,8 @@
B9B63B202B442D1500BBC82D /* DynamicTextEditor.swift */,
B98BC74A2B46CF0400595441 /* ListStyle.swift */,
B9BED5192B5D662D00C9B715 /* ShareSheetController.swift */,
B9D9C6C42B6A587700C26A41 /* NotificationRow.swift */,
B9D9C6C62B6A590F00C26A41 /* ProfilePicture.swift */,
);
path = Components;
sourceTree = "<group>";
@ -429,6 +449,7 @@
knownRegions = (
en,
Base,
fr,
);
mainGroup = B9FB944E2B2DEECE00D81C07;
packageReferences = (
@ -488,6 +509,7 @@
B98F47982B64670F0092000F /* ShopView.swift in Sources */,
B9842C0E2B2F21B700D9F3C1 /* CompactPostView.swift in Sources */,
B98BC7492B46CEDA00595441 /* AppearenceView.swift in Sources */,
B9D9C6C52B6A587700C26A41 /* NotificationRow.swift in Sources */,
B9FB94992B2EEB9400D81C07 /* AddInstanceView.swift in Sources */,
B9FB94972B2EDABF00D81C07 /* PrivacyView.swift in Sources */,
B9F8FA162B5D3AC30044DAB4 /* SafariView.swift in Sources */,
@ -496,8 +518,10 @@
B9B63B232B447B8000BBC82D /* PostCardView.swift in Sources */,
B9FB949B2B2EF09A00D81C07 /* Client.swift in Sources */,
B9FB949D2B2EF0D600D81C07 /* Instance.swift in Sources */,
B9D9C6C72B6A590F00C26A41 /* ProfilePicture.swift in Sources */,
B9842C102B2F228C00D9F3C1 /* Status.swift in Sources */,
B93B677A2B42EC51000892E9 /* MetaPicker.swift in Sources */,
B9D9C6C32B6A576C00C26A41 /* NotificationsView.swift in Sources */,
B9FB94722B2DF49700D81C07 /* ConnectView.swift in Sources */,
B9FB945B2B2DEECE00D81C07 /* ThreadedApp.swift in Sources */,
B9FB94862B2E211200D81C07 /* Account+Elms.swift in Sources */,
@ -525,6 +549,7 @@
B9FB948E2B2E28E800D81C07 /* MediaTransferables.swift in Sources */,
B98BC74D2B46CFCE00595441 /* UserPreferences.swift in Sources */,
B9FB94A22B2EF24A00D81C07 /* AppInfo.swift in Sources */,
B9D9C6C12B6A56E000C26A41 /* Notification.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -0,0 +1,95 @@
//Made by Lumaa
import SwiftUI
struct NotificationRow: View {
var notif: Notification = .placeholder()
var body: some View {
VStack {
HStack(spacing: 5) {
ProfilePicture(url: notif.account.avatar)
.padding(.trailing)
.overlay(alignment: .bottomTrailing) {
notifIcon()
.offset(x: -5, y: 5)
}
.padding()
Text(localizedString())
}
.padding(.horizontal)
}
}
private func localizedString() -> String {
switch (notif.supportedType) {
case .favourite:
return String(localized: "activity.favorite.%@").replacingOccurrences(of: "%@", with: "@\(notif.account.username)")
case .follow:
return String(localized: "activity.followed.%@").replacingOccurrences(of: "%@", with: "@\(notif.account.username)")
case .mention:
return String(localized: "activity.mentionned.%@").replacingOccurrences(of: "%@", with: "@\(notif.account.username)")
case .reblog:
return String(localized: "activity.reblogged.%@").replacingOccurrences(of: "%@", with: "@\(notif.account.username)")
case .status:
return String(localized: "activity.status.%@").replacingOccurrences(of: "%@", with: "@\(notif.account.username)")
default:
return String(localized: "activity.unknown")
}
}
private func notifColor() -> Color {
switch (notif.supportedType) {
case .favourite:
return Color.red
case .follow:
return Color.purple
case .mention:
return Color.blue
case .reblog:
return Color.pink
case .status:
return Color.yellow
default:
return Color.gray
}
}
@ViewBuilder
private func notifIcon() -> some View {
ZStack {
switch (notif.supportedType) {
case .favourite:
Image(systemName: "heart.fill")
.font(.caption)
case .follow:
Image(systemName: "person.fill.badge.plus")
.font(.caption)
case .mention:
Image(systemName: "tag.fill")
.font(.caption)
case .reblog:
Image(systemName: "bolt.horizontal.fill")
.font(.caption)
case .status:
Image(systemName: "text.badge.plus")
.font(.caption)
default:
Image(systemName: "questionmark")
.font(.caption)
}
}
.padding(5)
.background(notifColor())
.clipShape(.circle)
.overlay {
Circle()
.stroke(Color.appBackground, lineWidth: 3)
}
.fixedSize()
}
}
#Preview {
NotificationRow()
}

View File

@ -0,0 +1,25 @@
//Made by Lumaa
import SwiftUI
struct ProfilePicture: View {
@Environment(UserPreferences.self) private var pref
var url: URL
var cornerRadius: CGFloat {
return pref.profilePictureShape == .circle ? (50 / 2) : 15.0
}
init(url: URL) {
self.url = url
}
init(url: String) {
self.url = .init(string: url)!
}
var body: some View {
OnlineImage(url: url, size: 50, useNuke: true)
.frame(width: 40, height: 40)
.clipShape(RoundedRectangle(cornerRadius: cornerRadius))
}
}

View File

@ -2,6 +2,8 @@
import Foundation
//TODO: Change this to SwiftData
@Observable
public class AccountManager {
private var client: Client?
@ -46,7 +48,6 @@ public class AccountManager {
}
}
//TODO: Change this to SwiftData
public struct AppAccount: Codable, Identifiable, Hashable {
public let server: String
public var accountName: String?

View File

@ -26,7 +26,7 @@ struct FetchTimeline {
}
public mutating func addStatuses(lastStatusIndex: Int) async -> [Status] {
print("i: \(lastStatusIndex)\ndatasource-6: \(self.datasource.count - 6)")
// print("i: \(lastStatusIndex)\ndatasource-6: \(self.datasource.count - 6)")
guard client != nil && lastStatusIndex >= self.datasource.count - 6 else { return self.datasource }
self.statusesState = .loading

View File

@ -572,3 +572,39 @@ public struct MediaDescriptionData: Encodable, Sendable {
self.description = description
}
}
public enum Notifications: Endpoint {
case notifications(minId: String?,
maxId: String?,
types: [String]?,
limit: Int)
case notification(id: String)
case clear
public func path() -> String {
switch self {
case .notifications:
"notifications"
case let .notification(id):
"notifications/\(id)"
case .clear:
"notifications/clear"
}
}
public func queryItems() -> [URLQueryItem]? {
switch self {
case let .notifications(mindId, maxId, types, limit):
var params = makePaginationParam(sinceId: nil, maxId: maxId, mindId: mindId) ?? []
params.append(.init(name: "limit", value: String(limit)))
if let types {
for type in types {
params.append(.init(name: "exclude_types[]", value: type))
}
}
return params
default:
return nil
}
}
}

View File

@ -0,0 +1,30 @@
//Made by Lumaa
import Foundation
public struct Notification: Decodable, Identifiable, Equatable {
public enum NotificationType: String, CaseIterable {
case follow, follow_request, mention, reblog, status, favourite, poll, update
}
public let id: String
public let type: String
public let createdAt: ServerDate
public let account: Account
public let status: Status?
public var supportedType: NotificationType? {
.init(rawValue: type)
}
public static func placeholder() -> Notification {
.init(id: UUID().uuidString,
type: NotificationType.favourite.rawValue,
createdAt: ServerDate(),
account: .placeholder(),
status: .placeholder())
}
}
extension Notification: Sendable {}
extension Notification.NotificationType: Sendable {}

View File

@ -20,6 +20,12 @@
"state" : "translated",
"value" : "About"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "À propos"
}
}
}
},
@ -30,6 +36,12 @@
"state" : "translated",
"value" : "About Threaded"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "À propos de Threaded"
}
}
}
},
@ -71,6 +83,12 @@
"state" : "translated",
"value" : "An audio file"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Un fichier audio"
}
}
}
},
@ -82,6 +100,12 @@
"state" : "translated",
"value" : "An animated GIF"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Un GIF animé"
}
}
}
},
@ -93,6 +117,12 @@
"state" : "translated",
"value" : "An image"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Une image"
}
}
}
},
@ -104,6 +134,12 @@
"state" : "translated",
"value" : "A video"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Une vidéo"
}
}
}
},
@ -114,6 +150,12 @@
"state" : "translated",
"value" : "Follow"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Suivre"
}
}
}
},
@ -124,6 +166,12 @@
"state" : "translated",
"value" : "Follow back"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Suivre en retour"
}
}
}
},
@ -156,6 +204,12 @@
"state" : "translated",
"value" : "Mention"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Mentionner"
}
}
}
},
@ -166,6 +220,118 @@
"state" : "translated",
"value" : "Unfollow"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ne plus suivre"
}
}
}
},
"activity.favorite.%@" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ liked your post"
}
}
}
},
"activity.followed.%@" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ followed you"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ vous a suivi(e)"
}
}
}
},
"activity.mentionned.%@" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ mentionned you "
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ vous a mentionné(e)"
}
}
}
},
"activity.no-notifications" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "No notifications"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Pas de notifications"
}
}
}
},
"activity.reblogged.%@" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ reposted your post"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ a republié votre publication"
}
}
}
},
"activity.status.%@" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ posted"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ a publié une publication"
}
}
}
},
"activity.unknown" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Unknown activity"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Activité inconnue"
}
}
}
},
@ -176,6 +342,12 @@
"state" : "translated",
"value" : "Image Error"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Erreur Image"
}
}
}
},
@ -186,6 +358,12 @@
"state" : "translated",
"value" : "Experimental"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Expérimental"
}
}
}
},
@ -196,6 +374,12 @@
"state" : "translated",
"value" : "Rules"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Règles"
}
}
}
},
@ -206,6 +390,12 @@
"state" : "translated",
"value" : "Log in using Mastodon"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Connexion avec Mastodon"
}
}
}
},
@ -216,6 +406,12 @@
"state" : "translated",
"value" : "Log in your Mastodon account using its instance URL. You cannot create an account using Threaded."
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Connectez-vous à l'aide de votre compte Mastodon en utilisant l'URL de votre instance. Vous ne pouvez pas créer de compte via Threaded"
}
}
}
},
@ -226,6 +422,12 @@
"state" : "translated",
"value" : "Enter the instance's URL"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Entrez l'URL de l'instance"
}
}
}
},
@ -236,6 +438,12 @@
"state" : "translated",
"value" : "Log in"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Connexion"
}
}
}
},
@ -246,6 +454,12 @@
"state" : "translated",
"value" : "Verify"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Vérifier"
}
}
}
},
@ -256,6 +470,12 @@
"state" : "translated",
"value" : "This might not be a Mastodon instance."
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ce n'est sûrement pas une instance Mastodon"
}
}
}
},
@ -266,6 +486,12 @@
"state" : "translated",
"value" : "Stay anonymous"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Rester anonyme"
}
}
}
},
@ -276,6 +502,12 @@
"state" : "translated",
"value" : "Without an account, you cannot interact with posts, users and instances. You can only read posts and users' public data."
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Sans compte, vous ne pouvez pas interagir avec les publications, les utilisateurs et instances. Vous pouvez seulement lire données publiques des publications et des utilisateurs."
}
}
}
},
@ -286,6 +518,12 @@
"state" : "translated",
"value" : "Welcome to Threaded!"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Bienvenue sur Threaded !"
}
}
}
},
@ -296,6 +534,12 @@
"state" : "translated",
"value" : "Log out"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Déconnexion"
}
}
}
},
@ -306,6 +550,12 @@
"state" : "translated",
"value" : "Appearence"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Apparence"
}
}
}
},
@ -316,6 +566,12 @@
"state" : "translated",
"value" : "Open links"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ouvrir les liens"
}
}
}
},
@ -326,6 +582,12 @@
"state" : "translated",
"value" : "In-app"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Dans l'application"
}
}
}
},
@ -336,6 +598,12 @@
"state" : "translated",
"value" : "In a browser"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "En dehors de l'application"
}
}
}
},
@ -346,6 +614,12 @@
"state" : "translated",
"value" : "Displayed Name"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Nom affiché"
}
}
}
},
@ -356,6 +630,12 @@
"state" : "translated",
"value" : "Both"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Les deux"
}
}
}
},
@ -366,6 +646,12 @@
"state" : "translated",
"value" : "Display Name"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Le nom d'affichage"
}
}
}
},
@ -376,6 +662,12 @@
"state" : "translated",
"value" : "Username"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Le nom d'utilisateur"
}
}
}
},
@ -386,6 +678,12 @@
"state" : "translated",
"value" : "Shape of Profile Pictures"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Forme de la photo de profil"
}
}
}
},
@ -396,6 +694,12 @@
"state" : "translated",
"value" : "Circle"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Circulaire"
}
}
}
},
@ -406,6 +710,12 @@
"state" : "translated",
"value" : "Rounded Rectangle"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Rectangulaire"
}
}
}
},
@ -416,6 +726,12 @@
"state" : "translated",
"value" : "Show Reply Symbols"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Afficher les symboles de réponses"
}
}
}
},
@ -426,6 +742,12 @@
"state" : "translated",
"value" : "Show experimental features"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Afficher les fonctionnalités expérimentales"
}
}
}
},
@ -436,6 +758,12 @@
"state" : "translated",
"value" : "Privacy"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Confidentialité"
}
}
}
},
@ -446,6 +774,12 @@
"state" : "translated",
"value" : "Settings"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Paramètres"
}
}
}
},
@ -456,6 +790,12 @@
"state" : "translated",
"value" : "Cancel"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Annuler"
}
}
}
},
@ -466,6 +806,12 @@
"state" : "translated",
"value" : "Done"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Terminé"
}
}
}
},
@ -476,6 +822,12 @@
"state" : "translated",
"value" : "Cancel"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Annuler"
}
}
}
},
@ -496,6 +848,12 @@
"state" : "translated",
"value" : "Editing post"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Modification d'une publication"
}
}
}
},
@ -518,6 +876,24 @@
}
}
}
},
"fr" : {
"variations" : {
"plural" : {
"one" : {
"stringUnit" : {
"state" : "translated",
"value" : "%lld likes"
}
},
"other" : {
"stringUnit" : {
"state" : "new",
"value" : "%lld likes"
}
}
}
}
}
}
},
@ -528,6 +904,12 @@
"state" : "translated",
"value" : "Copy text"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Copier le texte"
}
}
}
},
@ -538,6 +920,12 @@
"state" : "translated",
"value" : "Delete"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Supprimer"
}
}
}
},
@ -548,6 +936,12 @@
"state" : "translated",
"value" : "Edit"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Modifier"
}
}
}
},
@ -558,6 +952,12 @@
"state" : "translated",
"value" : "Share"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Partager"
}
}
}
},
@ -568,6 +968,12 @@
"state" : "translated",
"value" : "Share as image"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Partager en tant qu'image"
}
}
}
},
@ -578,6 +984,12 @@
"state" : "translated",
"value" : "Share as link"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Partager le lien"
}
}
}
},
@ -588,6 +1000,12 @@
"state" : "translated",
"value" : "Pinned"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Épinglé"
}
}
}
},
@ -598,6 +1016,12 @@
"state" : "translated",
"value" : "New post"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Nouvelle publication "
}
}
}
},
@ -608,6 +1032,12 @@
"state" : "translated",
"value" : "Cancel"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Annuler"
}
}
}
},
@ -618,6 +1048,12 @@
"state" : "translated",
"value" : "No custom emojis"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Pas d'emojis personnalisés"
}
}
}
},
@ -628,6 +1064,12 @@
"state" : "translated",
"value" : "What's new?"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Quoi de neuf ?"
}
}
}
},
@ -638,6 +1080,12 @@
"state" : "translated",
"value" : "Post"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Publier"
}
}
}
},
@ -648,6 +1096,12 @@
"state" : "translated",
"value" : "Visibility"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Visibilité"
}
}
}
},
@ -658,6 +1112,12 @@
"state" : "translated",
"value" : "Direct Message"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Message privé"
}
}
}
},
@ -668,6 +1128,12 @@
"state" : "translated",
"value" : "Private"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Privé"
}
}
}
},
@ -678,6 +1144,12 @@
"state" : "translated",
"value" : "Public"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Public"
}
}
}
},
@ -688,6 +1160,12 @@
"state" : "translated",
"value" : "Unlisted"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Non répertorié"
}
}
}
},
@ -698,6 +1176,12 @@
"state" : "translated",
"value" : "Replied to @%@"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "A répondu à %@"
}
}
}
},
@ -720,6 +1204,24 @@
}
}
}
},
"fr" : {
"variations" : {
"plural" : {
"one" : {
"stringUnit" : {
"state" : "translated",
"value" : "%lld réponse"
}
},
"other" : {
"stringUnit" : {
"state" : "translated",
"value" : "%lld réponses"
}
}
}
}
}
}
},
@ -730,6 +1232,12 @@
"state" : "translated",
"value" : "%@ reposted"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ a republié"
}
}
}
},
@ -740,6 +1248,12 @@
"state" : "translated",
"value" : "No posts"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Pas de publications"
}
}
}
},
@ -750,6 +1264,12 @@
"state" : "translated",
"value" : "You don't have posts in your home timeline. Follow users or change timelines to see posts!"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Vous n'avez pas de publications dans votre chronologie. Suivez des utilisateurs ou changez de chronologie pour voir des publications !"
}
}
}
},
@ -770,4 +1290,4 @@
}
},
"version" : "1.0"
}
}

View File

@ -32,7 +32,7 @@ struct ContentView: View {
.tag(TabDestination.search)
//TODO: Messaging UI in Activity tab
Text(String("Activity"))
NotificationsView()
.background(Color.appBackground)
.tag(TabDestination.activity)
@ -62,6 +62,7 @@ struct ContentView: View {
.environment(accountManager)
.environment(navigator)
.environment(appDelegate)
.environment(preferences)
.onAppear {
do {
preferences = try UserPreferences.loadAsCurrent() ?? .defaultPreferences

View File

@ -0,0 +1,86 @@
//Made by Lumaa
import SwiftUI
struct NotificationsView: View {
@Environment(AccountManager.self) private var accountManager
@State private var navigator: Navigator = Navigator()
@State private var notifications: [Notification] = []
@State private var loadingNotifs: Bool = false
@State private var lastId: Int? = nil
private let notifLimit = 50
var body: some View {
NavigationStack(path: $navigator.path) {
if !notifications.isEmpty {
ScrollView(.vertical, showsIndicators: false) {
LazyVStack(alignment: .leading) {
ForEach(notifications) { notif in
NotificationRow(notif: notif)
.onDisappear() {
guard !notifications.isEmpty else { return }
lastId = notifications.firstIndex(where: { $0.id == notif.id })
}
}
}
.refreshable {
notifications = []
await fetchNotifications(lastId: nil)
}
.onChange(of: lastId ?? 0) { _, new in
guard !loadingNotifs else { return }
Task {
loadingNotifs = true
await fetchNotifications(lastId: new)
loadingNotifs = false
}
}
}
} else if loadingNotifs == false && notifications.isEmpty {
ZStack {
Color.appBackground
.ignoresSafeArea()
ContentUnavailableView("activity.no-notifications", systemImage: "bolt.heart")
}
} else if loadingNotifs == true && notifications.isEmpty {
ZStack {
Color.appBackground
.ignoresSafeArea()
ProgressView()
.progressViewStyle(.circular)
}
}
}
.task {
loadingNotifs = true
await fetchNotifications(lastId: nil)
loadingNotifs = false
}
}
func fetchNotifications(lastId: Int? = nil) async {
guard let client = accountManager.getClient() else { return }
if lastId != nil {
guard lastId! >= notifications.count - 6 else { return }
}
do {
let allCases = Notification.NotificationType.allCases.map({ $0.rawValue })
let notifs: [Notification] = try await client.get(endpoint: Notifications.notifications(minId: nil, maxId: nil, types: nil, limit: lastId != nil ? notifLimit : 30))
guard !notifs.isEmpty else { return }
if notifications.isEmpty {
notifications = notifs
} else {
notifications.append(contentsOf: notifs)
}
} catch {
print(error)
}
}
}