direct messages tip + tip style

This commit is contained in:
Lumaa 2024-02-08 08:34:17 +01:00
parent 9503a4d4ab
commit fc90c766d9
4 changed files with 260 additions and 4 deletions

View File

@ -1,6 +1,64 @@
//Made by Lumaa
import SwiftUI
import TipKit
struct HeadlineTipViewStyle: TipViewStyle {
var headlineType: HeadlineType = .tip
func makeBody(configuration: TipViewStyle.Configuration) -> some View {
VStack(alignment: .leading) {
if headlineType != .none {
HStack {
Text(String(localized: LocalizedStringResource(stringLiteral: headlineType.rawValue)).uppercased())
.font(.headline.smallCaps())
.foregroundStyle(Color.gray)
Spacer()
Button(action: { configuration.tip.invalidate(reason: .tipClosed) }) {
Image(systemName: "xmark")
.scaledToFit()
}
}
Divider()
.frame(height: 1.0)
}
HStack(alignment: .top) {
configuration.image?
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 48.0, height: 48.0)
VStack(alignment: .leading, spacing: 8.0) {
configuration.title?.font(.headline)
configuration.message?.font(.subheadline)
ForEach(configuration.actions) { action in
Button(action: action.handler) {
action.label().foregroundStyle(.blue)
}
}
}
.padding(.horizontal, 5)
}
}
.padding()
}
public enum HeadlineType: String {
case tip = "tip.headline.tip" // tip
case new = "tip.headline.new" // new
case update = "tip.headline.update" // updated
case meta = "tip.headline.meta" // just like meta
case none = ""
}
}
extension View {
func listThreaded(tint: Color = Color(uiColor: UIColor.label)) -> some View {

View File

@ -9,6 +9,9 @@
},
"%@" : {
},
"%lld" : {
},
"•" : {
@ -52,6 +55,12 @@
"state" : "translated",
"value" : "Threaded is a very simple Mastodon client, that is meant to look like the newest social media Threads made by Meta Platforms. It integrates perfectly with your Mastodon account, and matches the Threads vibe, while having Mastodon-only features.\n\nThreaded is a 100% free, made in France using SwiftUI, [open-source](https://github.com/lumaa-dev/ThreadedApp), and doesn't violate [your privacy](https://apps.lumaa.fr/legal/privacy).\n\nThreaded is not related or affiliated to Meta Platforms."
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Threaded is a very simple Mastodon client, that is meant to look like the newest social media Threads made by Meta Platforms. It integrates perfectly with your Mastodon account, and matches the Threads vibe, while having Mastodon-only features.\n\nThreaded is a 100% free, made in France using SwiftUI, [open-source](https://github.com/lumaa-dev/ThreadedApp), and doesn't violate [your privacy](https://apps.lumaa.fr/legal/privacy).\n\nThreaded is not related or affiliated to Meta Platforms."
}
}
}
},
@ -62,6 +71,12 @@
"state" : "translated",
"value" : "Threaded uses third-party open-source libraries and code:\n- [IceCubesApp](https://github.com/dimillian/IceCubesApp)\n- [SwiftSoup](https://github.com/scinfu/SwiftSoup)\n- [Nuke](https://github.com/kean/Nuke)\n- [EmojiText](https://github.com/divadretlaw/EmojiText)\n- [KeychainSwift](https://github.com/evgenyneu/keychain-swift)"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Threaded uses third-party open-source libraries and code:\n- [IceCubesApp](https://github.com/dimillian/IceCubesApp)\n- [SwiftSoup](https://github.com/scinfu/SwiftSoup)\n- [Nuke](https://github.com/kean/Nuke)\n- [EmojiText](https://github.com/divadretlaw/EmojiText)\n- [KeychainSwift](https://github.com/evgenyneu/keychain-swift)"
}
}
}
},
@ -212,7 +227,7 @@
},
"other" : {
"stringUnit" : {
"state" : "new",
"state" : "translated",
"value" : "%lld followers"
}
}
@ -381,6 +396,78 @@
}
}
},
"activity.status.attachments-%lld" : {
"localizations" : {
"en" : {
"variations" : {
"plural" : {
"one" : {
"stringUnit" : {
"state" : "translated",
"value" : "%lld attachment"
}
},
"other" : {
"stringUnit" : {
"state" : "translated",
"value" : "%lld attachments"
}
}
}
}
},
"fr" : {
"variations" : {
"plural" : {
"one" : {
"stringUnit" : {
"state" : "translated",
"value" : "%lld pièce-jointe"
}
},
"other" : {
"stringUnit" : {
"state" : "translated",
"value" : "%lld pièces-jointe"
}
}
}
}
}
}
},
"activity.tip.messages.desc" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Your direct messages and mentions are separated, access your direct messages using the upper-right corner button"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Vos messages privés et mentions sont séparés, accédez à vos messages privés en utilisant le bouton au coin supérieur droit"
}
}
}
},
"activity.tip.messages.title" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Direct Messages"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Messages privés"
}
}
}
},
"activity.unknown" : {
"localizations" : {
"en" : {
@ -900,6 +987,12 @@
"state" : "translated",
"value" : "Threaded+ - Description"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Threaded+ - Description"
}
}
}
},
@ -945,12 +1038,12 @@
"one" : {
"stringUnit" : {
"state" : "translated",
"value" : "%lld likes"
"value" : "%lld like"
}
},
"other" : {
"stringUnit" : {
"state" : "new",
"state" : "translated",
"value" : "%lld likes"
}
}
@ -1349,6 +1442,50 @@
},
"timeline.trending" : {
},
"tip.headline.meta" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Just like Meta!"
}
}
}
},
"tip.headline.tip" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Tip"
}
}
}
},
"tip.headline.update" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Updated"
}
}
}
},
"tip.healine.new" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "New!"
}
}
}
}
},
"version" : "1.0"

