[story_shell_labs] animate dragging feedback

Also anchors feedback widget around pointer.
This scales the mod that is being dragged so the user can see the rest of the story more easily.

MI4-2432 #done

Change-Id: Id03764000d6747babc469c122d4e052820eb24bc
diff --git a/public/dart/story_shell_labs/lib/src/layout/tile_presenter/widgets/editing_tile_chrome.dart b/public/dart/story_shell_labs/lib/src/layout/tile_presenter/widgets/editing_tile_chrome.dart
index e7cb5b1..5cd2f32 100644
--- a/public/dart/story_shell_labs/lib/src/layout/tile_presenter/widgets/editing_tile_chrome.dart
+++ b/public/dart/story_shell_labs/lib/src/layout/tile_presenter/widgets/editing_tile_chrome.dart
@@ -6,9 +6,7 @@
 import 'package:tiler/tiler.dart';
 import 'drop_target_widget.dart';
 
-const _kHighlightedBorderWidth = 3.0;
 const _kBorderWidth = 1.0;
-const _kBorderWidthDiff = _kHighlightedBorderWidth - _kBorderWidth;
 
 /// Chrome for a tiling layout presenter.
 class EditingTileChrome extends StatefulWidget {
@@ -57,12 +55,33 @@
 }
 
 class _EditingTileChromeState extends State<EditingTileChrome> {
+  // whether this tile is currently being dragged
   final _isDragging = ValueNotifier(false);
 
+  // equal to isDragging, but with 1 frame delay, useful for starting the feedback animation
+  final _isDraggingDelayed = ValueNotifier(false);
+
   // the direction that the tile is being hovered over by another tile, null if nothing is hovering
   final _hoverDirection = ValueNotifier<AxisDirection>(null);
 
   @override
+  void initState() {
+    _isDragging.addListener(_isDraggingListener);
+    super.initState();
+  }
+
+  void _isDraggingListener() async {
+    await Future.delayed(Duration(milliseconds: 100));
+    _isDraggingDelayed.value = _isDragging.value;
+  }
+
+  @override
+  void dispose() {
+    _isDragging.removeListener(_isDraggingListener);
+    super.dispose();
+  }
+
+  @override
   Widget build(BuildContext context) {
     return Stack(
       children: <Widget>[
@@ -83,6 +102,7 @@
             key: Key(widget.modName),
             data: widget.tile,
             feedback: _buildFeedback(),
+            dragAnchor: DragAnchor.pointer,
             childWhenDragging: const Offstage(),
             child: Stack(
               children: [
@@ -130,26 +150,42 @@
   }
 
   Widget _buildFeedback() {
-    final borderWidthDifference =
-        Offset(-_kBorderWidthDiff, -_kBorderWidthDiff);
+    final contentSize = widget.editingSize;
+    return AnimatedBuilder(
+      animation: _isDraggingDelayed,
+      builder: (_, child) {
+        final size = _isDraggingDelayed.value ? contentSize * .5 : contentSize;
+        return AnimatedContainer(
+          // ease in Quad -> ease out Expo:
+          curve: Cubic(0.455, 0.03, 0.0, 1.0),
 
-    return Transform.translate(
-      offset: borderWidthDifference,
-      child: SizedBox.fromSize(
-        size: widget.editingSize +
-            Offset(_kBorderWidthDiff, _kBorderWidthDiff) * 2,
-        child: Center(
+          // can have a long duration because  it's interactive the whole time
+          // and has a strong out easing curve so it spends most of the time at the end
+          duration: Duration(milliseconds: 500),
+
+          width: size.width,
+          height: size.height,
+          transform: Matrix4.translationValues(
+            size.width * -.5,
+            size.height * -.5,
+            0,
+          ),
           child: Material(
-            elevation: 16.0,
-            child: Container(
-              decoration: BoxDecoration(
-                border: Border.all(
-                  color: Color(0xFFFF8BCB),
-                  width: _kHighlightedBorderWidth,
-                ),
-              ),
-              child: widget.childView,
-            ),
+            color: Color(0xFFFF8BCB),
+            elevation: _isDraggingDelayed.value ? 16.0 : 8.5,
+            animationDuration: Duration(milliseconds: 500),
+            child: child,
+          ),
+        );
+      },
+      child: FittedBox(
+        fit: BoxFit.contain,
+        child: SizedBox(
+          width: contentSize.width,
+          height: contentSize.height,
+          child: Padding(
+            padding: const EdgeInsets.all(_kBorderWidth),
+            child: widget.childView,
           ),
         ),
       ),