diff --git a/lib/ui/home/photo_camera.dart b/lib/ui/home/photo_camera.dart index ec17996..1df837e 100644 --- a/lib/ui/home/photo_camera.dart +++ b/lib/ui/home/photo_camera.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:camera/camera.dart'; +import 'package:flutter/services.dart'; import 'package:mobdr/core/components/custom_camera_preview.dart'; class CameraPage extends StatefulWidget { @@ -19,6 +20,16 @@ class _CameraPageState extends State { @override void initState() { super.initState(); + + // Remove the constraint when entering, this is ok + // not to away since the app is in portrait mode by + // default + SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitUp, + DeviceOrientation.landscapeLeft, + DeviceOrientation.landscapeRight, + ]); + availableCameras().then((availableCameras) { cameras = availableCameras; if (cameras.isNotEmpty) { diff --git a/lib/ui/home/photo_detail.dart b/lib/ui/home/photo_detail.dart new file mode 100644 index 0000000..6b017f4 --- /dev/null +++ b/lib/ui/home/photo_detail.dart @@ -0,0 +1,795 @@ +import 'dart:io'; + +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:mobdr/config/constant.dart'; +import 'package:mobdr/config/global_style.dart'; +import 'package:mobdr/model/related_product_model.dart'; +import 'package:mobdr/model/review_model.dart'; +import 'package:mobdr/ui/general/chat_us.dart'; +import 'package:mobdr/ui/general/notification.dart'; +import 'package:mobdr/ui/general/product_detail/delivery_estimated.dart'; +import 'package:mobdr/ui/general/product_detail/product_description.dart'; +import 'package:mobdr/ui/general/product_detail/product_review.dart'; +import 'package:mobdr/ui/home/product_category.dart'; +import 'package:mobdr/ui/home/search.dart'; +import 'package:mobdr/ui/reusable/reusable_widget.dart'; +import 'package:mobdr/ui/shopping_cart/tab_shopping_cart.dart'; +import 'package:mobdr/ui/reusable/cache_image_network.dart'; +import 'package:mobdr/ui/reusable/global_function.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; + +class PhotoDetailPage extends StatefulWidget { + final String name; + final String image; + final double price; + final int photo; + final double rating; + final int review; + final int sale; + final String date; + + const PhotoDetailPage( + {Key? key, + this.name = '', + this.image = '', + this.price = 24, + this.photo = 1, + this.rating = 4, + this.review = 45, + this.sale = 63, + this.date = ''}) + : super(key: key); + + @override + _PhotoDetailPageState createState() => _PhotoDetailPageState(); +} + +class _PhotoDetailPageState extends State { + // initialize global function and reusable widget + final _globalFunction = GlobalFunction(); + final _reusableWidget = ReusableWidget(); + + final List _imgProductSlider = []; + int _currentImageSlider = 0; + + // size data + List _sizeList = ['XS', 'S', 'M', 'L', 'XL', 'XXL']; + int _sizeIndex = 0; + + // color data + List _colorList = [ + 'Red', + 'Black', + 'Green', + 'White', + 'Blue', + 'Yellow', + 'Pink' + ]; + int _colorIndex = 0; + + // wishlist + bool _isLove = false; + + // shopping cart count + int _shoppingCartCount = 3; + + @override + void initState() { + // image slider for the product + _imgProductSlider.add(widget.image); + _imgProductSlider.add(widget.image); + _imgProductSlider.add(widget.image); + _imgProductSlider.add(widget.image); + _imgProductSlider.add(widget.image); + + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final double boxImageSize = (MediaQuery.of(context).size.width / 3); + return Scaffold( + appBar: AppBar( + iconTheme: IconThemeData( + color: GlobalStyle.appBarIconThemeColor, + ), + elevation: GlobalStyle.appBarElevation, + titleSpacing: 0.0, + // create search text field in the app bar + title: Container( + margin: EdgeInsets.only(right: 16), + child: TextButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.resolveWith( + (Set states) => Colors.grey[100]!, + ), + overlayColor: MaterialStateProperty.all(Colors.transparent), + shape: MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5.0), + )), + ), + onPressed: () { + Navigator.push(context, + MaterialPageRoute(builder: (context) => SearchPage())); + }, + child: Row( + children: [ + SizedBox(width: 8), + Icon(Icons.search, color: Colors.grey[500], size: 18), + SizedBox(width: 8), + Text( + 'Search Product', + style: TextStyle( + fontSize: 13, + color: Colors.grey[600], + fontWeight: FontWeight.normal), + ) + ], + )), + ), + backgroundColor: GlobalStyle.appBarBackgroundColor, + systemOverlayStyle: GlobalStyle.appBarSystemOverlayStyle, + actions: [ + IconButton( + padding: EdgeInsets.all(0), + constraints: BoxConstraints(), + icon: _customShoppingCart(_shoppingCartCount), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => TabShoppingCartPage())); + }), + IconButton( + icon: _reusableWidget.customNotifIcon( + count: 8, notifColor: BLACK_GREY), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => NotificationPage())); + }), + ], + bottom: _reusableWidget.bottomAppBar(), + ), + body: WillPopScope( + onWillPop: () { + Navigator.pop(context); + return Future.value(true); + }, + child: Column( + children: [ + Flexible( + child: ListView( + children: [ + Image.file(File(widget.image), fit: BoxFit.cover), + //_createProductSlider(), + _createProductPriceTitleEtc(), + _createProductVariant(), + _createDeliveryEstimated(), + _createProductInformation(), + _createProductDescription(), + _createProductRelated(boxImageSize), + _createProductReview(), + SizedBox(height: 16) + ], + ), + ), + Container( + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.grey, + offset: Offset(0.0, 1.0), //(x,y) + blurRadius: 2.0, + ), + ], + ), + child: Row( + children: [ + Container( + child: GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ChatUsPage())); + }, + child: ClipOval( + child: Container( + color: SOFT_BLUE, + padding: EdgeInsets.all(9), + child: Icon(Icons.chat, + color: Colors.white, size: 16)), + ), + ), + ), + SizedBox( + width: 10, + ), + Expanded( + child: GestureDetector( + onTap: () { + setState(() { + _shoppingCartCount++; + }); + Fluttertoast.showToast( + msg: 'Item has been added to Shopping Cart', + toastLength: Toast.LENGTH_LONG); + }, + child: Container( + alignment: Alignment.center, + padding: EdgeInsets.fromLTRB(12, 8, 12, 8), + margin: EdgeInsets.only(right: 8), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(width: 1, color: SOFT_BLUE), + borderRadius: BorderRadius.all(Radius.circular( + 10) // <--- border radius here + )), + child: Text('Add to Shopping Cart', + style: TextStyle( + color: SOFT_BLUE, + fontWeight: FontWeight.bold)), + ), + ), + ), + ], + ), + ) + ], + ), + )); + } + + Widget _customShoppingCart(int count) { + return Stack( + children: [ + Icon(Icons.shopping_cart, color: BLACK_GREY), + Positioned( + right: 0, + child: Container( + padding: EdgeInsets.all(1), + decoration: BoxDecoration( + color: ASSENT_COLOR, + borderRadius: BorderRadius.circular(10), + ), + constraints: BoxConstraints( + minWidth: 14, + minHeight: 14, + ), + child: Center( + child: Text( + count.toString(), + style: TextStyle( + color: Colors.white, + fontSize: 8, + ), + textAlign: TextAlign.center, + ), + ), + ), + ) + ], + ); + } + + Widget _createProductSlider() { + return Stack( + children: [ + CarouselSlider( + items: _imgProductSlider + .map((item) => Container( + child: + buildCacheNetworkImage(width: 0, height: 0, url: item), + )) + .toList(), + options: CarouselOptions( + aspectRatio: 1, + viewportFraction: 1.0, + autoPlay: false, + enlargeCenterPage: false, + onPageChanged: (index, reason) { + setState(() { + _currentImageSlider = index; + }); + }), + ), + Positioned( + bottom: 16, + left: 16, + child: Container( + padding: EdgeInsets.fromLTRB(8, 4, 8, 4), + decoration: BoxDecoration( + color: SOFT_BLUE, borderRadius: BorderRadius.circular(4)), + child: Text( + (_currentImageSlider + 1).toString() + + '/' + + _imgProductSlider.length.toString(), + style: TextStyle(color: Colors.white, fontSize: 11)), + ), + ), + ], + ); + } + + Widget _createProductPriceTitleEtc() { + return Container( + color: Colors.white, + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text(widget.photo.toString() + ' photo(s)', + style: GlobalStyle.detailProductPrice), + GestureDetector( + onTap: () { + setState(() { + if (_isLove == true) { + _isLove = false; + Fluttertoast.showToast( + msg: "La photo n'est plus taguée comme favorite", + toastLength: Toast.LENGTH_LONG); + } else { + Fluttertoast.showToast( + msg: "La photo est taguée comme favorite", + toastLength: Toast.LENGTH_LONG); + _isLove = true; + } + }); + }, + child: Icon(Icons.favorite, + color: _isLove == true ? ASSENT_COLOR : BLACK_GREY, + size: 28), + ) + ], + ), + SizedBox(height: 12), + Text(widget.name, + style: TextStyle( + fontSize: 14, + )), + SizedBox(height: 12), + IntrinsicHeight( + child: Row( + children: [ + Text(widget.date, + style: TextStyle(fontSize: 13, color: BLACK_GREY)), + VerticalDivider( + width: 30, + thickness: 1, + color: Colors.grey[300], + ), + Icon(Icons.location_on, color: SOFT_GREY, size: 16), + Text('Brooklyn', + style: TextStyle(fontSize: 13, color: SOFT_GREY)) + ], + ), + ), + ], + ), + ); + } + + Widget _createProductVariant() { + return Container( + margin: EdgeInsets.only(top: 12), + padding: EdgeInsets.all(16), + color: Colors.white, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Variant', style: GlobalStyle.sectionTitle), + SizedBox( + height: 16, + ), + Row( + children: [ + Text('Size : ', + style: TextStyle(color: BLACK_GREY, fontSize: 14)), + Text(_sizeList[_sizeIndex], + style: TextStyle(fontWeight: FontWeight.bold)), + ], + ), + Wrap( + children: List.generate(_sizeList.length, (index) { + return radioSize(_sizeList[index], index); + }), + ), + SizedBox( + height: 16, + ), + Row( + children: [ + Text('Color : ', + style: TextStyle(color: BLACK_GREY, fontSize: 14)), + Text(_colorList[_colorIndex], + style: TextStyle(fontWeight: FontWeight.bold)), + ], + ), + Wrap( + children: List.generate(_colorList.length, (index) { + return radioColor(_colorList[index], index); + }), + ), + ], + )); + } + + Widget radioSize(String txt, int index) { + return GestureDetector( + onTap: () { + setState(() { + _sizeIndex = index; + }); + }, + child: Container( + padding: EdgeInsets.fromLTRB(12, 8, 12, 8), + margin: EdgeInsets.only(right: 8, top: 8), + decoration: BoxDecoration( + color: _sizeIndex == index ? SOFT_BLUE : Colors.white, + border: Border.all( + width: 1, + color: _sizeIndex == index ? SOFT_BLUE : Colors.grey[300]!), + borderRadius: BorderRadius.all( + Radius.circular(10) // <--- border radius here + )), + child: Text(txt, + style: TextStyle( + color: _sizeIndex == index ? Colors.white : CHARCOAL)), + ), + ); + } + + Widget radioColor(String txt, int index) { + return GestureDetector( + onTap: () { + setState(() { + _colorIndex = index; + }); + }, + child: Container( + padding: EdgeInsets.fromLTRB(12, 8, 12, 8), + margin: EdgeInsets.only(right: 8, top: 8), + decoration: BoxDecoration( + color: _colorIndex == index ? SOFT_BLUE : Colors.white, + border: Border.all( + width: 1, + color: _colorIndex == index ? SOFT_BLUE : Colors.grey[300]!), + borderRadius: BorderRadius.all( + Radius.circular(10) // <--- border radius here + )), + child: Text(txt, + style: TextStyle( + color: _colorIndex == index ? Colors.white : CHARCOAL)), + ), + ); + } + + Widget _createDeliveryEstimated() { + return GestureDetector( + onTap: () { + Navigator.push(context, + MaterialPageRoute(builder: (context) => DeliveryEstimatedPage())); + }, + child: Container( + margin: EdgeInsets.only(top: 12), + padding: EdgeInsets.all(16), + color: Colors.white, + child: Row( + children: [ + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Delivery', style: GlobalStyle.sectionTitle), + SizedBox( + height: 16, + ), + RichText( + text: new TextSpan( + // Note: Styles for TextSpans must be explicitly defined. + // Child text spans will inherit styles from parent + style: new TextStyle( + fontSize: 15.5, + color: BLACK_GREY, + ), + children: [ + new TextSpan( + text: + 'Calculate the estimated cost for shipping goods to '), + new TextSpan( + text: 'West New York, NJ', + style: + new TextStyle(fontWeight: FontWeight.bold)), + ], + ), + ), + ], + ), + ), + Icon(Icons.chevron_right, size: 36, color: CHARCOAL) + ], + )), + ); + } + + Widget _createProductInformation() { + return Container( + margin: EdgeInsets.only(top: 12), + padding: EdgeInsets.all(16), + color: Colors.white, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Information', style: GlobalStyle.sectionTitle), + SizedBox( + height: 16, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Weight', style: TextStyle(color: BLACK_GREY)), + Text('300 Gram', style: TextStyle(color: BLACK_GREY)) + ], + ), + SizedBox( + height: 12, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Condition', style: TextStyle(color: BLACK_GREY)), + Text('Second', style: TextStyle(color: BLACK_GREY)) + ], + ), + SizedBox( + height: 12, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Category', style: TextStyle(color: BLACK_GREY)), + GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ProductCategoryPage( + categoryId: 3, categoryName: 'Electronic'))); + }, + child: Text('Electronic', style: TextStyle(color: SOFT_BLUE)), + ) + ], + ), + ], + )); + } + + Widget _createProductDescription() { + return Container( + margin: EdgeInsets.only(top: 12), + padding: EdgeInsets.all(16), + color: Colors.white, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Description', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + SizedBox( + height: 16, + ), + Text( + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n\nQuisque tortor tortor, ultrices id scelerisque a, elementum id elit. Maecenas feugiat tellus sed augue malesuada, id tempus ex sodales.'), + SizedBox( + height: 16, + ), + GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ProductDescriptionPage( + name: widget.name, image: widget.image))); + }, + child: Center( + child: Text('Read More', style: TextStyle(color: SOFT_BLUE)), + ), + ), + ], + )); + } + + Widget _createProductRelated(boxImageSize) { + return Container( + margin: EdgeInsets.only(top: 12), + padding: EdgeInsets.only(bottom: 16), + color: Colors.white, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.all(16), + child: Text('Related Product', style: GlobalStyle.sectionTitle), + ), + Container( + height: boxImageSize * + GlobalStyle.horizontalProductHeightMultiplication, + child: ListView.builder( + padding: EdgeInsets.only(left: 12, right: 12), + scrollDirection: Axis.horizontal, + itemCount: relatedProductData.length, + itemBuilder: (BuildContext context, int index) { + return Container( + width: boxImageSize + 10, + child: Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + elevation: 2, + color: Colors.white, + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PhotoDetailPage( + name: relatedProductData[index].name, + image: relatedProductData[index].image, + price: relatedProductData[index].price, + rating: relatedProductData[index].rating, + review: relatedProductData[index].review, + sale: 36))); + }, + child: Column( + children: [ + ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(10), + topRight: Radius.circular(10)), + child: buildCacheNetworkImage( + width: boxImageSize + 10, + height: boxImageSize + 10, + url: relatedProductData[index].image)), + Container( + margin: EdgeInsets.fromLTRB(8, 8, 8, 8), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + relatedProductData[index].name, + style: GlobalStyle.productName, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + Container( + margin: EdgeInsets.only(top: 5), + child: Text( + '\$ ' + + _globalFunction + .removeDecimalZeroFormat( + relatedProductData[index] + .price), + style: GlobalStyle.productPrice), + ), + Container( + margin: EdgeInsets.only(top: 5), + child: Row( + children: [ + _reusableWidget.createRatingBar( + rating: relatedProductData[index] + .rating, + size: 12), + Text( + '(' + + relatedProductData[index] + .review + .toString() + + ')', + style: + GlobalStyle.productTotalReview) + ], + ), + ) + ], + ), + ), + ], + ), + ), + ), + ); + }, + ), + ), + ], + )); + } + + Widget _createProductReview() { + return Container( + margin: EdgeInsets.only(top: 12), + padding: EdgeInsets.all(16), + color: Colors.white, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Review', style: GlobalStyle.sectionTitle), + GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ProductReviewPage())); + }, + child: Text('View All', + style: GlobalStyle.viewAll.copyWith(color: SOFT_BLUE), + textAlign: TextAlign.end), + ) + ], + ), + SizedBox( + height: 8, + ), + Row( + children: [ + _reusableWidget.createRatingBar( + rating: widget.rating, size: 12), + Text('(' + widget.review.toString() + ')', + style: TextStyle(fontSize: 11, color: SOFT_GREY)) + ], + ), + Column( + children: List.generate(reviewData.length, (index) { + return Column( + children: [ + Divider( + height: 32, + color: Colors.grey[400], + ), + Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(reviewData[index].date, + style: TextStyle(fontSize: 13, color: SOFT_GREY)), + SizedBox(height: 4), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(reviewData[index].name, + style: TextStyle( + fontSize: 14, fontWeight: FontWeight.bold)), + _reusableWidget.createRatingBar( + rating: reviewData[index].rating, size: 12), + ], + ), + SizedBox(height: 4), + Text(reviewData[index].review) + ], + )) + ], + ); + })), + ], + )); + } +} diff --git a/lib/ui/home/photo_list.dart b/lib/ui/home/photo_list.dart index f184aed..c3a4816 100644 --- a/lib/ui/home/photo_list.dart +++ b/lib/ui/home/photo_list.dart @@ -9,7 +9,6 @@ import 'package:fluttertoast/fluttertoast.dart'; import 'package:image/image.dart' as img; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; -import 'package:flutter/painting.dart' show ImageProvider; import 'package:mobdr/config/constant.dart'; import 'package:mobdr/main.dart'; @@ -17,6 +16,7 @@ import 'package:mobdr/ui/reusable/global_widget.dart'; import 'package:mobdr/ui/home/photo_camera.dart'; import 'package:mobdr/model/photo_model.dart'; import 'package:mobdr/db/box_photo.dart'; +import 'package:mobdr/ui/home/photo_detail.dart'; // TODO Il faut supprimer les possibles photos du répertoire cache ! @@ -132,9 +132,18 @@ class _PhotoListPageState extends State { children: [ GestureDetector( onTap: () { - Fluttertoast.showToast( - msg: 'Click ' + photoData.image, - toastLength: Toast.LENGTH_SHORT); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PhotoDetailPage( + name: 'visiteData.name', + image: _photoData[index].image, + price: 0, + photo: 1, + rating: 0, + review: 0, + sale: 0, + date: ''))); }, child: Row( mainAxisAlignment: MainAxisAlignment.start, @@ -350,6 +359,13 @@ class _PhotoListPageState extends State { Route route = MaterialPageRoute( builder: (context) => CameraPage(photoFiles: photoFiles)); Navigator.push(context, route).then((val) { + // Restore the constraint before navigating away + // DO await here to avoid any other screen in + // landscape mode + SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitUp, + ]); + /// if the user has validated photos if (val == true) { savePhotos();