Создание приложения для электронной коммерции с помощью Flutter и Medusa

Магазины электронной коммерции — это интернет-магазины, которые помогают вести торговлю без участия третьего лица. Этот учебник научит вас, как создать наш магазин электронной коммерции на бэкенде Medusa.

Flutter — это кроссплатформенный набор инструментов пользовательского интерфейса, который разработан для повторного использования кода в операционных системах, таких как iOS и Android, а также позволяет приложениям напрямую взаимодействовать с базовыми сервисами платформы.

Medusa — это open source headless commence, позволяющий пользователям создавать масштабируемые и уникальные магазины электронной коммерции и беспрепятственно настраивать продукты. Она помогает разработчикам быстро и эффективно создавать, управлять и настраивать API.

Medusa обладает множеством возможностей, среди которых простая в использовании панель администратора, бесплатное использование, множество плагинов для различных операций и большая поддержка сообщества.

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

Ниже приведена ссылка на исходный код полного приложения Flutter

https://github.com/iamVictorSam/my-store.git

Содержание
  1. Предварительные условия
  2. Настройте рабочее пространство для Medusa
  3. Установите Medusa CLI
  4. Создать сервер Medusa
  5. Запуск сервера Medusa
  6. Установка Medusa Admin
  7. Запуск панели администратора
  8. Создание нашего пользовательского продукта
  9. Настройка рабочего пространства для приложения Flutter
  10. Создайте приложение Flutter
  11. Установка пакетов
  12. Структура папок
  13. Создание всплывающего экрана
  14. Экран всплеска
  15. Добавление файла логотипа
  16. Настройка модели
  17. Создание нашей модели
  18. Выполнение запроса API из sever
  19. Настройка урла базы
  20. Получение продуктов из API
  21. Управление состоянием с помощью Getx
  22. Построение главного экрана
  23. Создание компонента карточки товара
  24. Создание компонента AllProducts
  25. Создание компонентов для дома
  26. Построение экрана подробностей
  27. Создание цены продукта
  28. Создание компонента Size
  29. Построение экрана подробной информации о продукте
  30. Что дальше
  31. Исходный код
  32. Заключение

Предварительные условия

Чтобы следовать этому руководству, на вашей машине должны быть установлены эти двоичные файлы:

  • Node и NPM: Node.js — это внутренняя среда выполнения JavaScript с открытым исходным кодом, которая запускается вне веб-браузера с помощью движка V8 и выполняет код JavaScript. Node можно загрузить с помощью страницы загрузки Node.js.
  • Flutter SDK: Это инструмент командной строки для создания и запуска проектов Flutter Flutter. Посетите сайт https://flutter.dev/docs/get-started/install, чтобы установить Flutter SDK. В этом учебнике будет использоваться последняя версия Flutter 3.0.
  • Git: Это инструмент DevOps, используемый для контроля версий и управления кодом. Посетите сайт https://git-scm.com/download, чтобы загрузить git на ваше устройство.

Настройте рабочее пространство для Medusa

Установите Medusa CLI

Прежде чем запускать сервер Medusa, сначала установите Medusa cli. Это даст доступ к серверу Medusa. Выполните приведенную ниже команду в терминале.

npm install -g @medusajs/medusa-cli
Войти в полноэкранный режим Выйти из полноэкранного режима

Создать сервер Medusa

После этого выполните приведенную ниже команду.

medusa new flutter-store --seed
Войти в полноэкранный режим Выйти из полноэкранного режима

Приведенная выше команда создает проект сервера, а также устанавливает необходимые зависимости. Флаг --seed в конце команды создает магазин по умолчанию с демонстрационными товарами.

Для загрузки изображений товаров на админку необходимо установить файловый сервис типа MinIO, S3 или DigitalOcean Spaces.

Запуск сервера Medusa

После выполнения вышеуказанной команды, устанавливающей все необходимые пакеты Medusa, ваш сервер готов к тестированию! Сначала измените директорию на папку проекта, выполнив команду ниже.

cd flutter-store

Наконец, запустите сервер с помощью команды develop.

medusa develop
Вход в полноэкранный режим Выйти из полноэкранного режима

Приведенная выше команда по умолчанию запустит ваш сервер на localhost:9000/.

Установка Medusa Admin

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

Medusa имеет встроенную панель администратора, которая помогает создавать и изменять продукты, клиентов, заказы, купоны, подарочные карты и многое другое.

