Aggiunge annunci in-app/sito, contatti e migliora UI copertina sul portale.
Include admin/API/banner nativi, form contatti e reset copertina standard con layout edit più ampio. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -22,6 +22,7 @@ struct MatchesScreen: View {
|
||||
@State private var snackbar: String?
|
||||
@State private var showLanguagePicker = false
|
||||
@State private var languageTick = 0
|
||||
@State private var announcements: [AnnouncementDto] = []
|
||||
|
||||
var body: some View {
|
||||
MatchScreenScaffold(
|
||||
@@ -48,6 +49,18 @@ struct MatchesScreen: View {
|
||||
.padding(.vertical, 8)
|
||||
},
|
||||
content: {
|
||||
VStack(spacing: 0) {
|
||||
if !announcements.isEmpty {
|
||||
AnnouncementBanners(
|
||||
announcements: announcements,
|
||||
onDismiss: { id in
|
||||
container.announcementStore.dismiss(id)
|
||||
announcements.removeAll { $0.id == id }
|
||||
}
|
||||
)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.top, 8)
|
||||
}
|
||||
Group {
|
||||
if loading {
|
||||
ProgressView().tint(MatchColors.primaryRed)
|
||||
@@ -135,6 +148,7 @@ struct MatchesScreen: View {
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
}
|
||||
)
|
||||
.task(id: refreshToken) { reload(showSpinner: refreshToken == 0) }
|
||||
.alert(L10n.t("sheet.configure.now.title"), isPresented: Binding(get: { resumeMatch != nil }, set: { if !$0 { resumeMatch = nil } })) {
|
||||
@@ -277,6 +291,7 @@ struct MatchesScreen: View {
|
||||
} catch {
|
||||
self.error = UserFacingError.message(for: error) ?? L10n.t("matches.msg.load.error")
|
||||
}
|
||||
announcements = await container.visibleAnnouncements()
|
||||
loading = false
|
||||
refreshing = false
|
||||
}
|
||||
@@ -418,6 +433,77 @@ private struct MatchListCard: View {
|
||||
}
|
||||
}
|
||||
|
||||
struct AnnouncementBanners: View {
|
||||
let announcements: [AnnouncementDto]
|
||||
let onDismiss: (String) -> Void
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 8) {
|
||||
ForEach(announcements) { item in
|
||||
AnnouncementBanner(item: item, onDismiss: { onDismiss(item.id) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct AnnouncementBanner: View {
|
||||
let item: AnnouncementDto
|
||||
let onDismiss: () -> Void
|
||||
@Environment(\.openURL) private var openURL
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack(alignment: .top, spacing: 12) {
|
||||
Image(systemName: iconName)
|
||||
.foregroundStyle(accent)
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(item.title)
|
||||
.font(MatchTypography.titleMedium)
|
||||
.foregroundStyle(accent)
|
||||
Text(item.body)
|
||||
.font(MatchTypography.bodyMedium)
|
||||
}
|
||||
Spacer(minLength: 8)
|
||||
if item.dismissible {
|
||||
Button(action: onDismiss) {
|
||||
Image(systemName: "xmark")
|
||||
.foregroundStyle(MatchColors.textSecondary)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityLabel(L10n.t("announcement.dismiss"))
|
||||
}
|
||||
}
|
||||
if let urlString = item.actionUrl, let url = URL(string: urlString), !urlString.isEmpty {
|
||||
Button(item.actionLabel?.isEmpty == false ? item.actionLabel! : L10n.t("announcement.open")) {
|
||||
openURL(url)
|
||||
}
|
||||
.font(MatchTypography.labelLarge)
|
||||
.foregroundStyle(accent)
|
||||
}
|
||||
}
|
||||
.padding(14)
|
||||
.background(accent.opacity(0.16), in: RoundedRectangle(cornerRadius: 12))
|
||||
}
|
||||
|
||||
private var accent: Color {
|
||||
if item.severity == "critical" || item.kind == "maintenance" {
|
||||
return MatchColors.accentYellow
|
||||
}
|
||||
if item.kind == "update" {
|
||||
return MatchColors.successGreen
|
||||
}
|
||||
return MatchColors.primaryRed
|
||||
}
|
||||
|
||||
private var iconName: String {
|
||||
switch item.kind {
|
||||
case "maintenance": return "wrench.and.screwdriver.fill"
|
||||
case "update": return "arrow.down.app.fill"
|
||||
default: return "megaphone.fill"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct NewMatchSheet: View {
|
||||
let onSchedule: () -> Void
|
||||
let onQuickStart: () -> Void
|
||||
|
||||
Reference in New Issue
Block a user