db65db6232
Android UI Tests / ui-tests (push) Has been cancelled
Localize all user-facing strings across iOS (SwiftUI), shared Kotlin, and Android Compose into en/es/fr/de/pt/it/ja/ko/nl/zh: - iOS String Catalogs: main + widget Localizable.xcstrings, InfoPlist.xcstrings (permissions), plural variations, ~200 new keys translated - Shared Kotlin ClientStrings table + Android composeResources/values-* (884 keys ×10), routed Api/ViewModel/util error & UI strings through localization - Backend-localized lookups/suggestions consumed via display names - Widget extension catalog; theme names, home-profile fallbacks, validation, network errors, accessibility labels all localized Add re-runnable verification gates: - scripts/i18n_audit.py — enumerate every literal, partition to GAP=0 - scripts/i18n_coverage.py — all 10 locales translated, format-specifier parity Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
220 lines
6.6 KiB
Swift
220 lines
6.6 KiB
Swift
import SwiftUI
|
|
import ComposeApp
|
|
|
|
// Action buttons accept a shared TaskViewModel from the parent view to avoid redundant instances.
|
|
|
|
// MARK: - Edit Task Button
|
|
struct EditTaskButton: View {
|
|
let taskId: Int32
|
|
let onCompletion: () -> Void
|
|
let onError: (String) -> Void
|
|
|
|
var body: some View {
|
|
Button(action: {
|
|
// Edit navigates to edit screen - handled by parent
|
|
onCompletion()
|
|
}) {
|
|
Label("Edit", systemImage: "pencil")
|
|
.font(.subheadline)
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
.buttonStyle(.bordered)
|
|
.accessibilityLabel("Edit task")
|
|
.accessibilityHint("Double tap to edit this task")
|
|
}
|
|
}
|
|
|
|
// MARK: - Cancel Task Button
|
|
struct CancelTaskButton: View {
|
|
let taskId: Int32
|
|
let onCompletion: () -> Void
|
|
let onError: (String) -> Void
|
|
|
|
let viewModel: TaskViewModel
|
|
@State private var showConfirmation = false
|
|
|
|
var body: some View {
|
|
Button(action: {
|
|
showConfirmation = true
|
|
}) {
|
|
Label("Cancel", systemImage: "xmark.circle")
|
|
.font(.subheadline)
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
.buttonStyle(.bordered)
|
|
.tint(Color.appError)
|
|
.accessibilityLabel("Cancel task")
|
|
.accessibilityHint("Double tap to cancel this task")
|
|
.alert("Cancel Task", isPresented: $showConfirmation) {
|
|
Button("Cancel", role: .cancel) { }
|
|
Button("Cancel Task", role: .destructive) {
|
|
viewModel.cancelTask(id: taskId) { success in
|
|
if success {
|
|
onCompletion()
|
|
} else {
|
|
onError(String(localized: "Failed to cancel task"))
|
|
}
|
|
}
|
|
}
|
|
} message: {
|
|
Text("Are you sure you want to cancel this task? You can undo this later.")
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Uncancel (Restore) Task Button
|
|
struct UncancelTaskButton: View {
|
|
let taskId: Int32
|
|
let onCompletion: () -> Void
|
|
let onError: (String) -> Void
|
|
|
|
let viewModel: TaskViewModel
|
|
|
|
var body: some View {
|
|
Button(action: {
|
|
viewModel.uncancelTask(id: taskId) { success in
|
|
if success {
|
|
onCompletion()
|
|
} else {
|
|
onError(String(localized: "Failed to restore task"))
|
|
}
|
|
}
|
|
}) {
|
|
Label("Restore", systemImage: "arrow.uturn.backward")
|
|
.font(.subheadline)
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
.buttonStyle(.borderedProminent)
|
|
.tint(Color.appPrimary)
|
|
.accessibilityLabel("Restore task")
|
|
.accessibilityHint("Double tap to restore this task")
|
|
}
|
|
}
|
|
|
|
// MARK: - Mark In Progress Button
|
|
struct MarkInProgressButton: View {
|
|
let taskId: Int32
|
|
let onCompletion: () -> Void
|
|
let onError: (String) -> Void
|
|
|
|
let viewModel: TaskViewModel
|
|
|
|
var body: some View {
|
|
Button(action: {
|
|
viewModel.markInProgress(id: taskId) { success in
|
|
if success {
|
|
onCompletion()
|
|
} else {
|
|
onError(String(localized: "Failed to mark task in progress"))
|
|
}
|
|
}
|
|
}) {
|
|
HStack {
|
|
Image(systemName: "play.circle.fill")
|
|
.resizable()
|
|
.frame(width: 18, height: 18)
|
|
Text("In Progress")
|
|
.font(.subheadline.weight(.semibold))
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
.buttonStyle(.bordered)
|
|
.tint(Color.appAccent)
|
|
.accessibilityLabel("Mark as in progress")
|
|
.accessibilityHint("Double tap to mark this task as in progress")
|
|
}
|
|
}
|
|
|
|
// MARK: - Complete Task Button
|
|
struct CompleteTaskButton: View {
|
|
let taskId: Int32
|
|
let onCompletion: () -> Void
|
|
let onError: (String) -> Void
|
|
|
|
var body: some View {
|
|
Button(action: {
|
|
// Complete shows dialog - handled by parent
|
|
onCompletion()
|
|
}) {
|
|
HStack {
|
|
Image(systemName: "checkmark.circle.fill")
|
|
.resizable()
|
|
.frame(width: 18, height: 18)
|
|
Text("Complete")
|
|
.font(.subheadline.weight(.semibold))
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
.buttonStyle(.borderedProminent)
|
|
.accessibilityLabel("Complete task")
|
|
.accessibilityHint("Double tap to complete this task")
|
|
}
|
|
}
|
|
|
|
// MARK: - Archive Task Button
|
|
struct ArchiveTaskButton: View {
|
|
let taskId: Int32
|
|
let onCompletion: () -> Void
|
|
let onError: (String) -> Void
|
|
|
|
let viewModel: TaskViewModel
|
|
@State private var showConfirmation = false
|
|
|
|
var body: some View {
|
|
Button(action: {
|
|
showConfirmation = true
|
|
}) {
|
|
Label("Archive", systemImage: "archivebox")
|
|
.font(.subheadline)
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
.buttonStyle(.bordered)
|
|
.tint(.gray)
|
|
.accessibilityLabel("Archive task")
|
|
.accessibilityHint("Double tap to archive this task")
|
|
.alert("Archive Task", isPresented: $showConfirmation) {
|
|
Button("Cancel", role: .cancel) { }
|
|
Button("Archive", role: .destructive) {
|
|
viewModel.archiveTask(id: taskId) { success in
|
|
if success {
|
|
onCompletion()
|
|
} else {
|
|
onError(String(localized: "Failed to archive task"))
|
|
}
|
|
}
|
|
}
|
|
} message: {
|
|
Text("Are you sure you want to archive this task? You can unarchive it later from archived tasks.")
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Unarchive Task Button
|
|
struct UnarchiveTaskButton: View {
|
|
let taskId: Int32
|
|
let onCompletion: () -> Void
|
|
let onError: (String) -> Void
|
|
|
|
let viewModel: TaskViewModel
|
|
|
|
var body: some View {
|
|
Button(action: {
|
|
viewModel.unarchiveTask(id: taskId) { success in
|
|
if success {
|
|
onCompletion()
|
|
} else {
|
|
onError(String(localized: "Failed to unarchive task"))
|
|
}
|
|
}
|
|
}) {
|
|
Label("Unarchive", systemImage: "tray.and.arrow.up")
|
|
.font(.subheadline)
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
.buttonStyle(.bordered)
|
|
.tint(Color.appPrimary)
|
|
.accessibilityLabel("Unarchive task")
|
|
.accessibilityHint("Double tap to unarchive this task")
|
|
}
|
|
}
|