Чтобы установить Medusa Admin, клонируйте репозиторий Admin с github.

git clone https://github.com/medusajs/admin my-admin 
Войдите в полноэкранный режим Выйдите из полноэкранного режима

После клонирования измените каталог на каталог my-admin с помощью команды ниже.

cd my-admin
Войти в полноэкранный режим Выйти из полноэкранного режима

Находясь в директории, выполните приведенную ниже команду для установки пакетов, необходимых для запуска панели администратора.

npm install
Войти в полноэкранный режим Выйти из полноэкранного режима

Запуск панели администратора

После того как все зависимости и пакеты будут загружены и установлены должным образом, выполните приведенную ниже команду, чтобы запустить панель администратора в браузере.

N.B.: Чтобы иметь возможность использовать панель администратора, необходимо, чтобы сервер Medusa был запущен.

npm start
Войти в полноэкранный режим Выйти из полноэкранного режима

Приведенная выше команда создаст и запустит панель администратора в вашем браузере на localhost:7000 по умолчанию.

Войдите в систему, используя email admin@medusa-test.com и пароль supersecret.

Создание нашего пользовательского продукта

Перейдите на вкладку Продукты и нажмите на Новый продукт в правом верхнем углу приборной панели, чтобы создать продукт.

Создайте столько продуктов, сколько захотите, а затем нажмите кнопку Publish, чтобы запустить их в работу.

Настройка рабочего пространства для приложения Flutter

Мы успешно опубликовали множество продуктов в нашей панели администратора. Давайте создадим наше приложение для тестирования нашего API.

Создайте приложение Flutter

Выполните приведенную ниже команду в выбранной вами директории, чтобы создать приложение flutter

flutter create my_store
Войдите в полноэкранный режим Выйти из полноэкранного режима

Установка пакетов

В этом уроке мы будем использовать некоторые пакеты flutter

  • http: Это сетевой пакет, который помогает в потреблении HTTP и сетевых вызовах
  • get: Get — это пакет управления состоянием Flutter.
  • animated_splash_screen: Этот пакет будет обрабатывать нашу заставку с анимацией.

Давайте обновим наш файл pubspec.yaml, импортировав вышеуказанные пакеты.

name: my_store

description: A new Flutter project.

publish_to: "none" # Remove this line if you wish to publish to pub.dev

version: 1.0.0+1

environment:  
  sdk: ">=2.17.0 <3.0.0"

dependencies:  
  flutter:  
    sdk: flutter  
  cupertino_icons: ^1.0.2  
  get: ^4.6.3  
  http: ^0.13.4  
  animated_splash_screen: ^1.2.0

dev_dependencies:  
  flutter_test:  
    sdk: flutter  
  flutter_lints: ^2.0.0

flutter:  
  uses-material-design: true
Вход в полноэкранный режим Выход из полноэкранного режима

Выполните команду flutter pub get, чтобы установить пакеты локально.

Структура папок

Давайте посмотрим, какой будет структура папок и файлов в этом проекте.

📦lib  
 ┣ 📂api  
 ┃ ┣ 📜base.dart  
 ┃ ┗ 📜products_api.dart  
 ┣ 📂controller  
 ┃ ┗ 📜productController.dart  
 ┣ 📂model  
 ┃ ┗ 📜product_model.dart  
 ┣ 📂screens  
 ┃ ┣ 📂details  
 ┃ ┃ ┣ 📂components  
 ┃ ┃ ┃ ┣ 📜product_price.dart  
 ┃ ┃ ┃ ┗ 📜product_size.dart  
 ┃ ┃ ┗ 📜product_details.dart  
 ┃ ┣ 📂home  
 ┃ ┃ ┣ 📂components  
 ┃ ┃ ┃ ┣ 📜products.dart  
 ┃ ┃ ┃ ┗ 📜product_card.dart  
 ┃ ┃ ┗ 📜home.dart  
 ┃ ┗ 📜splash_screen.dart  
 ┗ 📜main.dart
Вход в полноэкранный режим Выход из полноэкранного режима
  • api: Эта папка содержит файл api.dart, который мы будем использовать для выполнения вызовов API, и base.dart, содержащий наш базовый URL.
  • controller: Содержит файл dart для управления состоянием.
  • модель: Папка model содержит файл модели, который мы будем использовать для отображения нашего json-ответа.
  • screens: Папка screen содержит все наши пользовательские интерфейсы. Она содержит две папки, а именно:
  • splash_screen.dart: Здесь находится логотип приложения и анимация
  • home: Эта папка содержит домашний пользовательский интерфейс нашего приложения и его компоненты.
  • details: В этой папке находится экран подробностей о нашем продукте и его компоненты.

