mirror of
https://github.com/home-assistant/iOS.git
synced 2026-06-24 10:49:41 -05:00
<!-- Thank you for submitting a Pull Request and helping to improve Home Assistant. Please complete the following sections to help the processing and review of your changes. Please do not delete anything from this template. --> ## Summary <!-- Provide a brief summary of the changes you have made and most importantly what they aim to achieve --> ## Screenshots <!-- If this is a user-facing change not in the frontend, please include screenshots in light and dark mode. --> <img width="1206" height="2622" alt="Simulator Screenshot - Daily tester 2 - 2026-06-08 at 11 49 31" src="https://github.com/user-attachments/assets/f4e5e291-2377-46d5-8ac5-bc0c6279bba7" /> ## Link to pull request in Documentation repository <!-- Pull requests that add, change or remove functionality must have a corresponding pull request in the Companion App Documentation repository (https://github.com/home-assistant/companion.home-assistant). Please add the number of this pull request after the "#" --> Documentation: home-assistant/companion.home-assistant# ## Any other notes <!-- If there is any other information of note, like if this Pull Request is part of a bigger change, please include it here. -->
156 lines
4.8 KiB
Swift
156 lines
4.8 KiB
Swift
import SFSafeSymbols
|
|
import Shared
|
|
import SwiftUI
|
|
import UIKit
|
|
|
|
struct TestFlightCommunicationView: View {
|
|
let message: TestFlightMessage
|
|
let onViewed: () -> Void
|
|
|
|
@Environment(\.dismiss) private var dismiss
|
|
@State private var didRecordView = false
|
|
|
|
var body: some View {
|
|
NavigationView {
|
|
ScrollView {
|
|
VStack(alignment: .leading, spacing: DesignSystem.Spaces.five) {
|
|
header
|
|
items
|
|
}
|
|
.padding(.horizontal, DesignSystem.Spaces.four)
|
|
.padding(.top, DesignSystem.Spaces.two)
|
|
.frame(maxWidth: .infinity, alignment: .center)
|
|
}
|
|
.toolbar {
|
|
ToolbarItem(placement: .cancellationAction) {
|
|
CloseButton {
|
|
dismiss()
|
|
}
|
|
}
|
|
}
|
|
.safeAreaInset(edge: .bottom) {
|
|
bottomButtons
|
|
}
|
|
.onAppear {
|
|
recordViewIfNeeded()
|
|
}
|
|
}
|
|
.navigationViewStyle(.stack)
|
|
}
|
|
|
|
private var header: some View {
|
|
VStack(alignment: .center, spacing: DesignSystem.Spaces.one) {
|
|
Label(L10n.TestFlightCommunication.badgeTitle, systemSymbol: .testtube2)
|
|
.font(.caption.bold())
|
|
.foregroundStyle(.orange)
|
|
Text(message.title)
|
|
.font(.title.bold())
|
|
.foregroundStyle(.primary)
|
|
.multilineTextAlignment(.center)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
}
|
|
.frame(maxWidth: .infinity, alignment: .center)
|
|
}
|
|
|
|
private var items: some View {
|
|
VStack(alignment: .leading, spacing: DesignSystem.Spaces.four) {
|
|
ForEach(Array(message.items.enumerated()), id: \.element.id) { offset, item in
|
|
TestFlightItemRow(item: item, iconColor: TestFlightColors.iconColor(for: offset))
|
|
}
|
|
}
|
|
}
|
|
|
|
private var bottomButtons: some View {
|
|
VStack(spacing: DesignSystem.Spaces.two) {
|
|
if let callToAction = message.callToAction {
|
|
Link(destination: callToAction.url) {
|
|
Text(callToAction.title)
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
.buttonStyle(.primaryButton)
|
|
}
|
|
if message.callToAction != nil {
|
|
Button {
|
|
dismiss()
|
|
} label: {
|
|
Text(L10n.continueLabel)
|
|
}
|
|
.buttonStyle(.secondaryButton)
|
|
} else {
|
|
Button {
|
|
dismiss()
|
|
} label: {
|
|
Text(L10n.continueLabel)
|
|
}
|
|
.buttonStyle(.primaryButton)
|
|
}
|
|
}
|
|
.padding([.horizontal, .bottom])
|
|
}
|
|
|
|
private func recordViewIfNeeded() {
|
|
guard !didRecordView else { return }
|
|
didRecordView = true
|
|
onViewed()
|
|
}
|
|
}
|
|
|
|
private struct TestFlightItemRow: View {
|
|
let item: WhatsNewItem
|
|
let iconColor: UIColor
|
|
|
|
var body: some View {
|
|
HStack(alignment: .center, spacing: DesignSystem.Spaces.three) {
|
|
TestFlightIconView(icon: item.icon, color: iconColor)
|
|
.frame(width: 48, height: 48)
|
|
.accessibilityHidden(true)
|
|
|
|
VStack(alignment: .leading, spacing: DesignSystem.Spaces.half) {
|
|
Text(item.title)
|
|
.font(.headline)
|
|
.foregroundStyle(.primary)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
|
|
Text(item.body)
|
|
.font(.body)
|
|
.foregroundStyle(.secondary)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
}
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
}
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
}
|
|
}
|
|
|
|
private struct TestFlightIconView: View {
|
|
let icon: WhatsNewIcon
|
|
let color: UIColor
|
|
|
|
var body: some View {
|
|
switch icon {
|
|
case let .sfSymbol(symbol):
|
|
Image(systemSymbol: symbol)
|
|
.font(.system(size: 38, weight: .regular))
|
|
.foregroundStyle(Color(uiColor: color))
|
|
case let .materialDesign(icon):
|
|
Image(uiImage: icon.image(ofSize: CGSize(width: 38, height: 38), color: color))
|
|
.renderingMode(.template)
|
|
.foregroundStyle(Color(uiColor: color))
|
|
}
|
|
}
|
|
}
|
|
|
|
private enum TestFlightColors {
|
|
private static let colors: [UIColor] = [
|
|
.systemOrange,
|
|
.haPrimary,
|
|
.systemBlue,
|
|
.systemGreen,
|
|
.systemRed,
|
|
]
|
|
|
|
static func iconColor(for index: Int) -> UIColor {
|
|
colors[index % colors.count]
|
|
}
|
|
}
|