diff --git a/IceCubesApp.xcodeproj/project.pbxproj b/IceCubesApp.xcodeproj/project.pbxproj index 94778ee5..ca721a9f 100644 --- a/IceCubesApp.xcodeproj/project.pbxproj +++ b/IceCubesApp.xcodeproj/project.pbxproj @@ -87,6 +87,9 @@ 9FB183292AE9449100BBB692 /* IceCubesApp+Scene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB183282AE9449100BBB692 /* IceCubesApp+Scene.swift */; }; 9FBFE63D292A715500C250E9 /* IceCubesApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBFE63C292A715500C250E9 /* IceCubesApp.swift */; }; 9FBFE64E292A72BD00C250E9 /* Network in Frameworks */ = {isa = PBXBuildFile; productRef = 9FBFE64D292A72BD00C250E9 /* Network */; }; + 9FC14EF22B494D180006CEE1 /* TagsGroupSettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC14EF12B494D180006CEE1 /* TagsGroupSettingView.swift */; }; + 9FC14EF42B494D940006CEE1 /* RemoteTimelinesSettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC14EF32B494D940006CEE1 /* RemoteTimelinesSettingView.swift */; }; + 9FC14EF62B494DFF0006CEE1 /* RecenTagsSettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC14EF52B494DFF0006CEE1 /* RecenTagsSettingView.swift */; }; 9FD34823293D06E800DB0EE9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9FD34822293D06E800DB0EE9 /* Assets.xcassets */; }; 9FD542E72962D2FF0045321A /* Lists in Frameworks */ = {isa = PBXBuildFile; productRef = 9FD542E62962D2FF0045321A /* Lists */; }; 9FE151A6293C90F900E9683D /* IconSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FE151A5293C90F900E9683D /* IconSelectorView.swift */; }; @@ -225,6 +228,9 @@ 9FB183282AE9449100BBB692 /* IceCubesApp+Scene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IceCubesApp+Scene.swift"; sourceTree = ""; }; 9FBFE639292A715500C250E9 /* Ice Cubes.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Ice Cubes.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 9FBFE63C292A715500C250E9 /* IceCubesApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IceCubesApp.swift; sourceTree = ""; }; + 9FC14EF12B494D180006CEE1 /* TagsGroupSettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagsGroupSettingView.swift; sourceTree = ""; }; + 9FC14EF32B494D940006CEE1 /* RemoteTimelinesSettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteTimelinesSettingView.swift; sourceTree = ""; }; + 9FC14EF52B494DFF0006CEE1 /* RecenTagsSettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecenTagsSettingView.swift; sourceTree = ""; }; 9FC60CB82AE6C2F600C6EAD2 /* IceCubesActionExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = IceCubesActionExtension.entitlements; sourceTree = ""; }; 9FCBB3D22984EFD5009B77EE /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; 9FCBB3D429859615009B77EE /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -518,6 +524,9 @@ D08A9C3429956CFA00204A4A /* SwipeActionsSettingsView.swift */, 9FA6FD6129C04A8800E2312C /* TranslationSettingsView.swift */, 9F15D6012B3D6E280008C220 /* TabbarEntriesSettingsView.swift */, + 9FC14EF12B494D180006CEE1 /* TagsGroupSettingView.swift */, + 9FC14EF32B494D940006CEE1 /* RemoteTimelinesSettingView.swift */, + 9FC14EF52B494DFF0006CEE1 /* RecenTagsSettingView.swift */, ); path = Settings; sourceTree = ""; @@ -706,7 +715,6 @@ be, uk, "zh-Hant", - Base, ); mainGroup = 9FBFE630292A715500C250E9; packageReferences = ( @@ -814,6 +822,8 @@ 9F35DB4C2952005C00B3281A /* MessagesTab.swift in Sources */, 9FAD85CF2975B68900496AB1 /* SideBarView.swift in Sources */, 9FAE4ACB293783B000772766 /* SettingsTab.swift in Sources */, + 9FC14EF42B494D940006CEE1 /* RemoteTimelinesSettingView.swift in Sources */, + 9FC14EF22B494D180006CEE1 /* TagsGroupSettingView.swift in Sources */, 9F15D6022B3D6E280008C220 /* TabbarEntriesSettingsView.swift in Sources */, 9F7335F92968576500AFF0BA /* DisplaySettingsView.swift in Sources */, 9F2A540729699698009B2D7C /* SupportAppView.swift in Sources */, @@ -831,6 +841,7 @@ 9F654BEF299AC45B00D27FA5 /* ReportView.swift in Sources */, D08A9C3529956CFA00204A4A /* SwipeActionsSettingsView.swift in Sources */, 9F7335F22967608F00AFF0BA /* AddRemoteTimelineView.swift in Sources */, + 9FC14EF62B494DFF0006CEE1 /* RecenTagsSettingView.swift in Sources */, 9F6028562B3F36AE00476078 /* AppView.swift in Sources */, 9F55C68D2955968700F94077 /* ExploreTab.swift in Sources */, 9F1E8B47298EBCBB00609F80 /* HapticSettingsView.swift in Sources */, diff --git a/IceCubesApp/Resources/Localization/Localizable.xcstrings b/IceCubesApp/Resources/Localization/Localizable.xcstrings index 8a52a328..01eef702 100644 --- a/IceCubesApp/Resources/Localization/Localizable.xcstrings +++ b/IceCubesApp/Resources/Localization/Localizable.xcstrings @@ -46648,6 +46648,124 @@ } } }, + "settings.general.recent-tags" : { + "localizations" : { + "be" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Recently Used Tags" + } + }, + "ca" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Recently Used Tags" + } + }, + "de" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Recently Used Tags" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Recently Used Tags" + } + }, + "en-GB" : { + "stringUnit" : { + "state" : "translated", + "value" : "Recently Used Tags" + } + }, + "es" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Recently Used Tags" + } + }, + "eu" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Recently Used Tags" + } + }, + "fr" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Recently Used Tags" + } + }, + "it" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Recently Used Tags" + } + }, + "ja" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Recently Used Tags" + } + }, + "ko" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Recently Used Tags" + } + }, + "nb" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Recently Used Tags" + } + }, + "nl" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Recently Used Tags" + } + }, + "pl" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Recently Used Tags" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Recently Used Tags" + } + }, + "tr" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Recently Used Tags" + } + }, + "uk" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Recently Used Tags" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Recently Used Tags" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Recently Used Tags" + } + } + } + }, "settings.general.remote-timelines" : { "localizations" : { "be" : { diff --git a/Packages/Status/Sources/Status/Editor/Components/AutoComplete/ExpandedView.swift b/Packages/Status/Sources/Status/Editor/Components/AutoComplete/ExpandedView.swift new file mode 100644 index 00000000..30ba44cc --- /dev/null +++ b/Packages/Status/Sources/Status/Editor/Components/AutoComplete/ExpandedView.swift @@ -0,0 +1,57 @@ +import DesignSystem +import EmojiText +import Foundation +import SwiftUI +import Models +import SwiftData + + +extension StatusEditorAutoCompleteView { + struct ExpandedView: View { + @Environment(\.modelContext) private var context + @Environment(Theme.self) private var theme + + var viewModel: StatusEditorViewModel + @Binding var isTagSuggestionExpanded: Bool + + @Query(sort: \RecentTag.lastUse, order: .reverse) var recentTags: [RecentTag] + + var body: some View { + ScrollView(.vertical) { + LazyVStack(alignment: .leading, spacing: 12) { + Text("status.editor.language-select.recently-used") + .font(.scaledSubheadline) + .foregroundStyle(theme.labelColor) + .fontWeight(.bold) + ForEach(recentTags) { tag in + HStack { + Button { + tag.lastUse = Date() + withAnimation { + isTagSuggestionExpanded = false + viewModel.selectHashtagSuggestion(tag: tag.title) + } + } label: { + Text("#\(tag.title)") + .font(.scaledFootnote) + .fontWeight(.bold) + .foregroundColor(theme.labelColor) + } + Spacer() + } + } + } + .padding(.horizontal, .layoutPadding) + } + .frame(height: 200) + .gesture(DragGesture(minimumDistance: 0, coordinateSpace: .local) + .onEnded({ value in + withAnimation { + if value.translation.height > 0 { + isTagSuggestionExpanded = false + } + } + })) + } + } +} diff --git a/Packages/Status/Sources/Status/Editor/Components/AutoComplete/MentionsView.swift b/Packages/Status/Sources/Status/Editor/Components/AutoComplete/MentionsView.swift new file mode 100644 index 00000000..45d5aa2d --- /dev/null +++ b/Packages/Status/Sources/Status/Editor/Components/AutoComplete/MentionsView.swift @@ -0,0 +1,39 @@ +import DesignSystem +import EmojiText +import Foundation +import SwiftUI +import Models +import SwiftData + + +extension StatusEditorAutoCompleteView { + struct MentionsView: View { + @Environment(Theme.self) private var theme + + var viewModel: StatusEditorViewModel + + var body: some View { + ForEach(viewModel.mentionsSuggestions) { account in + Button { + viewModel.selectMentionSuggestion(account: account) + } label: { + HStack { + AvatarView(account.avatar, config: AvatarView.FrameConfig.badge) + VStack(alignment: .leading) { + EmojiTextApp(.init(stringValue: account.safeDisplayName), + emojis: account.emojis) + .emojiSize(Font.scaledFootnoteFont.emojiSize) + .emojiBaselineOffset(Font.scaledFootnoteFont.emojiBaselineOffset) + .font(.scaledFootnote) + .fontWeight(.bold) + .foregroundColor(theme.labelColor) + Text("@\(account.acct)") + .font(.scaledFootnote) + .foregroundStyle(theme.tintColor) + } + } + } + } + } + } +} diff --git a/Packages/Status/Sources/Status/Editor/Components/AutoComplete/RecentTagsView.swift b/Packages/Status/Sources/Status/Editor/Components/AutoComplete/RecentTagsView.swift new file mode 100644 index 00000000..db62371a --- /dev/null +++ b/Packages/Status/Sources/Status/Editor/Components/AutoComplete/RecentTagsView.swift @@ -0,0 +1,37 @@ +import DesignSystem +import EmojiText +import Foundation +import SwiftUI +import Models +import SwiftData + + +extension StatusEditorAutoCompleteView { + struct RecentTagsView: View { + @Environment(Theme.self) private var theme + + var viewModel: StatusEditorViewModel + @Binding var isTagSuggestionExpanded: Bool + + @Query(sort: \RecentTag.lastUse, order: .reverse) var recentTags: [RecentTag] + + var body: some View { + ForEach(recentTags) { tag in + Button { + withAnimation { + isTagSuggestionExpanded = false + viewModel.selectHashtagSuggestion(tag: tag.title) + } + tag.lastUse = Date() + } label: { + VStack(alignment: .leading) { + Text("#\(tag.title)") + .font(.scaledFootnote) + .fontWeight(.bold) + .foregroundColor(theme.labelColor) + } + } + } + } + } +} diff --git a/Packages/Status/Sources/Status/Editor/Components/AutoComplete/RemoteTagsView.swift b/Packages/Status/Sources/Status/Editor/Components/AutoComplete/RemoteTagsView.swift new file mode 100644 index 00000000..df8aa2e5 --- /dev/null +++ b/Packages/Status/Sources/Status/Editor/Components/AutoComplete/RemoteTagsView.swift @@ -0,0 +1,45 @@ +import DesignSystem +import EmojiText +import Foundation +import SwiftUI +import Models +import SwiftData + + +extension StatusEditorAutoCompleteView { + struct RemoteTagsView: View { + @Environment(\.modelContext) private var context + @Environment(Theme.self) private var theme + + var viewModel: StatusEditorViewModel + @Binding var isTagSuggestionExpanded: Bool + + @Query(sort: \RecentTag.lastUse, order: .reverse) var recentTags: [RecentTag] + + var body: some View { + ForEach(viewModel.tagsSuggestions) { tag in + Button { + withAnimation { + isTagSuggestionExpanded = false + viewModel.selectHashtagSuggestion(tag: tag.name) + } + if let index = recentTags.firstIndex(where: { $0.title.lowercased() == tag.name.lowercased() }) { + recentTags[index].lastUse = Date() + } else { + context.insert(RecentTag(title: tag.name)) + } + } label: { + VStack(alignment: .leading) { + Text("#\(tag.name)") + .font(.scaledFootnote) + .fontWeight(.bold) + .foregroundColor(theme.labelColor) + Text("tag.suggested.mentions-\(tag.totalUses)") + .font(.scaledFootnote) + .foregroundStyle(theme.tintColor) + } + } + } + } + } +} diff --git a/Packages/Status/Sources/Status/Editor/Components/AutoComplete/StatusEditorAutoCompleteView.swift b/Packages/Status/Sources/Status/Editor/Components/AutoComplete/StatusEditorAutoCompleteView.swift new file mode 100644 index 00000000..180a1be5 --- /dev/null +++ b/Packages/Status/Sources/Status/Editor/Components/AutoComplete/StatusEditorAutoCompleteView.swift @@ -0,0 +1,66 @@ +import DesignSystem +import EmojiText +import Foundation +import SwiftUI +import Models +import SwiftData + +@MainActor +struct StatusEditorAutoCompleteView: View { + @Environment(\.modelContext) var context + + @Environment(Theme.self) var theme + + var viewModel: StatusEditorViewModel + + @State private var isTagSuggestionExpanded: Bool = false + + @Query(sort: \RecentTag.lastUse, order: .reverse) var recentTags: [RecentTag] + + var body: some View { + if !viewModel.mentionsSuggestions.isEmpty || + !viewModel.tagsSuggestions.isEmpty || + (viewModel.showRecentsTagsInline && !recentTags.isEmpty) { + VStack { + HStack { + ScrollView(.horizontal, showsIndicators: false) { + LazyHStack { + if !viewModel.mentionsSuggestions.isEmpty { + Self.MentionsView(viewModel: viewModel) + } else { + suggestionsTagView + } + } + .padding(.horizontal, .layoutPadding) + } + .scrollContentBackground(.hidden) + if !viewModel.tagsSuggestions.isEmpty { + Spacer() + Button { + withAnimation { + isTagSuggestionExpanded.toggle() + } + } label: { + Image(systemName: isTagSuggestionExpanded ? "chevron.down.circle" : "chevron.up.circle") + } + .padding(.trailing, 8) + } + } + .frame(height: 40) + if isTagSuggestionExpanded { + Self.ExpandedView(viewModel: viewModel, isTagSuggestionExpanded: $isTagSuggestionExpanded) + } + } + .background(.thinMaterial) + } + } + + @ViewBuilder + private var suggestionsTagView: some View { + if viewModel.showRecentsTagsInline { + Self.RecentTagsView(viewModel: viewModel, isTagSuggestionExpanded: $isTagSuggestionExpanded) + } else { + Self.RemoteTagsView(viewModel: viewModel, isTagSuggestionExpanded: $isTagSuggestionExpanded) + } + } +} diff --git a/Packages/Status/Sources/Status/Editor/Components/StatusEditorAutoCompleteView.swift b/Packages/Status/Sources/Status/Editor/Components/StatusEditorAutoCompleteView.swift deleted file mode 100644 index 03cb6d5a..00000000 --- a/Packages/Status/Sources/Status/Editor/Components/StatusEditorAutoCompleteView.swift +++ /dev/null @@ -1,173 +0,0 @@ -import DesignSystem -import EmojiText -import Foundation -import SwiftUI -import Models -import SwiftData - -@MainActor -struct StatusEditorAutoCompleteView: View { - @Environment(\.modelContext) private var context - - @Environment(Theme.self) private var theme - - var viewModel: StatusEditorViewModel - - @State private var isTagSuggestionExpanded: Bool = false - - @Query(sort: \RecentTag.lastUse, order: .reverse) var recentTags: [RecentTag] - - var body: some View { - if !viewModel.mentionsSuggestions.isEmpty || - !viewModel.tagsSuggestions.isEmpty || - (viewModel.showRecentsTagsInline && !recentTags.isEmpty) { - VStack { - HStack { - ScrollView(.horizontal, showsIndicators: false) { - LazyHStack { - if !viewModel.mentionsSuggestions.isEmpty { - suggestionsMentionsView - } else { - suggestionsTagView - } - } - .padding(.horizontal, .layoutPadding) - } - .scrollContentBackground(.hidden) - if !viewModel.tagsSuggestions.isEmpty { - Spacer() - Button { - withAnimation { - isTagSuggestionExpanded.toggle() - } - } label: { - Image(systemName: isTagSuggestionExpanded ? "chevron.down.circle" : "chevron.up.circle") - } - .padding(.trailing, 8) - } - } - .frame(height: 40) - if isTagSuggestionExpanded { - expandedTagsSuggestionView - } - } - .background(.thinMaterial) - } - } - - private var suggestionsMentionsView: some View { - ForEach(viewModel.mentionsSuggestions) { account in - Button { - viewModel.selectMentionSuggestion(account: account) - } label: { - HStack { - AvatarView(account.avatar, config: AvatarView.FrameConfig.badge) - VStack(alignment: .leading) { - EmojiTextApp(.init(stringValue: account.safeDisplayName), - emojis: account.emojis) - .emojiSize(Font.scaledFootnoteFont.emojiSize) - .emojiBaselineOffset(Font.scaledFootnoteFont.emojiBaselineOffset) - .font(.scaledFootnote) - .fontWeight(.bold) - .foregroundColor(theme.labelColor) - Text("@\(account.acct)") - .font(.scaledFootnote) - .foregroundStyle(theme.tintColor) - } - } - } - } - } - - @ViewBuilder - private var suggestionsTagView: some View { - if viewModel.showRecentsTagsInline { - recentTagsSuggestionView - } else { - remoteTagsSuggestionView - } - } - - private var recentTagsSuggestionView: some View { - ForEach(recentTags) { tag in - Button { - withAnimation { - isTagSuggestionExpanded = false - viewModel.selectHashtagSuggestion(tag: tag.title) - } - tag.lastUse = Date() - } label: { - VStack(alignment: .leading) { - Text("#\(tag.title)") - .font(.scaledFootnote) - .fontWeight(.bold) - .foregroundColor(theme.labelColor) - } - } - } - } - - private var remoteTagsSuggestionView: some View { - ForEach(viewModel.tagsSuggestions) { tag in - Button { - withAnimation { - isTagSuggestionExpanded = false - viewModel.selectHashtagSuggestion(tag: tag.name) - } - if let index = recentTags.firstIndex(where: { $0.title.lowercased() == tag.name.lowercased() }) { - recentTags[index].lastUse = Date() - } else { - context.insert(RecentTag(title: tag.name)) - } - } label: { - VStack(alignment: .leading) { - Text("#\(tag.name)") - .font(.scaledFootnote) - .fontWeight(.bold) - .foregroundColor(theme.labelColor) - Text("tag.suggested.mentions-\(tag.totalUses)") - .font(.scaledFootnote) - .foregroundStyle(theme.tintColor) - } - } - } - } - - private var expandedTagsSuggestionView: some View { - ScrollView(.vertical) { - LazyVStack(alignment: .leading, spacing: 12) { - Text("status.editor.language-select.recently-used") - .font(.scaledSubheadline) - .foregroundStyle(theme.labelColor) - .fontWeight(.bold) - ForEach(recentTags) { tag in - HStack { - Button { - tag.lastUse = Date() - withAnimation { - isTagSuggestionExpanded = false - viewModel.selectHashtagSuggestion(tag: tag.title) - } - } label: { - Text("#\(tag.title)") - .font(.scaledFootnote) - .fontWeight(.bold) - .foregroundColor(theme.labelColor) - } - Spacer() - } - } - } - .padding(.horizontal, .layoutPadding) - } - .frame(height: 200) - .gesture(DragGesture(minimumDistance: 0, coordinateSpace: .local) - .onEnded({ value in - withAnimation { - if value.translation.height > 0 { - isTagSuggestionExpanded = false - } - } - })) - } -}