main.dart: Это точка входа в наше приложение. Он содержит функцию runApp(), которая запускает виджет MyApp.

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

Создание всплывающего экрана

Прежде чем приступить к созданию Splash-экрана, давайте очистим наш файл lib/main.dart, удалив виджеты MyHomePage и _myHomePageState и заменив виджет MyApp приведенным ниже кодом.

import 'package:flutter/material.dart';  
import 'package:get/get.dart';  
import 'package:my_store/screens/splash_screen.dart';

void main() {  
  runApp(const MyApp());  
}

class MyApp extends StatelessWidget {  
  const MyApp({Key? key}) : super(key: key);  
  // This widget is the root of your application.  
  @override  
  Widget build(BuildContext context) {  
    return GetMaterialApp(  
      title: 'Medusa Store',  
      debugShowCheckedModeBanner: false,  
      theme: ThemeData(  
        primarySwatch: Colors.blue,  
        useMaterial3: true,  
      ),  
      home: const SplashScreen(),  
    );  
  }  
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Исходя из приведенного выше кода, мы используем GetMaterialApp из пакета get для нашего материального дизайна. Это дает нам доступ к навигации и управлению состоянием get. Мы также включаем новый дизайн Flutter useMaterial3, а затем, наконец, маршрутизируемся на наш первый экран, который является Splashscreen. Это нормально, если вы увидите ошибку на SplashScreen, поскольку он еще не создан.

Экран всплеска

Splash screen — это причудливый экран длительностью 2,5 секунды, который придает нашему приложению приятное эстетическое вступление.

Добавление файла логотипа

Создайте папку assets в корневой папке проекта и добавьте в нее изображение logo.png. Перейдите к файлу pubspec.yaml и обновите свойство assets, указав путь к изображению, как показано ниже.

...

flutter:  
  uses-material-design: true  
  assets:  
    - assets/logo.png
Войдите в полноэкранный режим Выйдите из полноэкранного режима

Создайте файл splash_screen.dart в папке lib/screens и вставьте в него код, приведенный ниже

import 'package:animated_splash_screen/animated_splash_screen.dart';  
import 'package:flutter/material.dart';  
import 'package:my_store/screens/home/home.dart';

class SplashScreen extends StatelessWidget {  
  const SplashScreen({Key? key}) : super(key: key);  
  @override  
  Widget build(BuildContext context) {  
    return AnimatedSplashScreen(  
      duration: 2500,  
      splash: 'assets/logo.png',  
      splashIconSize: 300,  
      nextScreen: HomeScreen(),  
      splashTransition: SplashTransition.fadeTransition,  
      backgroundColor: Colors.black,  
    );  
  }  
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Приведенный выше код отвечает за анимацию заставки. Она длится 2,5 секунды с красивой затухающей анимацией, которая отображает наш логотип на черном фоне, а затем переходит в HomeScreen. После вставки приведенного выше фрагмента кода.

StatelessWidgets — это статические виджеты во Flutter, которые не хранят в себе никакого состояния. Они не могут быть перерендерированы или манипулированы при взаимодействии с пользователем, в то время как StatelessWidget может быть перерендерирован во время выполнения при взаимодействии и активности пользователя.

Настройка модели

Главный экран отвечает за отображение наших продуктов для пользователей. Но прежде чем мы начнем отображать продукты, давайте создадим модель, которая будет отображать ответы от нашего http-вызова.

Создание нашей модели

Создайте файл product_model.dart в папке lib/model и вставьте в него код, приведенный ниже

import 'dart:convert';

class Products {  
  Products({  
    required this.id,  
    required this.title,  
    required this.description,  
    required this.thumbnail,  
    required this.variants,  
    required this.options,  
    required this.images,  
  });  
  String id;  
  String title;  
  String description;  
  String thumbnail;  
  List<Variant> variants;  
  List<Option> options;  
  List<Image> images;  
  factory Products.fromJson(Map<String, dynamic> json) => Products(  
        id: json["id"],  
        title: json["title"],  
        description: json["description"],  
        thumbnail: json["thumbnail"],  
        variants: List<Variant>.from(  
            json["variants"].map((x) => Variant.fromJson(x))),  
        options:  
            List<Option>.from(json["options"].map((x) => Option.fromJson(x))),  
        images: List<Image>.from(json["images"].map((x) => Image.fromJson(x))),  
      );  
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Свойства variants, options и images содержат данные на втором уровне, в отличие от свойств thumbnail, title, id и description, данные которых находятся на первом уровне. Чтобы получить наши значения со второго уровня, нам нужно будет создать отдельный класс для каждого still в этом файле model.dart.

По-прежнему в файле product_model.dart вставьте следующие блоки кода ниже метода factory, описанного выше.

class Image {  
  Image({  
    required this.url,  
  });  
  String url;  
  factory Image.fromJson(Map<String, dynamic> json) => Image(  
        url: json["url"],  
      );  
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Исходя из приведенного выше кода, мы получаем наше изображение url из второго класса, таким образом, сопоставляя его с классом image все еще в файле модели.

class Option {  
  Option({  
    required this.title,  
    required this.values,  
  });  
  String title;  
  List<Value> values;  
  factory Option.fromJson(Map<String, dynamic> json) => Option(  
        title: json["title"],  
        values: List<Value>.from(json["values"].map((x) => Value.fromJson(x))),  
      );  
}

class Value {  
  Value({  
    required this.id,  
    required this.value,  
  });  
  String id;  
  String value;  
  factory Value.fromJson(Map<String, dynamic> json) => Value(  
        id: json["id"],  
        value: json["value"]!,  
      );  
}
Вход в полноэкранный режим Выход из полноэкранного режима

Исходя из приведенного выше кода, класс Value моделируется в класс Option, чтобы дать ему доступ к данным третьего уровня. Здесь мы пытаемся получить свойство Size для наших продуктов.

class Variant {  
  Variant({  
    required this.id,  
    required this.title,  
    required this.prices,  
    required this.options,  
  });  
  String id;  
  String title;  
  List<Price> prices;  
  List<Value> options;  
  factory Variant.fromJson(Map<String, dynamic> json) => Variant(  
        id: json["id"],  
        title: json["title"]!,  
        prices: List<Price>.from(json["prices"].map((x) => Price.fromJson(x))),  
        options:  
            List<Value>.from(json["options"].map((x) => Value.fromJson(x))),  
      );  
}

class Price {  
  Price({  
    required this.id,  
    required this.currencyCode,  
    required this.amount,  
  });  
  String id;  
  String currencyCode;  
  int amount;  
  factory Price.fromJson(Map<String, dynamic> json) => Price(  
        id: json["id"],  
        currencyCode: json["currency_code"]!,  
        amount: json["amount"],  
      );  
}
Вход в полноэкранный режим Выход из полноэкранного режима

Класс Variant хранит цены для продукта, которые также находятся на третьем уровне. Поэтому нам потребуется создать отдельный класс Price, чтобы получить сумму и код валюты для нас.

Приведенная выше модель продукта была создана на основе информации, которую мы хотим получить от нашего API Medusa, включая изображение, название, описание, размер и цену нашего продукта, затем мы сопоставляем ее с нашим входящим json-ответом на API.

Выполнение запроса API из sever

Теперь у нас есть готовая модель, давайте сделаем HTTP-вызов к нашему API.

Настройка урла базы

Создайте файл base.dart в папке lib/api и вставьте в него фрагмент кода, приведенный ниже. Приведенный ниже фрагмент будет динамически обрабатывать наш URL.

class Base {  
   static const String baseUrl = '<http://192.168.0.104:9000>';  
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Примечание: URL следует заменить на IP-адрес вашей системы. Это связано с тем, что flutter не распознает localhost.

N.B. Flutter также не поддерживает запрос http://, так как считает его небезопасным. Чтобы исправить это, перейдите в файл android/app/src/main/AndroidManifest.xml и вставьте приведенный ниже код в тег activity для android

<activity  

