Allow for custom alignment for Dialogs (#88984)

diff --git a/packages/flutter/lib/src/material/dialog.dart b/packages/flutter/lib/src/material/dialog.dart
index 14b1868..c95f60a 100644
--- a/packages/flutter/lib/src/material/dialog.dart
+++ b/packages/flutter/lib/src/material/dialog.dart
@@ -47,6 +47,7 @@
     this.insetPadding = _defaultInsetPadding,
     this.clipBehavior = Clip.none,
     this.shape,
+    this.alignment,
     this.child,
   }) : assert(clipBehavior != null),
        super(key: key);
@@ -114,6 +115,14 @@
   /// {@endtemplate}
   final ShapeBorder? shape;
 
+  /// {@template flutter.material.dialog.alignment}
+  /// How to align the [Dialog].
+  ///
+  /// If null, then [DialogTheme.alignment] is used. If that is also null, the
+  /// default is [Alignment.center].
+  /// {@endtemplate}
+  final AlignmentGeometry? alignment;
+
   /// The widget below this widget in the tree.
   ///
   /// {@macro flutter.widgets.ProxyWidget.child}
@@ -137,7 +146,8 @@
         removeRight: true,
         removeBottom: true,
         context: context,
-        child: Center(
+        child: Align(
+          alignment: alignment ?? dialogTheme.alignment ?? Alignment.center,
           child: ConstrainedBox(
             constraints: const BoxConstraints(minWidth: 280.0),
             child: Material(
@@ -260,6 +270,7 @@
     this.insetPadding = _defaultInsetPadding,
     this.clipBehavior = Clip.none,
     this.shape,
+    this.alignment,
     this.scrollable = false,
   }) : assert(contentPadding != null),
        assert(clipBehavior != null),
@@ -437,6 +448,9 @@
   /// {@macro flutter.material.dialog.shape}
   final ShapeBorder? shape;
 
+  /// {@macro flutter.material.dialog.shape}
+  final AlignmentGeometry? alignment;
+
   /// Determines whether the [title] and [content] widgets are wrapped in a
   /// scrollable.
   ///
@@ -577,6 +591,7 @@
       insetPadding: insetPadding,
       clipBehavior: clipBehavior,
       shape: shape,
+      alignment: alignment,
       child: dialogChild,
     );
   }
@@ -742,6 +757,7 @@
     this.insetPadding = _defaultInsetPadding,
     this.clipBehavior = Clip.none,
     this.shape,
+    this.alignment,
   }) : assert(titlePadding != null),
        assert(contentPadding != null),
        super(key: key);
@@ -818,6 +834,9 @@
   /// {@macro flutter.material.dialog.shape}
   final ShapeBorder? shape;
 
+  /// {@macro flutter.material.dialog.shape}
+  final AlignmentGeometry? alignment;
+
   @override
   Widget build(BuildContext context) {
     assert(debugCheckHasMaterialLocalizations(context));
@@ -908,6 +927,7 @@
       insetPadding: insetPadding,
       clipBehavior: clipBehavior,
       shape: shape,
+      alignment: alignment,
       child: dialogChild,
     );
   }
diff --git a/packages/flutter/lib/src/material/dialog_theme.dart b/packages/flutter/lib/src/material/dialog_theme.dart
index dc7c9a5..f3718fd 100644
--- a/packages/flutter/lib/src/material/dialog_theme.dart
+++ b/packages/flutter/lib/src/material/dialog_theme.dart
@@ -34,6 +34,7 @@
     this.backgroundColor,
     this.elevation,
     this.shape,
+    this.alignment,
     this.titleTextStyle,
     this.contentTextStyle,
   });
@@ -52,6 +53,11 @@
   /// Default value for [Dialog.shape].
   final ShapeBorder? shape;
 
+  /// Default value for [Dialog.alignment].
+  ///
+  /// If null, the [Dialog] alignment defaults to [Alignment.center].
+  final AlignmentGeometry? alignment;
+
   /// Used to configure the [DefaultTextStyle] for the [AlertDialog.title] widget.
   ///
   /// If null, defaults to [TextTheme.headline6] of [ThemeData.textTheme].
@@ -68,6 +74,7 @@
     Color? backgroundColor,
     double? elevation,
     ShapeBorder? shape,
+    AlignmentGeometry? alignment,
     TextStyle? titleTextStyle,
     TextStyle? contentTextStyle,
   }) {
@@ -75,6 +82,7 @@
       backgroundColor: backgroundColor ?? this.backgroundColor,
       elevation: elevation ?? this.elevation,
       shape: shape ?? this.shape,
+      alignment: alignment ?? this.alignment,
       titleTextStyle: titleTextStyle ?? this.titleTextStyle,
       contentTextStyle: contentTextStyle ?? this.contentTextStyle,
     );
@@ -96,6 +104,7 @@
       backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
       elevation: lerpDouble(a?.elevation, b?.elevation, t),
       shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
+      alignment: AlignmentGeometry.lerp(a?.alignment, b?.alignment, t),
       titleTextStyle: TextStyle.lerp(a?.titleTextStyle, b?.titleTextStyle, t),
       contentTextStyle: TextStyle.lerp(a?.contentTextStyle, b?.contentTextStyle, t),
     );
@@ -114,6 +123,7 @@
         && other.backgroundColor == backgroundColor
         && other.elevation == elevation
         && other.shape == shape
