* Implement language selection in status editor * Apply the correct language on replies and edits * Use sheet for language selector Co-authored-by: Thomas Ricouard <ricouard77@gmail.com>
This commit is contained in:
parent
e0f8c9a3c9
commit
382ebcf8f7
|
@ -50,6 +50,7 @@ public protocol AnyStatus {
|
|||
var spoilerText: String { get }
|
||||
var filtered: [Filtered]? { get }
|
||||
var sensitive: Bool { get }
|
||||
var language: String? { get }
|
||||
}
|
||||
|
||||
|
||||
|
@ -83,6 +84,7 @@ public struct Status: AnyStatus, Codable, Identifiable {
|
|||
public let spoilerText: String
|
||||
public let filtered: [Filtered]?
|
||||
public let sensitive: Bool
|
||||
public let language: String?
|
||||
|
||||
public static func placeholder() -> Status {
|
||||
.init(id: UUID().uuidString,
|
||||
|
@ -109,7 +111,8 @@ public struct Status: AnyStatus, Codable, Identifiable {
|
|||
poll: nil,
|
||||
spoilerText: "",
|
||||
filtered: [],
|
||||
sensitive: false)
|
||||
sensitive: false,
|
||||
language: nil)
|
||||
}
|
||||
|
||||
public static func placeholders() -> [Status] {
|
||||
|
@ -146,4 +149,5 @@ public struct ReblogStatus: AnyStatus, Codable, Identifiable {
|
|||
public let spoilerText: String
|
||||
public let filtered: [Filtered]?
|
||||
public let sensitive: Bool
|
||||
public let language: String?
|
||||
}
|
||||
|
|
|
@ -80,7 +80,8 @@ public struct StatusData: Encodable {
|
|||
public let spoilerText: String?
|
||||
public let mediaIds: [String]?
|
||||
public let poll: PollData?
|
||||
|
||||
public let language: String?
|
||||
|
||||
public struct PollData: Encodable {
|
||||
public let options: [String]
|
||||
public let multiple: Bool
|
||||
|
@ -98,12 +99,14 @@ public struct StatusData: Encodable {
|
|||
inReplyToId: String? = nil,
|
||||
spoilerText: String? = nil,
|
||||
mediaIds: [String]? = nil,
|
||||
poll: PollData? = nil) {
|
||||
poll: PollData? = nil,
|
||||
language: String? = nil) {
|
||||
self.status = status
|
||||
self.visibility = visibility
|
||||
self.inReplyToId = inReplyToId
|
||||
self.spoilerText = spoilerText
|
||||
self.mediaIds = mediaIds
|
||||
self.poll = poll
|
||||
self.language = language
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,15 +5,16 @@ import Models
|
|||
import Env
|
||||
|
||||
struct StatusEditorAccessoryView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@EnvironmentObject private var preferences: UserPreferences
|
||||
@EnvironmentObject private var theme: Theme
|
||||
@EnvironmentObject private var currentInstance: CurrentInstance
|
||||
|
||||
@FocusState<Bool>.Binding var isSpoilerTextFocused: Bool
|
||||
@ObservedObject var viewModel: StatusEditorViewModel
|
||||
|
||||
@State private var isDrafsSheetDisplayed: Bool = false
|
||||
@State private var isLanguageSheetDisplayed: Bool = false
|
||||
@State private var languageSearch: String = ""
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
|
@ -25,18 +26,6 @@ struct StatusEditorAccessoryView: View {
|
|||
}
|
||||
.disabled(viewModel.showPoll)
|
||||
|
||||
Button {
|
||||
viewModel.insertStatusText(text: " @")
|
||||
} label: {
|
||||
Image(systemName: "at")
|
||||
}
|
||||
|
||||
Button {
|
||||
viewModel.insertStatusText(text: " #")
|
||||
} label: {
|
||||
Image(systemName: "number")
|
||||
}
|
||||
|
||||
Button {
|
||||
withAnimation {
|
||||
viewModel.showPoll.toggle()
|
||||
|
@ -61,9 +50,18 @@ struct StatusEditorAccessoryView: View {
|
|||
} label: {
|
||||
Image(systemName: "archivebox")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Button {
|
||||
isLanguageSheetDisplayed.toggle()
|
||||
} label: {
|
||||
if let language = viewModel.selectedLanguage {
|
||||
Text(language.uppercased())
|
||||
} else {
|
||||
Image(systemName: "globe")
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
characterCountView
|
||||
|
@ -76,6 +74,54 @@ struct StatusEditorAccessoryView: View {
|
|||
.sheet(isPresented: $isDrafsSheetDisplayed) {
|
||||
draftsSheetView
|
||||
}
|
||||
.sheet(isPresented: $isLanguageSheetDisplayed, content: {
|
||||
languageSheetView
|
||||
})
|
||||
.onAppear {
|
||||
viewModel.setInitialLanguageSelection(preference: preferences.serverPreferences?.postLanguage)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func languageTextView(isoCode: String, nativeName: String?, name: String?) -> some View {
|
||||
if let nativeName = nativeName, let name = name {
|
||||
Text("\(nativeName) (\(name))")
|
||||
} else {
|
||||
Text(isoCode.uppercased())
|
||||
}
|
||||
}
|
||||
|
||||
private var languageSheetView: some View {
|
||||
NavigationStack {
|
||||
List {
|
||||
ForEach(availableLanguages, id: \.0) { (isoCode, nativeName, name) in
|
||||
HStack {
|
||||
languageTextView(isoCode: isoCode, nativeName: nativeName, name: name)
|
||||
.tag(isoCode)
|
||||
Spacer()
|
||||
if isoCode == viewModel.selectedLanguage {
|
||||
Image(systemName: "checkmark")
|
||||
}
|
||||
}
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
viewModel.selectedLanguage = isoCode
|
||||
isLanguageSheetDisplayed = false
|
||||
}
|
||||
}
|
||||
}
|
||||
.searchable(text: $languageSearch)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("Cancel", action: { isLanguageSheetDisplayed = false })
|
||||
}
|
||||
}
|
||||
.navigationTitle("Select Languages")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
}
|
||||
}
|
||||
|
||||
private var draftsSheetView: some View {
|
||||
|
@ -98,7 +144,7 @@ struct StatusEditorAccessoryView: View {
|
|||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("Cancel", action: { dismiss() })
|
||||
Button("Cancel", action: { isDrafsSheetDisplayed = false })
|
||||
}
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
|
@ -115,4 +161,23 @@ struct StatusEditorAccessoryView: View {
|
|||
.foregroundColor(.gray)
|
||||
.font(.callout)
|
||||
}
|
||||
|
||||
private var availableLanguages: [(String, String?, String?)] {
|
||||
Locale.LanguageCode.isoLanguageCodes
|
||||
.filter { $0.identifier.count == 2 } // Mastodon only supports ISO 639-1 (two-letter) codes
|
||||
.map { lang in
|
||||
let nativeLocale = Locale(languageComponents: Locale.Language.Components(languageCode: lang))
|
||||
return (
|
||||
lang.identifier,
|
||||
nativeLocale.localizedString(forLanguageCode: lang.identifier),
|
||||
Locale.current.localizedString(forLanguageCode: lang.identifier)
|
||||
)
|
||||
}
|
||||
.filter { (identifier, nativeLocale, locale) in
|
||||
guard !languageSearch.isEmpty else {
|
||||
return true
|
||||
}
|
||||
return nativeLocale?.lowercased().hasPrefix(languageSearch.lowercased()) == true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@ public class StatusEditorViewModel: ObservableObject {
|
|||
|
||||
@Published var mentionsSuggestions: [Account] = []
|
||||
@Published var tagsSuggestions: [Tag] = []
|
||||
@Published var selectedLanguage: String?
|
||||
private var currentSuggestionRange: NSRange?
|
||||
|
||||
private var embededStatusURL: URL? {
|
||||
|
@ -94,6 +95,17 @@ public class StatusEditorViewModel: ObservableObject {
|
|||
statusText = .init(string: text)
|
||||
selectedRange = .init(location: text.utf16.count, length: 0)
|
||||
}
|
||||
|
||||
func setInitialLanguageSelection(preference: String?) {
|
||||
switch mode {
|
||||
case .replyTo(let status), .edit(let status):
|
||||
selectedLanguage = status.language
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
selectedLanguage = selectedLanguage ?? preference ?? currentAccount?.source?.language
|
||||
}
|
||||
|
||||
private func getPollOptionsForAPI() -> [String]? {
|
||||
let options = pollOptions.filter { !$0.trimmingCharacters(in: .whitespaces).isEmpty }
|
||||
|
@ -116,7 +128,8 @@ public class StatusEditorViewModel: ObservableObject {
|
|||
inReplyToId: mode.replyToStatus?.id,
|
||||
spoilerText: spoilerOn ? spoilerText : nil,
|
||||
mediaIds: mediasImages.compactMap{ $0.mediaAttachement?.id },
|
||||
poll: pollData)
|
||||
poll: pollData,
|
||||
language: selectedLanguage)
|
||||
switch mode {
|
||||
case .new, .replyTo, .quote, .mention, .shareExtension:
|
||||
postStatus = try await client.post(endpoint: Statuses.postStatus(json: data))
|
||||
|
|
Loading…
Reference in New Issue