import SwiftUI import UniformTypeIdentifiers struct RootView: View { @EnvironmentObject private var model: AppModel @Environment(\.scenePhase) private var scenePhase @State private var showImporter = false @Namespace private var modeNamespace var body: some View { ZStack { AppBackground() ScrollView { VStack(spacing: 22) { header modePicker content footer } .padding(.horizontal, 20) .padding(.top, 14) .padding(.bottom, 36) .frame(maxWidth: 620) .frame(maxWidth: .infinity) } .scrollDismissesKeyboard(.interactively) if let notice = model.notice { VStack { Spacer() ToastView(message: notice) .padding(.bottom, 24) .transition(.move(edge: .bottom).combined(with: .opacity)) } .animation(.spring(response: 0.35, dampingFraction: 0.82), value: model.notice) .allowsHitTesting(false) } } .fileImporter( isPresented: $showImporter, allowedContentTypes: [.data], allowsMultipleSelection: true ) { result in model.handleImport(result) } .alert( "Something went wrong", isPresented: Binding( get: { model.errorMessage != nil }, set: { if !$0 { model.errorMessage = nil } } ) ) { Button("OK", role: .cancel) { model.errorMessage = nil } } message: { Text(model.errorMessage ?? "") } .onChange(of: scenePhase) { phase in model.handleScenePhase(phase) } } private var header: some View { HStack(alignment: .center, spacing: 13) { LogoMark() VStack(alignment: .leading, spacing: 1) { Text("share") .font(.system(size: 27, weight: .bold, design: .rounded)) .foregroundStyle(.white) Text("direct device-to-device transfer") .font(.system(size: 12, weight: .medium, design: .rounded)) .foregroundStyle(Theme.secondaryText) } Spacer() } } private var modePicker: some View { HStack(spacing: 5) { ForEach(RoomMode.allCases) { mode in Button { withAnimation(.spring(response: 0.34, dampingFraction: 0.82)) { model.mode = mode } } label: { HStack(spacing: 7) { Image(systemName: mode.icon) .font(.system(size: 14, weight: .semibold)) Text(mode.title) .font(.system(.subheadline, design: .rounded).weight(.semibold)) .lineLimit(1) .minimumScaleFactor(0.85) } .foregroundStyle( model.mode == mode ? Color(hex: 0x06211C) : Theme.secondaryText ) .frame(maxWidth: .infinity) .padding(.vertical, 10) .background { if model.mode == mode { RoundedRectangle(cornerRadius: 14, style: .continuous) .fill( LinearGradient( colors: mode == .send ? [Theme.mint, Theme.mint.opacity(0.8)] : [Theme.blue, Theme.blue.opacity(0.8)], startPoint: .topLeading, endPoint: .bottomTrailing ) ) .matchedGeometryEffect(id: "mode", in: modeNamespace) } } } .buttonStyle(.plain) .frame(maxWidth: .infinity) .accessibilityAddTraits(model.mode == mode ? .isSelected : []) } } .frame(maxWidth: .infinity) .padding(4) .background(Color.white.opacity(0.055), in: RoundedRectangle(cornerRadius: 18, style: .continuous)) .overlay { RoundedRectangle(cornerRadius: 18, style: .continuous) .strokeBorder(Theme.stroke, lineWidth: 1) } .disabled(model.hasRoom || model.isBusy) .opacity(model.hasRoom || model.isBusy ? 0.55 : 1) } @ViewBuilder private var content: some View { if model.hasRoom { RoomView() .transition(.asymmetric( insertion: .move(edge: .trailing).combined(with: .opacity), removal: .move(edge: .leading).combined(with: .opacity) )) } else { SetupView(showImporter: $showImporter) .transition(.asymmetric( insertion: .move(edge: .leading).combined(with: .opacity), removal: .move(edge: .trailing).combined(with: .opacity) )) } } private var footer: some View { VStack(spacing: 6) { HStack(spacing: 6) { Image(systemName: "lock.shield") .font(.system(size: 11, weight: .semibold)) Text("End-to-end encrypted with a room-code key") .font(.system(size: 11, weight: .medium, design: .rounded)) } .foregroundStyle(Theme.tertiaryText) Text("The relay only helps peers find each other. File data goes directly between devices, or through an encrypted relay pipe when a direct path is blocked.") .font(.system(size: 10.5, design: .rounded)) .foregroundStyle(Theme.tertiaryText.opacity(0.8)) .multilineTextAlignment(.center) .lineSpacing(2) } .padding(.top, 2) .padding(.horizontal, 8) } }