  android:usesCleartextTraffic="true" //paste this

....  
</activity>
Войти в полноэкранный режим Выйти из полноэкранного режима

и вставьте это в файл info.plist, расположенный в папке ios/Runner внутри тега <dist>.

<dist>  
        <key>NSAppTransportSecurity</key>  
        <dict>  
            <key>NSAllowsArbitraryLoads</key>  
            <true/>  
        </dict>

 ...  
</dict>
Вход в полноэкранный режим Выйти из полноэкранного режима

Получение продуктов из API

Создайте файл products_api.dart в папке lib/api и вставьте фрагмент кода ниже

import 'dart:convert';  
import 'package:http/http.dart' as http;  
import 'package:my_store/api/base.dart';  
import 'package:my_store/model/product_model.dart';

class ProductApi {  
  Future<List<Products>> getAllProducts() async {  
    final url = Uri.parse('${Base.baseUrl}/store/products');  
    try {  
      final response = await http.get(url);  
      if (response.statusCode == 200) {  
        var result = jsonDecode(response.body);  
        var data = result['products'];

 return List<Products>.from(data.map((x) => Products.fromJson(x)));  
      } else {  
        return [];  
      }  
    } catch (e) {  
      print(e.toString());  
    }  
    throw 'error';  
  }  
}
Вход в полноэкранный режим Выйти из полноэкранного режима

В приведенном выше коде мы делаем HTTP вызов на наш сервер, используя пакет http и наш base_url, и если запрос успешен, мы декодируем ответ, который сохраняется в переменной result. Объект product отфильтровывается в переменную data, которая затем проходит через модель.

Управление состоянием с помощью Getx

Нам нужно управлять состоянием для входящих данных. Создайте файл productController.dart в папке lib/controller.

import 'package:get/get.dart';  
import 'package:my_store/api/products_api.dart';  
import 'package:my_store/model/product_model.dart';

class AllProductsController extends GetxController {  
  var getAllProducts = <Products>[].obs;  
  var isLoading = true.obs;

