moisey312

MapScreen

Sep 16th, 2020
657
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import 'dart:async';
  2. import 'dart:typed_data';
  3.  
  4. import 'package:flutter/cupertino.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:flutter/services.dart';
  7. import 'package:google_maps_flutter/google_maps_flutter.dart';
  8. import 'package:intl/date_symbol_data_local.dart';
  9. import 'package:lite_rolling_switch/lite_rolling_switch.dart';
  10. import 'package:maps_toolkit/maps_toolkit.dart' as mapUtils;
  11. import 'package:tireservice/analytics/Analytics.dart';
  12. import 'package:tireservice/dto/TireserviceDto.dart';
  13. import 'package:tireservice/dto/CityDto.dart';
  14. import 'package:tireservice/repository/Database.dart';
  15. import 'package:tireservice/ui/AdsUI.dart';
  16. import 'package:tireservice/ui/BusinessFilterUI.dart';
  17. import 'package:tireservice/ui/TireserviceDetailsScreen.dart';
  18. import 'package:tireservice/ui/CustomColors.dart';
  19. import 'package:tireservice/ui/FilterUI.dart';
  20. import 'package:tireservice/ui/SearchAnywhereDelegate.dart';
  21. import 'package:tireservice/ui/Style.dart';
  22. import 'package:tireservice/ui/UIComponents.dart';
  23. import 'package:tireservice/utils/Extensions.dart';
  24. import 'dart:ui' as ui;
  25. import 'package:shimmer/shimmer.dart';
  26. import 'package:location/location.dart';
  27. import 'package:url_launcher/url_launcher.dart';
  28.  
  29. class MapScreen extends StatefulWidget {
  30.   @override
  31.   State<StatefulWidget> createState() => _MapScreenState();
  32. }
  33.  
  34. class _MapScreenState extends State<MapScreen> {
  35.   static const cardHeight = 120.0;
  36.   static const LatLng centerMsk = const LatLng(55.746438, 37.621644);
  37.   static const LatLng centerKzn = const LatLng(55.825397, 49.115336);
  38.   static const double defaultMapZoom = 11.0;
  39.  
  40.  
  41.   Set<Marker> _businessMarkers = Set();
  42.   final List<TireserviceDto> _filteredBusiness = List();
  43.  
  44.  
  45.   Set<Marker> _markers = Set();
  46.   final Set<Marker> _filteredMarkers = Set();
  47.   List<TireserviceDto> _tireservices = List();
  48.   final List<TireserviceDto> _filteredTireservices = List();
  49.   var _location = new Location()
  50.     ..changeSettings(distanceFilter: 30, interval: 3000);
  51.   final _database = Database();
  52.   PageController _listController = PageController(
  53.     viewportFraction: 0.85,
  54.   );
  55.   LatLng _lastMapPosition = centerKzn;
  56.   double _lastMapZoomLevel = defaultMapZoom;
  57.   String _mapStyle;
  58.   GoogleMapController _googleMapController;
  59.  
  60.   LocationData _currentLocation;
  61.   StreamSubscription _getTireservicesSubscription;
  62.   StreamSubscription _getCitySubscription;
  63.   String _currentsUserCity;
  64.   CityDto _currentCityDto;
  65.   FilterEngine _filterEngine = FilterEngine();
  66.   String _currentSearchedPromo;
  67.   Marker _myLocation;
  68.   var adsOnMapVisible = false;
  69.   var userAgreedLocationPermission = false;
  70.   var rotation = 0.0;
  71.   bool isBusiness = false;
  72.  
  73.   @override
  74.   void initState() {
  75.     super.initState();
  76.     initializeDateFormatting("ru_RU");
  77.     rootBundle.loadString('assets/default_map_style.json').then((string) {
  78.       _mapStyle = string;
  79.       print('map is loaded');
  80.     });
  81.     _invalidateCitySelection();
  82.     _restoreState();
  83.     _createMyLocation();
  84.  
  85.     _location.hasPermission().then((hasPermission) {
  86.       if (hasPermission != null) {
  87.         userAgreedLocationPermission = hasPermission.index!=0?true:false;
  88.         _listenLocationChange();
  89.       }
  90.     });
  91.   }
  92.  
  93.   @override
  94.   void deactivate() {
  95.     _database.saveUserMapLocation(_lastMapPosition);
  96.     _database.saveUserMapZoomLevel(_lastMapZoomLevel);
  97.     super.deactivate();
  98.   }
  99.  
  100.   @override
  101.   void dispose() {
  102.     _getTireservicesSubscription?.cancel();
  103.     _getCitySubscription?.cancel();
  104.     super.dispose();
  105.   }
  106.  
  107.   _listenLocationChange() {
  108.     _location.onLocationChanged().listen((locationData) {
  109.       userAgreedLocationPermission = true;
  110.  
  111.       mapUtils.LatLng lastKnownLocation;
  112.       if (_currentLocation != null) {
  113.         lastKnownLocation = mapUtils.LatLng(
  114.             _currentLocation.latitude, _currentLocation.longitude);
  115.       }
  116.       _currentLocation = locationData;
  117.       if (_myLocation != null) {
  118.         if (lastKnownLocation != null) {
  119.           final currentLocation = mapUtils.LatLng(
  120.               _currentLocation.latitude, _currentLocation.longitude);
  121.           rotation = mapUtils.SphericalUtil.computeHeading(
  122.               lastKnownLocation, currentLocation);
  123.         }
  124.         _invalidateCarIcon();
  125.       }
  126.  
  127.       redraw();
  128.     });
  129.   }
  130.  
  131.   _invalidateCarIcon() {
  132.     if (_myLocation != null && _currentLocation != null) {
  133.       _myLocation = _myLocation.copyWith(
  134.         visibleParam: userAgreedLocationPermission,
  135.         rotationParam: rotation,
  136.         positionParam:
  137.             LatLng(_currentLocation.latitude, _currentLocation.longitude),
  138.       );
  139.       this._filteredMarkers.add(_myLocation);
  140.       redraw();
  141.     }
  142.   }
  143.  
  144.   _createMyLocation() {
  145.     _createMyLocationMarker().then((marker) {
  146.       this._myLocation = marker;
  147.       _invalidateCarIcon();
  148.     });
  149.   }
  150.  
  151.   _restoreState() {
  152.     _database.getUserMapZoomLevel().then((zoom) {
  153.       if (zoom != null) {
  154.         _lastMapZoomLevel = zoom;
  155.         redraw();
  156.       }
  157.     });
  158.  
  159.     _database.getUserMapLocation().then((position) {
  160.       if (position != null) {
  161.         _lastMapPosition = position;
  162.         redraw();
  163.       }
  164.     });
  165.   }
  166.  
  167.   myCurrentLocation() async {
  168.     try {
  169.       _currentLocation = await _location.getLocation();
  170.       print("locationLatitude: ${_currentLocation.latitude.toString()}");
  171.       print("locationLongitude: ${_currentLocation.longitude.toString()}");
  172.       userAgreedLocationPermission = true;
  173.  
  174.       _invalidateCarIcon();
  175.     } on PlatformException catch (e) {
  176.       if (e.code == 'PERMISSION_DENIED') {
  177.         String error = 'Permission denied';
  178.         print(error);
  179.       }
  180.       _currentLocation = null;
  181.     }
  182.   }
  183.  
  184.   Future<Marker> _createMyLocationMarker() async {
  185.     int size = 80;
  186.     final iconAsset = 'assets/icons/car.png';
  187.     final Uint8List markerIcon = await getBytesFromAsset(iconAsset, size);
  188.  
  189.     return Marker(
  190.       rotation: rotation,
  191.       anchor: Offset(0.5, 0.5),
  192.       markerId: MarkerId("myLocation"),
  193.       position: LatLng(0.0, 0.0),
  194.       zIndex: 2.0,
  195.       visible: false,
  196.       icon: BitmapDescriptor.fromBytes(markerIcon),
  197.     );
  198.   }
  199.  
  200.   void _onCameraMove(CameraPosition position) {
  201.     _lastMapPosition = position.target;
  202.     _lastMapZoomLevel = position.zoom;
  203.   }
  204.  
  205.   Future<Uint8List> getBytesFromAsset(String path, int width) async {
  206.     ByteData data = await rootBundle.load(path);
  207.     ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List(),
  208.         targetWidth: width);
  209.     ui.FrameInfo fi = await codec.getNextFrame();
  210.     return (await fi.image.toByteData(format: ui.ImageByteFormat.png))
  211.         .buffer
  212.         .asUint8List();
  213.   }
  214.  
  215.   Future<Marker> createMarkerFromTireservice(TireserviceDto tireservice) async {
  216.     String iconAsset = TireserviceDtoAssets(tireservice).getIconAsset();
  217.     int size;
  218.     double zIndex = 0.0;
  219.  
  220.     if (tireservice.isPremium()) {
  221.       size = 40;
  222.       zIndex = 1.0;
  223.     } else if (tireservice.isForTaxi()) {
  224.       size = 40;
  225.       zIndex = 2.0;
  226.     } else if (tireservice.isMobileTireservice()) {
  227.       size = 30;
  228.     } else {
  229.       size = 30;
  230.     }
  231.  
  232.     final Uint8List markerIcon = await getBytesFromAsset(iconAsset, size);
  233.     var position = tireservice.getLatLng();
  234.     return Marker(
  235.         anchor: Offset(0.5, 0.5),
  236.         markerId: MarkerId(tireservice.tireserviceId),
  237.         position: position,
  238.         zIndex: zIndex,
  239.         icon: BitmapDescriptor.fromBytes(markerIcon),
  240.         onTap: () {
  241.           _performSelectTireserviceInList(tireservice);
  242.         });
  243.   }
  244.  
  245.   _performSelectTireserviceInList(TireserviceDto tireservice) {
  246.     _listController.animateToPage(
  247.         _getPageForTireservice(tireservice.tireserviceId),
  248.         duration: Duration(milliseconds: 250),
  249.         curve: Curves.fastLinearToSlowEaseIn);
  250.   }
  251.  
  252.   int _getPageForTireservice(String id) {
  253.     return this
  254.         ._filteredTireservices
  255.         .indexWhere((tireservice) => tireservice.tireserviceId == id);
  256.   }
  257.  
  258.   void _onMapCreated(GoogleMapController controller) {
  259.     this._googleMapController = controller;
  260.     _googleMapController.setMapStyle(_mapStyle);
  261.  
  262.     _invalidateCitySelection();
  263.   }
  264.  
  265.   _fetchTireservices() {
  266.     _getTireservicesSubscription = _database
  267.         .streamTireservicesByCity(_currentsUserCity)
  268.         .asyncMap((documents) {
  269.       return documents.map((document) {
  270.         return TireserviceDto.createFromDocument(document);
  271.       }).where((tireservice) {
  272.         var isPromoSearchValid = true;
  273.         if (_currentSearchedPromo != null) {
  274.           isPromoSearchValid = tireservice.promo == null
  275.               ? false
  276.               : tireservice.promo.contains(_currentSearchedPromo);
  277.         }
  278.         return isPromoSearchValid &&
  279.             _filterEngine.isTireserviceValid(tireservice);
  280.       }).map((tireservice) {
  281.         _tireservices.removeWhere((prevTireservice) {
  282.           return prevTireservice.tireserviceId == tireservice.tireserviceId;
  283.         });
  284.         _tireservices.add(tireservice);
  285.         return createMarkerFromTireservice(tireservice);
  286.       });
  287.     }).map((list) {
  288.       list.toList().removeWhere((value) => value == null);
  289.       return list.toSet();
  290.     }).listen((set) {
  291.       Future.wait(set).then((set) {
  292.         this._markers = set.toSet();
  293.         this._filteredMarkers.clear();
  294.         this._filteredMarkers.addAll(this._markers);
  295.         _invalidateCarIcon();
  296.         this._filteredTireservices.clear();
  297.         this._filteredTireservices.addAll(this._tireservices);
  298.         redraw();
  299.       });
  300.     });
  301.   }
  302.  
  303.   _fetchCityDto() {
  304.     _getCitySubscription =
  305.         _database.streamCityByName(_currentsUserCity).listen((doc) {
  306.       _currentCityDto = CityDto.createFromDocument(doc);
  307.       redraw();
  308.  
  309.       this._markers.clear();
  310.       this._filteredMarkers.clear();
  311.       this._tireservices.clear();
  312.       this._filteredTireservices.clear();
  313.  
  314.       _invalidateCarIcon();
  315.  
  316.       _fetchTireservices();
  317.     });
  318.   }
  319.  
  320.   _callPhone(String phone) async {
  321.     if (phone == null || phone.isEmpty) return;
  322.  
  323.     final validPhone =
  324.         phone.length == 11 ? phone.substring(1) : phone.substring(2);
  325.     final url = "tel:+7$validPhone";
  326.     if (await canLaunch(url)) {
  327.       await launch(url);
  328.     } else {
  329.       throw 'Could not launch $url';
  330.     }
  331.   }
  332.  
  333.   _invalidateCitySelection() {
  334.     _database.getUserCity().then((userCity) {
  335.       _currentsUserCity = userCity;
  336.       _fetchCityDto();
  337.     });
  338.   }
  339.  
  340.   _onApplyFilter(FilterEngine filterEngine) {
  341.     this._filterEngine = filterEngine;
  342.     Analytics.applyFilter(filterEngine.filterState);
  343.     _invalidateCitySelection();
  344.   }
  345.  
  346.   _showSearch() async {
  347.     Analytics.showSearch();
  348.     await showSearch(
  349.       context: context,
  350.       delegate: SearchAnywhereDelegate(_currentsUserCity),
  351.     ).then((SearchResult searchResult) {
  352.       if (searchResult != null) {
  353.         if (searchResult.foundedTireservice != null) {
  354.           _performSelectTireserviceInList(searchResult.foundedTireservice);
  355.         } else if (searchResult.promo != null) {
  356.           _currentSearchedPromo = searchResult.promo;
  357.           _invalidateCitySelection();
  358.           _googleMapController.animateCamera(CameraUpdate.zoomTo(8));
  359.         }
  360.       }
  361.     });
  362.   }
  363.  
  364.   _showAdsOnMap() async {
  365.     Analytics.showAdsOnMap();
  366.     adsOnMapVisible = true;
  367.     redraw();
  368.   }
  369.  
  370.   _hideAdsOnMap() {
  371.     adsOnMapVisible = false;
  372.     redraw();
  373.   }
  374.  
  375.   CameraTargetBounds _getCurrentBounds() {
  376.     return CameraTargetBounds(
  377.       LatLngBounds(
  378.           southwest: _currentCityDto?.getSouthWest(),
  379.           northeast: _currentCityDto?.getNorthEast()),
  380.     );
  381.   }
  382.  
  383.   MinMaxZoomPreference _getZoomPreference() {
  384.     return MinMaxZoomPreference(
  385.         _currentCityDto.minZoom.toDouble(), _currentCityDto.maxZoom.toDouble());
  386.   }
  387.  
  388.   void redraw() {
  389.     this.setState(() {});
  390.   }
  391.  
  392.   void _showFiltersTireServices(context) {
  393.     Analytics.showFilter();
  394.     showModalBottomSheet(
  395.       isScrollControlled: true,
  396.       shape: RoundedRectangleBorder(
  397.           borderRadius: BorderRadius.vertical(top: Radius.circular(8))),
  398.       context: context,
  399.       builder: (BuildContext bc) {
  400.         return FilterUI(
  401.             _filterEngine, _invalidateCitySelection, _onApplyFilter);
  402.       },
  403.     );
  404.   }
  405.  
  406.   void _showBusinessTireServices(context) {
  407.     Analytics.showBusiness();
  408.     showModalBottomSheet(
  409.       isScrollControlled: true,
  410.       shape: RoundedRectangleBorder(
  411.           borderRadius: BorderRadius.vertical(top: Radius.circular(8))),
  412.       context: context,
  413.       builder: (BuildContext bc) {
  414.         return BusinessUI(_invalidateCitySelection);
  415.       },
  416.     );
  417.   }
  418.  
  419.   void _setStatusBarToWhiteText() {
  420.     SystemChrome.setSystemUIOverlayStyle(
  421.         SystemUiOverlayStyle(statusBarBrightness: Brightness.dark));
  422.   }
  423.  
  424.   @override
  425.   Widget build(BuildContext context) {
  426.     _setStatusBarToWhiteText();
  427.     return MaterialApp(
  428.       home: Scaffold(
  429.         body: Stack(
  430.           children: <Widget>[
  431.             map(),
  432.             mapControls(),
  433.             searchText(),
  434.             search(),
  435.             isBusiness?Container():tireserviceList(),
  436.           ],
  437.         ),
  438.       ),
  439.     );
  440.   }
  441.  
  442.   Widget map() {
  443.     return GoogleMap(
  444.       myLocationButtonEnabled: false,
  445.       compassEnabled: false,
  446.       zoomControlsEnabled: false,
  447.       mapToolbarEnabled: false,
  448.       cameraTargetBounds: _currentCityDto == null
  449.           ? CameraTargetBounds.unbounded
  450.           : _getCurrentBounds(),
  451.       minMaxZoomPreference: _currentCityDto == null
  452.           ? MinMaxZoomPreference.unbounded
  453.           : _getZoomPreference(),
  454.       markers: isBusiness?null:_filteredMarkers,
  455.       onMapCreated: _onMapCreated,
  456.       onCameraMove: _onCameraMove,
  457.       initialCameraPosition: CameraPosition(
  458.         target: _lastMapPosition,
  459.         zoom: _lastMapZoomLevel,
  460.       ),
  461.     );
  462.   }
  463.  
  464.   Widget search() {
  465.     return Padding(
  466.       padding: const EdgeInsets.fromLTRB(16, 50, 16, cardHeight),
  467.       child: Align(
  468.         alignment: Alignment.topLeft,
  469.         child: Column(
  470.           crossAxisAlignment: CrossAxisAlignment.start,
  471.           children: <Widget>[
  472.             FloatingActionButton(
  473.               mini: true,
  474.               onPressed: _showSearch,
  475.               materialTapTargetSize: MaterialTapTargetSize.padded,
  476.               backgroundColor: Colors.white,
  477.               child: const Icon(
  478.                 Icons.search,
  479.                 size: 20.0,
  480.                 color: Colors.black87,
  481.               ),
  482.             ),
  483.             adsMapArea(),
  484.           ],
  485.         ),
  486.       ),
  487.     );
  488.   }
  489.  
  490.   Widget searchText() {
  491.     if (_currentSearchedPromo == null) return Container();
  492.     return Padding(
  493.       padding: const EdgeInsets.fromLTRB(20, 54, 16, 0),
  494.       child: Align(
  495.         alignment: Alignment.topLeft,
  496.         child: Container(
  497.           decoration: BoxDecoration(
  498.             color: Colors.white,
  499.             borderRadius: BorderRadius.circular(20),
  500.           ),
  501.           padding: EdgeInsets.fromLTRB(46, 0, 16, 0),
  502.           height: 40,
  503.           width: double.infinity,
  504.           child: Row(
  505.             children: <Widget>[
  506.               Expanded(
  507.                 child: Text54Black14(_currentSearchedPromo),
  508.               ),
  509.               InkWell(
  510.                 onTap: () {
  511.                   _currentSearchedPromo = null;
  512.                   _invalidateCitySelection();
  513.                 },
  514.                 child: Icon(Icons.close, size: 20.0, color: Colors.black87),
  515.               )
  516.             ],
  517.           ),
  518.         ),
  519.       ),
  520.     );
  521.   }
  522.  
  523.   Widget mapControls() {
  524.     return Align(
  525.       alignment: FractionalOffset.centerRight,
  526.       child: Column(
  527.         children: <Widget>[
  528.           Spacer(),
  529.           roundButton(Icons.add, () {
  530.             _googleMapController.animateCamera(CameraUpdate.zoomIn());
  531.           }, Colors.white),
  532.           roundButton(Icons.remove, () {
  533.             _googleMapController.animateCamera(CameraUpdate.zoomOut());
  534.           }, Colors.white),
  535.           roundButton(Icons.my_location, () async {
  536.             await myCurrentLocation();
  537.             _googleMapController.animateCamera(CameraUpdate.newLatLng(
  538.                 LatLng(_currentLocation.latitude, _currentLocation.longitude)));
  539.           }, Colors.white),
  540.           roundButton(
  541.               Icons.filter_list,
  542.               () => isBusiness?_showBusinessTireServices(context):_showFiltersTireServices(context),
  543.               Colors.white //showFilters(context),
  544.               ),
  545.           roundButton(Icons.business_center, () {
  546.             setState(() {
  547.               isBusiness = !isBusiness;
  548.             });
  549.           }, isBusiness?Colors.blue:Colors.white),
  550.           Spacer(),
  551.         ],
  552.       ),
  553.     );
  554.   }
  555.  
  556. //  Widget mapControlsAdsButton() {
  557. //    return Align(
  558. //      alignment: FractionalOffset.centerLeft,
  559. //      child: Column(
  560. //        children: <Widget>[
  561. //          Spacer(),
  562. //          roundButton(Icons.add, () {
  563. //            _googleMapController.animateCamera(CameraUpdate.zoomIn());
  564. //          }),
  565. //        ],
  566. //      ),
  567. //    );
  568. //  }
  569.  
  570.   Widget roundButton(IconData icon, Function onTap, Color color) {
  571.     return Padding(
  572.       padding: EdgeInsets.fromLTRB(0, 8, 8, 0),
  573.       child: Material(
  574.         color: Colors.white,
  575.         borderRadius: new BorderRadius.circular(20.0),
  576.         child: InkWell(
  577.           borderRadius: new BorderRadius.circular(20.0),
  578.           onTap: onTap,
  579.           child: Container(
  580.             child: ClipRRect(
  581.                 borderRadius: new BorderRadius.circular(20.0),
  582.                 child: Container(
  583.                   decoration: BoxDecoration(color: color),
  584.                   width: 36,
  585.                   height: 36,
  586.                   child: Icon(
  587.                     icon,
  588.                     size: 20,
  589.                   ),
  590.                 )),
  591.           ),
  592.         ),
  593.       ),
  594.     );
  595.   }
  596.  
  597.   Widget tireserviceList() {
  598.     return Align(
  599.       alignment: Alignment.bottomLeft,
  600.       child: SafeArea(
  601.         child: Container(
  602.           height: 140,
  603.           child: PageView.builder(
  604.             controller: _listController,
  605.             onPageChanged: (index) {
  606.               var tireservice = _filteredTireservices[index];
  607.               _googleMapController.animateCamera(
  608.                 CameraUpdate.newLatLng(tireservice.getLatLng()),
  609.               );
  610.             },
  611.             scrollDirection: Axis.horizontal,
  612.             itemCount: _filteredTireservices.length,
  613.             itemBuilder: (context, index) => Padding(
  614.               padding: EdgeInsets.all(8.0),
  615.               child: _tireserviceCard(_filteredTireservices[index]),
  616.             ),
  617.           ),
  618.         ),
  619.       ),
  620.     );
  621.   }
  622.  
  623.   Widget businessList() {
  624.     return Align(
  625.       alignment: Alignment.bottomLeft,
  626.       child: SafeArea(
  627.         child: Container(
  628.           height: 140,
  629.           child: PageView.builder(
  630.             controller: _listController,
  631.             onPageChanged: (index) {
  632.               var tireservice = _filteredTireservices[index];
  633.               _googleMapController.animateCamera(
  634.                 CameraUpdate.newLatLng(tireservice.getLatLng()),
  635.               );
  636.             },
  637.             scrollDirection: Axis.horizontal,
  638.             itemCount: _filteredTireservices.length,
  639.             itemBuilder: (context, index) => Padding(
  640.               padding: EdgeInsets.all(8.0),
  641.               child: _tireserviceCard(_filteredTireservices[index]),
  642.             ),
  643.           ),
  644.         ),
  645.       ),
  646.     );
  647.   }
  648.   Widget _tireserviceCard(TireserviceDto info) {
  649.     return InkWell(
  650.       onTap: () {
  651.         Navigator.push(
  652.           context,
  653.           _createRoute(info),
  654.         );
  655.       },
  656.       child: Container(
  657.         child: new FittedBox(
  658.           child: Material(
  659.               color: Colors.white,
  660.               elevation: 2.0,
  661.               borderRadius: BorderRadius.circular(24.0),
  662.               shadowColor: Color(0x802196F3),
  663.               child: Row(
  664.                 mainAxisAlignment: MainAxisAlignment.spaceBetween,
  665.                 children: <Widget>[
  666.                   const SizedBox(
  667.                     width: 8,
  668.                   ),
  669.                   Container(
  670.                     width: cardHeight,
  671.                     height: cardHeight,
  672.                     child: ClipRRect(
  673.                       borderRadius: new BorderRadius.circular(20.0),
  674.                       child: Container(
  675.                         decoration: new BoxDecoration(color: Colors.grey[200]),
  676.                         child: Hero(
  677.                           tag: "tireserviceImage${info.tireserviceId}",
  678.                           child: Image(
  679.                             fit: BoxFit.fitHeight,
  680.                             image: NetworkImage(info.picturesURLs.first),
  681.                             loadingBuilder: (BuildContext context, Widget child,
  682.                                 ImageChunkEvent loadingProgress) {
  683.                               if (loadingProgress == null) return child;
  684.                               return Shimmer.fromColors(
  685.                                 child: Container(
  686.                                   child: Icon(
  687.                                     Icons.image,
  688.                                     size: cardHeight,
  689.                                   ),
  690.                                 ),
  691.                                 baseColor: Colors.grey[300],
  692.                                 highlightColor: Colors.white,
  693.                                 period: const Duration(milliseconds: 500),
  694.                               );
  695.                             },
  696.                           ),
  697.                         ),
  698.                       ),
  699.                     ),
  700.                   ),
  701.                   Container(
  702.                     child: Padding(
  703.                       padding: const EdgeInsets.all(8.0),
  704.                       child: Container(
  705.                           width: 200,
  706.                           height: cardHeight,
  707.                           child: Padding(
  708.                             padding: const EdgeInsets.fromLTRB(4, 4, 4, 0),
  709.                             child: Column(
  710.                               children: <Widget>[
  711.                                 Expanded(
  712.                                   child: Align(
  713.                                     alignment: Alignment.centerLeft,
  714.                                     child: TextMediumBlack16(info.title),
  715.                                   ),
  716.                                 ),
  717.                                 Padding(
  718.                                   padding:
  719.                                       const EdgeInsets.fromLTRB(0, 2, 0, 0),
  720.                                   child: Row(
  721.                                     children: <Widget>[
  722.                                       Text54Black14(
  723.                                           "${info.getRatingString()}"),
  724.                                       RatingBarReadOnlySmall(
  725.                                           rating: info.rating),
  726.                                     ],
  727.                                   ),
  728.                                 ),
  729.                                 Padding(
  730.                                   padding:
  731.                                       const EdgeInsets.fromLTRB(0, 6, 0, 0),
  732.                                   child: Row(
  733.                                     children: <Widget>[
  734.                                       Text54Black12("Время работы: "),
  735.                                       Text54Black12(info.getTimeWorking()),
  736.                                     ],
  737.                                   ),
  738.                                 ),
  739.                                 Spacer(),
  740.                                 Row(
  741.                                   mainAxisSize: MainAxisSize.min,
  742.                                   children: <Widget>[
  743. //                                    Spacer(),
  744.                                     CallButtonSmall(
  745.                                       onPressed: () {
  746.                                         Analytics.logOnCallBtnTappedFromMap(
  747.                                             info);
  748.                                         _callPhone(info.phone);
  749.                                       },
  750.                                     ),
  751.                                     Spacer(),
  752. //                                    ToNavigationButtonSmall(
  753. //                                      onPressed: () {},
  754. //                                    ),
  755.                                     Spacer(),
  756.                                   ],
  757.                                 )
  758.                               ],
  759.                             ),
  760.                           )),
  761.                     ),
  762.                   ),
  763.                 ],
  764.               )),
  765.         ),
  766.       ),
  767.     );
  768.   }
  769.  
  770.   Widget adsMapArea() {
  771.     final stack = List<Widget>();
  772.  
  773.     if (adsOnMapVisible) {
  774.       stack.add(Card(
  775.         color: Colors.white,
  776.         shape:
  777.             RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
  778.         child: Column(
  779.           crossAxisAlignment: CrossAxisAlignment.end,
  780.           children: <Widget>[
  781.             InkWell(
  782.               onTap: _hideAdsOnMap,
  783.               child: Padding(
  784.                 padding: EdgeInsets.all(12),
  785.                 child: Icon(
  786.                   Icons.close,
  787.                   size: 20,
  788.                 ),
  789.               ),
  790.             ),
  791.             AdsUI(_currentsUserCity, AdCallSource.map),
  792.           ],
  793.         ),
  794.       ));
  795.  
  796.       stack.add(
  797.         Align(
  798.             alignment: Alignment.center,
  799.             child: Padding(
  800.               padding: EdgeInsets.all(16),
  801.               child: TextMediumBlack16("Скидки от партнёров!"),
  802.             )),
  803.       );
  804.     }
  805.  
  806.     stack.add(FloatingActionButton(
  807.       heroTag: "adsFloating",
  808.       mini: true,
  809.       onPressed: _showAdsOnMap,
  810.       materialTapTargetSize: MaterialTapTargetSize.padded,
  811.       backgroundColor: CustomColors.yellow,
  812.       child: const Icon(
  813.         Icons.card_giftcard,
  814.         size: 20.0,
  815.         color: Colors.black87,
  816.       ),
  817.     ));
  818.     return Stack(children: stack);
  819.   }
  820.  
  821.   Route _createRoute(TireserviceDto info) {
  822.     return PageRouteBuilder(
  823.       pageBuilder: (context, animation, secondaryAnimation) =>
  824.           TireserviceDetailsScreen(tireservice: info),
  825.       transitionsBuilder: (context, animation, secondaryAnimation, child) {
  826.         var begin = Offset(0.0, 1.0);
  827.         var end = Offset.zero;
  828.         var curve = Curves.ease;
  829.  
  830.         var tween =
  831.             Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
  832.  
  833.         return SlideTransition(
  834.           position: animation.drive(tween),
  835.           child: child,
  836.         );
  837.       },
  838.       settings: RouteSettings(name: 'TireserviceDetailsScreen'),
  839.     );
  840.   }
  841. }
  842.  
RAW Paste Data