Skip to content

Make CupertinoAlertDialog clearly visible in dark mode #80921

@mleonhard

Description

@mleonhard

Steps to Reproduce

Run the following program in Simulator or on your iOS device, in dark mode. Hold down the Cancel button and take a screenshot.

Flutter main.dart
import 'package:flutter/cupertino.dart'
    show
    showCupertinoDialog,
    CupertinoAlertDialog,
    CupertinoApp,
    CupertinoButton,
    CupertinoDialogAction,
    CupertinoNavigationBar,
    CupertinoPage,
    CupertinoPageScaffold,
    CupertinoTheme,
    CupertinoThemeData;
import 'package:flutter/foundation.dart'
    show
    debugBrightnessOverride,
    kReleaseMode;
import 'package:flutter/widgets.dart'
    show
    runApp,
    Builder,
    BuildContext,
    Color,
    DefaultTextStyle,
    Key,
    MediaQuery,
    MediaQueryData,
    Page,
    State,
    StatefulWidget,
    StatelessWidget,
    Text,
    Navigator,
    Widget,
    WidgetsBinding,
    WidgetsBindingObserver;
import 'dart:ui' show Brightness;

// Copied from flutter/lib/src/widgets/app.dart:_MediaQueryFromWindow .
class MediaQueryFromWindow extends StatefulWidget {
  const MediaQueryFromWindow({Key key, this.child}) : super(key: key);

  final Widget child;

  @override
  MediaQueryFromWindowsState createState() => MediaQueryFromWindowsState();
}

class MediaQueryFromWindowsState extends State<MediaQueryFromWindow>
    with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void didChangeAccessibilityFeatures() {
    setState(() {});
  }

  @override
  void didChangeMetrics() {
    setState(() {});
  }

  @override
  void didChangeTextScaleFactor() {
    setState(() {});
  }

  @override
  void didChangePlatformBrightness() {
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    MediaQueryData data = MediaQueryData.fromWindow(
        WidgetsBinding.instance.window);
    if (!kReleaseMode) {
      data = data.copyWith(platformBrightness: debugBrightnessOverride);
    }
    return MediaQuery(
      data: data,
      child: widget.child,
    );
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }
}

Page homePage() =>
    CupertinoPage(
      title: 'Page1',
      child: Builder(builder: (BuildContext ctx) =>
          CupertinoPageScaffold(
            navigationBar: CupertinoNavigationBar(),
            child: CupertinoButton(
              child: Text('Button1'),
              onPressed: () =>
                  showCupertinoDialog(
                    context: ctx,
                    builder: (BuildContext ctx) =>
                        CupertinoAlertDialog(
                          title: Text('Alert1'),
                          content: Text('Content1'),
                          actions: [
                            CupertinoDialogAction(
                              isDefaultAction: false,
                              isDestructiveAction: false,
                              child: Text('Disabled1'),
                            ),
                            CupertinoDialogAction(
                              isDefaultAction: false,
                              isDestructiveAction: false,
                              onPressed: () =>
                                  Navigator.pop(
                                      ctx, false),
                              child: Text('Action1'),
                            ),
                            CupertinoDialogAction(
                              isDefaultAction: false,
                              isDestructiveAction: true,
                              onPressed: () =>
                                  Navigator.pop(
                                      ctx, false),
                              child: Text('Destructive1'),
                            ),
                            CupertinoDialogAction(
                              isDefaultAction: true,
                              isDestructiveAction: false,
                              onPressed: () =>
                                  Navigator.pop(
                                      ctx, false),
                              child: Text('Cancel1'),
                            ),
                          ],
                        ),
                  ),
            ),
          ),
      ),
    );