 @override  
  void onInit() {  
    super.onInit();  
    fetchAllProduct();  
  }

 void fetchAllProduct() async {  
    isLoading(true);  
    try {  
      var getAllProductResult = await ProductApi().getAllProducts();  
      getAllProducts.value = getAllProductResult;  
    } finally {  
      isLoading(false);  
    }  
  }  
}
Войдите в полноэкранный режим Выйти из полноэкранного режима

В блоке кода выше мы расширяем наш класс AllProductsController с помощью GetxController, чтобы предоставить нам доступ к системе управления get, затем назначаем входящий ответ от вызова API в наблюдаемый список getAllProduct, который автоматически обновляется при изменении данных в нем, и, наконец, мы устанавливаем значение isLoading в false. Свойство isLoading поможет отобразить индикатор загрузки, когда продукт будет получен на сервере.

Построение главного экрана

Главный экран содержит два набора компонентов: карточку продукта и компонент популярных продуктов. Карточка продукта — это пользовательский интерфейс отдельного продукта, а популярный продукт динамически заполняет экран на основе данных из нашего API.

Создание компонента карточки товара

Карточка продукта управляет внешним видом отдельного продукта. Создайте файл product_card.dart в папке lib/screens/home/components и вставьте приведенный ниже код.

import 'package:flutter/material.dart';  
import 'package:get/get.dart';  
import 'package:my_store/model/product_model.dart' as model;

class ProductCard extends StatelessWidget {  
  const ProductCard({Key? key, required this.product}) : super(key: key);

 final model.Products product;

