Guest User

Untitled

a guest
Mar 13th, 2020
1,895
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Dart 21.14 KB | None | 0 0
  1. import 'dart:async';
  2.  
  3. import 'package:flutter/foundation.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:flutter/scheduler.dart';
  6. import 'package:flutter/widgets.dart';
  7.  
  8. const Duration _bottomSheetDuration = Duration(milliseconds: 200);
  9. const double _minFlingVelocity = 700.0;
  10. const double _closeProgressThreshold = 0.5;
  11.  
  12. /// A material design bottom sheet.
  13. ///
  14. /// There are two kinds of bottom sheets in material design:
  15. ///
  16. ///  * _Persistent_. A persistent bottom sheet shows information that
  17. ///    supplements the primary content of the app. A persistent bottom sheet
  18. ///    remains visible even when the user interacts with other parts of the app.
  19. ///    Persistent bottom sheets can be created and displayed with the
  20. ///    [ScaffoldState.showBottomSheet] function or by specifying the
  21. ///    [Scaffold.bottomSheet] constructor parameter.
  22. ///
  23. ///  * _Modal_. A modal bottom sheet is an alternative to a menu or a dialog and
  24. ///    prevents the user from interacting with the rest of the app. Modal bottom
  25. ///    sheets can be created and displayed with the [showModalBottomSheet]
  26. ///    function.
  27. ///
  28. /// The [BottomSheet] widget itself is rarely used directly. Instead, prefer to
  29. /// create a persistent bottom sheet with [ScaffoldState.showBottomSheet] or
  30. /// [Scaffold.bottomSheet], and a modal bottom sheet with [showModalBottomSheet].
  31. ///
  32. /// See also:
  33. ///
  34. ///  * [showBottomSheet] and [ScaffoldState.showBottomSheet], for showing
  35. ///    non-modal "persistent" bottom sheets.
  36. ///  * [showModalBottomSheet], which can be used to display a modal bottom
  37. ///    sheet.
  38. ///  * <https://material.io/design/components/sheets-bottom.html>
  39. class BottomSheet extends StatefulWidget {
  40.   /// Creates a bottom sheet.
  41.   ///
  42.   /// Typically, bottom sheets are created implicitly by
  43.   /// [ScaffoldState.showBottomSheet], for persistent bottom sheets, or by
  44.   /// [showModalBottomSheet], for modal bottom sheets.
  45.   const BottomSheet({
  46.     Key key,
  47.     this.animationController,
  48.     this.enableDrag = true,
  49.     this.backgroundColor,
  50.     this.elevation,
  51.     this.shape,
  52.     this.clipBehavior,
  53.     @required this.onClosing,
  54.     @required this.builder,
  55.   })  : assert(enableDrag != null),
  56.         assert(onClosing != null),
  57.         assert(builder != null),
  58.         assert(elevation == null || elevation >= 0.0),
  59.         super(key: key);
  60.  
  61.   /// The animation controller that controls the bottom sheet's entrance and
  62.   /// exit animations.
  63.   ///
  64.   /// The BottomSheet widget will manipulate the position of this animation, it
  65.   /// is not just a passive observer.
  66.   final AnimationController animationController;
  67.  
  68.   /// Called when the bottom sheet begins to close.
  69.   ///
  70.   /// A bottom sheet might be prevented from closing (e.g., by user
  71.   /// interaction) even after this callback is called. For this reason, this
  72.   /// callback might be call multiple times for a given bottom sheet.
  73.   final VoidCallback onClosing;
  74.  
  75.   /// A builder for the contents of the sheet.
  76.   ///
  77.   /// The bottom sheet will wrap the widget produced by this builder in a
  78.   /// [Material] widget.
  79.   final WidgetBuilder builder;
  80.  
  81.   /// If true, the bottom sheet can be dragged up and down and dismissed by
  82.   /// swiping downwards.
  83.   ///
  84.   /// Default is true.
  85.   final bool enableDrag;
  86.  
  87.   /// The bottom sheet's background color.
  88.   ///
  89.   /// Defines the bottom sheet's [Material.color].
  90.   ///
  91.   /// Defaults to null and falls back to [Material]'s default.
  92.   final Color backgroundColor;
  93.  
  94.   /// The z-coordinate at which to place this material relative to its parent.
  95.   ///
  96.   /// This controls the size of the shadow below the material.
  97.   ///
  98.   /// Defaults to 0. The value is non-negative.
  99.   final double elevation;
  100.  
  101.   /// The shape of the bottom sheet.
  102.   ///
  103.   /// Defines the bottom sheet's [Material.shape].
  104.   ///
  105.   /// Defaults to null and falls back to [Material]'s default.
  106.   final ShapeBorder shape;
  107.  
  108.   /// {@macro flutter.widgets.Clip}
  109.   ///
  110.   /// Defines the bottom sheet's [Material.clipBehavior].
  111.   ///
  112.   /// Use this property to enable clipping of content when the bottom sheet has
  113.   /// a custom [shape] and the content can extend past this shape. For example,
  114.   /// a bottom sheet with rounded corners and an edge-to-edge [Image] at the
  115.   /// top.
  116.   ///
  117.   /// If this property is null then [ThemeData.bottomSheetTheme.clipBehavior] is
  118.   /// used. If that's null then the behavior will be [Clip.none].
  119.   final Clip clipBehavior;
  120.  
  121.   @override
  122.   _BottomSheetState createState() => _BottomSheetState();
  123.  
  124.   /// Creates an [AnimationController] suitable for a
  125.   /// [BottomSheet.animationController].
  126.   ///
  127.   /// This API available as a convenience for a Material compliant bottom sheet
  128.   /// animation. If alternative animation durations are required, a different
  129.   /// animation controller could be provided.
  130.   static AnimationController createAnimationController(TickerProvider vsync) {
  131.     return AnimationController(
  132.       duration: _bottomSheetDuration,
  133.       debugLabel: 'BottomSheet',
  134.       vsync: vsync,
  135.     );
  136.   }
  137. }
  138.  
  139. class _BottomSheetState extends State<BottomSheet> {
  140.   final GlobalKey _childKey = GlobalKey(debugLabel: 'BottomSheet child');
  141.  
  142.   double get _childHeight {
  143.     final RenderBox renderBox = _childKey.currentContext.findRenderObject() as RenderBox;
  144.     return renderBox.size.height;
  145.   }
  146.  
  147.   bool get _dismissUnderway => widget.animationController.status == AnimationStatus.reverse;
  148.  
  149.   void _handleDragUpdate(DragUpdateDetails details) {
  150.     assert(widget.enableDrag);
  151.     if (_dismissUnderway) return;
  152.     widget.animationController.value -= details.primaryDelta / (_childHeight ?? details.primaryDelta);
  153.   }
  154.  
  155.   void _handleDragEnd(DragEndDetails details) {
  156.     assert(widget.enableDrag);
  157.     if (_dismissUnderway) return;
  158.     if (details.velocity.pixelsPerSecond.dy > _minFlingVelocity) {
  159.       final double flingVelocity = -details.velocity.pixelsPerSecond.dy / _childHeight;
  160.       if (widget.animationController.value > 0.0) {
  161.         widget.animationController.fling(velocity: flingVelocity);
  162.       }
  163.       if (flingVelocity < 0.0) {
  164.         widget.onClosing();
  165.       }
  166.     } else if (widget.animationController.value < _closeProgressThreshold) {
  167.       if (widget.animationController.value > 0.0) widget.animationController.fling(velocity: -1.0);
  168.       widget.onClosing();
  169.     } else {
  170.       widget.animationController.forward();
  171.     }
  172.   }
  173.  
  174.   bool extentChanged(DraggableScrollableNotification notification) {
  175.     if (notification.extent == notification.minExtent) {
  176.       widget.onClosing();
  177.     }
  178.     return false;
  179.   }
  180.  
  181.   @override
  182.   Widget build(BuildContext context) {
  183.     final BottomSheetThemeData bottomSheetTheme = Theme.of(context).bottomSheetTheme;
  184.     final Color color = widget.backgroundColor ?? bottomSheetTheme.backgroundColor;
  185.     final double elevation = widget.elevation ?? bottomSheetTheme.elevation ?? 0;
  186.     final ShapeBorder shape = widget.shape ?? bottomSheetTheme.shape;
  187.     final Clip clipBehavior = widget.clipBehavior ?? bottomSheetTheme.clipBehavior ?? Clip.none;
  188.  
  189.     final Widget bottomSheet = Material(
  190.       key: _childKey,
  191.       color: color,
  192.       elevation: elevation,
  193.       shape: shape,
  194.       clipBehavior: clipBehavior,
  195.       child: NotificationListener<DraggableScrollableNotification>(
  196.         onNotification: extentChanged,
  197.         child: widget.builder(context),
  198.       ),
  199.     );
  200.     return !widget.enableDrag
  201.         ? bottomSheet
  202.         : GestureDetector(
  203.             onVerticalDragUpdate: _handleDragUpdate,
  204.             onVerticalDragEnd: _handleDragEnd,
  205.             child: bottomSheet,
  206.             excludeFromSemantics: true,
  207.           );
  208.   }
  209. }
  210.  
  211. // PERSISTENT BOTTOM SHEETS
  212.  
  213. // See scaffold.dart
  214.  
  215. // MODAL BOTTOM SHEETS
  216. class _ModalBottomSheetLayout extends SingleChildLayoutDelegate {
  217.   _ModalBottomSheetLayout(this.progress, this.isScrollControlled);
  218.  
  219.   final double progress;
  220.   final bool isScrollControlled;
  221.  
  222.   @override
  223.   BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
  224.     return BoxConstraints(
  225.       minWidth: constraints.maxWidth,
  226.       maxWidth: constraints.maxWidth,
  227.       minHeight: 0.0,
  228.       maxHeight: isScrollControlled ? constraints.maxHeight : constraints.maxHeight * 9.0 / 16.0,
  229.     );
  230.   }
  231.  
  232.   @override
  233.   Offset getPositionForChild(Size size, Size childSize) {
  234.     return Offset(0.0, size.height - childSize.height * progress);
  235.   }
  236.  
  237.   @override
  238.   bool shouldRelayout(_ModalBottomSheetLayout oldDelegate) {
  239.     return progress != oldDelegate.progress;
  240.   }
  241. }
  242.  
  243. class _ModalBottomSheet<T> extends StatefulWidget {
  244.   const _ModalBottomSheet({
  245.     Key key,
  246.     this.route,
  247.     this.backgroundColor,
  248.     this.elevation,
  249.     this.shape,
  250.     this.clipBehavior,
  251.     this.isScrollControlled = false,
  252.     this.enableDrag = true,
  253.   })  : assert(isScrollControlled != null),
  254.         assert(enableDrag != null),
  255.         super(key: key);
  256.  
  257.   final _ModalBottomSheetRoute<T> route;
  258.   final bool isScrollControlled;
  259.   final Color backgroundColor;
  260.   final double elevation;
  261.   final ShapeBorder shape;
  262.   final Clip clipBehavior;
  263.   final bool enableDrag;
  264.  
  265.   @override
  266.   _ModalBottomSheetState<T> createState() => _ModalBottomSheetState<T>();
  267. }
  268.  
  269. class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
  270.   String _getRouteLabel(MaterialLocalizations localizations) {
  271.     switch (Theme.of(context).platform) {
  272.       case TargetPlatform.iOS:
  273.       case TargetPlatform.macOS:
  274.         return '';
  275.       case TargetPlatform.android:
  276.       case TargetPlatform.fuchsia:
  277.         return localizations.dialogLabel;
  278.     }
  279.     return null;
  280.   }
  281.  
  282.   @override
  283.   Widget build(BuildContext context) {
  284.     assert(debugCheckHasMediaQuery(context));
  285.     assert(debugCheckHasMaterialLocalizations(context));
  286.     final MediaQueryData mediaQuery = MediaQuery.of(context);
  287.     final MaterialLocalizations localizations = MaterialLocalizations.of(context);
  288.     final String routeLabel = _getRouteLabel(localizations);
  289.  
  290.     return AnimatedBuilder(
  291.       animation: widget.route.animation,
  292.       builder: (BuildContext context, Widget child) {
  293.         // Disable the initial animation when accessible navigation is on so
  294.         // that the semantics are added to the tree at the correct time.
  295.         final double animationValue = mediaQuery.accessibleNavigation ? 1.0 : widget.route.animation.value;
  296.         return Semantics(
  297.           scopesRoute: true,
  298.           namesRoute: true,
  299.           label: routeLabel,
  300.           explicitChildNodes: true,
  301.           child: ClipRect(
  302.             child: CustomSingleChildLayout(
  303.               delegate: _ModalBottomSheetLayout(animationValue, widget.isScrollControlled),
  304.               child: BottomSheet(
  305.                 animationController: widget.route._animationController,
  306.                 onClosing: () {
  307.                   if (widget.route.isCurrent) {
  308.                     Navigator.pop(context);
  309.                   }
  310.                 },
  311.                 builder: widget.route.builder,
  312.                 backgroundColor: widget.backgroundColor,
  313.                 elevation: widget.elevation,
  314.                 shape: widget.shape,
  315.                 clipBehavior: widget.clipBehavior,
  316.                 enableDrag: widget.enableDrag,
  317.               ),
  318.             ),
  319.           ),
  320.         );
  321.       },
  322.     );
  323.   }
  324. }
  325.  
  326. class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
  327.   _ModalBottomSheetRoute({
  328.     this.builder,
  329.     this.theme,
  330.     this.barrierLabel,
  331.     this.backgroundColor,
  332.     this.elevation,
  333.     this.shape,
  334.     this.clipBehavior,
  335.     this.modalBarrierColor,
  336.     this.isDismissible = true,
  337.     this.enableDrag = true,
  338.     @required this.isScrollControlled,
  339.     RouteSettings settings,
  340.   })  : assert(isScrollControlled != null),
  341.         assert(isDismissible != null),
  342.         assert(enableDrag != null),
  343.         super(settings: settings);
  344.  
  345.   final WidgetBuilder builder;
  346.   final ThemeData theme;
  347.   final bool isScrollControlled;
  348.   final Color backgroundColor;
  349.   final double elevation;
  350.   final ShapeBorder shape;
  351.   final Clip clipBehavior;
  352.   final Color modalBarrierColor;
  353.   final bool isDismissible;
  354.   final bool enableDrag;
  355.  
  356.   @override
  357.   Duration get transitionDuration => _bottomSheetDuration;
  358.  
  359.   @override
  360.   bool get barrierDismissible => isDismissible;
  361.  
  362.   @override
  363.   final String barrierLabel;
  364.  
  365.   @override
  366.   Color get barrierColor => modalBarrierColor ?? Colors.black54;
  367.  
  368.   AnimationController _animationController;
  369.  
  370.   @override
  371.   AnimationController createAnimationController() {
  372.     assert(_animationController == null);
  373.     _animationController = BottomSheet.createAnimationController(navigator.overlay);
  374.     return _animationController;
  375.   }
  376.  
  377.   @override
  378.   Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
  379.     final BottomSheetThemeData sheetTheme = theme?.bottomSheetTheme ?? Theme.of(context).bottomSheetTheme;
  380.     // By definition, the bottom sheet is aligned to the bottom of the page
  381.     // and isn't exposed to the top padding of the MediaQuery.
  382.     Widget bottomSheet = SafeArea(
  383.       child: _ModalBottomSheet<T>(
  384.         route: this,
  385.         backgroundColor: backgroundColor ?? sheetTheme?.modalBackgroundColor ?? sheetTheme?.backgroundColor,
  386.         elevation: elevation ?? sheetTheme?.modalElevation ?? sheetTheme?.elevation,
  387.         shape: shape,
  388.         clipBehavior: clipBehavior,
  389.         isScrollControlled: isScrollControlled,
  390.         enableDrag: enableDrag,
  391.       ),
  392.     );
  393.     if (theme != null) bottomSheet = Theme(data: theme, child: bottomSheet);
  394.     return bottomSheet;
  395.   }
  396. }
  397.  
  398. /// Shows a modal material design bottom sheet.
  399. ///
  400. /// A modal bottom sheet is an alternative to a menu or a dialog and prevents
  401. /// the user from interacting with the rest of the app.
  402. ///
  403. /// A closely related widget is a persistent bottom sheet, which shows
  404. /// information that supplements the primary content of the app without
  405. /// preventing the use from interacting with the app. Persistent bottom sheets
  406. /// can be created and displayed with the [showBottomSheet] function or the
  407. /// [ScaffoldState.showBottomSheet] method.
  408. ///
  409. /// The `context` argument is used to look up the [Navigator] and [Theme] for
  410. /// the bottom sheet. It is only used when the method is called. Its
  411. /// corresponding widget can be safely removed from the tree before the bottom
  412. /// sheet is closed.
  413. ///
  414. /// The `isScrollControlled` parameter specifies whether this is a route for
  415. /// a bottom sheet that will utilize [DraggableScrollableSheet]. If you wish
  416. /// to have a bottom sheet that has a scrollable child such as a [ListView] or
  417. /// a [GridView] and have the bottom sheet be draggable, you should set this
  418. /// parameter to true.
  419. ///
  420. /// The `useRootNavigator` parameter ensures that the root navigator is used to
  421. /// display the [BottomSheet] when set to `true`. This is useful in the case
  422. /// that a modal [BottomSheet] needs to be displayed above all other content
  423. /// but the caller is inside another [Navigator].
  424. ///
  425. /// The [isDismissible] parameter specifies whether the bottom sheet will be
  426. /// dismissed when user taps on the scrim.
  427. ///
  428. /// The [enableDrag] parameter specifies whether the bottom sheet can be
  429. /// dragged up and down and dismissed by swiping downards.
  430. ///
  431. /// The optional [backgroundColor], [elevation], [shape], and [clipBehavior]
  432. /// parameters can be passed in to customize the appearance and behavior of
  433. /// modal bottom sheets.
  434. ///
  435. /// Returns a `Future` that resolves to the value (if any) that was passed to
  436. /// [Navigator.pop] when the modal bottom sheet was closed.
  437. ///
  438. /// {@animation 350 622 https://flutter.github.io/assets-for-api-docs/assets/material/show_modal_bottom_sheet.mp4}
  439. ///
  440. /// {@tool sample --template=stateless_widget_scaffold}
  441. ///
  442. /// This example demonstrates how to use `showModalBottomSheet` to display a
  443. /// bottom sheet that obscures the content behind it when a user taps a button.
  444. /// It also demonstrates how to close the bottom sheet using the [Navigator]
  445. /// when a user taps on a button inside the bottom sheet.
  446. ///
  447. /// ```dart
  448. /// Widget build(BuildContext context) {
  449. ///   return Center(
  450. ///     child: RaisedButton(
  451. ///       child: const Text('showModalBottomSheet'),
  452. ///       onPressed: () {
  453. ///         showModalBottomSheet<void>(
  454. ///           context: context,
  455. ///           builder: (BuildContext context) {
  456. ///             return Container(
  457. ///               height: 200,
  458. ///               color: Colors.amber,
  459. ///               child: Center(
  460. ///                 child: Column(
  461. ///                   mainAxisAlignment: MainAxisAlignment.center,
  462. ///                   mainAxisSize: MainAxisSize.min,
  463. ///                   children: <Widget>[
  464. ///                     const Text('Modal BottomSheet'),
  465. ///                     RaisedButton(
  466. ///                       child: const Text('Close BottomSheet'),
  467. ///                       onPressed: () => Navigator.pop(context),
  468. ///                     )
  469. ///                   ],
  470. ///                 ),
  471. ///               ),
  472. ///             );
  473. ///           },
  474. ///         );
  475. ///       },
  476. ///     ),
  477. ///   );
  478. /// }
  479. /// ```
  480. /// {@end-tool}
  481. /// See also:
  482. ///
  483. ///  * [BottomSheet], which becomes the parent of the widget returned by the
  484. ///    function passed as the `builder` argument to [showModalBottomSheet].
  485. ///  * [showBottomSheet] and [ScaffoldState.showBottomSheet], for showing
  486. ///    non-modal bottom sheets.
  487. ///  * [DraggableScrollableSheet], which allows you to create a bottom sheet
  488. ///    that grows and then becomes scrollable once it reaches its maximum size.
  489. ///  * <https://material.io/design/components/sheets-bottom.html#modal-bottom-sheet>
  490. Future<T> showModalBottomSheet<T>({
  491.   @required BuildContext context,
  492.   @required WidgetBuilder builder,
  493.   Color backgroundColor,
  494.   double elevation,
  495.   ShapeBorder shape,
  496.   Clip clipBehavior,
  497.   Color barrierColor,
  498.   bool isScrollControlled = false,
  499.   bool useRootNavigator = false,
  500.   bool isDismissible = true,
  501.   bool enableDrag = true,
  502. }) {
  503.   assert(context != null);
  504.   assert(builder != null);
  505.   assert(isScrollControlled != null);
  506.   assert(useRootNavigator != null);
  507.   assert(isDismissible != null);
  508.   assert(enableDrag != null);
  509.   assert(debugCheckHasMediaQuery(context));
  510.   assert(debugCheckHasMaterialLocalizations(context));
  511.  
  512.   return Navigator.of(context, rootNavigator: useRootNavigator).push(_ModalBottomSheetRoute<T>(
  513.     builder: builder,
  514.     theme: Theme.of(context, shadowThemeOnly: true),
  515.     isScrollControlled: isScrollControlled,
  516.     barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
  517.     backgroundColor: backgroundColor,
  518.     elevation: elevation,
  519.     shape: shape,
  520.     clipBehavior: clipBehavior,
  521.     isDismissible: isDismissible,
  522.     modalBarrierColor: barrierColor,
  523.     enableDrag: enableDrag,
  524.   ));
  525. }
  526.  
  527. /// Shows a material design bottom sheet in the nearest [Scaffold] ancestor. If
  528. /// you wish to show a persistent bottom sheet, use [Scaffold.bottomSheet].
  529. ///
  530. /// Returns a controller that can be used to close and otherwise manipulate the
  531. /// bottom sheet.
  532. ///
  533. /// The optional [backgroundColor], [elevation], [shape], and [clipBehavior]
  534. /// parameters can be passed in to customize the appearance and behavior of
  535. /// persistent bottom sheets.
  536. ///
  537. /// To rebuild the bottom sheet (e.g. if it is stateful), call
  538. /// [PersistentBottomSheetController.setState] on the controller returned by
  539. /// this method.
  540. ///
  541. /// The new bottom sheet becomes a [LocalHistoryEntry] for the enclosing
  542. /// [ModalRoute] and a back button is added to the app bar of the [Scaffold]
  543. /// that closes the bottom sheet.
  544. ///
  545. /// To create a persistent bottom sheet that is not a [LocalHistoryEntry] and
  546. /// does not add a back button to the enclosing Scaffold's app bar, use the
  547. /// [Scaffold.bottomSheet] constructor parameter.
  548. ///
  549. /// A closely related widget is a modal bottom sheet, which is an alternative
  550. /// to a menu or a dialog and prevents the user from interacting with the rest
  551. /// of the app. Modal bottom sheets can be created and displayed with the
  552. /// [showModalBottomSheet] function.
  553. ///
  554. /// The `context` argument is used to look up the [Scaffold] for the bottom
  555. /// sheet. It is only used when the method is called. Its corresponding widget
  556. /// can be safely removed from the tree before the bottom sheet is closed.
  557. ///
  558. /// See also:
  559. ///
  560. ///  * [BottomSheet], which becomes the parent of the widget returned by the
  561. ///    `builder`.
  562. ///  * [showModalBottomSheet], which can be used to display a modal bottom
  563. ///    sheet.
  564. ///  * [Scaffold.of], for information about how to obtain the [BuildContext].
  565. ///  * <https://material.io/design/components/sheets-bottom.html#standard-bottom-sheet>
  566. PersistentBottomSheetController<T> showBottomSheet<T>({
  567.   @required BuildContext context,
  568.   @required WidgetBuilder builder,
  569.   Color backgroundColor,
  570.   double elevation,
  571.   ShapeBorder shape,
  572.   Clip clipBehavior,
  573. }) {
  574.   assert(context != null);
  575.   assert(builder != null);
  576.   assert(debugCheckHasScaffold(context));
  577.  
  578.   return Scaffold.of(context).showBottomSheet<T>(
  579.     builder,
  580.     backgroundColor: backgroundColor,
  581.     elevation: elevation,
  582.     shape: shape,
  583.     clipBehavior: clipBehavior,
  584.   );
  585. }
Advertisement
Add Comment
Please, Sign In to add comment