class AppWidget extends StatelessWidget {
  @override
  Widget build(BuildContext ctx) {
    final isLight = MediaQuery
        .of(ctx)
        .platformBrightness == Brightness.light;
    final cupertinoThemeData = isLight ? CupertinoThemeData(
      // Light
      primaryColor: Color(0xff664600),
      barBackgroundColor: Color(0xffFFF0D0),
    ) : CupertinoThemeData(
      // Dark
      primaryColor: Color(0xffFFF0D0),
      barBackgroundColor: Color(0xff664600),
    );
    return CupertinoApp(
      theme: cupertinoThemeData,
      title: 'App1',
      home: Builder(builder: (BuildContext ctx) =>
          DefaultTextStyle(
            style: CupertinoTheme
                .of(ctx)
                .textTheme
                .textStyle,
            child: Navigator(
              pages: [homePage()],
              onPopPage: (route, result) => true,
            ),
          ),
      ),
    );
  }
}

void main() {
  runApp(MediaQueryFromWindow(child: AppWidget()));
}

Actual results:

The Flutter app dialog on the right has two problems:

  1. The dialog background is very dark. Some users may be confused. They may not realize that a dialog has appeared.
  2. The buttons are separated by very faint lines. Users could have trouble hitting the right button.

A very minor additional difference: when pressing a button, the button background lightens only a little bit.

Native Flutter v2.0.5
IMG_3547 IMG_3549
Logs
$ flutter doctor -v
[✓] Flutter (Channel stable, 2.0.5, on Mac OS X 10.15.7 19H524 darwin-x64, locale en)
    • Flutter version 2.0.5 at /Users/user/tools/flutter
    • Framework revision adc687823a (6 days ago), 2021-04-16 09:40:20 -0700
    • Engine revision b09f014e96
    • Dart version 2.12.3

[✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0-rc2)
    • Android SDK at /Users/user/Library/Android/sdk
    • Platform android-30, build-tools 31.0.0-rc2
    • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Xcode 12.4, Build version 12D4e
    • CocoaPods version 1.10.1

[✓] Android Studio (version 4.1)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495)

[✓] Connected device (1 available)
    • Michael’s iPhone (mobile) • **** • ios            • iOS 14.4.2

• No issues found!

Fix

flutter/lib/src/cupertino/dialog.dart :

const Color _kDialogColor = CupertinoDynamicColor.withBrightness(
  color: Color(0xCCF2F2F2),
  darkColor: Color(0xBF1E1E1E), // <--- Change to 0xCC2d2d2d.
);

const Color _kDialogPressedColor = CupertinoDynamicColor.withBrightness(
  color: Color(0xFFE1E1E1),
  darkColor: Color(0xFF2E2E2E), // <-- Change to 0xFF404040.
);

flutter/lib/src/cupertino/colors.dart :

  static const CupertinoDynamicColor separator = CupertinoDynamicColor(
    debugLabel: 'separator',
    color: Color.fromARGB(73, 60, 60, 67),
    darkColor: Color.fromARGB(153, 84, 84, 88),
    highContrastColor: Color.fromARGB(94, 60, 60, 67),
    darkHighContrastColor: Color.fromARGB(173, 84, 84, 88),
    elevatedColor: Color.fromARGB(73, 60, 60, 67),
    darkElevatedColor: Color.fromARGB(153, 84, 84, 88), // <-- Change to 210, 210, 210.
    highContrastElevatedColor: Color.fromARGB(94, 60, 60, 67),
    darkHighContrastElevatedColor: Color.fromARGB(173, 84, 84, 88),
  );

With these changes, the Flutter app better matches the native iOS dialog.

Native Patched Flutter v2.0.5
IMG_3547 IMG_3553

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work lista: fidelityMatching the OEM platforms bettera: qualityA truly polished experiencef: cupertinoflutter/packages/flutter/cupertino repositoryfound in release: 2.0Found to occur in 2.0found in release: 2.2Found to occur in 2.2frameworkflutter/packages/flutter repository. See also f: labels.good first issueRelatively approachable for first-time contributorshas reproducible stepsThe issue has been confirmed reproducible and is ready to work onteam-designOwned by Design Languages teamtriaged-designTriaged by Design Languages team

    Type

    No type

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions