mirror of
https://github.com/carey314/mio-plugin-airdrop.git
synced 2026-06-11 12:04:31 +00:00
136 lines
5.0 KiB
Swift
136 lines
5.0 KiB
Swift
|
|
//
|
|||
|
|
// ExpandedView.swift
|
|||
|
|
// MioIsland AirDrop Plugin
|
|||
|
|
//
|
|||
|
|
// Main panel rendered when the user opens the AirDrop plugin in
|
|||
|
|
// Mio Island. Card-in-container layout: top transparent strip for
|
|||
|
|
// the notch + floating back chip, themed card below with the drop
|
|||
|
|
// zone + "choose files" button.
|
|||
|
|
//
|
|||
|
|
|
|||
|
|
import AppKit
|
|||
|
|
import SwiftUI
|
|||
|
|
|
|||
|
|
struct ExpandedView: View {
|
|||
|
|
@ObservedObject private var state: AirDropState = .shared
|
|||
|
|
|
|||
|
|
private static let ink = Color.white
|
|||
|
|
private static let base = Color(red: 0x0A / 255.0, green: 0x0A / 255.0, blue: 0x0A / 255.0)
|
|||
|
|
|
|||
|
|
// MARK: - Body
|
|||
|
|
|
|||
|
|
var body: some View {
|
|||
|
|
Group {
|
|||
|
|
if HostVersionCheck.isOK() {
|
|||
|
|
playingCard
|
|||
|
|
} else {
|
|||
|
|
hostUpgradeCard
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
.animation(.easeInOut(duration: 0.2), value: state.phase)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// MARK: - Main panel (when host version OK)
|
|||
|
|
|
|||
|
|
private var playingCard: some View {
|
|||
|
|
// Layout (panel 440×280):
|
|||
|
|
// 40 top transparent (notch + back chevron area)
|
|||
|
|
// 20 margin
|
|||
|
|
// 180 card (14 pad + ~135 content + 14 pad)
|
|||
|
|
// 20 margin
|
|||
|
|
// = 260pt used, 20pt buffer for SwiftUI rounding.
|
|||
|
|
VStack(spacing: 0) {
|
|||
|
|
Color.clear.frame(height: 40) // notch strip
|
|||
|
|
Color.clear.frame(height: 20) // margin top
|
|||
|
|
|
|||
|
|
cardContent
|
|||
|
|
.padding(EdgeInsets(top: 14, leading: 16, bottom: 14, trailing: 16))
|
|||
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|||
|
|
.background(
|
|||
|
|
RoundedRectangle(cornerRadius: 14, style: .continuous)
|
|||
|
|
.fill(Self.base.opacity(0.88))
|
|||
|
|
)
|
|||
|
|
.overlay(
|
|||
|
|
RoundedRectangle(cornerRadius: 14, style: .continuous)
|
|||
|
|
.strokeBorder(Color.white.opacity(0.12), lineWidth: 0.5)
|
|||
|
|
)
|
|||
|
|
.clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous))
|
|||
|
|
|
|||
|
|
Color.clear.frame(height: 20) // margin bottom
|
|||
|
|
}
|
|||
|
|
.padding(.horizontal, 16)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private var cardContent: some View {
|
|||
|
|
VStack(spacing: 10) {
|
|||
|
|
// Header row — just the title. The whole DropZoneView
|
|||
|
|
// below is the tap target now; no redundant chip button.
|
|||
|
|
HStack(alignment: .center, spacing: 6) {
|
|||
|
|
Image(systemName: "airplayaudio")
|
|||
|
|
.font(.system(size: 13, weight: .semibold))
|
|||
|
|
.foregroundColor(Self.ink.opacity(0.9))
|
|||
|
|
Text(L10n.title)
|
|||
|
|
.font(.system(size: 14, weight: .semibold))
|
|||
|
|
.foregroundColor(Self.ink.opacity(0.95))
|
|||
|
|
Spacer()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Big tap target for the whole card body.
|
|||
|
|
DropZoneView()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// MARK: - Host upgrade card
|
|||
|
|
|
|||
|
|
private var hostUpgradeCard: some View {
|
|||
|
|
VStack(spacing: 0) {
|
|||
|
|
Color.clear.frame(height: 40)
|
|||
|
|
Color.clear.frame(height: 20)
|
|||
|
|
|
|||
|
|
VStack(spacing: 12) {
|
|||
|
|
HStack(spacing: 14) {
|
|||
|
|
Image(systemName: "exclamationmark.triangle.fill")
|
|||
|
|
.font(.system(size: 24, weight: .regular))
|
|||
|
|
.foregroundColor(.orange)
|
|||
|
|
.frame(width: 48, height: 48)
|
|||
|
|
.background(
|
|||
|
|
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
|||
|
|
.fill(Color.orange.opacity(0.12))
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
VStack(alignment: .leading, spacing: 4) {
|
|||
|
|
Text(L10n.hostUpgradeTitle)
|
|||
|
|
.font(.system(size: 14, weight: .semibold))
|
|||
|
|
.foregroundColor(Self.ink.opacity(0.9))
|
|||
|
|
Text(L10n.hostUpgradeHint)
|
|||
|
|
.font(.system(size: 11))
|
|||
|
|
.foregroundColor(Self.ink.opacity(0.55))
|
|||
|
|
.lineLimit(2)
|
|||
|
|
.fixedSize(horizontal: false, vertical: true)
|
|||
|
|
}
|
|||
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Text("Host: v\(HostVersionCheck.hostVersion()) · required: v\(HostVersionCheck.minHost)+")
|
|||
|
|
.font(.system(size: 10, design: .monospaced))
|
|||
|
|
.foregroundColor(Self.ink.opacity(0.4))
|
|||
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|||
|
|
}
|
|||
|
|
.padding(EdgeInsets(top: 18, leading: 16, bottom: 18, trailing: 16))
|
|||
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|||
|
|
.background(
|
|||
|
|
RoundedRectangle(cornerRadius: 14, style: .continuous)
|
|||
|
|
.fill(Self.base.opacity(0.88))
|
|||
|
|
)
|
|||
|
|
.overlay(
|
|||
|
|
RoundedRectangle(cornerRadius: 14, style: .continuous)
|
|||
|
|
.strokeBorder(Color.white.opacity(0.12), lineWidth: 0.5)
|
|||
|
|
)
|
|||
|
|
.clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous))
|
|||
|
|
|
|||
|
|
Color.clear.frame(height: 20)
|
|||
|
|
}
|
|||
|
|
.padding(.horizontal, 16)
|
|||
|
|
}
|
|||
|
|
}
|