+        && other.alignment == alignment
         && other.titleTextStyle == titleTextStyle
         && other.contentTextStyle == contentTextStyle;
   }
@@ -122,8 +132,9 @@
   void debugFillProperties(DiagnosticPropertiesBuilder properties) {
     super.debugFillProperties(properties);
     properties.add(ColorProperty('backgroundColor', backgroundColor));
-    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
     properties.add(DoubleProperty('elevation', elevation));
+    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
+    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null));
     properties.add(DiagnosticsProperty<TextStyle>('titleTextStyle', titleTextStyle, defaultValue: null));
     properties.add(DiagnosticsProperty<TextStyle>('contentTextStyle', contentTextStyle, defaultValue: null));
   }
diff --git a/packages/flutter/test/material/dialog_test.dart b/packages/flutter/test/material/dialog_test.dart
index f18300b..411c3c9 100644
--- a/packages/flutter/test/material/dialog_test.dart
+++ b/packages/flutter/test/material/dialog_test.dart
@@ -113,6 +113,11 @@
     expect(materialWidget.color, Colors.grey[800]);
     expect(materialWidget.shape, _defaultDialogShape);
     expect(materialWidget.elevation, 24.0);
+
+    final Offset bottomLeft = tester.getBottomLeft(
+      find.descendant(of: find.byType(Dialog), matching: find.byType(Material)),
+    );
+    expect(bottomLeft.dy, 360.0);
   });
 
   testWidgets('Custom dialog elevation', (WidgetTester tester) async {
@@ -237,6 +242,23 @@
     expect(materialWidget.shape, customBorder);
   });
 
+  testWidgets('Custom dialog alignment', (WidgetTester tester) async {
+    const AlertDialog dialog = AlertDialog(
+      actions: <Widget>[ ],
+      alignment: Alignment.bottomLeft,
+    );
+    await tester.pumpWidget(_buildAppWithDialog(dialog));
+
+    await tester.tap(find.text('X'));
+    await tester.pumpAndSettle();
+
+    final Offset bottomLeft = tester.getBottomLeft(
+      find.descendant(of: find.byType(Dialog), matching: find.byType(Material)),
+    );
+    expect(bottomLeft.dx, 40.0);
+    expect(bottomLeft.dy, 576.0);
+  });
+
   testWidgets('Simple dialog control test', (WidgetTester tester) async {
     await tester.pumpWidget(
       const MaterialApp(
diff --git a/packages/flutter/test/material/dialog_theme_test.dart b/packages/flutter/test/material/dialog_theme_test.dart
index a82c887..2826884 100644
--- a/packages/flutter/test/material/dialog_theme_test.dart
+++ b/packages/flutter/test/material/dialog_theme_test.dart
@@ -48,6 +48,7 @@
       backgroundColor: Color(0xff123456),
       elevation: 8.0,
       shape: null,
+      alignment: Alignment.bottomLeft,
       titleTextStyle: TextStyle(color: Color(0xffffffff)),
       contentTextStyle: TextStyle(color: Color(0xff000000)),
     ).debugFillProperties(builder);
@@ -57,6 +58,7 @@
     expect(description, <String>[
       'backgroundColor: Color(0xff123456)',
       'elevation: 8.0',
+      'alignment: Alignment.bottomLeft',
       'titleTextStyle: TextStyle(inherit: true, color: Color(0xffffffff))',
       'contentTextStyle: TextStyle(inherit: true, color: Color(0xff000000))',
     ]);
@@ -115,6 +117,47 @@
     expect(materialWidget.shape, customBorder);
   });
 
+  testWidgets('Custom dialog alignment', (WidgetTester tester) async {
+    const AlertDialog dialog = AlertDialog(
+      title: Text('Title'),
+      actions: <Widget>[ ],
+    );
+    final ThemeData theme = ThemeData(dialogTheme: const DialogTheme(alignment: Alignment.bottomLeft));
+
+    await tester.pumpWidget(
+      _appWithDialog(tester, dialog, theme: theme),
+    );
+    await tester.tap(find.text('X'));
+    await tester.pumpAndSettle();
+
+    final Offset bottomLeft = tester.getBottomLeft(
+      find.descendant(of: find.byType(Dialog), matching: find.byType(Material)),
+    );
+    expect(bottomLeft.dx, 40.0);
+    expect(bottomLeft.dy, 576.0);
+  });
+
+  testWidgets('Dialog alignment takes priority over theme', (WidgetTester tester) async {
+    const AlertDialog dialog = AlertDialog(
+      title: Text('Title'),
+      actions: <Widget>[ ],
+      alignment: Alignment.topRight,
+    );
+    final ThemeData theme = ThemeData(dialogTheme: const DialogTheme(alignment: Alignment.bottomLeft));
+
+    await tester.pumpWidget(
+      _appWithDialog(tester, dialog, theme: theme),
+    );
+    await tester.tap(find.text('X'));
+    await tester.pumpAndSettle();
+
+    final Offset bottomLeft = tester.getBottomLeft(
+      find.descendant(of: find.byType(Dialog), matching: find.byType(Material)),
+    );
+    expect(bottomLeft.dx, 480.0);
+    expect(bottomLeft.dy, 104.0);
+  });
+
   testWidgets('Custom dialog shape matches golden', (WidgetTester tester) async {
     const RoundedRectangleBorder customBorder =
       RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0)));