 @override  
  Widget build(BuildContext context) {  
    return SizedBox(  
        width: 140,  
        child: GestureDetector(  
            onTap: (){  
                                //Todo: Route to Product Details Screen  
                        },  
            child: Column(  
              crossAxisAlignment: CrossAxisAlignment.start,  
              children: [  
                Expanded(  
                  child: Hero(  
                    tag: product.id.toString(),  
                    child: Container(  
                        padding: const EdgeInsets.all(20),  
                        decoration: BoxDecoration(  
                            color: Colors.white,  
                            borderRadius: BorderRadius.circular(10),  
                            image: DecorationImage(  
                                image: NetworkImage(product.thumbnail),  
                                fit: BoxFit.cover))),  
                  ),  
                ),  
                const SizedBox(height: 10),  
                Padding(  
                    padding: const EdgeInsets.symmetric(horizontal: 10.0),  
                    child: Text(  
                      product.title,  
                      style: const TextStyle(  
                          color: Colors.black, fontWeight: FontWeight.w600),  
                      maxLines: 2,  
                    )),  
                const SizedBox(height: 10),  
                Padding(  
                    padding: const EdgeInsets.symmetric(horizontal: 10.0),  
                    child: Text("\$${product.variants[0].prices[1].amount / 100}",  
                        style: const TextStyle(  
                          fontSize: 18,  
                          fontWeight: FontWeight.w600,  
                          color: Colors.teal,  
                        ))),  
                const SizedBox(height: 10),  
              ],  
            )));  
  }  
}
Вход в полноэкранный режим Выйти из полноэкранного режима

В приведенном выше коде PopularCard принимает параметр продукта типа Products. Это дает нам доступ к модели отображения, созданной ранее, чтобы заполнить наши виджеты изображениями, ценами и названиями продуктов, которые мы хотим отобразить. Вся карточка обернута GestureDetector, который дает нам возможность клика, таким образом, при клике на каждой карточке, пользователи будут перенаправлены на экран ProductDetails, который отображает подробности.

N.B. Модель *Products* и свойство image в *DecoratedImage* имеют конфликтующие классы изображений. Чтобы исправить это, нам пришлось привести модель к *model*.

Создание компонента AllProducts

Компонент All Products содержит наш виджет, который отображает товары для пользователей. Создайте файл products.dart в папке lib/screens/home/components и вставьте приведенный ниже код.

import 'package:flutter/material.dart';  
import 'package:get/get.dart';  
import 'package:my_store/controller/productController.dart';  
import 'package:my_store/screens/home/components/product_card.dart';

class AllProducts extends StatelessWidget {  
  AllProducts({Key? key}) : super(key: key);

 final productController = Get.put(AllProductsController());

 @override  
  Widget build(BuildContext context) {  
    return SingleChildScrollView(  
        child: Column(children: [  
     Text(  
          "All Products",  
          style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
 const SizedBox(height: (20)),
 Obx(  
        () => SizedBox(  
            width: double.infinity,  
            child: productController.isLoading.value  
                ? const Center(  
                    child: CircularProgressIndicator(),  
                  )  
                : Padding(  
                    padding: const EdgeInsets.symmetric(horizontal: 20.0),  
                    child: GridView.builder(  
                        physics: const NeverScrollableScrollPhysics(),  
                        shrinkWrap: true,  
                        itemCount: productController.getAllProducts.length,  
                        gridDelegate:  
                            const SliverGridDelegateWithFixedCrossAxisCount(  
                          crossAxisCount: 2,  
                          crossAxisSpacing: 20,  
                          mainAxisSpacing: 20,  
                          childAspectRatio: 0.6,  
                        ),  
                        itemBuilder: (BuildContext context, int index) =>  
                            ProductCard(  
                              product: productController.getAllProducts[index],  
                            )),  
                  )),  
      ),  
    ]));  
  }  
}
Вход в полноэкранный режим Выход из полноэкранного режима

Компонент PopularProducts содержит Gridview.builder, который автоматически генерирует продукт из длины списка getAllProduct, созданного в классе контроллера AllProductsController, который мы создали ранее. Но чтобы это произошло, мы должны были инициализировать контроллер при загрузке класса PopularProduct.

Gridview автоматически перебирает продукты в цикле getAllProduct и предоставляет нам индекс, который заполняет экран каждым продуктом в ProductCard.

Создание компонентов для дома

Когда все компоненты готовы, давайте создадим наш главный экран. Создайте файл home.dart в папке lib/screens/home и вставьте в него приведенный ниже код

import 'package:flutter/material.dart';  
import 'package:my_store/screens/home/components/products.dart';

class HomeScreen extends StatelessWidget {  
  const HomeScreen({Key? key}) : super(key: key);  
  @override  
  Widget build(BuildContext context) {  
    return Scaffold(  
      appBar: AppBar(  
        title: const Text('Home Page'),  
      ),  
      body: AllProducts(),  
    );  
  }  
}
Вход в полноэкранный режим Выход из полноэкранного режима

Главный экран состоит из каркаса, который служит элементом верхнего уровня нашего приложения. Он содержит панель App, которая используется для отображения заголовка «Главная страница» в верхней части экрана, и тело, которое принимает в качестве значения виджет PopularProducts().

Чтобы проверить наш прогресс, давайте повторно запустим приложение с помощью команды ниже.

flutter run
Войти в полноэкранный режим Выйти из полноэкранного режима

Учитывая достигнутый прогресс, мы должны быть в состоянии получить все продукты на главный экран.

Построение экрана подробностей

Давайте построим наш экран подробностей, начав с его компонентов.

Создание цены продукта

Наш API предоставляет нам две цены, одну в евро и другую в долларах США. Чтобы получить их, создайте файл product_price.dart в папке lib/screens/details/components и вставьте приведенный ниже код.

import 'package:flutter/material.dart';  
import 'package:my_store/model/product_model.dart';

class ProductPrice extends StatelessWidget {  
  const ProductPrice({Key? key, required this.product}) : super(key: key);