View File

@ -1,6 +1,7 @@
//Made by Lumaa
import SwiftUI
import TipKit
@main
struct ThreadedApp: App {
@ -11,6 +12,14 @@ struct ThreadedApp: App {
.onAppear {
HapticManager.prepareHaptics()
}
.task {
Tips.showAllTipsForTesting()
try? Tips.configure([
.displayFrequency(.immediate),
.datastoreLocation(.applicationDefault)
])
}
}
}
}

View File

@ -1,16 +1,23 @@
//Made by Lumaa
import SwiftUI
import TipKit
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 loadingNotifs: Bool = true
@State private var lastId: Int? = nil
private let notifLimit = 50
@State private var messages: [Notification] = []
private var msgBadge: Int {
messages.map({ $0.account.id }).uniqued().count
}
private let msgTip: MsgTip = .init()
var body: some View {
NavigationStack(path: $navigator.path) {
@ -47,6 +54,29 @@ struct NotificationsView: View {
await fetchNotifications(lastId: nil)
}
.navigationTitle(String(localized: "activity"))
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button {
msgTip.invalidate(reason: .actionPerformed)
} label: {
Image(systemName: "paperplane")
.foregroundStyle(Color(uiColor: UIColor.label))
.overlay(alignment: .topTrailing) {
if msgBadge > 0 {
Text("\(msgBadge)")
.foregroundStyle(Color.white)
.font(.caption)
.padding(5)
.background(Color.red)
.clipShape(Circle())
.offset(x: 5, y: -7)
}
}
}
.popoverTip(msgTip, arrowEdge: .top)
.tipViewStyle(HeadlineTipViewStyle(headlineType: .meta))
}
}
} else if loadingNotifs == false && notifications.isEmpty {
ZStack {
Color.appBackground
@ -87,8 +117,30 @@ struct NotificationsView: View {
} else {
notifications.append(contentsOf: notifs)
}
filterMessages()
} catch {
print(error)
}
}
func filterMessages() {
guard !notifications.isEmpty else { return }
messages = notifications.filter({ $0.status?.visibility == .direct })
notifications.removeAll(where: { $0.status?.visibility == .direct })
}
struct MsgTip: Tip {
var title: Text = Text("activity.tip.messages.title")
var message: Text? = Text("activity.tip.messages.desc")
var id: String = "fr.lumaa.Threaded.MsgTip"
var image: Image? = Image(systemName: "paperplane")
}
}
public extension Array where Element: Hashable {
func uniqued() -> [Element] {
var seen = Set<Element>()
return filter { seen.insert($0).inserted }
}
}