From af2af54cc6a06d1450b7291bd3dbecad237a428e Mon Sep 17 00:00:00 2001 From: ShiftHackZ <41302477+ShiftHackZ@users.noreply.github.com> Date: Thu, 16 Feb 2023 23:39:47 +0200 Subject: [PATCH] Wish List feature (#2) * Implement WishList domain layer * UI WishList base implementation * UI WishList base implementation patch 2 * WishList implementation complete --- assets/translations/uk.json | 2 +- lib/api/interceptor_logger.dart | 4 +- lib/constants/translations.dart | 16 +++ lib/database/database.dart | 132 ++++++++++++----- .../{ => entity}/cart_cache_item.dart | 0 .../{ => entity}/cart_cache_item.g.dart | 0 lib/database/{ => entity}/filter.dart | 2 +- lib/database/{ => entity}/filter.g.dart | 0 lib/database/{ => entity}/filter_active.dart | 0 .../{ => entity}/filter_active.g.dart | 0 lib/database/{ => entity}/filter_value.dart | 0 lib/database/{ => entity}/filter_value.g.dart | 0 lib/database/{ => entity}/product.dart | 0 lib/database/{ => entity}/product.g.dart | 0 lib/database/{ => entity}/user.dart | 1 - lib/database/{ => entity}/user.g.dart | 0 lib/database/entity/wish_list_cache_item.dart | 13 ++ .../entity/wish_list_cache_item.g.dart | 44 ++++++ lib/datasource/cart_data_source.dart | 1 - .../category_attribute_data_source.dart | 2 +- lib/datasource/customer_auth_data_source.dart | 2 +- lib/datasource/wish_list_data_source.dart | 105 ++++++++++++++ lib/locator.dart | 2 + lib/main.dart | 54 ++++--- lib/model/wish_list_entry.dart | 10 ++ lib/screens/cart/cart_view.dart | 33 ++--- .../category/fliter/category_filter_item.dart | 4 +- .../fliter/category_filter_state.dart | 2 +- .../category/fliter/category_filter_view.dart | 2 +- lib/screens/featured/featured.dart | 7 +- lib/screens/product/product_cubit.dart | 4 +- lib/screens/product/product_view.dart | 25 +++- lib/screens/profile/profile_cubit.dart | 1 - lib/screens/profile/profile_screen.dart | 1 - lib/screens/profile/profile_view.dart | 12 +- lib/screens/settings/settings.dart | 127 ++++++++-------- .../viewed/viewed_products_screen.dart | 2 +- lib/screens/viewed/viewed_products_state.dart | 2 +- lib/screens/wishlist/wishlist_cubit.dart | 31 ++++ lib/screens/wishlist/wishlist_screen.dart | 12 ++ lib/screens/wishlist/wishlist_state.dart | 15 ++ lib/screens/wishlist/wishlist_view.dart | 92 ++++++++++++ lib/widget/widget_catalog_products.dart | 2 - lib/widget/widget_order_item.dart | 60 +++++--- lib/widget/widget_product_actions.dart | 136 +++++++++++++----- lib/widget/widget_product_grid.dart | 27 ++-- lib/widget/widget_product_info.dart | 14 +- lib/widget/widget_product_recent.dart | 2 +- lib/widget/widget_settings_language.dart | 29 ++-- 49 files changed, 772 insertions(+), 260 deletions(-) create mode 100644 lib/constants/translations.dart rename lib/database/{ => entity}/cart_cache_item.dart (100%) rename lib/database/{ => entity}/cart_cache_item.g.dart (100%) rename lib/database/{ => entity}/filter.dart (84%) rename lib/database/{ => entity}/filter.g.dart (100%) rename lib/database/{ => entity}/filter_active.dart (100%) rename lib/database/{ => entity}/filter_active.g.dart (100%) rename lib/database/{ => entity}/filter_value.dart (100%) rename lib/database/{ => entity}/filter_value.g.dart (100%) rename lib/database/{ => entity}/product.dart (100%) rename lib/database/{ => entity}/product.g.dart (100%) rename lib/database/{ => entity}/user.dart (99%) rename lib/database/{ => entity}/user.g.dart (100%) create mode 100644 lib/database/entity/wish_list_cache_item.dart create mode 100644 lib/database/entity/wish_list_cache_item.g.dart create mode 100644 lib/datasource/wish_list_data_source.dart create mode 100644 lib/model/wish_list_entry.dart create mode 100644 lib/screens/wishlist/wishlist_cubit.dart create mode 100644 lib/screens/wishlist/wishlist_screen.dart create mode 100644 lib/screens/wishlist/wishlist_state.dart create mode 100644 lib/screens/wishlist/wishlist_view.dart diff --git a/assets/translations/uk.json b/assets/translations/uk.json index 0bbcbf8..e971db6 100644 --- a/assets/translations/uk.json +++ b/assets/translations/uk.json @@ -70,7 +70,7 @@ "rate_app": "Оцініть додаток", "in_stock": "В наявності", "out_of_stock": "Немає в наявності", - "back_order": "Передзамовлення", + "back_order": "Під замовлення", "sort_date_asc": "Спочатку старі", "sort_date_desc": "Спочатку нові", "sort_alphabet_asc": "За алфавітом (А-Я)", diff --git a/lib/api/interceptor_logger.dart b/lib/api/interceptor_logger.dart index 57c17d8..7c87006 100644 --- a/lib/api/interceptor_logger.dart +++ b/lib/api/interceptor_logger.dart @@ -7,7 +7,7 @@ class PrinterInterceptor extends Interceptor { print('''------------------- START REQUEST Method: ${options.method} Headers: ${options.headers} -Path: ${options.path} +Path: ${options.baseUrl}${options.path} Query parameters: ${options.queryParameters} Data: ${options.data} Content type: ${options.contentType} @@ -20,6 +20,7 @@ Content type: ${options.contentType} print('''------------------- START RESPONSE Status code: ${response.statusCode} Response data: ${response.data} +Path: ${response.realUri} ------------------- END RESPONSE'''); super.onResponse(response, handler);//Headers: ${response.headers} } @@ -30,6 +31,7 @@ Response data: ${response.data} Response: ${err.response} Error type: ${err.type} Error message: ${err.message} +Path: ${err.response?.realUri} ------------------- END ERROR'''); super.onError(err, handler); } diff --git a/lib/constants/translations.dart b/lib/constants/translations.dart new file mode 100644 index 0000000..7957464 --- /dev/null +++ b/lib/constants/translations.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +class Language { + final Locale locale; + final String title; + + const Language(this.locale, this.title); +} + +class Translations { + static const languages = [ + Language(Locale('en'), 'English'), + Language(Locale('uk'), 'Українська'), + Language(Locale('ru'), 'Русский'), + ]; +} diff --git a/lib/database/database.dart b/lib/database/database.dart index 16dfeda..0c1a444 100644 --- a/lib/database/database.dart +++ b/lib/database/database.dart @@ -1,48 +1,53 @@ import 'package:hive_flutter/hive_flutter.dart'; -import 'package:wooapp/database/cart_cache_item.dart'; -import 'package:wooapp/database/filter.dart'; -import 'package:wooapp/database/filter_active.dart'; -import 'package:wooapp/database/filter_value.dart'; -import 'package:wooapp/database/product.dart'; -import 'package:wooapp/database/user.dart'; +import 'package:wooapp/core/pair.dart'; +import 'package:wooapp/database/entity/cart_cache_item.dart'; +import 'package:wooapp/database/entity/filter.dart'; +import 'package:wooapp/database/entity/filter_active.dart'; +import 'package:wooapp/database/entity/filter_value.dart'; +import 'package:wooapp/database/entity/product.dart'; +import 'package:wooapp/database/entity/user.dart'; +import 'package:wooapp/database/entity/wish_list_cache_item.dart'; import 'package:wooapp/model/attribute.dart'; import 'package:wooapp/model/product.dart'; +/// WooApp local database orchestrator. +/// Docs: https://docs.hivedb.dev/ +/// +/// To generate adapters, execute: +/// flutter packages pub run build_runner build class AppDb { - static const String boxUser = 'box_user'; static const String boxViewedProduct = 'box_viewed'; static const String boxFilters = 'box_filters'; static const String boxApplied = 'box_applied'; static const String boxCartCache = 'box_cart_cache'; + static const String boxWishListCache = 'box_wish_list_cache'; static const String keyUser = 'key_user'; static const String keyViewedProduct = 'key_viewed'; static const String keyFilter = 'key_viewed'; static const String keyApplied = 'key_applied'; static const String keyCart = 'key_cart'; + static const String keyWishList = 'key_wish_list'; Future clear() async { - var box1 = await Hive.openBox(boxUser); - var box2 = await Hive.openBox(boxViewedProduct); - await box1.clear(); - await box2.clear(); - box1.close(); - return box2.close(); + // var box1 = await Hive.openBox(boxUser); + // var box2 = await Hive.openBox(boxViewedProduct); + // await box1.clear(); + // await box2.clear(); + // box1.close(); + // return box2.close(); + await Hive.deleteFromDisk(); } Future isActiveFilter(int filterId, int valueId) async { var box = await Hive.openBox(boxApplied); if (box.containsKey('$keyApplied$filterId')) { var applied = box.get('$keyApplied$filterId') as ActiveFilter; - if (applied.termIds.contains(valueId)) { - return true; - } else { - return false; - } - } else { - return false; + if (applied.termIds.contains(valueId)) return true; + //return false; } + return false; } Future> getActiveFilters() async { @@ -80,14 +85,16 @@ class AppDb { var box = await Hive.openBox(boxFilters); // if (box.containsKey('$keyFilter${attribute.id}')) return box.put( - '$keyFilter${attribute.id}', - Filter( - attribute.id, - attribute.name, - attribute.slug, - attribute.type, - terms.map((term) => FilterValue(term.id, term.name, term.slug)).toList() - ) + '$keyFilter${attribute.id}', + Filter( + attribute.id, + attribute.name, + attribute.slug, + attribute.type, + terms + .map((term) => FilterValue(term.id, term.name, term.slug)) + .toList(), + ), ); } @@ -99,7 +106,7 @@ class AppDb { Future addToCart(int id, {String name = ''}) async { var box = await Hive.openBox(boxCartCache); - if (box.containsKey('$keyCart$id}')) return box.close(); + if (box.containsKey('$keyCart$id')) return box.close(); box.put('$keyCart$id', CartCacheItem(id, name)); return box.close(); } @@ -123,17 +130,70 @@ class AppDb { return box.close(); } + Future insertWishList(List> raw) async { + var box = await Hive.openBox(boxWishListCache); + await box.clear(); + for (var pair in raw) { + var key = '$keyWishList${pair.first}'; + box.put(key, WishListCacheItem(pair.first, pair.second)); + } + await box.close(); + return; + } + + Future addToWishList(String itemId, int productId) async { + var box = await Hive.openBox(boxWishListCache); + var key = '$keyWishList$itemId'; + if (box.containsKey(key)) { + //await box.close(); + } + box.put(key, WishListCacheItem(itemId, productId)); + //await box.close(); + return; + } + + Future getWishListItemIdByProductId(int productId) async { + var box = await Hive.openBox(boxWishListCache); + for (WishListCacheItem item in box.values) { + if (item.productId == productId) return item.itemId; + } + return ''; + } + + Future deleteFromWishList(String itemId) async { + var box = await Hive.openBox(boxWishListCache); + var key = '$keyWishList$itemId'; + if (box.containsKey(key)) { + await box.delete(key); + } + //await box.close(); + return; + } + + Future isInWishList(String itemId) async { + var box = await Hive.openBox(boxWishListCache); + return box.containsKey('$keyWishList$itemId'); + } + + Future isInWishListByProductId(int productId) async { + var box = await Hive.openBox(boxWishListCache); + for (WishListCacheItem item in box.values) { + if (item.productId == productId) return true; + } + return false; + } + Future saveProductView(Product product) async { var box = await Hive.openBox(boxViewedProduct); if (box.containsKey('$keyViewedProduct${product.id}')) return box.close(); box.put( - '$keyViewedProduct${product.id}', - ViewedProduct( - product.id, - product.name, - product.price, - product.images[0].src - ) + '$keyViewedProduct${product.id}', + ViewedProduct( + product.id, + product.name, + product.price, + product.images[0].src, + ), ); return box.close(); } diff --git a/lib/database/cart_cache_item.dart b/lib/database/entity/cart_cache_item.dart similarity index 100% rename from lib/database/cart_cache_item.dart rename to lib/database/entity/cart_cache_item.dart diff --git a/lib/database/cart_cache_item.g.dart b/lib/database/entity/cart_cache_item.g.dart similarity index 100% rename from lib/database/cart_cache_item.g.dart rename to lib/database/entity/cart_cache_item.g.dart diff --git a/lib/database/filter.dart b/lib/database/entity/filter.dart similarity index 84% rename from lib/database/filter.dart rename to lib/database/entity/filter.dart index 7bc3109..9e09ccc 100644 --- a/lib/database/filter.dart +++ b/lib/database/entity/filter.dart @@ -1,5 +1,5 @@ import 'package:hive/hive.dart'; -import 'package:wooapp/database/filter_value.dart'; +import 'package:wooapp/database/entity/filter_value.dart'; part 'filter.g.dart'; diff --git a/lib/database/filter.g.dart b/lib/database/entity/filter.g.dart similarity index 100% rename from lib/database/filter.g.dart rename to lib/database/entity/filter.g.dart diff --git a/lib/database/filter_active.dart b/lib/database/entity/filter_active.dart similarity index 100% rename from lib/database/filter_active.dart rename to lib/database/entity/filter_active.dart diff --git a/lib/database/filter_active.g.dart b/lib/database/entity/filter_active.g.dart similarity index 100% rename from lib/database/filter_active.g.dart rename to lib/database/entity/filter_active.g.dart diff --git a/lib/database/filter_value.dart b/lib/database/entity/filter_value.dart similarity index 100% rename from lib/database/filter_value.dart rename to lib/database/entity/filter_value.dart diff --git a/lib/database/filter_value.g.dart b/lib/database/entity/filter_value.g.dart similarity index 100% rename from lib/database/filter_value.g.dart rename to lib/database/entity/filter_value.g.dart diff --git a/lib/database/product.dart b/lib/database/entity/product.dart similarity index 100% rename from lib/database/product.dart rename to lib/database/entity/product.dart diff --git a/lib/database/product.g.dart b/lib/database/entity/product.g.dart similarity index 100% rename from lib/database/product.g.dart rename to lib/database/entity/product.g.dart diff --git a/lib/database/user.dart b/lib/database/entity/user.dart similarity index 99% rename from lib/database/user.dart rename to lib/database/entity/user.dart index 567b457..87f6019 100644 --- a/lib/database/user.dart +++ b/lib/database/entity/user.dart @@ -1,4 +1,3 @@ - import 'package:hive/hive.dart'; part 'user.g.dart'; diff --git a/lib/database/user.g.dart b/lib/database/entity/user.g.dart similarity index 100% rename from lib/database/user.g.dart rename to lib/database/entity/user.g.dart diff --git a/lib/database/entity/wish_list_cache_item.dart b/lib/database/entity/wish_list_cache_item.dart new file mode 100644 index 0000000..bc3811f --- /dev/null +++ b/lib/database/entity/wish_list_cache_item.dart @@ -0,0 +1,13 @@ +import 'package:hive/hive.dart'; + +part 'wish_list_cache_item.g.dart'; + +@HiveType(typeId: 7) +class WishListCacheItem { + @HiveField(0) + String itemId; + @HiveField(1) + int productId; + + WishListCacheItem(this.itemId, this.productId); +} diff --git a/lib/database/entity/wish_list_cache_item.g.dart b/lib/database/entity/wish_list_cache_item.g.dart new file mode 100644 index 0000000..8bcb450 --- /dev/null +++ b/lib/database/entity/wish_list_cache_item.g.dart @@ -0,0 +1,44 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'wish_list_cache_item.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class WishListCacheItemAdapter extends TypeAdapter { + @override + final int typeId = 7; + + @override + WishListCacheItem read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return WishListCacheItem( + fields[0] as String, + fields[1] as int, + ); + } + + @override + void write(BinaryWriter writer, WishListCacheItem obj) { + writer + ..writeByte(2) + ..writeByte(0) + ..write(obj.itemId) + ..writeByte(1) + ..write(obj.productId); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is WishListCacheItemAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/datasource/cart_data_source.dart b/lib/datasource/cart_data_source.dart index 20cc465..2943937 100644 --- a/lib/datasource/cart_data_source.dart +++ b/lib/datasource/cart_data_source.dart @@ -116,7 +116,6 @@ abstract class CartDataSource { Map mapVariations(List variations, Map input) { Map result = {}; - print('DBG0 - $input'); for (var key in input.keys) { Variation? variation = variations.firstWhere((v) => v.name == key, orElse: null); if (variation == null) continue; diff --git a/lib/datasource/category_attribute_data_source.dart b/lib/datasource/category_attribute_data_source.dart index 135b9fd..3b94250 100644 --- a/lib/datasource/category_attribute_data_source.dart +++ b/lib/datasource/category_attribute_data_source.dart @@ -2,7 +2,7 @@ import 'package:wooapp/api/woo_api_client.dart'; import 'package:wooapp/api/wp_api_client.dart'; import 'package:wooapp/constants/config.dart'; import 'package:wooapp/database/database.dart'; -import 'package:wooapp/database/filter_active.dart'; +import 'package:wooapp/database/entity/filter_active.dart'; import 'package:wooapp/locator.dart'; import 'package:wooapp/model/attribute.dart'; import 'package:wooapp/model/product.dart'; diff --git a/lib/datasource/customer_auth_data_source.dart b/lib/datasource/customer_auth_data_source.dart index ff55a2c..48dc73d 100644 --- a/lib/datasource/customer_auth_data_source.dart +++ b/lib/datasource/customer_auth_data_source.dart @@ -7,7 +7,7 @@ import 'package:wooapp/api/cocart_api_client.dart'; import 'package:wooapp/api/woo_api_client.dart'; import 'package:wooapp/api/wp_api_client.dart'; import 'package:wooapp/database/database.dart'; -import 'package:wooapp/database/user.dart'; +import 'package:wooapp/database/entity/user.dart'; import 'package:wooapp/locator.dart'; import 'package:wooapp/model/auth_register_response.dart'; import 'package:wooapp/model/customer_profile.dart'; diff --git a/lib/datasource/wish_list_data_source.dart b/lib/datasource/wish_list_data_source.dart new file mode 100644 index 0000000..2290ede --- /dev/null +++ b/lib/datasource/wish_list_data_source.dart @@ -0,0 +1,105 @@ +import 'package:wooapp/api/woo_api_client.dart'; +import 'package:wooapp/api/wp_api_client.dart'; +import 'package:wooapp/core/pair.dart'; +import 'package:wooapp/database/database.dart'; +import 'package:wooapp/locator.dart'; +import 'package:wooapp/model/product.dart'; +import 'package:wooapp/model/wish_list_entry.dart'; + +class WishListDataSourceImpl extends WishListDataSource { + final AppDb _db = locator(); + final WpApiClient _wp = locator(); + final WooApiClient _woo = locator(); + + @override + Future> getWishListEntries() => _db + .getUserId() + .then((userId) => _woo.dio.get('wishlist/get_by_user/$userId')) + .then((response) => (response.data as List)[0]['share_key']) + .then((shareKey) => _woo.dio.get('wishlist/$shareKey/get_products')) + .then( + (response) => (response.data as List) + .map((e) => WishListEntry.fromJson(e)) + .toList(), + ); + + @override + Future>> getWishListWithProducts() => + getWishListEntries().then((entries) async { + var ids = entries.map((e) => '${e.productId},') + .toList() + .toString() + .replaceAll('[', '') + .replaceAll(']', ''); + var products = + await _woo.dio.get('products?status=publish&include=$ids').then( + (response) => (response.data as List) + .map((item) => Product.fromJson(item)) + .toList(), + ); + var data = + await _mapWishEntriesToProducts(entries, products); + + return data; + }); + + @override + Future addProduct(int productId) => _db + .getUserId() + .then((userId) => _woo.dio.get('wishlist/get_by_user/$userId')) + .then((response) => (response.data as List)[0]['share_key']) + .then( + (shareKey) => _woo.dio.post( + 'wishlist/$shareKey/add_product', + data: { + 'product_id': '$productId', + }, + ), + ) + .then((response) => (response.data as List)[0]['item_id'].toString()) + .then((itemId) => _db.addToWishList(itemId, productId)); + + @override + Future removeProductByItemId(itemId) => _woo.dio + .get('wishlist/remove_product/$itemId') + .then((_) => _db.deleteFromWishList(itemId)); + + @override + Future removeProductByProductId(int productId) => _db + .getWishListItemIdByProductId(productId) + .then((itemId) => removeProductByItemId(itemId)); + + List> _mapWishEntriesToProducts( + List entries, + List products, + ) { + var result = >[]; + var raw = >[]; + for (var entry in entries) { + var product = products.firstWhere((p) => p.id == entry.productId, orElse: null); + result.add(Pair(entry, product)); + raw.add(Pair(entry.itemId.toString(), product.id)); + _db.addToWishList(entry.itemId.toString(), entry.productId); + } + // await _db.insertWishList(raw); + return result; + } + + @override + Future isInWishList(String itemId) => + _db.isInWishList(itemId); + + @override + Future isInWishListByProductId(int productId) => + _db.isInWishListByProductId(productId); +} + +abstract class WishListDataSource { + Future> getWishListEntries(); + Future>> getWishListWithProducts(); + Future addProduct(int productId); + Future removeProductByItemId(String itemId); + Future removeProductByProductId(int productId); + Future isInWishList(String itemId); + Future isInWishListByProductId(int productId); +} diff --git a/lib/locator.dart b/lib/locator.dart index 07f1ad1..193ba2e 100644 --- a/lib/locator.dart +++ b/lib/locator.dart @@ -16,6 +16,7 @@ import 'package:wooapp/datasource/products_home_data_source.dart'; import 'package:wooapp/datasource/catalog_data_source.dart'; import 'package:wooapp/datasource/shipping_method_data_source.dart'; import 'package:wooapp/datasource/shopmap_data_source.dart'; +import 'package:wooapp/datasource/wish_list_data_source.dart'; final GetIt locator = GetIt.instance; @@ -26,6 +27,7 @@ Future setupDependencies() async { void registerDataSources() async { locator + ..registerLazySingleton(() => WishListDataSourceImpl()) ..registerLazySingleton(() => CustomerAuthDataSourceImpl()) ..registerLazySingleton(() => CustomerProfileDataSourceImpl()) ..registerLazySingleton(() => OrdersDataSourceImpl()) diff --git a/lib/main.dart b/lib/main.dart index 194f151..559fc09 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,31 +2,40 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:wooapp/database/cart_cache_item.dart'; -import 'package:wooapp/database/filter.dart'; -import 'package:wooapp/database/filter_value.dart'; -import 'package:wooapp/database/filter_active.dart'; -import 'package:wooapp/database/product.dart'; -import 'package:wooapp/database/user.dart'; +import 'package:wooapp/constants/translations.dart'; +import 'package:wooapp/database/entity/filter.dart'; +import 'package:wooapp/database/entity/filter_value.dart'; +import 'package:wooapp/database/entity/filter_active.dart'; +import 'package:wooapp/database/entity/product.dart'; +import 'package:wooapp/database/entity/user.dart'; +import 'package:wooapp/database/entity/wish_list_cache_item.dart'; import 'package:wooapp/locator.dart'; import 'package:wooapp/screens/splash/splash.dart'; +import 'package:wooapp/database/entity/cart_cache_item.dart'; +/// WooApp +/// Flutter WooCommerce online mobile application for customers. +/// +/// Commercial usage of this app is not allowed without author official permission. +/// +/// Copyright © 2020 - 2023 +/// All rights reserved +/// Author: Dmitriy Moroz (ShiftHackZ) +/// dmitriy@moroz.cc +/// https://dmitriy.moroz.cc +/// void main() async { await dotenv.load(fileName: ".env"); WidgetsFlutterBinding.ensureInitialized(); await EasyLocalization.ensureInitialized(); runApp( EasyLocalization( - supportedLocales: [ - Locale('en'), - Locale('uk'), - Locale('ru'), - ], + supportedLocales: Translations.languages.map((l) => l.locale).toList(), + fallbackLocale: Translations.languages[0].locale, path: 'assets/translations', - fallbackLocale: Locale('en'), saveLocale: true, child: WooShopApp(), - ) + ), ); } @@ -58,14 +67,13 @@ class _WooShopAppState extends State { ), ); - void initHive() async { - Hive - ..initFlutter() - ..registerAdapter(CartCacheItemAdapter()) - ..registerAdapter(FilterValueAdapter()) - ..registerAdapter(ActiveFilterAdapter()) - ..registerAdapter(FilterAdapter()) - ..registerAdapter(ViewedProductAdapter()) - ..registerAdapter(UserAdapter()); - } + void initHive() => Hive + ..initFlutter() + ..registerAdapter(CartCacheItemAdapter()) + ..registerAdapter(WishListCacheItemAdapter()) + ..registerAdapter(FilterValueAdapter()) + ..registerAdapter(ActiveFilterAdapter()) + ..registerAdapter(FilterAdapter()) + ..registerAdapter(ViewedProductAdapter()) + ..registerAdapter(UserAdapter()); } diff --git a/lib/model/wish_list_entry.dart b/lib/model/wish_list_entry.dart new file mode 100644 index 0000000..b4fcd78 --- /dev/null +++ b/lib/model/wish_list_entry.dart @@ -0,0 +1,10 @@ +class WishListEntry { + final itemId; + final productId; + final dateAdded; + + WishListEntry.fromJson(Map json) + : itemId = json['item_id'], + productId = json['product_id'], + dateAdded = json['date_added']; +} diff --git a/lib/screens/cart/cart_view.dart b/lib/screens/cart/cart_view.dart index 43a7b22..7293534 100644 --- a/lib/screens/cart/cart_view.dart +++ b/lib/screens/cart/cart_view.dart @@ -18,24 +18,17 @@ import 'package:wooapp/widget/widget_cart_total.dart'; import 'package:wooapp/widget/widget_retry.dart'; class CartView extends StatelessWidget { - final VoidCallback shoppingCallback; CartView(this.shoppingCallback); @override Widget build(BuildContext context) => StatefulWrapper( - onInit: () { - context.read().getCart(); - }, + onInit: () => context.read().getCart(), child: Scaffold( backgroundColor: Colors.white, body: BlocListener( - listener: (context, state) { - switch (state.runtimeType) { - //ToDo ... - } - }, + listener: (context, state) {}, child: BlocBuilder( builder: (context, state) { switch (state.runtimeType) { @@ -67,9 +60,7 @@ class CartView extends StatelessWidget { }); Widget _emptyState(BuildContext context) => Scaffold( - appBar: AppBar( - title: Text('tab_cart').tr(), - ), + appBar: _appBar(), // body: ViewedProductsScreen(), body: SafeArea( child: SingleChildScrollView( @@ -85,9 +76,7 @@ class CartView extends StatelessWidget { ); Widget _contentState(BuildContext context, CartResponse cart) => Scaffold( - appBar: AppBar( - title: Text('tab_cart').tr(), - ), + appBar: _appBar(), bottomNavigationBar: Container( height: 60, child: Padding( @@ -247,19 +236,14 @@ class CartView extends StatelessWidget { ); Widget _loadingState() => Scaffold( - appBar: AppBar( - title: Text('tab_cart').tr(), - ), + appBar: _appBar(), body: SafeArea( child: CartListShimmer(), ), ); Widget _errorState(BuildContext context) => Scaffold( - appBar: AppBar( - // leading: Icon(Icons.shopping_cart), - title: Text('tab_cart').tr(), - ), + appBar: _appBar(), body: SafeArea( child: SingleChildScrollView( child: Column( @@ -273,4 +257,9 @@ class CartView extends StatelessWidget { ), ), ); + + AppBar _appBar() => AppBar( + leading: Icon(Icons.shopping_cart), + title: Text('tab_cart').tr(), + ); } \ No newline at end of file diff --git a/lib/screens/category/fliter/category_filter_item.dart b/lib/screens/category/fliter/category_filter_item.dart index bebf45c..0b7795a 100644 --- a/lib/screens/category/fliter/category_filter_item.dart +++ b/lib/screens/category/fliter/category_filter_item.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:wooapp/database/database.dart'; -import 'package:wooapp/database/filter.dart'; -import 'package:wooapp/database/filter_value.dart'; +import 'package:wooapp/database/entity/filter.dart'; +import 'package:wooapp/database/entity/filter_value.dart'; import 'package:wooapp/locator.dart'; class CategoryFilterItem extends StatelessWidget { diff --git a/lib/screens/category/fliter/category_filter_state.dart b/lib/screens/category/fliter/category_filter_state.dart index 18f2d16..d07359e 100644 --- a/lib/screens/category/fliter/category_filter_state.dart +++ b/lib/screens/category/fliter/category_filter_state.dart @@ -1,4 +1,4 @@ -import 'package:wooapp/database/filter.dart'; +import 'package:wooapp/database/entity/filter.dart'; import 'package:wooapp/model/attribute.dart'; import 'category_filter_model.dart'; diff --git a/lib/screens/category/fliter/category_filter_view.dart b/lib/screens/category/fliter/category_filter_view.dart index a3ab9ec..e63026a 100644 --- a/lib/screens/category/fliter/category_filter_view.dart +++ b/lib/screens/category/fliter/category_filter_view.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:wooapp/database/filter.dart'; +import 'package:wooapp/database/entity/filter.dart'; import 'package:wooapp/model/attribute.dart'; import 'package:wooapp/screens/category/fliter/category_filter_cubit.dart'; import 'package:wooapp/screens/category/fliter/category_filter_model.dart'; diff --git a/lib/screens/featured/featured.dart b/lib/screens/featured/featured.dart index bbcd944..0361c45 100644 --- a/lib/screens/featured/featured.dart +++ b/lib/screens/featured/featured.dart @@ -190,7 +190,10 @@ class _FeaturedListState extends State { showNewPageErrorIndicatorAsGridChild: false, showNoMoreItemsIndicatorAsGridChild: false, builderDelegate: PagedChildBuilderDelegate( - itemBuilder: (context, item, index) => ProductGridItem(item), + itemBuilder: (context, item, index) => ProductGridItem( + product: item, + detailRouteCallback: (_) {}, + ), firstPageProgressIndicatorBuilder: (_) => FeaturedShimmer(true), newPageProgressIndicatorBuilder: (_) => FeaturedShimmer(false), firstPageErrorIndicatorBuilder: (_) => ErrorRetryWidget(() { @@ -202,7 +205,7 @@ class _FeaturedListState extends State { gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( childAspectRatio: MediaQuery.of(context).size.width / ((MediaQuery.of(context).size.height / 2) + 10), crossAxisCount: 2, - ) + ), ), ], ), diff --git a/lib/screens/product/product_cubit.dart b/lib/screens/product/product_cubit.dart index c9850db..5eb7955 100644 --- a/lib/screens/product/product_cubit.dart +++ b/lib/screens/product/product_cubit.dart @@ -9,7 +9,7 @@ import 'package:wooapp/screens/product/product_state.dart'; class ProductCubit extends Cubit { final int _productId; - final ProductDataSource _ds = locator(); + final ProductDataSource _dsProduct = locator(); final AppDb _db = locator(); ProductCubit(this._productId) : super(InitialProductState()) { @@ -18,7 +18,7 @@ class ProductCubit extends Cubit { void _getProduct() { emit(LoadingProductState()); - _ds.getProduct(_productId).then((product) { + _dsProduct.getProduct(_productId).then((product) { _db.saveProductView(product); emit(ContentProductState(product)); }).catchError((error, stacktrace) { diff --git a/lib/screens/product/product_view.dart b/lib/screens/product/product_view.dart index f624f9e..9a75b42 100644 --- a/lib/screens/product/product_view.dart +++ b/lib/screens/product/product_view.dart @@ -21,14 +21,23 @@ import 'package:wooapp/widget/widget_product_info.dart'; import 'package:wooapp/widget/widget_section.dart'; import 'package:wooapp/widget/widget_text_expanded.dart'; +class ProductViewWillPopController { + bool value; + + ProductViewWillPopController(this.value); +} + class ProductView extends StatelessWidget { final _sliderController = PageController(); final _sliderNotifier = ValueNotifier(0); + + final _willPopController = ProductViewWillPopController(false); ProductView(); @override - Widget build(BuildContext context) => Scaffold( + Widget build(BuildContext context) => WillPopScope( + child: Scaffold( backgroundColor: Colors.white, body: BlocListener( listener: (context, state) {}, @@ -52,7 +61,12 @@ class ProductView extends StatelessWidget { }, ), ), - ); + ), + onWillPop: () { + Navigator.pop(context, _willPopController.value); + return Future(() => false); + }, + ); Widget _loadingState() => ProductScreenShimmer(); @@ -96,7 +110,12 @@ class ProductView extends StatelessWidget { style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600), ), ), - ProductActionsWidget(product), + ProductActionsWidget( + product: product, + changeCallback: () { + _willPopController.value = true; + }, + ), Divider(), Padding( padding: EdgeInsets.only(top: 8.0), diff --git a/lib/screens/profile/profile_cubit.dart b/lib/screens/profile/profile_cubit.dart index 34d6edb..79a39e8 100644 --- a/lib/screens/profile/profile_cubit.dart +++ b/lib/screens/profile/profile_cubit.dart @@ -5,7 +5,6 @@ import 'package:wooapp/locator.dart'; import 'package:wooapp/screens/profile/profile_state.dart'; class ProfileCubit extends Cubit { - final CustomerProfileDataSource _ds = locator(); final AppDb _db = locator(); diff --git a/lib/screens/profile/profile_screen.dart b/lib/screens/profile/profile_screen.dart index c644e1e..9d59a45 100644 --- a/lib/screens/profile/profile_screen.dart +++ b/lib/screens/profile/profile_screen.dart @@ -4,7 +4,6 @@ import 'package:wooapp/screens/profile/profile_cubit.dart'; import 'package:wooapp/screens/profile/profile_view.dart'; class ProfileScreen extends StatelessWidget { - ProfileScreen(); @override diff --git a/lib/screens/profile/profile_view.dart b/lib/screens/profile/profile_view.dart index 06eeba5..2ef8f59 100644 --- a/lib/screens/profile/profile_view.dart +++ b/lib/screens/profile/profile_view.dart @@ -19,6 +19,7 @@ import 'package:wooapp/screens/profile/profile_cubit.dart'; import 'package:wooapp/screens/profile/profile_state.dart'; import 'package:wooapp/screens/profile/shipping/shipping_edit_screen.dart'; import 'package:wooapp/screens/settings/settings.dart'; +import 'package:wooapp/screens/wishlist/wishlist_screen.dart'; import 'package:wooapp/widget/shimmer.dart'; import 'package:wooapp/widget/stateful_wrapper.dart'; @@ -56,7 +57,7 @@ class ProfileView extends StatelessWidget { }, ), ), - ) + ), ); Widget _noAuth(BuildContext context) => NoAuthScreen(tr('tab_profile'), () { @@ -230,7 +231,14 @@ class ProfileView extends StatelessWidget { }), ), SizedBox(height: 8), - _profileSection(FaIcon(FontAwesomeIcons.heart), tr('wish_list'), () {}), + _profileSection( + FaIcon(FontAwesomeIcons.heart), + tr('wish_list'), + () => Navigator.push( + context, + MaterialPageRoute(builder: (_) => WishListScreen()), + ), + ), _profileSection( FaIcon(FontAwesomeIcons.shoppingBag), tr('orders'), diff --git a/lib/screens/settings/settings.dart b/lib/screens/settings/settings.dart index 569e68a..f5a5f31 100644 --- a/lib/screens/settings/settings.dart +++ b/lib/screens/settings/settings.dart @@ -12,70 +12,79 @@ class SettingsScreen extends StatefulWidget { class _SettingsScreenState extends State { @override Widget build(BuildContext context) => Scaffold( - appBar: AppBar( - title: Text('settings').tr(), - ), - body: SafeArea( - child: SingleChildScrollView( - child: Column( - children: [ - SizedBox(height: 8), - _section( - FaIcon(FontAwesomeIcons.globe), - Text('lang').tr(), - tr('settings_language'), - () => showBottomOptions(context, LanguageWidget(context.locale, (lang) { - context.setLocale(Locale(lang.code)); - })) - ), - _section( - FaIcon(FontAwesomeIcons.key), - SizedBox.shrink(), - tr('settings_password'), - () {} - ), - ], + appBar: AppBar( + title: Text('settings').tr(), ), - ), - ), - ); - - Widget _section(Widget icon, Widget endChild, String text, VoidCallback action, {Color iconBackground = Colors.transparent}) => Padding( - padding: EdgeInsets.only(left: 8, right: 8), - child: Card( - color: Colors.white70, - child: InkWell( - onTap: action, - child: Padding( - padding: EdgeInsets.symmetric(vertical: 10, horizontal: 4), - child: Row( - children: [ - SizedBox(width: 8), - Container( - decoration: BoxDecoration( - color: iconBackground, - border: Border.all( - color: iconBackground + body: SafeArea( + child: SingleChildScrollView( + child: Column( + children: [ + SizedBox(height: 8), + _section( + FaIcon(FontAwesomeIcons.globe), + Text('lang').tr(), + tr('settings_language'), + () => showBottomOptions( + context, + LanguageWidget( + context.locale, + (lang) => context.setLocale(lang.locale), + ), ), - borderRadius: BorderRadius.all(Radius.circular(4)), ), - width: 34, - height: 34, - child: Padding( - padding: EdgeInsets.all(4), - child: Center(child: icon), + _section( + FaIcon(FontAwesomeIcons.key), + SizedBox.shrink(), + tr('settings_password'), + () {}, ), + ], + ), + ), + ), + ); + + Widget _section( + Widget icon, + Widget endChild, + String text, + VoidCallback action, { + Color iconBackground = Colors.transparent, + }) => + Padding( + padding: EdgeInsets.only(left: 8, right: 8), + child: Card( + color: Colors.white70, + child: InkWell( + onTap: action, + child: Padding( + padding: EdgeInsets.symmetric(vertical: 10, horizontal: 4), + child: Row( + children: [ + SizedBox(width: 8), + Container( + decoration: BoxDecoration( + color: iconBackground, + border: Border.all(color: iconBackground), + borderRadius: BorderRadius.all(Radius.circular(4)), + ), + width: 34, + height: 34, + child: Padding( + padding: EdgeInsets.all(4), + child: Center(child: icon), + ), + ), + SizedBox(width: 12), + Text(text, style: TextStyle(fontSize: 17)), + Spacer(), + endChild, + SizedBox(width: 8), + Icon(Icons.arrow_forward_ios), + ], ), - SizedBox(width: 12), - Text(text, style: TextStyle(fontSize: 17)), - Spacer(), - endChild, - SizedBox(width: 8), - Icon(Icons.arrow_forward_ios), - ], + ), ), ), - ), - ), - ); + ); } diff --git a/lib/screens/viewed/viewed_products_screen.dart b/lib/screens/viewed/viewed_products_screen.dart index cc69345..349f80d 100644 --- a/lib/screens/viewed/viewed_products_screen.dart +++ b/lib/screens/viewed/viewed_products_screen.dart @@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:wooapp/database/product.dart'; +import 'package:wooapp/database/entity/product.dart'; import 'package:wooapp/screens/viewed/viewed_products_cubit.dart'; import 'package:wooapp/screens/viewed/viewed_products_state.dart'; import 'package:wooapp/widget/stateful_wrapper.dart'; diff --git a/lib/screens/viewed/viewed_products_state.dart b/lib/screens/viewed/viewed_products_state.dart index 3076cc3..95e196a 100644 --- a/lib/screens/viewed/viewed_products_state.dart +++ b/lib/screens/viewed/viewed_products_state.dart @@ -1,4 +1,4 @@ -import 'package:wooapp/database/product.dart'; +import 'package:wooapp/database/entity/product.dart'; abstract class ViewedProductsState {} diff --git a/lib/screens/wishlist/wishlist_cubit.dart b/lib/screens/wishlist/wishlist_cubit.dart new file mode 100644 index 0000000..a34df1e --- /dev/null +++ b/lib/screens/wishlist/wishlist_cubit.dart @@ -0,0 +1,31 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:wooapp/datasource/wish_list_data_source.dart'; +import 'package:wooapp/locator.dart'; +import 'package:wooapp/screens/wishlist/wishlist_state.dart'; + +class WishListCubit extends Cubit { + final WishListDataSource _ds = locator(); + + WishListCubit() : super(InitialWishListState()) { + initialLoad(); + } + + void removeItem(int index) { + if (state is! ContentWishListState) return; + var entry = (state as ContentWishListState).wishlist[index]; + var newList = (state as ContentWishListState).wishlist..removeAt(index); + emit(ContentWishListState(newList)); + _ds.removeProductByItemId(entry.first.itemId.toString()); + } + + void initialLoad() { + emit(LoadingWishListState()); + _load(); + } + + void _load() { + _ds.getWishListWithProducts().then((value) { + emit(ContentWishListState(value)); + }); + } +} diff --git a/lib/screens/wishlist/wishlist_screen.dart b/lib/screens/wishlist/wishlist_screen.dart new file mode 100644 index 0000000..1b1ae59 --- /dev/null +++ b/lib/screens/wishlist/wishlist_screen.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:wooapp/screens/wishlist/wishlist_cubit.dart'; +import 'package:wooapp/screens/wishlist/wishlist_view.dart'; + +class WishListScreen extends StatelessWidget { + @override + Widget build(BuildContext context) => BlocProvider( + create: (_) => WishListCubit(), + child: WishListView(), + ); +} diff --git a/lib/screens/wishlist/wishlist_state.dart b/lib/screens/wishlist/wishlist_state.dart new file mode 100644 index 0000000..fe28922 --- /dev/null +++ b/lib/screens/wishlist/wishlist_state.dart @@ -0,0 +1,15 @@ +import 'package:wooapp/core/pair.dart'; +import 'package:wooapp/model/product.dart'; +import 'package:wooapp/model/wish_list_entry.dart'; + +abstract class WishListState {} + +class InitialWishListState extends WishListState {} + +class LoadingWishListState extends WishListState {} + +class ContentWishListState extends WishListState { + List> wishlist; + + ContentWishListState(this.wishlist); +} diff --git a/lib/screens/wishlist/wishlist_view.dart b/lib/screens/wishlist/wishlist_view.dart new file mode 100644 index 0000000..56b4450 --- /dev/null +++ b/lib/screens/wishlist/wishlist_view.dart @@ -0,0 +1,92 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:wooapp/model/product.dart'; +import 'package:wooapp/screens/wishlist/wishlist_cubit.dart'; +import 'package:wooapp/screens/wishlist/wishlist_state.dart'; +import 'package:wooapp/widget/shimmer.dart'; +import 'package:wooapp/widget/widget_product_grid.dart'; + +class WishListView extends StatelessWidget { + @override + Widget build(BuildContext context) => Scaffold( + appBar: AppBar( + title: Text('wish_list').tr(), + ), + backgroundColor: Colors.white, + body: BlocListener( + listener: (context, state) {}, + child: BlocBuilder( + builder: (context, state) { + switch (state.runtimeType) { + // case InitialProfileState: + // return _loadingState(context); + // case LoadingProfileState: + // return _loadingState(context); + // case ContentProfileState: + // return _contentState(context, (state as ContentProfileState).profile); + // case ErrorProfileState: + // return _errorState(); + // case NoAuthProfileState: + // return _noAuth(context); + case ContentWishListState: + return _contentState(context, state as ContentWishListState); + default: + return _loadingState(context); + } + }, + ), + ), + ); + + Widget _contentState(BuildContext context, ContentWishListState state) => + GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + ), + itemCount: state.wishlist.length, + itemBuilder: (ctx, i) => _buildProductItem( + context, + i, + state.wishlist[i].second, + ), + ); + + Widget _buildProductItem( + BuildContext context, + int index, + Product product, + ) => + Stack( + alignment: Alignment.topRight, + children: [ + ProductGridItem( + product: product, + detailRouteCallback: (result) { + if (result == true) { + context.read().initialLoad(); + } + }, + ), + Padding( + padding: EdgeInsets.only( + top: 8, + right: 8, + ), + child: InkWell( + onTap: () => context.read().removeItem(index), + child: CircleAvatar( + backgroundColor: Colors.redAccent, + radius: 16, + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(16)), + child: Icon(Icons.delete_outline, color: Colors.white), + ), + ), + ), + ), + ], + ); + + Widget _loadingState(BuildContext context) => FeaturedShimmer(true); +} diff --git a/lib/widget/widget_catalog_products.dart b/lib/widget/widget_catalog_products.dart index 9078fc7..8ba2819 100644 --- a/lib/widget/widget_catalog_products.dart +++ b/lib/widget/widget_catalog_products.dart @@ -10,8 +10,6 @@ import 'package:wooapp/model/product.dart'; import 'package:wooapp/screens/product/product_screen.dart'; import 'package:wooapp/widget/stateful_wrapper.dart'; import 'package:wooapp/widget/widget_price.dart'; -import 'package:wooapp/widget/widget_product_grid.dart'; -import 'package:wooapp/widget/widget_product_recent.dart'; abstract class CatalogProductState {} diff --git a/lib/widget/widget_order_item.dart b/lib/widget/widget_order_item.dart index f60dead..5845aa4 100644 --- a/lib/widget/widget_order_item.dart +++ b/lib/widget/widget_order_item.dart @@ -21,7 +21,7 @@ class OrderItem extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ _buildHeader(context), - _buildLineItems(), + _buildLineItems(context), SizedBox(height: 8), Divider(thickness: 1.4), ], @@ -47,35 +47,55 @@ class OrderItem extends StatelessWidget { ), ); - Widget _buildLineItems() => Container( + Widget _buildLineItems(BuildContext context) => Container( margin: EdgeInsets.only(right: 16, left: 16, top: 16), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - for (var lineItem in order.lineItems) _buildLineItem(lineItem), + for (var lineItem in order.lineItems) _buildLineItem(context, lineItem), _buildOrderTotal() ], ), ); - Widget _buildLineItem(LineItem item) => Container( - margin: EdgeInsets.only(top: 4), - child: Row( - children: [ - Text( - '${item.name}', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), - ), - // Spacer(), - DotSpacer(), - Text( - '${item.quantity} x ${item.price}${AppConfig.currency}', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.w400), - ), - ], - ), - ); + Widget _buildLineItem(BuildContext context, LineItem item) => Container( + margin: EdgeInsets.only(top: 4), + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + Container( + width: MediaQuery.of(context).size.width, + margin: EdgeInsets.only(bottom: 8), + child: DotSpacer(), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + flex: 1, + fit: FlexFit.loose, + child: Container( + color: Colors.white, + child: Text( + '${item.name}', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), + ), + ), + ), + Container( + color: Colors.white, + child: Text( + '${item.quantity} x ${item.price}${AppConfig.currency}', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w400), + ), + ), + ], + ), + ], + )); Widget _buildOrderTotal() => Container( margin: EdgeInsets.only(top: 6), diff --git a/lib/widget/widget_product_actions.dart b/lib/widget/widget_product_actions.dart index 73638e2..ecb98fb 100644 --- a/lib/widget/widget_product_actions.dart +++ b/lib/widget/widget_product_actions.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:share/share.dart'; +import 'package:wooapp/datasource/wish_list_data_source.dart'; +import 'package:wooapp/locator.dart'; import 'package:wooapp/model/product.dart'; import 'package:wooapp/screens/reviews/reviews.dart'; import 'package:wooapp/widget/widget_product_stock.dart'; @@ -8,54 +10,116 @@ import 'package:wooapp/widget/widget_product_stock.dart'; class ProductActionsWidget extends StatefulWidget { static const double iconRadius = 20; static const double faRadius = 18; + final Product product; - ProductActionsWidget(this.product); + final WishListDataSource _ds = locator(); + final Function() changeCallback; + + ProductActionsWidget({ + required this.product, + required this.changeCallback, + }); @override State createState() => _ProductActionsWidgetState(); } class _ProductActionsWidgetState extends State { + bool _wishListPresent = false; + bool _wishListOperationExecution = false; + + @override + void initState() { + setState(() { + _wishListOperationExecution = true; + }); + widget._ds + .isInWishListByProductId(widget.product.id) + .then((value) { + setState(() { + _wishListOperationExecution = false; + _wishListPresent = value; + }); + }).onError((error, stackTrace) { + setState(() { + _wishListOperationExecution = false; + }); + }); + super.initState(); + } + @override Widget build(BuildContext context) => Row( - children: [ - ProductStockWidget(widget.product), - Spacer(), - _buildIcon( - FaIcon(FontAwesomeIcons.heart, color: Colors.white), - () { } - ), - SizedBox(width: 8), - _buildIcon( - FaIcon(FontAwesomeIcons.comment, color: Colors.white), - () => Navigator.of(context) - .push(MaterialPageRoute(builder: (_) => ReviewsScreen(widget.product.id))) - ), - SizedBox(width: 8), - _buildIcon( - FaIcon(FontAwesomeIcons.share, color: Colors.white), - () => Share.share( + children: [ + ProductStockWidget(widget.product), + Spacer(), + _buildIcon( + FaIcon( + _wishListPresent + ? FontAwesomeIcons.solidHeart + : FontAwesomeIcons.heart, + color: Colors.white, + ), + () { + if (_wishListOperationExecution) return; + var initialValue = _wishListPresent; + widget.changeCallback(); + setState(() { + _wishListOperationExecution = true; + _wishListPresent = !_wishListPresent; + }); + if (!initialValue) { + widget._ds.addProduct(widget.product.id).then((_) { + _wishListOperationExecution = false; + }).onError((error, stackTrace) { + _wishListOperationExecution = false; + }); + } else { + widget._ds.removeProductByProductId(widget.product.id).then((_) { + _wishListOperationExecution = false; + }).onError((error, stackTrace) { + _wishListOperationExecution = false; + }); + } + }, + ), + SizedBox(width: 8), + _buildIcon( + FaIcon(FontAwesomeIcons.comment, color: Colors.white), + () => Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => ReviewsScreen(widget.product.id), + ), + ), + ), + SizedBox(width: 8), + _buildIcon( + FaIcon(FontAwesomeIcons.share, color: Colors.white), + () => Share.share( 'Check out: ${widget.product.name}\n${widget.product.permalink}', - subject: 'Share ${widget.product.name}' + subject: 'Share ${widget.product.name}', + ), ), - ), - ], - ); + ], + ); Widget _buildIcon( - Widget icon, - VoidCallback action, { - Color backgroundColor = Colors.grey, - }) => CircleAvatar( - backgroundColor: backgroundColor, - radius: ProductActionsWidget.iconRadius, - child: GestureDetector( - onTap: action, - child: ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(ProductActionsWidget.iconRadius)), - child: icon, - ), - ), - ); + Widget icon, + VoidCallback action, { + Color backgroundColor = Colors.grey, + }) => + GestureDetector( + onTap: action, + child: Container( + padding: EdgeInsets.all(7.0), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.all( + Radius.circular(ProductActionsWidget.iconRadius), + ), + ), + child: icon, + ), + ); } diff --git a/lib/widget/widget_product_grid.dart b/lib/widget/widget_product_grid.dart index 54efe71..4252b9f 100644 --- a/lib/widget/widget_product_grid.dart +++ b/lib/widget/widget_product_grid.dart @@ -9,18 +9,25 @@ import 'package:wooapp/screens/product/product_screen.dart'; import 'package:wooapp/widget/widget_price.dart'; class ProductGridItem extends StatelessWidget { - final Product _product; + final Product product; + final Function(dynamic) detailRouteCallback; - const ProductGridItem(this._product); + const ProductGridItem({ + required this.product, + required this.detailRouteCallback, + }); @override Widget build(BuildContext context) => InkWell( - onTap: () { + onTap: () async { hideKeyboardForce(context); - Navigator.push( + var result = await Navigator.push( context, - MaterialPageRoute(builder: (context) => ProductScreen(_product.id)) + MaterialPageRoute(builder: (context) => ProductScreen(product.id)) ); + if (result != null) { + detailRouteCallback(result); + } }, child: Card( shape: RoundedRectangleBorder( @@ -33,7 +40,7 @@ class ProductGridItem extends StatelessWidget { Container( height: (MediaQuery.of(context).size.height / 6), child: CachedNetworkImage( - imageUrl: _product.images.length != 0 ? _product.images[0].src : '', + imageUrl: product.images.length != 0 ? product.images[0].src : '', imageBuilder: (context, imageProvider) => Container( decoration: BoxDecoration( image: DecorationImage( @@ -54,7 +61,7 @@ class ProductGridItem extends StatelessWidget { Padding( padding: EdgeInsets.only(top: 10, left: 8, right: 4), child: Text( - _product.name, + product.name, maxLines: 1, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500), ), @@ -63,10 +70,10 @@ class ProductGridItem extends StatelessWidget { padding: EdgeInsets.only(top: 4, left: 8, right: 4), child: Row( children: [ - PriceWidget.withProduct(_product), + PriceWidget.withProduct(product), Spacer(), RatingBarIndicator( - rating: _product.rating, + rating: product.rating, itemBuilder: (context, index) => Icon( Icons.star, color: Colors.amber, @@ -75,7 +82,7 @@ class ProductGridItem extends StatelessWidget { itemSize: 12, direction: Axis.horizontal, ), - Text(' ${_product.rating.toString()}') + Text(' ${product.rating.toString()}') ], ), ), diff --git a/lib/widget/widget_product_info.dart b/lib/widget/widget_product_info.dart index 9f2dbff..5579c31 100644 --- a/lib/widget/widget_product_info.dart +++ b/lib/widget/widget_product_info.dart @@ -48,7 +48,7 @@ class ProductInfoWidget extends StatelessWidget { key, textAlign: TextAlign.start, style: TextStyle( - fontSize: 3.9 + fontSize: 3.9, ), ), ), @@ -60,12 +60,12 @@ class ProductInfoWidget extends StatelessWidget { alignment: Alignment.centerLeft, width: 50.0, child: Text( - value, - textAlign: TextAlign.start, - style: TextStyle( - fontSize: 4.4, - fontWeight: FontWeight.w600 - ) + value, + textAlign: TextAlign.start, + style: TextStyle( + fontSize: 4.4, + fontWeight: FontWeight.w600, + ), ), ), ), diff --git a/lib/widget/widget_product_recent.dart b/lib/widget/widget_product_recent.dart index db9919b..f01009b 100644 --- a/lib/widget/widget_product_recent.dart +++ b/lib/widget/widget_product_recent.dart @@ -1,7 +1,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:shimmer_animation/shimmer_animation.dart'; -import 'package:wooapp/database/product.dart'; +import 'package:wooapp/database/entity/product.dart'; import 'package:wooapp/extensions/extensions_context.dart'; import 'package:wooapp/screens/product/product_screen.dart'; import 'package:wooapp/widget/widget_price.dart'; diff --git a/lib/widget/widget_settings_language.dart b/lib/widget/widget_settings_language.dart index 774a975..3d9bb1c 100644 --- a/lib/widget/widget_settings_language.dart +++ b/lib/widget/widget_settings_language.dart @@ -1,16 +1,10 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; - -class Lang { - String code; - String title; - - Lang(this.code, this.title); -} +import 'package:wooapp/constants/translations.dart'; class LanguageWidget extends StatefulWidget { final Locale locale; - final ValueSetter callback; + final ValueSetter callback; LanguageWidget(this.locale, this.callback); @@ -20,23 +14,17 @@ class LanguageWidget extends StatefulWidget { } class _LanguageWidgetState extends State { - final List _languages = [ - Lang('en', 'English'), - Lang('uk', 'Українська'), - Lang('ru', 'Русский'), - ]; - @override Widget build(BuildContext context) => Container( padding: EdgeInsets.only(bottom: 8), child: ListView.builder( shrinkWrap: true, - itemCount: _languages.length, - itemBuilder: (context, index) => _langItem(_languages[index]), + itemCount: Translations.languages.length, + itemBuilder: (_, index) => _langItem(Translations.languages[index]), ), ); - Widget _langItem(Lang lang) => GestureDetector( + Widget _langItem(Language lang) => GestureDetector( onTap: () { widget.callback(lang); Navigator.of(context).pop(); @@ -65,12 +53,13 @@ class _LanguageWidgetState extends State { Text( '${lang.title}', style: TextStyle( - fontSize: widget.locale.languageCode.contains(lang.code) ? 18 : 17, - fontWeight: widget.locale.languageCode.contains(lang.code) ? FontWeight.w800 : FontWeight.w400 + fontSize: widget.locale.languageCode.contains(lang.locale.languageCode) ? 18 : 17, + fontWeight: widget.locale.languageCode.contains(lang.locale.languageCode) ? FontWeight.w800 : FontWeight.w400 ), ), Spacer(), - if (widget.locale.languageCode.contains(lang.code)) Icon(Icons.check), + if (widget.locale.languageCode.contains(lang.locale.languageCode)) + Icon(Icons.check), ], ), ),