 final Products product;

 @override  
  Widget build(BuildContext context) {  
    return SizedBox(  
      height: 50.0,  
      child: ListView.separated(  
        itemCount: product.variants[0].prices.length,  
        scrollDirection: Axis.horizontal,  
        shrinkWrap: true,  
        itemBuilder: (context, index) => Padding(  
          padding: const EdgeInsets.symmetric(horizontal:20.0),  
          child: Row(  
            children: [  
              Text(product.variants[0].prices[index].currencyCode.toUpperCase(),  
                  style:  
                      const TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),  
              const SizedBox(width: 10.0),  
              Text('${product.variants[0].prices[index].amount / 100}',  
                  style: const TextStyle(  
                      fontWeight: FontWeight.bold,  
                      fontSize: 20,  
                      color: Colors.teal)),  
            ],  
          ),  
        ),  
        separatorBuilder: (context, index) {  
          return const SizedBox(width: 10.0);  
        },  
      ),  
    );  
  }  
}
Вход в полноэкранный режим Выйти из полноэкранного режима

На основе приведенного выше кода мы перебираем прайс-лист, чтобы получить код валюты и сумму, которую мы делим на 100, а затем отображаем их в строке.

Создание компонента Size

Наконец, давайте создадим компонент Size для отображения различных размеров, доступных для данного товара. Создайте файл product_size.dart в папке lib/screens/details/components и вставьте приведенный ниже код.

import 'package:flutter/material.dart';  
import 'package:my_store/model/product_model.dart';

class ProductSize extends StatelessWidget {  
  ProductSize({Key? key, required this.product}) : super(key: key);

 final Products product;  
  Color secondary = const Color(0xFFE7B944);  
  Color textLightColor = const Color(0xFF605A65);

 @override  
  Widget build(BuildContext context) {  
    return Padding(  
      padding: const EdgeInsets.symmetric(horizontal: 20.0),  
      child: SingleChildScrollView(  
        scrollDirection: Axis.horizontal,  
        child: Row(  
          children: [  
            const Text('Sizes Available',  
                style: TextStyle(fontSize: 18, color: Colors.grey)),  
            const SizedBox(width: 20),  
            ...List.generate(  
                product.variants.length,  
                (index) => Container(  
                    margin: const EdgeInsets.only(right: 10.0),  
                    padding: const EdgeInsets.symmetric(horizontal: 15.0),  
                    decoration: BoxDecoration(  
                        color: secondary,  
                        borderRadius: BorderRadius.circular(5.0)),  
                    child: Center(  
                        child: Text(  
                      product.variants[index].title,  
                      style: TextStyle(color: textLightColor),  
                    )))),  
          ],  
        ),  
      ),  
    );  
  }  
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Код выше возвращает Row с заголовком «Sizes Available» и виджет List.generate, который работает точно так же, как Listview.builder. Виджет List.generate просматривает весь массив цен и отображает каждую доступную цену товара.

Построение экрана подробной информации о продукте

Наконец, мы построим экран подробностей о продукте, чтобы дать более подробное описание каждого продукта. Создайте файл product_details.dart в папке lib/screens/details и вставьте приведенный ниже код.

import 'package:flutter/material.dart';  
import 'package:get/get.dart';  
import 'package:my_store/model/product_model.dart' as model;  
import 'package:my_store/screens/details/components/product_price.dart';  
import 'package:my_store/screens/details/components/product_size.dart';

class ProductDetails extends StatefulWidget {  
  ProductDetails({Key? key, required this.product}) : super(key: key);

 final model.Products product;

 @override  
  State<ProductDetails> createState() => _ProductDetailsState();  
}

class _ProductDetailsState extends State<ProductDetails> {  
  Color white = const Color(0xFFFFFFFF);

 int selectedImage = 0;

