| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- import SwiftUI
- struct CreateAppSheetView: View {
- struct NewAppInput {
- let name: String
- let url: URL
- }
- @Environment(\.dismiss) private var dismiss
- @State private var name = ""
- @State private var urlText = ""
- @State private var urlError: String?
- let onSave: (NewAppInput) -> Void
- var body: some View {
- ZStack {
- backgroundLayer
- VStack(spacing: 22) {
- header
- formContent
- footer
- }
- .padding(.horizontal, 20)
- .padding(.vertical, 16)
- }
- }
- private var backgroundLayer: some View {
- ZStack {
- LinearGradient(
- colors: [Color.black.opacity(0.95), Color(red: 0.05, green: 0.08, blue: 0.1)],
- startPoint: .topLeading,
- endPoint: .bottomTrailing
- )
- .ignoresSafeArea()
- Circle()
- .fill(Color.blue.opacity(0.18))
- .blur(radius: 90)
- .frame(width: 360, height: 360)
- .offset(x: -70, y: -120)
- Circle()
- .fill(Color.green.opacity(0.16))
- .blur(radius: 100)
- .frame(width: 380, height: 380)
- .offset(x: 210, y: 80)
- }
- }
- private var header: some View {
- HStack {
- Button(action: { dismiss() }) {
- Image(systemName: "xmark")
- .font(.system(size: 17, weight: .medium))
- .foregroundStyle(.white.opacity(0.86))
- }
- .buttonStyle(.plain)
- Spacer()
- Button("Save") {
- saveApp()
- }
- .buttonStyle(.plain)
- .font(.system(size: 17, weight: .semibold))
- .foregroundStyle(canSave ? Color.white.opacity(0.9) : Color.white.opacity(0.4))
- .disabled(!canSave)
- }
- }
- private var formContent: some View {
- HStack(alignment: .top, spacing: 18) {
- RoundedRectangle(cornerRadius: 16, style: .continuous)
- .fill(Color.white.opacity(0.32))
- .frame(width: 160, height: 160)
- .overlay(
- Image(systemName: "globe")
- .font(.system(size: 38, weight: .regular))
- .foregroundStyle(.white.opacity(0.24))
- )
- VStack(spacing: 10) {
- inputField(title: "Name", text: $name)
- inputField(title: "URL", text: $urlText)
- if let urlError {
- Text(urlError)
- .font(.system(size: 12, weight: .medium))
- .foregroundStyle(.red.opacity(0.9))
- .frame(maxWidth: .infinity, alignment: .leading)
- }
- }
- }
- }
- private var footer: some View {
- HStack {
- VStack(alignment: .leading, spacing: 5) {
- Text("Open with")
- .font(.system(size: 41 / 2.2, weight: .semibold))
- .foregroundStyle(.white.opacity(0.92))
- Text("This app opens in your in-app browser window.")
- .font(.system(size: 13 / 1.35))
- .foregroundStyle(.white.opacity(0.65))
- }
- Spacer()
- HStack(spacing: 8) {
- RoundedRectangle(cornerRadius: 10, style: .continuous)
- .fill(Color.white.opacity(0.14))
- .overlay(
- Image(systemName: "app.badge")
- .font(.system(size: 17, weight: .semibold))
- .foregroundStyle(.white.opacity(0.9))
- )
- .frame(width: 42, height: 42)
- Text("Apps for Google App")
- .font(.system(size: 42 / 2.2, weight: .medium))
- Image(systemName: "chevron.down")
- .font(.system(size: 11, weight: .semibold))
- .offset(y: 1)
- }
- .minimumScaleFactor(0.6)
- .lineLimit(1)
- .foregroundStyle(.white.opacity(0.88))
- }
- .padding(.top, 2)
- }
- private var canSave: Bool {
- !name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty &&
- !urlText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
- }
- private func inputField(title: String, text: Binding<String>) -> some View {
- TextField(title, text: text)
- .textFieldStyle(.plain)
- .font(.system(size: 39 / 2.2, weight: .medium))
- .foregroundStyle(.white.opacity(0.95))
- .padding(.horizontal, 22)
- .padding(.vertical, 16)
- .background(
- RoundedRectangle(cornerRadius: 20, style: .continuous)
- .fill(Color.white.opacity(0.06))
- )
- .overlay(
- RoundedRectangle(cornerRadius: 20, style: .continuous)
- .stroke(Color.white.opacity(0.3), lineWidth: 1.7)
- )
- }
- private func saveApp() {
- let trimmedName = name.trimmingCharacters(in: .whitespacesAndNewlines)
- let trimmedURL = urlText.trimmingCharacters(in: .whitespacesAndNewlines)
- guard !trimmedName.isEmpty, !trimmedURL.isEmpty else { return }
- let candidate = normalizedURLString(trimmedURL)
- guard let url = URL(string: candidate), let scheme = url.scheme, !scheme.isEmpty else {
- urlError = "Please enter a valid URL."
- return
- }
- urlError = nil
- onSave(NewAppInput(name: trimmedName, url: url))
- dismiss()
- }
- private func normalizedURLString(_ value: String) -> String {
- if value.lowercased().hasPrefix("http://") || value.lowercased().hasPrefix("https://") {
- return value
- }
- return "https://\(value)"
- }
- }
- #Preview {
- CreateAppSheetView { _ in }
- .frame(width: 800, height: 390)
- }
|