mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-11-25 17:51:01 +00:00
Tag groups (#1506)
* Implemented tag groups * Cleanup --------- Co-authored-by: Thomas Ricouard <ricouard77@gmail.com>
This commit is contained in:
parent
c4c705aa10
commit
5951bcec38
32 changed files with 553 additions and 37 deletions
|
@ -105,6 +105,8 @@
|
||||||
E9DF420129830FEC0003AAD2 /* ActionRequestHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9DF420029830FEC0003AAD2 /* ActionRequestHandler.swift */; };
|
E9DF420129830FEC0003AAD2 /* ActionRequestHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9DF420029830FEC0003AAD2 /* ActionRequestHandler.swift */; };
|
||||||
E9DF420329830FEC0003AAD2 /* Action.js in Resources */ = {isa = PBXBuildFile; fileRef = E9DF420229830FEC0003AAD2 /* Action.js */; };
|
E9DF420329830FEC0003AAD2 /* Action.js in Resources */ = {isa = PBXBuildFile; fileRef = E9DF420229830FEC0003AAD2 /* Action.js */; };
|
||||||
E9DF420729830FEC0003AAD2 /* IceCubesActionExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = E9DF41FA29830FEC0003AAD2 /* IceCubesActionExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
E9DF420729830FEC0003AAD2 /* IceCubesActionExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = E9DF41FA29830FEC0003AAD2 /* IceCubesActionExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
|
FA31A9AB2A66BF7C00D5F662 /* AddTagGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA31A9AA2A66BF7C00D5F662 /* AddTagGroupView.swift */; };
|
||||||
|
FAD203D02A66D8A80030A7FD /* Symbols.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAD203CF2A66D8A80030A7FD /* Symbols.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
@ -272,6 +274,8 @@
|
||||||
E9DF420229830FEC0003AAD2 /* Action.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = Action.js; sourceTree = "<group>"; };
|
E9DF420229830FEC0003AAD2 /* Action.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = Action.js; sourceTree = "<group>"; };
|
||||||
E9DF420429830FEC0003AAD2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
E9DF420429830FEC0003AAD2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
F355EEDA297A8BD500E362C0 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
F355EEDA297A8BD500E362C0 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
|
FA31A9AA2A66BF7C00D5F662 /* AddTagGroupView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddTagGroupView.swift; sourceTree = "<group>"; };
|
||||||
|
FAD203CF2A66D8A80030A7FD /* Symbols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Symbols.swift; sourceTree = "<group>"; };
|
||||||
FF8259FB298E42E000BEAB69 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = "<group>"; };
|
FF8259FB298E42E000BEAB69 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
FF8259FC298E42E000BEAB69 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
FF8259FC298E42E000BEAB69 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
@ -401,6 +405,8 @@
|
||||||
children = (
|
children = (
|
||||||
9F398AB229360A4C00A889F2 /* TimelineTab.swift */,
|
9F398AB229360A4C00A889F2 /* TimelineTab.swift */,
|
||||||
9F7335F12967608F00AFF0BA /* AddRemoteTimelineView.swift */,
|
9F7335F12967608F00AFF0BA /* AddRemoteTimelineView.swift */,
|
||||||
|
FA31A9AA2A66BF7C00D5F662 /* AddTagGroupView.swift */,
|
||||||
|
FAD203CF2A66D8A80030A7FD /* Symbols.swift */,
|
||||||
);
|
);
|
||||||
path = Timeline;
|
path = Timeline;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -829,6 +835,8 @@
|
||||||
9F7335F92968576500AFF0BA /* DisplaySettingsView.swift in Sources */,
|
9F7335F92968576500AFF0BA /* DisplaySettingsView.swift in Sources */,
|
||||||
9F2A540729699698009B2D7C /* SupportAppView.swift in Sources */,
|
9F2A540729699698009B2D7C /* SupportAppView.swift in Sources */,
|
||||||
9F2B92F6295AE04800DE16D0 /* Tabs.swift in Sources */,
|
9F2B92F6295AE04800DE16D0 /* Tabs.swift in Sources */,
|
||||||
|
FA31A9AB2A66BF7C00D5F662 /* AddTagGroupView.swift in Sources */,
|
||||||
|
FAD203D02A66D8A80030A7FD /* Symbols.swift in Sources */,
|
||||||
9F398AB329360A4C00A889F2 /* TimelineTab.swift in Sources */,
|
9F398AB329360A4C00A889F2 /* TimelineTab.swift in Sources */,
|
||||||
9F398AA62935FE8A00A889F2 /* AppRouter.swift in Sources */,
|
9F398AA62935FE8A00A889F2 /* AppRouter.swift in Sources */,
|
||||||
9FBFE63D292A715500C250E9 /* IceCubesApp.swift in Sources */,
|
9FBFE63D292A715500C250E9 /* IceCubesApp.swift in Sources */,
|
||||||
|
|
|
@ -82,6 +82,9 @@ extension View {
|
||||||
case .addRemoteLocalTimeline:
|
case .addRemoteLocalTimeline:
|
||||||
AddRemoteTimelineView()
|
AddRemoteTimelineView()
|
||||||
.withEnvironments()
|
.withEnvironments()
|
||||||
|
case .addTagGroup:
|
||||||
|
AddTagGroupView()
|
||||||
|
.withEnvironments()
|
||||||
case let .statusEditHistory(status):
|
case let .statusEditHistory(status):
|
||||||
StatusEditHistoryView(statusId: status)
|
StatusEditHistoryView(statusId: status)
|
||||||
.withEnvironments()
|
.withEnvironments()
|
||||||
|
|
|
@ -139,6 +139,9 @@ struct SettingsTabs: View {
|
||||||
NavigationLink(destination: remoteLocalTimelinesView) {
|
NavigationLink(destination: remoteLocalTimelinesView) {
|
||||||
Label("settings.general.remote-timelines", systemImage: "dot.radiowaves.right")
|
Label("settings.general.remote-timelines", systemImage: "dot.radiowaves.right")
|
||||||
}
|
}
|
||||||
|
NavigationLink(destination: tagGroupsView) {
|
||||||
|
Label("timeline.filter.tag-groups", systemImage: "number")
|
||||||
|
}
|
||||||
NavigationLink(destination: ContentSettingsView()) {
|
NavigationLink(destination: ContentSettingsView()) {
|
||||||
Label("settings.general.content", systemImage: "rectangle.stack")
|
Label("settings.general.content", systemImage: "rectangle.stack")
|
||||||
}
|
}
|
||||||
|
@ -261,6 +264,36 @@ struct SettingsTabs: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var tagGroupsView: some View {
|
||||||
|
Form {
|
||||||
|
ForEach(preferences.tagGroups, id: \.self) { group in
|
||||||
|
Text(group.title)
|
||||||
|
}
|
||||||
|
.onDelete { indexes in
|
||||||
|
if let index = indexes.first {
|
||||||
|
_ = preferences.tagGroups.remove(at: index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onMove { source, destination in
|
||||||
|
preferences.tagGroups.move(fromOffsets: source, toOffset: destination)
|
||||||
|
}
|
||||||
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
|
||||||
|
Button {
|
||||||
|
routerPath.presentedSheet = .addTagGroup
|
||||||
|
} label: {
|
||||||
|
Label("timeline.filter.add-tag-groups", systemImage: "plus")
|
||||||
|
}
|
||||||
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
}
|
||||||
|
.navigationTitle("timeline.filter.tag-groups")
|
||||||
|
.scrollContentBackground(.hidden)
|
||||||
|
.background(theme.secondaryBackgroundColor)
|
||||||
|
.toolbar {
|
||||||
|
EditButton()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var remoteLocalTimelinesView: some View {
|
private var remoteLocalTimelinesView: some View {
|
||||||
Form {
|
Form {
|
||||||
|
|
197
IceCubesApp/App/Tabs/Timeline/AddTagGroupView.swift
Normal file
197
IceCubesApp/App/Tabs/Timeline/AddTagGroupView.swift
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
import Combine
|
||||||
|
import DesignSystem
|
||||||
|
import Env
|
||||||
|
import Models
|
||||||
|
import Network
|
||||||
|
import NukeUI
|
||||||
|
import Shimmer
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct AddTagGroupView: View {
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
|
@EnvironmentObject private var preferences: UserPreferences
|
||||||
|
@EnvironmentObject private var theme: Theme
|
||||||
|
|
||||||
|
@State private var title: String = ""
|
||||||
|
@State private var sfSymbolName: String = ""
|
||||||
|
@State private var tags: [String] = []
|
||||||
|
@State private var newTag: String = ""
|
||||||
|
|
||||||
|
private var canSave: Bool {
|
||||||
|
!title.isEmpty &&
|
||||||
|
// At least have 2 tags, one main and one additional.
|
||||||
|
tags.count >= 2
|
||||||
|
}
|
||||||
|
|
||||||
|
@FocusState private var focusedField: Focus?
|
||||||
|
|
||||||
|
enum Focus {
|
||||||
|
case title
|
||||||
|
case symbol
|
||||||
|
case new
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationStack {
|
||||||
|
Form {
|
||||||
|
metadataSection
|
||||||
|
keywordsSection
|
||||||
|
}
|
||||||
|
.formStyle(.grouped)
|
||||||
|
.navigationTitle("timeline.filter.add-tag-groups")
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.scrollContentBackground(.hidden)
|
||||||
|
.background(theme.secondaryBackgroundColor)
|
||||||
|
.scrollDismissesKeyboard(.immediately)
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .navigationBarLeading) {
|
||||||
|
Button("action.cancel", action: { dismiss() })
|
||||||
|
}
|
||||||
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
|
Button("action.save", action: { save() })
|
||||||
|
.disabled(!canSave)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
focusedField = .title
|
||||||
|
}
|
||||||
|
.overlay(alignment: .bottom) {
|
||||||
|
symbolsSuggestionView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var metadataSection: some View {
|
||||||
|
Section {
|
||||||
|
TextField("add-tag-groups.edit.title.field", text: $title, axis: .horizontal)
|
||||||
|
.focused($focusedField, equals: Focus.title)
|
||||||
|
.onSubmit {
|
||||||
|
focusedField = Focus.symbol
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
TextField("add-tag-groups.edit.icon.field", text: $sfSymbolName, axis: .horizontal)
|
||||||
|
.textInputAutocapitalization(.never)
|
||||||
|
.autocorrectionDisabled()
|
||||||
|
.focused($focusedField, equals: Focus.symbol)
|
||||||
|
.onSubmit {
|
||||||
|
focusedField = Focus.new
|
||||||
|
}
|
||||||
|
.onChange(of: sfSymbolName) { name in
|
||||||
|
popupTagsPresented = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Image(systemName: sfSymbolName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@State private var popupTagsPresented = false
|
||||||
|
|
||||||
|
private var keywordsSection: some View {
|
||||||
|
Section("add-tag-groups.edit.tags") {
|
||||||
|
ForEach(tags, id: \.self) { tag in
|
||||||
|
HStack {
|
||||||
|
Text(tag)
|
||||||
|
Spacer()
|
||||||
|
Button {
|
||||||
|
deleteTag(tag)
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "trash")
|
||||||
|
.tint(.red)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onDelete { indexes in
|
||||||
|
if let index = indexes.first {
|
||||||
|
let tag = tags[index]
|
||||||
|
deleteTag(tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HStack {
|
||||||
|
TextField("add-tag-groups.edit.tags.add", text: $newTag, axis: .horizontal)
|
||||||
|
.textInputAutocapitalization(.never)
|
||||||
|
.autocorrectionDisabled()
|
||||||
|
.onSubmit {
|
||||||
|
addNewTag()
|
||||||
|
}
|
||||||
|
.focused($focusedField, equals: Focus.new)
|
||||||
|
Spacer()
|
||||||
|
if !newTag.isEmpty {
|
||||||
|
Button {
|
||||||
|
addNewTag()
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "checkmark.circle.fill")
|
||||||
|
.tint(.green)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addNewTag() {
|
||||||
|
addTag(newTag.trimmingCharacters(in: .whitespaces))
|
||||||
|
newTag = ""
|
||||||
|
focusedField = Focus.new
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addTag(_ tag: String) {
|
||||||
|
guard !tag.isEmpty else { return }
|
||||||
|
tags.append(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func deleteTag(_ tag: String) {
|
||||||
|
tags.removeAll(where: { $0 == tag })
|
||||||
|
}
|
||||||
|
|
||||||
|
private func save() {
|
||||||
|
var toSave = tags
|
||||||
|
let main = toSave.removeFirst()
|
||||||
|
preferences.tagGroups.append(.init(
|
||||||
|
title: title.trimmingCharacters(in: .whitespaces),
|
||||||
|
sfSymbolName: sfSymbolName,
|
||||||
|
main: main,
|
||||||
|
additional: toSave
|
||||||
|
))
|
||||||
|
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var symbolsSuggestionView: some View {
|
||||||
|
if focusedField == .symbol && !sfSymbolName.isEmpty {
|
||||||
|
let filteredMatches = allSymbols
|
||||||
|
.filter { $0.contains(sfSymbolName) }
|
||||||
|
if !filteredMatches.isEmpty {
|
||||||
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
|
LazyHStack {
|
||||||
|
ForEach(filteredMatches, id: \.self) { symbolName in
|
||||||
|
Button {
|
||||||
|
sfSymbolName = symbolName
|
||||||
|
} label: {
|
||||||
|
Image(systemName: symbolName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, .layoutPadding)
|
||||||
|
}
|
||||||
|
.frame(height: 40)
|
||||||
|
.background(.ultraThinMaterial)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct AddTagGroupView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
AddTagGroupView()
|
||||||
|
.withEnvironments()
|
||||||
|
}
|
||||||
|
}
|
19
IceCubesApp/App/Tabs/Timeline/Symbols.swift
Normal file
19
IceCubesApp/App/Tabs/Timeline/Symbols.swift
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
//
|
||||||
|
// Symbols.swift
|
||||||
|
// IceCubesApp
|
||||||
|
//
|
||||||
|
// Created by Alejandro Martinez on 18/7/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
|
let allSymbols: [String] = {
|
||||||
|
if let bundle = Bundle(identifier: "com.apple.CoreGlyphs"),
|
||||||
|
let resourcePath = bundle.path(forResource: "symbol_search", ofType: "plist"),
|
||||||
|
let plist = NSDictionary(contentsOfFile: resourcePath) {
|
||||||
|
|
||||||
|
return plist.allKeys as? [String] ?? []
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}()
|
|
@ -151,6 +151,25 @@ struct TimelineTab: View {
|
||||||
Label("timeline.filter.add-local", systemImage: "badge.plus.radiowaves.right")
|
Label("timeline.filter.add-local", systemImage: "badge.plus.radiowaves.right")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Menu("timeline.filter.tag-groups") {
|
||||||
|
ForEach(preferences.tagGroups, id: \.self) { group in
|
||||||
|
Button {
|
||||||
|
timeline = .tagGroup(group)
|
||||||
|
} label: {
|
||||||
|
VStack {
|
||||||
|
let icon = group.sfSymbolName.isEmpty ? "number" : group.sfSymbolName
|
||||||
|
Label(group.title, systemImage: icon)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
routerPath.presentedSheet = .addTagGroup
|
||||||
|
} label: {
|
||||||
|
Label("timeline.filter.add-tag-groups", systemImage: "plus")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var addAccountButton: some View {
|
private var addAccountButton: some View {
|
||||||
|
|
|
@ -245,6 +245,8 @@
|
||||||
"timeline.filter.lists" = "Спісы";
|
"timeline.filter.lists" = "Спісы";
|
||||||
"timeline.filter.local" = "Мясцовыя шкалы часу";
|
"timeline.filter.local" = "Мясцовыя шкалы часу";
|
||||||
"timeline.filter.tags" = "Адсочваемыя тэгі";
|
"timeline.filter.tags" = "Адсочваемыя тэгі";
|
||||||
|
"timeline.filter.tag-groups" = "Tag Groups";
|
||||||
|
"timeline.filter.add-tag-groups" = "Add tag group";
|
||||||
|
|
||||||
// MARK: Package: AppAccount
|
// MARK: Package: AppAccount
|
||||||
"app-account.button.add" = "Дадаць уліковы запіс";
|
"app-account.button.add" = "Дадаць уліковы запіс";
|
||||||
|
@ -603,3 +605,9 @@
|
||||||
|
|
||||||
"tag.suggested.mentions-%lld" = "%lld mentions";
|
"tag.suggested.mentions-%lld" = "%lld mentions";
|
||||||
|
|
||||||
|
// MARK: Tag Groups
|
||||||
|
"add-tag-groups.edit.title.field" = "Tag Group Title";
|
||||||
|
"add-tag-groups.edit.icon.field" = "Tag Group Icon (SFSymbol name)";
|
||||||
|
"add-tag-groups.edit.tags" = "Add tags to the group";
|
||||||
|
"add-tag-groups.edit.tags.add" = "Tag";
|
||||||
|
|
||||||
|
|
|
@ -239,6 +239,8 @@
|
||||||
"timeline.filter.lists" = "Llistes";
|
"timeline.filter.lists" = "Llistes";
|
||||||
"timeline.filter.local" = "Línies de temps locals";
|
"timeline.filter.local" = "Línies de temps locals";
|
||||||
"timeline.filter.tags" = "Etiquetes seguides";
|
"timeline.filter.tags" = "Etiquetes seguides";
|
||||||
|
"timeline.filter.tag-groups" = "Grups d'Etiquetes";
|
||||||
|
"timeline.filter.add-tag-groups" = "Afegeix grup";
|
||||||
|
|
||||||
// MARK: Package: AppAccount
|
// MARK: Package: AppAccount
|
||||||
"app-account.button.add" = "Afegeix un compte";
|
"app-account.button.add" = "Afegeix un compte";
|
||||||
|
@ -596,3 +598,15 @@
|
||||||
"status.action.report" = "Report Post";
|
"status.action.report" = "Report Post";
|
||||||
|
|
||||||
"tag.suggested.mentions-%lld" = "%lld mentions";
|
"tag.suggested.mentions-%lld" = "%lld mentions";
|
||||||
|
|
||||||
|
// MARK: Tag Groups
|
||||||
|
"add-tag-groups.edit.title.field" = "Títol del Grup d'Etiquetes";
|
||||||
|
"add-tag-groups.edit.icon.field" = "Icona del Grup d'Etiquetes (SFSymbol)";
|
||||||
|
"add-tag-groups.edit.tags" = "Afegeix etiquetes al grup";
|
||||||
|
"add-tag-groups.edit.tags.add" = "Etiqueta";
|
||||||
|
|
||||||
|
// MARK: Tag Groups
|
||||||
|
"add-tag-groups.edit.title.field" = "Tag Group Title";
|
||||||
|
"add-tag-groups.edit.icon.field" = "Tag Group Icon (SFSymbol name)";
|
||||||
|
"add-tag-groups.edit.tags" = "Add tags to the group";
|
||||||
|
"add-tag-groups.edit.tags.add" = "Tag";
|
||||||
|
|
|
@ -242,6 +242,8 @@
|
||||||
"timeline.filter.lists" = "Listen";
|
"timeline.filter.lists" = "Listen";
|
||||||
"timeline.filter.local" = "Lokale Timelines";
|
"timeline.filter.local" = "Lokale Timelines";
|
||||||
"timeline.filter.tags" = "Gefolgte Hashtags";
|
"timeline.filter.tags" = "Gefolgte Hashtags";
|
||||||
|
"timeline.filter.tag-groups" = "Tag Groups";
|
||||||
|
"timeline.filter.add-tag-groups" = "Add tag group";
|
||||||
|
|
||||||
// MARK: Package: AppAccount
|
// MARK: Package: AppAccount
|
||||||
"app-account.button.add" = "Konto hinzufügen";
|
"app-account.button.add" = "Konto hinzufügen";
|
||||||
|
@ -583,3 +585,9 @@
|
||||||
"report.title" = "Beitrag melden";
|
"report.title" = "Beitrag melden";
|
||||||
"report.action.send" = "Absenden";
|
"report.action.send" = "Absenden";
|
||||||
"status.action.report" = "Beitrag melden";
|
"status.action.report" = "Beitrag melden";
|
||||||
|
|
||||||
|
// MARK: Tag Groups
|
||||||
|
"add-tag-groups.edit.title.field" = "Tag Group Title";
|
||||||
|
"add-tag-groups.edit.icon.field" = "Tag Group Icon (SFSymbol name)";
|
||||||
|
"add-tag-groups.edit.tags" = "Add tags to the group";
|
||||||
|
"add-tag-groups.edit.tags.add" = "Tag";
|
||||||
|
|
|
@ -243,6 +243,8 @@
|
||||||
"timeline.filter.lists" = "Lists";
|
"timeline.filter.lists" = "Lists";
|
||||||
"timeline.filter.local" = "Local Timelines";
|
"timeline.filter.local" = "Local Timelines";
|
||||||
"timeline.filter.tags" = "Followed Tags";
|
"timeline.filter.tags" = "Followed Tags";
|
||||||
|
"timeline.filter.tag-groups" = "Tag Groups";
|
||||||
|
"timeline.filter.add-tag-groups" = "Add tag group";
|
||||||
|
|
||||||
// MARK: Package: AppAccount
|
// MARK: Package: AppAccount
|
||||||
"app-account.button.add" = "Add Account";
|
"app-account.button.add" = "Add Account";
|
||||||
|
@ -597,3 +599,9 @@
|
||||||
"status.action.report" = "Report Post";
|
"status.action.report" = "Report Post";
|
||||||
|
|
||||||
"tag.suggested.mentions-%lld" = "%lld mentions";
|
"tag.suggested.mentions-%lld" = "%lld mentions";
|
||||||
|
|
||||||
|
// MARK: Tag Groups
|
||||||
|
"add-tag-groups.edit.title.field" = "Tag Group Title";
|
||||||
|
"add-tag-groups.edit.icon.field" = "Tag Group Icon (SFSymbol name)";
|
||||||
|
"add-tag-groups.edit.tags" = "Add tags to the group";
|
||||||
|
"add-tag-groups.edit.tags.add" = "Tag";
|
||||||
|
|
|
@ -241,6 +241,8 @@
|
||||||
"timeline.filter.lists" = "Lists";
|
"timeline.filter.lists" = "Lists";
|
||||||
"timeline.filter.local" = "Local Timelines";
|
"timeline.filter.local" = "Local Timelines";
|
||||||
"timeline.filter.tags" = "Followed Tags";
|
"timeline.filter.tags" = "Followed Tags";
|
||||||
|
"timeline.filter.tag-groups" = "Tag Groups";
|
||||||
|
"timeline.filter.add-tag-groups" = "Add tag group";
|
||||||
|
|
||||||
// MARK: Package: AppAccount
|
// MARK: Package: AppAccount
|
||||||
"app-account.button.add" = "Add Account";
|
"app-account.button.add" = "Add Account";
|
||||||
|
@ -599,3 +601,9 @@
|
||||||
"status.action.report" = "Report Post";
|
"status.action.report" = "Report Post";
|
||||||
|
|
||||||
"tag.suggested.mentions-%lld" = "%lld mentions";
|
"tag.suggested.mentions-%lld" = "%lld mentions";
|
||||||
|
|
||||||
|
// MARK: Tag Groups
|
||||||
|
"add-tag-groups.edit.title.field" = "Tag Group Title";
|
||||||
|
"add-tag-groups.edit.icon.field" = "Tag Group Icon (SFSymbol name)";
|
||||||
|
"add-tag-groups.edit.tags" = "Add tags to the group";
|
||||||
|
"add-tag-groups.edit.tags.add" = "Tag";
|
||||||
|
|
|
@ -241,6 +241,8 @@
|
||||||
"timeline.filter.lists" = "Listas";
|
"timeline.filter.lists" = "Listas";
|
||||||
"timeline.filter.local" = "Cronologías locales";
|
"timeline.filter.local" = "Cronologías locales";
|
||||||
"timeline.filter.tags" = "Etiquetas que sigues";
|
"timeline.filter.tags" = "Etiquetas que sigues";
|
||||||
|
"timeline.filter.tag-groups" = "Grupos de Etiquetas";
|
||||||
|
"timeline.filter.add-tag-groups" = "Añadir grupo";
|
||||||
|
|
||||||
// MARK: Package: AppAccount
|
// MARK: Package: AppAccount
|
||||||
"app-account.button.add" = "Añadir cuenta";
|
"app-account.button.add" = "Añadir cuenta";
|
||||||
|
@ -598,3 +600,9 @@
|
||||||
"status.action.report" = "Denunciar publicación";
|
"status.action.report" = "Denunciar publicación";
|
||||||
|
|
||||||
"tag.suggested.mentions-%lld" = "%lld menciones";
|
"tag.suggested.mentions-%lld" = "%lld menciones";
|
||||||
|
|
||||||
|
// MARK: Tag Groups
|
||||||
|
"add-tag-groups.edit.title.field" = "Título del Grupo de Etiquetas";
|
||||||
|
"add-tag-groups.edit.icon.field" = "Icono del Grupo de Etiquetas (SFSymbol)";
|
||||||
|
"add-tag-groups.edit.tags" = "Añade etiquetas al grupo";
|
||||||
|
"add-tag-groups.edit.tags.add" = "Etiqueta";
|
||||||
|
|
|
@ -242,6 +242,8 @@
|
||||||
"timeline.filter.lists" = "Zerrendak";
|
"timeline.filter.lists" = "Zerrendak";
|
||||||
"timeline.filter.local" = "Denbora-lerro lokalak";
|
"timeline.filter.local" = "Denbora-lerro lokalak";
|
||||||
"timeline.filter.tags" = "Jarraitutako traolak";
|
"timeline.filter.tags" = "Jarraitutako traolak";
|
||||||
|
"timeline.filter.tag-groups" = "Tag Groups";
|
||||||
|
"timeline.filter.add-tag-groups" = "Add tag group";
|
||||||
|
|
||||||
// MARK: Package: AppAccount
|
// MARK: Package: AppAccount
|
||||||
"app-account.button.add" = "Gehitu kontua";
|
"app-account.button.add" = "Gehitu kontua";
|
||||||
|
@ -584,3 +586,9 @@
|
||||||
"report.title" = "Salaketa";
|
"report.title" = "Salaketa";
|
||||||
"report.action.send" = "Bidali";
|
"report.action.send" = "Bidali";
|
||||||
"status.action.report" = "Salatu edukia";
|
"status.action.report" = "Salatu edukia";
|
||||||
|
|
||||||
|
// MARK: Tag Groups
|
||||||
|
"add-tag-groups.edit.title.field" = "Tag Group Title";
|
||||||
|
"add-tag-groups.edit.icon.field" = "Tag Group Icon (SFSymbol name)";
|
||||||
|
"add-tag-groups.edit.tags" = "Add tags to the group";
|
||||||
|
"add-tag-groups.edit.tags.add" = "Tag";
|
||||||
|
|
|
@ -240,6 +240,8 @@
|
||||||
"timeline.filter.lists" = "Listes";
|
"timeline.filter.lists" = "Listes";
|
||||||
"timeline.filter.local" = "Chronologies locales";
|
"timeline.filter.local" = "Chronologies locales";
|
||||||
"timeline.filter.tags" = "Tags suivis";
|
"timeline.filter.tags" = "Tags suivis";
|
||||||
|
"timeline.filter.tag-groups" = "Tag Groups";
|
||||||
|
"timeline.filter.add-tag-groups" = "Add tag group";
|
||||||
|
|
||||||
// MARK: Package: AppAccount
|
// MARK: Package: AppAccount
|
||||||
"app-account.button.add" = "Ajouter un compte";
|
"app-account.button.add" = "Ajouter un compte";
|
||||||
|
@ -593,3 +595,9 @@
|
||||||
"status.action.report" = "Signaler la publication";
|
"status.action.report" = "Signaler la publication";
|
||||||
|
|
||||||
"tag.suggested.mentions-%lld" = "%lld mentions";
|
"tag.suggested.mentions-%lld" = "%lld mentions";
|
||||||
|
|
||||||
|
// MARK: Tag Groups
|
||||||
|
"add-tag-groups.edit.title.field" = "Tag Group Title";
|
||||||
|
"add-tag-groups.edit.icon.field" = "Tag Group Icon (SFSymbol name)";
|
||||||
|
"add-tag-groups.edit.tags" = "Add tags to the group";
|
||||||
|
"add-tag-groups.edit.tags.add" = "Tag";
|
||||||
|
|
|
@ -239,6 +239,8 @@
|
||||||
"timeline.filter.lists" = "Liste";
|
"timeline.filter.lists" = "Liste";
|
||||||
"timeline.filter.local" = "Timeline locali";
|
"timeline.filter.local" = "Timeline locali";
|
||||||
"timeline.filter.tags" = "Tag seguiti";
|
"timeline.filter.tags" = "Tag seguiti";
|
||||||
|
"timeline.filter.tag-groups" = "Tag Groups";
|
||||||
|
"timeline.filter.add-tag-groups" = "Add tag group";
|
||||||
|
|
||||||
// MARK: Package: AppAccount
|
// MARK: Package: AppAccount
|
||||||
"app-account.button.add" = "Aggiungi account";
|
"app-account.button.add" = "Aggiungi account";
|
||||||
|
@ -597,3 +599,9 @@
|
||||||
"status.action.report" = "Segnala il messaggio";
|
"status.action.report" = "Segnala il messaggio";
|
||||||
|
|
||||||
"tag.suggested.mentions-%lld" = "%lld mentions";
|
"tag.suggested.mentions-%lld" = "%lld mentions";
|
||||||
|
|
||||||
|
// MARK: Tag Groups
|
||||||
|
"add-tag-groups.edit.title.field" = "Tag Group Title";
|
||||||
|
"add-tag-groups.edit.icon.field" = "Tag Group Icon (SFSymbol name)";
|
||||||
|
"add-tag-groups.edit.tags" = "Add tags to the group";
|
||||||
|
"add-tag-groups.edit.tags.add" = "Tag";
|
||||||
|
|
|
@ -240,6 +240,8 @@
|
||||||
"timeline.filter.lists" = "リスト";
|
"timeline.filter.lists" = "リスト";
|
||||||
"timeline.filter.local" = "ローカルタイムライン";
|
"timeline.filter.local" = "ローカルタイムライン";
|
||||||
"timeline.filter.tags" = "フォローしたタグ";
|
"timeline.filter.tags" = "フォローしたタグ";
|
||||||
|
"timeline.filter.tag-groups" = "Tag Groups";
|
||||||
|
"timeline.filter.add-tag-groups" = "Add tag group";
|
||||||
|
|
||||||
// MARK: Package: AppAccount
|
// MARK: Package: AppAccount
|
||||||
"app-account.button.add" = "アカウントの追加";
|
"app-account.button.add" = "アカウントの追加";
|
||||||
|
@ -596,3 +598,9 @@
|
||||||
"status.action.report" = "投稿を報告";
|
"status.action.report" = "投稿を報告";
|
||||||
|
|
||||||
"tag.suggested.mentions-%lld" = "返信:%lld";
|
"tag.suggested.mentions-%lld" = "返信:%lld";
|
||||||
|
|
||||||
|
// MARK: Tag Groups
|
||||||
|
"add-tag-groups.edit.title.field" = "Tag Group Title";
|
||||||
|
"add-tag-groups.edit.icon.field" = "Tag Group Icon (SFSymbol name)";
|
||||||
|
"add-tag-groups.edit.tags" = "Add tags to the group";
|
||||||
|
"add-tag-groups.edit.tags.add" = "Tag";
|
||||||
|
|
|
@ -241,6 +241,8 @@
|
||||||
"timeline.filter.local" = "원격 로컬 타임라인";
|
"timeline.filter.local" = "원격 로컬 타임라인";
|
||||||
"timeline.filter.tags" = "팔로우한 태그";
|
"timeline.filter.tags" = "팔로우한 태그";
|
||||||
"timeline-new-posts %lld" = "%lld개 새 글";
|
"timeline-new-posts %lld" = "%lld개 새 글";
|
||||||
|
"timeline.filter.tag-groups" = "Tag Groups";
|
||||||
|
"timeline.filter.add-tag-groups" = "Add tag group";
|
||||||
|
|
||||||
// MARK: Package: AppAccount
|
// MARK: Package: AppAccount
|
||||||
"app-account.button.add" = "계정 추가";
|
"app-account.button.add" = "계정 추가";
|
||||||
|
@ -600,3 +602,9 @@
|
||||||
"status.action.report" = "글 신고";
|
"status.action.report" = "글 신고";
|
||||||
|
|
||||||
"tag.suggested.mentions-%lld" = "%lld개 글";
|
"tag.suggested.mentions-%lld" = "%lld개 글";
|
||||||
|
|
||||||
|
// MARK: Tag Groups
|
||||||
|
"add-tag-groups.edit.title.field" = "Tag Group Title";
|
||||||
|
"add-tag-groups.edit.icon.field" = "Tag Group Icon (SFSymbol name)";
|
||||||
|
"add-tag-groups.edit.tags" = "Add tags to the group";
|
||||||
|
"add-tag-groups.edit.tags.add" = "Tag";
|
||||||
|
|
|
@ -240,6 +240,8 @@
|
||||||
"timeline.filter.lists" = "Lister";
|
"timeline.filter.lists" = "Lister";
|
||||||
"timeline.filter.local" = "Lokale tidslinjer";
|
"timeline.filter.local" = "Lokale tidslinjer";
|
||||||
"timeline.filter.tags" = "Fulgte tagger";
|
"timeline.filter.tags" = "Fulgte tagger";
|
||||||
|
"timeline.filter.tag-groups" = "Tag Groups";
|
||||||
|
"timeline.filter.add-tag-groups" = "Add tag group";
|
||||||
|
|
||||||
// MARK: Package: AppAccount
|
// MARK: Package: AppAccount
|
||||||
"app-account.button.add" = "Legg til konto";
|
"app-account.button.add" = "Legg til konto";
|
||||||
|
@ -597,3 +599,9 @@
|
||||||
"status.action.report" = "Rapporter innlegg";
|
"status.action.report" = "Rapporter innlegg";
|
||||||
|
|
||||||
"tag.suggested.mentions-%lld" = "%lld omtaler";
|
"tag.suggested.mentions-%lld" = "%lld omtaler";
|
||||||
|
|
||||||
|
// MARK: Tag Groups
|
||||||
|
"add-tag-groups.edit.title.field" = "Tag Group Title";
|
||||||
|
"add-tag-groups.edit.icon.field" = "Tag Group Icon (SFSymbol name)";
|
||||||
|
"add-tag-groups.edit.tags" = "Add tags to the group";
|
||||||
|
"add-tag-groups.edit.tags.add" = "Tag";
|
||||||
|
|
|
@ -237,6 +237,8 @@
|
||||||
"timeline.filter.lists" = "Lijsten";
|
"timeline.filter.lists" = "Lijsten";
|
||||||
"timeline.filter.local" = "Lokale tijdlijnen";
|
"timeline.filter.local" = "Lokale tijdlijnen";
|
||||||
"timeline.filter.tags" = "Gevolgde hashtags";
|
"timeline.filter.tags" = "Gevolgde hashtags";
|
||||||
|
"timeline.filter.tag-groups" = "Tag Groups";
|
||||||
|
"timeline.filter.add-tag-groups" = "Add tag group";
|
||||||
|
|
||||||
// MARK: Package: AppAccount
|
// MARK: Package: AppAccount
|
||||||
"app-account.button.add" = "Account toevoegen";
|
"app-account.button.add" = "Account toevoegen";
|
||||||
|
@ -594,3 +596,9 @@
|
||||||
"status.action.report" = "Meld post";
|
"status.action.report" = "Meld post";
|
||||||
|
|
||||||
"tag.suggested.mentions-%lld" = "%lld vermeldingen";
|
"tag.suggested.mentions-%lld" = "%lld vermeldingen";
|
||||||
|
|
||||||
|
// MARK: Tag Groups
|
||||||
|
"add-tag-groups.edit.title.field" = "Tag Group Title";
|
||||||
|
"add-tag-groups.edit.icon.field" = "Tag Group Icon (SFSymbol name)";
|
||||||
|
"add-tag-groups.edit.tags" = "Add tags to the group";
|
||||||
|
"add-tag-groups.edit.tags.add" = "Tag";
|
||||||
|
|
|
@ -240,6 +240,8 @@
|
||||||
"timeline.filter.lists" = "Listy";
|
"timeline.filter.lists" = "Listy";
|
||||||
"timeline.filter.local" = "Strumienie lokalne";
|
"timeline.filter.local" = "Strumienie lokalne";
|
||||||
"timeline.filter.tags" = "Obserwowane hasztagi";
|
"timeline.filter.tags" = "Obserwowane hasztagi";
|
||||||
|
"timeline.filter.tag-groups" = "Tag Groups";
|
||||||
|
"timeline.filter.add-tag-groups" = "Add tag group";
|
||||||
|
|
||||||
// MARK: Package: AppAccount
|
// MARK: Package: AppAccount
|
||||||
"app-account.button.add" = "Dodaj konto";
|
"app-account.button.add" = "Dodaj konto";
|
||||||
|
@ -587,3 +589,9 @@
|
||||||
"report.action.send" = "Wyślij";
|
"report.action.send" = "Wyślij";
|
||||||
"status.action.report" = "Zgłoś post";
|
"status.action.report" = "Zgłoś post";
|
||||||
|
|
||||||
|
// MARK: Tag Groups
|
||||||
|
"add-tag-groups.edit.title.field" = "Tag Group Title";
|
||||||
|
"add-tag-groups.edit.icon.field" = "Tag Group Icon (SFSymbol name)";
|
||||||
|
"add-tag-groups.edit.tags" = "Add tags to the group";
|
||||||
|
"add-tag-groups.edit.tags.add" = "Tag";
|
||||||
|
|
||||||
|
|
|
@ -240,6 +240,8 @@
|
||||||
"timeline.filter.lists" = "Listas";
|
"timeline.filter.lists" = "Listas";
|
||||||
"timeline.filter.local" = "Timelines Locais";
|
"timeline.filter.local" = "Timelines Locais";
|
||||||
"timeline.filter.tags" = "Hashtags Seguidas";
|
"timeline.filter.tags" = "Hashtags Seguidas";
|
||||||
|
"timeline.filter.tag-groups" = "Tag Groups";
|
||||||
|
"timeline.filter.add-tag-groups" = "Add tag group";
|
||||||
|
|
||||||
// MARK: Package: AppAccount
|
// MARK: Package: AppAccount
|
||||||
"app-account.button.add" = "Adicionar Conta";
|
"app-account.button.add" = "Adicionar Conta";
|
||||||
|
@ -597,3 +599,9 @@
|
||||||
"status.action.report" = "Denunciar";
|
"status.action.report" = "Denunciar";
|
||||||
|
|
||||||
"tag.suggested.mentions-%lld" = "%lld menções";
|
"tag.suggested.mentions-%lld" = "%lld menções";
|
||||||
|
|
||||||
|
// MARK: Tag Groups
|
||||||
|
"add-tag-groups.edit.title.field" = "Tag Group Title";
|
||||||
|
"add-tag-groups.edit.icon.field" = "Tag Group Icon (SFSymbol name)";
|
||||||
|
"add-tag-groups.edit.tags" = "Add tags to the group";
|
||||||
|
"add-tag-groups.edit.tags.add" = "Tag";
|
||||||
|
|
|
@ -240,6 +240,8 @@
|
||||||
"timeline.filter.lists" = "Listeler";
|
"timeline.filter.lists" = "Listeler";
|
||||||
"timeline.filter.local" = "Yerel Zaman Dilimleri";
|
"timeline.filter.local" = "Yerel Zaman Dilimleri";
|
||||||
"timeline.filter.tags" = "Takip Edilen Etiketler";
|
"timeline.filter.tags" = "Takip Edilen Etiketler";
|
||||||
|
"timeline.filter.tag-groups" = "Tag Groups";
|
||||||
|
"timeline.filter.add-tag-groups" = "Add tag group";
|
||||||
|
|
||||||
// MARK: Package: AppAccount
|
// MARK: Package: AppAccount
|
||||||
"app-account.button.add" = "Hesap Ekle";
|
"app-account.button.add" = "Hesap Ekle";
|
||||||
|
@ -597,3 +599,9 @@
|
||||||
"status.action.report" = "Report Post";
|
"status.action.report" = "Report Post";
|
||||||
|
|
||||||
"tag.suggested.mentions-%lld" = "%lld mentions";
|
"tag.suggested.mentions-%lld" = "%lld mentions";
|
||||||
|
|
||||||
|
// MARK: Tag Groups
|
||||||
|
"add-tag-groups.edit.title.field" = "Tag Group Title";
|
||||||
|
"add-tag-groups.edit.icon.field" = "Tag Group Icon (SFSymbol name)";
|
||||||
|
"add-tag-groups.edit.tags" = "Add tags to the group";
|
||||||
|
"add-tag-groups.edit.tags.add" = "Tag";
|
||||||
|
|
|
@ -241,6 +241,8 @@
|
||||||
"timeline.filter.lists" = "Списки";
|
"timeline.filter.lists" = "Списки";
|
||||||
"timeline.filter.local" = "Локальна стрічка";
|
"timeline.filter.local" = "Локальна стрічка";
|
||||||
"timeline.filter.tags" = "Хештеґи";
|
"timeline.filter.tags" = "Хештеґи";
|
||||||
|
"timeline.filter.tag-groups" = "Tag Groups";
|
||||||
|
"timeline.filter.add-tag-groups" = "Add tag group";
|
||||||
|
|
||||||
// MARK: Package: AppAccount
|
// MARK: Package: AppAccount
|
||||||
"app-account.button.add" = "Додати обліковий запис";
|
"app-account.button.add" = "Додати обліковий запис";
|
||||||
|
@ -599,3 +601,9 @@
|
||||||
|
|
||||||
"tag.suggested.mentions-%lld" = "%lld згадок";
|
"tag.suggested.mentions-%lld" = "%lld згадок";
|
||||||
|
|
||||||
|
// MARK: Tag Groups
|
||||||
|
"add-tag-groups.edit.title.field" = "Tag Group Title";
|
||||||
|
"add-tag-groups.edit.icon.field" = "Tag Group Icon (SFSymbol name)";
|
||||||
|
"add-tag-groups.edit.tags" = "Add tags to the group";
|
||||||
|
"add-tag-groups.edit.tags.add" = "Tag";
|
||||||
|
|
||||||
|
|
|
@ -238,6 +238,8 @@
|
||||||
"timeline.filter.lists" = "列表";
|
"timeline.filter.lists" = "列表";
|
||||||
"timeline.filter.local" = "远程时间线";
|
"timeline.filter.local" = "远程时间线";
|
||||||
"timeline.filter.tags" = "关注的标签";
|
"timeline.filter.tags" = "关注的标签";
|
||||||
|
"timeline.filter.tag-groups" = "Tag Groups";
|
||||||
|
"timeline.filter.add-tag-groups" = "Add tag group";
|
||||||
|
|
||||||
// MARK: Package: AppAccount
|
// MARK: Package: AppAccount
|
||||||
"app-account.button.add" = "添加账户";
|
"app-account.button.add" = "添加账户";
|
||||||
|
@ -597,3 +599,9 @@
|
||||||
"status.action.report" = "举报嘟文";
|
"status.action.report" = "举报嘟文";
|
||||||
|
|
||||||
"tag.suggested.mentions-%lld" = "%lld 个提及";
|
"tag.suggested.mentions-%lld" = "%lld 个提及";
|
||||||
|
|
||||||
|
// MARK: Tag Groups
|
||||||
|
"add-tag-groups.edit.title.field" = "Tag Group Title";
|
||||||
|
"add-tag-groups.edit.icon.field" = "Tag Group Icon (SFSymbol name)";
|
||||||
|
"add-tag-groups.edit.tags" = "Add tags to the group";
|
||||||
|
"add-tag-groups.edit.tags.add" = "Tag";
|
||||||
|
|
|
@ -242,6 +242,8 @@
|
||||||
"timeline.filter.lists" = "列表";
|
"timeline.filter.lists" = "列表";
|
||||||
"timeline.filter.local" = "本站時間軸";
|
"timeline.filter.local" = "本站時間軸";
|
||||||
"timeline.filter.tags" = "跟隨標籤";
|
"timeline.filter.tags" = "跟隨標籤";
|
||||||
|
"timeline.filter.tag-groups" = "Tag Groups";
|
||||||
|
"timeline.filter.add-tag-groups" = "Add tag group";
|
||||||
|
|
||||||
// MARK: Package: AppAccount
|
// MARK: Package: AppAccount
|
||||||
"app-account.button.add" = "新增帳號";
|
"app-account.button.add" = "新增帳號";
|
||||||
|
@ -599,3 +601,9 @@
|
||||||
"status.action.report" = "檢舉嘟文";
|
"status.action.report" = "檢舉嘟文";
|
||||||
|
|
||||||
"tag.suggested.mentions-%lld" = "%lld 提及";
|
"tag.suggested.mentions-%lld" = "%lld 提及";
|
||||||
|
|
||||||
|
// MARK: Tag Groups
|
||||||
|
"add-tag-groups.edit.title.field" = "Tag Group Title";
|
||||||
|
"add-tag-groups.edit.icon.field" = "Tag Group Icon (SFSymbol name)";
|
||||||
|
"add-tag-groups.edit.tags" = "Add tags to the group";
|
||||||
|
"add-tag-groups.edit.tags.add" = "Tag";
|
||||||
|
|
|
@ -33,6 +33,7 @@ public enum SheetDestination: Identifiable {
|
||||||
case listAddAccount(account: Account)
|
case listAddAccount(account: Account)
|
||||||
case addAccount
|
case addAccount
|
||||||
case addRemoteLocalTimeline
|
case addRemoteLocalTimeline
|
||||||
|
case addTagGroup
|
||||||
case statusEditHistory(status: String)
|
case statusEditHistory(status: String)
|
||||||
case settings
|
case settings
|
||||||
case accountPushNotficationsSettings
|
case accountPushNotficationsSettings
|
||||||
|
@ -50,6 +51,8 @@ public enum SheetDestination: Identifiable {
|
||||||
return "listAddAccount"
|
return "listAddAccount"
|
||||||
case .addAccount:
|
case .addAccount:
|
||||||
return "addAccount"
|
return "addAccount"
|
||||||
|
case .addTagGroup:
|
||||||
|
return "addTagGroup"
|
||||||
case .addRemoteLocalTimeline:
|
case .addRemoteLocalTimeline:
|
||||||
return "addRemoteLocalTimeline"
|
return "addRemoteLocalTimeline"
|
||||||
case .statusEditHistory:
|
case .statusEditHistory:
|
||||||
|
|
|
@ -12,6 +12,7 @@ public class UserPreferences: ObservableObject {
|
||||||
private var client: Client?
|
private var client: Client?
|
||||||
|
|
||||||
@AppStorage("remote_local_timeline") public var remoteLocalTimelines: [String] = []
|
@AppStorage("remote_local_timeline") public var remoteLocalTimelines: [String] = []
|
||||||
|
@AppStorage("tag_groups") public var tagGroups: [TagGroup] = []
|
||||||
@AppStorage("preferred_browser") public var preferredBrowser: PreferredBrowser = .inAppSafari
|
@AppStorage("preferred_browser") public var preferredBrowser: PreferredBrowser = .inAppSafari
|
||||||
@AppStorage("draft_posts") public var draftsPosts: [String] = []
|
@AppStorage("draft_posts") public var draftsPosts: [String] = []
|
||||||
@AppStorage("show_translate_button_inline") public var showTranslateButton: Bool = true
|
@AppStorage("show_translate_button_inline") public var showTranslateButton: Bool = true
|
||||||
|
|
|
@ -62,3 +62,29 @@ public struct FeaturedTag: Codable, Identifiable {
|
||||||
extension Tag: Sendable {}
|
extension Tag: Sendable {}
|
||||||
extension Tag.History: Sendable {}
|
extension Tag.History: Sendable {}
|
||||||
extension FeaturedTag: Sendable {}
|
extension FeaturedTag: Sendable {}
|
||||||
|
|
||||||
|
|
||||||
|
public struct TagGroup: Codable, Equatable, Hashable {
|
||||||
|
public init(title: String, sfSymbolName: String, main: String, additional: [String]) {
|
||||||
|
self.title = title
|
||||||
|
self.sfSymbolName = sfSymbolName
|
||||||
|
self.main = main
|
||||||
|
self.additional = additional
|
||||||
|
}
|
||||||
|
|
||||||
|
public let title: String
|
||||||
|
public let sfSymbolName: String
|
||||||
|
public let main: String
|
||||||
|
public let additional: [String]
|
||||||
|
|
||||||
|
public var tags: [String] {
|
||||||
|
[main] + additional
|
||||||
|
}
|
||||||
|
|
||||||
|
public var description: String {
|
||||||
|
tags
|
||||||
|
.map { "#\($0)" }
|
||||||
|
.joined(separator: " ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ public enum Timelines: Endpoint {
|
||||||
case pub(sinceId: String?, maxId: String?, minId: String?, local: Bool)
|
case pub(sinceId: String?, maxId: String?, minId: String?, local: Bool)
|
||||||
case home(sinceId: String?, maxId: String?, minId: String?)
|
case home(sinceId: String?, maxId: String?, minId: String?)
|
||||||
case list(listId: String, sinceId: String?, maxId: String?, minId: String?)
|
case list(listId: String, sinceId: String?, maxId: String?, minId: String?)
|
||||||
case hashtag(tag: String, maxId: String?)
|
case hashtag(tag: String, additional: [String]?, maxId: String?)
|
||||||
|
|
||||||
public func path() -> String {
|
public func path() -> String {
|
||||||
switch self {
|
switch self {
|
||||||
|
@ -14,11 +14,11 @@ public enum Timelines: Endpoint {
|
||||||
return "timelines/home"
|
return "timelines/home"
|
||||||
case let .list(listId, _, _, _):
|
case let .list(listId, _, _, _):
|
||||||
return "timelines/list/\(listId)"
|
return "timelines/list/\(listId)"
|
||||||
case let .hashtag(tag, _):
|
case let .hashtag(tag, _, _):
|
||||||
return "timelines/tag/\(tag)"
|
return "timelines/tag/\(tag)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func queryItems() -> [URLQueryItem]? {
|
public func queryItems() -> [URLQueryItem]? {
|
||||||
switch self {
|
switch self {
|
||||||
case let .pub(sinceId, maxId, minId, local):
|
case let .pub(sinceId, maxId, minId, local):
|
||||||
|
@ -29,8 +29,11 @@ public enum Timelines: Endpoint {
|
||||||
return makePaginationParam(sinceId: sinceId, maxId: maxId, mindId: mindId)
|
return makePaginationParam(sinceId: sinceId, maxId: maxId, mindId: mindId)
|
||||||
case let .list(_, sinceId, maxId, mindId):
|
case let .list(_, sinceId, maxId, mindId):
|
||||||
return makePaginationParam(sinceId: sinceId, maxId: maxId, mindId: mindId)
|
return makePaginationParam(sinceId: sinceId, maxId: maxId, mindId: mindId)
|
||||||
case let .hashtag(_, maxId):
|
case let .hashtag(_, additional, maxId):
|
||||||
return makePaginationParam(sinceId: nil, maxId: maxId, mindId: nil)
|
var params = makePaginationParam(sinceId: nil, maxId: maxId, mindId: nil) ?? []
|
||||||
|
params.append(contentsOf: (additional ?? [])
|
||||||
|
.map { URLQueryItem(name: "any[]", value: $0) })
|
||||||
|
return params
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,8 @@ public enum RemoteTimelineFilter: String, CaseIterable, Hashable, Equatable {
|
||||||
|
|
||||||
public enum TimelineFilter: Hashable, Equatable {
|
public enum TimelineFilter: Hashable, Equatable {
|
||||||
case home, local, federated, trending
|
case home, local, federated, trending
|
||||||
case hashtag(tag: String, accountId: String?)
|
case hashtag(tag: String, accountId: String?)
|
||||||
|
case tagGroup(TagGroup)
|
||||||
case list(list: Models.List)
|
case list(list: Models.List)
|
||||||
case remoteLocal(server: String, filter: RemoteTimelineFilter)
|
case remoteLocal(server: String, filter: RemoteTimelineFilter)
|
||||||
case latest
|
case latest
|
||||||
|
@ -72,6 +73,8 @@ public enum TimelineFilter: Hashable, Equatable {
|
||||||
return "Home"
|
return "Home"
|
||||||
case let .hashtag(tag, _):
|
case let .hashtag(tag, _):
|
||||||
return "#\(tag)"
|
return "#\(tag)"
|
||||||
|
case let .tagGroup(group):
|
||||||
|
return group.title
|
||||||
case let .list(list):
|
case let .list(list):
|
||||||
return list.title
|
return list.title
|
||||||
case let .remoteLocal(server, _):
|
case let .remoteLocal(server, _):
|
||||||
|
@ -93,6 +96,8 @@ public enum TimelineFilter: Hashable, Equatable {
|
||||||
return "timeline.home"
|
return "timeline.home"
|
||||||
case let .hashtag(tag, _):
|
case let .hashtag(tag, _):
|
||||||
return "#\(tag)"
|
return "#\(tag)"
|
||||||
|
case let .tagGroup(group):
|
||||||
|
return LocalizedStringKey(group.title) // ?? not sure since this can't be localized.
|
||||||
case let .list(list):
|
case let .list(list):
|
||||||
return LocalizedStringKey(list.title)
|
return LocalizedStringKey(list.title)
|
||||||
case let .remoteLocal(server, _):
|
case let .remoteLocal(server, _):
|
||||||
|
@ -142,8 +147,10 @@ public enum TimelineFilter: Hashable, Equatable {
|
||||||
if let accountId {
|
if let accountId {
|
||||||
return Accounts.statuses(id: accountId, sinceId: nil, tag: tag, onlyMedia: nil, excludeReplies: nil, pinned: nil)
|
return Accounts.statuses(id: accountId, sinceId: nil, tag: tag, onlyMedia: nil, excludeReplies: nil, pinned: nil)
|
||||||
} else {
|
} else {
|
||||||
return Timelines.hashtag(tag: tag, maxId: maxId)
|
return Timelines.hashtag(tag: tag, additional: nil, maxId: maxId)
|
||||||
}
|
}
|
||||||
|
case let .tagGroup(group):
|
||||||
|
return Timelines.hashtag(tag: group.main, additional: group.additional, maxId: maxId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,6 +162,7 @@ extension TimelineFilter: Codable {
|
||||||
case federated
|
case federated
|
||||||
case trending
|
case trending
|
||||||
case hashtag
|
case hashtag
|
||||||
|
case tagGroup
|
||||||
case list
|
case list
|
||||||
case remoteLocal
|
case remoteLocal
|
||||||
case latest
|
case latest
|
||||||
|
@ -180,6 +188,9 @@ extension TimelineFilter: Codable {
|
||||||
tag: tag,
|
tag: tag,
|
||||||
accountId: accountId
|
accountId: accountId
|
||||||
)
|
)
|
||||||
|
case .tagGroup:
|
||||||
|
let group = try container.decode(TagGroup.self, forKey: .tagGroup)
|
||||||
|
self = .tagGroup(group)
|
||||||
case .list:
|
case .list:
|
||||||
let list = try container.decode(
|
let list = try container.decode(
|
||||||
Models.List.self,
|
Models.List.self,
|
||||||
|
@ -221,6 +232,8 @@ extension TimelineFilter: Codable {
|
||||||
var nestedContainer = container.nestedUnkeyedContainer(forKey: .hashtag)
|
var nestedContainer = container.nestedUnkeyedContainer(forKey: .hashtag)
|
||||||
try nestedContainer.encode(tag)
|
try nestedContainer.encode(tag)
|
||||||
try nestedContainer.encode(accountId)
|
try nestedContainer.encode(accountId)
|
||||||
|
case let .tagGroup(group):
|
||||||
|
try container.encode(group, forKey: .tagGroup)
|
||||||
case let .list(list):
|
case let .list(list):
|
||||||
try container.encode(list, forKey: .list)
|
try container.encode(list, forKey: .list)
|
||||||
case let .remoteLocal(server, filter):
|
case let .remoteLocal(server, filter):
|
||||||
|
|
|
@ -39,7 +39,9 @@ public struct TimelineView: View {
|
||||||
ScrollViewReader { proxy in
|
ScrollViewReader { proxy in
|
||||||
ZStack(alignment: .top) {
|
ZStack(alignment: .top) {
|
||||||
List {
|
List {
|
||||||
if viewModel.tag == nil {
|
if viewModel.tagGroup != nil {
|
||||||
|
tagGroupHeaderView
|
||||||
|
} else if viewModel.tag == nil {
|
||||||
scrollToTopView
|
scrollToTopView
|
||||||
} else {
|
} else {
|
||||||
tagHeaderView
|
tagHeaderView
|
||||||
|
@ -180,40 +182,64 @@ public struct TimelineView: View {
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var tagHeaderView: some View {
|
private var tagHeaderView: some View {
|
||||||
if let tag = viewModel.tag {
|
if let tag = viewModel.tag {
|
||||||
VStack(alignment: .leading) {
|
headerView {
|
||||||
Spacer()
|
HStack {
|
||||||
HStack {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
Text("#\(tag.name)")
|
||||||
Text("#\(tag.name)")
|
.font(.scaledHeadline)
|
||||||
.font(.scaledHeadline)
|
Text("timeline.n-recent-from-n-participants \(tag.totalUses) \(tag.totalAccounts)")
|
||||||
Text("timeline.n-recent-from-n-participants \(tag.totalUses) \(tag.totalAccounts)")
|
.font(.scaledFootnote)
|
||||||
.font(.scaledFootnote)
|
.foregroundColor(.gray)
|
||||||
.foregroundColor(.gray)
|
|
||||||
}
|
|
||||||
.accessibilityElement(children: .combine)
|
|
||||||
Spacer()
|
|
||||||
Button {
|
|
||||||
Task {
|
|
||||||
if tag.following {
|
|
||||||
viewModel.tag = await account.unfollowTag(id: tag.name)
|
|
||||||
} else {
|
|
||||||
viewModel.tag = await account.followTag(id: tag.name)
|
|
||||||
}
|
}
|
||||||
|
.accessibilityElement(children: .combine)
|
||||||
|
Spacer()
|
||||||
|
Button {
|
||||||
|
Task {
|
||||||
|
if tag.following {
|
||||||
|
viewModel.tag = await account.unfollowTag(id: tag.name)
|
||||||
|
} else {
|
||||||
|
viewModel.tag = await account.followTag(id: tag.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Text(tag.following ? "account.follow.following" : "account.follow.follow")
|
||||||
|
}.buttonStyle(.bordered)
|
||||||
}
|
}
|
||||||
} label: {
|
|
||||||
Text(tag.following ? "account.follow.following" : "account.follow.follow")
|
|
||||||
}.buttonStyle(.bordered)
|
|
||||||
}
|
}
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.listRowBackground(theme.secondaryBackgroundColor)
|
|
||||||
.listRowSeparator(.hidden)
|
|
||||||
.listRowInsets(.init(top: 8,
|
|
||||||
leading: .layoutPadding,
|
|
||||||
bottom: 8,
|
|
||||||
trailing: .layoutPadding))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var tagGroupHeaderView: some View {
|
||||||
|
if let group = viewModel.tagGroup {
|
||||||
|
headerView {
|
||||||
|
HStack {
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
Text(group.description)
|
||||||
|
.font(.scaledHeadline)
|
||||||
|
}
|
||||||
|
.accessibilityElement(children: .combine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private func headerView(
|
||||||
|
@ViewBuilder content: () -> some View
|
||||||
|
) -> some View {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Spacer()
|
||||||
|
content()
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.listRowBackground(theme.secondaryBackgroundColor)
|
||||||
|
.listRowSeparator(.hidden)
|
||||||
|
.listRowInsets(.init(top: 8,
|
||||||
|
leading: .layoutPadding,
|
||||||
|
bottom: 8,
|
||||||
|
trailing: .layoutPadding))
|
||||||
|
}
|
||||||
|
|
||||||
private var scrollToTopView: some View {
|
private var scrollToTopView: some View {
|
||||||
HStack { EmptyView() }
|
HStack { EmptyView() }
|
||||||
|
|
|
@ -40,6 +40,13 @@ class TimelineViewModel: ObservableObject {
|
||||||
private var timelineTask: Task<Void, Never>?
|
private var timelineTask: Task<Void, Never>?
|
||||||
|
|
||||||
@Published var tag: Tag?
|
@Published var tag: Tag?
|
||||||
|
|
||||||
|
var tagGroup: TagGroup? {
|
||||||
|
if case let .tagGroup(group) = timeline {
|
||||||
|
return group
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Internal source of truth for a timeline.
|
// Internal source of truth for a timeline.
|
||||||
private var datasource = TimelineDatasource()
|
private var datasource = TimelineDatasource()
|
||||||
|
|
Loading…
Reference in a new issue