 @override  
  Widget build(BuildContext context) {  
    return Scaffold(  
        body: SingleChildScrollView(  
      child: Column(  
        crossAxisAlignment: CrossAxisAlignment.start,  
        children: [  
          SizedBox(  
              width: Get.width,  
              height: Get.height * 0.35,  
              child: Hero(  
                  tag: widget.product.id,  
                  child: Image.network(widget.product.images[selectedImage].url,  
                      fit: BoxFit.cover))),  
          Row(  
            mainAxisAlignment: MainAxisAlignment.center,  
            children: List.generate(  
                widget.product.images.length, (index) => productReview(index)),  
          ),
 Padding(  
              padding: const EdgeInsets.all(20),  
              child: Text(widget.product.title,  
                  style: const TextStyle(  
                      fontWeight: FontWeight.bold, fontSize: 24))),  
          ProductPrice(product: widget.product),  
          const SizedBox(height: 10.0),  
          const Padding(  
              padding: EdgeInsets.symmetric(horizontal: 20.0),  
              child: Text('Product Details',  
                  style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18))),  
          const SizedBox(height: 10.0),  
          Padding(  
              padding: const EdgeInsets.symmetric(horizontal: 20.0),  
              child: Text(widget.product.description,  
                  style: const TextStyle(fontSize: 18, color: Colors.grey))),  
          const SizedBox(height: 10.0),  
          ProductSize(product: widget.product),  
        ],  
      ),  
    ));  
  }

 GestureDetector productReview(int index) {  
    return GestureDetector(  
      onTap: () {  
        setState(() => selectedImage = index);  
      },  
      child: Container(  
        margin: const EdgeInsets.only(right: 15, top: 15),  
        padding: const EdgeInsets.all(3),  
        width: 70,  
        height: 70,  
        decoration: BoxDecoration(  
          color: white,  
          borderRadius: BorderRadius.circular(10),  
          border: Border.all(  
            width: 2,  
            color: selectedImage == index  
                ? const Color(0xFFE7B944)  
                : Colors.transparent,  
          ),  
        ),  
        child:  
            Image.network(widget.product.images[index].url, fit: BoxFit.cover),  
      ),  
    );  
  }  
}
Вход в полноэкранный режим Выйти из полноэкранного режима

На основе приведенного выше кода экран ProductDetails возвращает Scaffold со свойством body. В теле находится виджет Column, который выравнивает дочерние элементы по вертикали на экране, и виджет SingleChildScrollView, который делает колонку прокручиваемой. Column содержит виджеты изображений, productImages, title, product details и product size.

Мы также создаем компонент productReview, который возвращает список вариантов изображений нашего продукта, а при нажатии на каждое из изображений происходит переключение между каждым изображением в строке.

N.B. Мы также приводим модель продукта к имени *model*, чтобы избежать и устранить проблему конфликта имен.

Давайте решим нашу Todo в функции onpressed путем маршрутизации на экран ProductDetails. Вернитесь к файлу product_card.dart в папке lib/screens/home/components и добавьте приведенный ниже импорт.

import 'package:my_store/screens/details/product_details.dart';
Вход в полноэкранный режим Выйти из полноэкранного режима

Затем добавьте маршрут к виджету GestureDetector на экране продукта.

onTap: () {   
        //Todo: Route to Product Details Screen  
        Get.to(() => ProductDetails(product: product));  
},
Войти в полноэкранный режим Выход из полноэкранного режима

В приведенном выше блоке кода мы маршрутизируем на экран ProductDetails, передавая продукт в качестве реквизита.

Мы закончили! Запустите приложение заново, но убедитесь, что сервер запущен, иначе запустите его с помощью medusa develop.

Что дальше

Поздравляем вас с тем, что вы дошли до этого момента. В этом руководстве мы рассмотрели, что такое Medusa, некоторые ее возможности и панель администратора. Мы также создали наш магазин, используя Flutter, новый материальный дизайн 3 и API Medusa, из которого мы получали созданные нами товары и отображали их для наших пользователей. Вы можете разместить свою панель администратора на любой хостинговой платформе, например, Netlify.

Исходный код

Вот ссылка на исходный код

https://github.com/iamVictorSam/my-store.git

Заключение

Medusa имеет массу возможностей для реализации. Начиная от системы отслеживания заказов и заканчивая доставкой и оплатой, все упрощено для вас. Также есть боты уведомлений для получения заказов из внешних источников, таких как Slack. Чтобы начать работу с Medusa и внедрить ее плагины, вам не понадобятся специальные навыки, документация уже подготовлена.

Оцените статью
devanswers.ru
Добавить комментарий