pretty_qr_code 3.5.0 copy "pretty_qr_code: ^3.5.0" to clipboard
pretty_qr_code: ^3.5.0 copied to clipboard

A highly customizable Flutter widget that makes it easy to render QR codes.

example/lib/main.dart

// ignore_for_file: deprecated_member_use

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:pretty_qr_code/pretty_qr_code.dart';

import 'package:pretty_qr_code_example/features/save_image_io.dart'
    if (dart.library.js_interop) 'package:pretty_qr_code_example/features/save_image_web.dart';

void main() {
  runApp(const PrettyQrExampleApp());
}

class PrettyQrExampleApp extends StatelessWidget {
  const PrettyQrExampleApp({
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.black,
        ),
      ),
      home: const PrettyQrHomePage(),
    );
  }
}

class PrettyQrHomePage extends StatefulWidget {
  const PrettyQrHomePage({
    super.key,
  });

  @override
  State<PrettyQrHomePage> createState() => _PrettyQrHomePageState();
}

class _PrettyQrHomePageState extends State<PrettyQrHomePage> {
  @protected
  late QrCode qrCode;

  @protected
  late QrImage qrImage;

  @protected
  late PrettyQrDecoration decoration;

  @override
  void initState() {
    super.initState();

    qrCode = QrCode.fromData(
      data: 'https://pub.dev/packages/pretty_qr_code',
      errorCorrectLevel: QrErrorCorrectLevel.H,
    );

    qrImage = QrImage(qrCode);

    decoration = const PrettyQrDecoration(
      shape: PrettyQrSmoothSymbol(
        color: _PrettyQrSettings.kDefaultQrDecorationBrush,
      ),
      image: _PrettyQrSettings.kDefaultQrDecorationImage,
      background: Colors.transparent,
      quietZone: PrettyQrQuietZone.zero,
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: const Text('Pretty QR Code'),
      ),
      body: Align(
        alignment: Alignment.topCenter,
        child: ConstrainedBox(
          constraints: const BoxConstraints(
            maxWidth: 1024,
          ),
          child: LayoutBuilder(
            builder: (context, constraints) {
              final safePadding = MediaQuery.of(context).padding;
              return Row(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  if (constraints.maxWidth >= 720)
                    Flexible(
                      flex: 3,
                      child: Padding(
                        padding: EdgeInsets.only(
                          left: safePadding.left + 24,
                          right: safePadding.right + 24,
                          bottom: 24,
                        ),
                        child: _PrettyQrAnimatedView(
                          qrImage: qrImage,
                          decoration: decoration,
                        ),
                      ),
                    ),
                  Flexible(
                    flex: 2,
                    child: Column(
                      children: [
                        if (constraints.maxWidth < 720)
                          Padding(
                            padding: safePadding.copyWith(
                              top: 0,
                              bottom: 0,
                            ),
                            child: _PrettyQrAnimatedView(
                              qrImage: qrImage,
                              decoration: decoration,
                            ),
                          ),
                        Expanded(
                          child: SingleChildScrollView(
                            padding: safePadding.copyWith(top: 0),
                            child: _PrettyQrSettings(
                              decoration: decoration,
                              onChanged: (value) => setState(() {
                                decoration = value;
                              }),
                              onExportPressed: (size) {
                                return qrImage.exportAsImage(
                                  context,
                                  size: size,
                                  decoration: decoration,
                                );
                              },
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                ],
              );
            },
          ),
        ),
      ),
    );
  }
}

class _PrettyQrAnimatedView extends StatefulWidget {
  @protected
  final QrImage qrImage;

  @protected
  final PrettyQrDecoration decoration;

  const _PrettyQrAnimatedView({
    required this.qrImage,
    required this.decoration,
  });

  @override
  State<_PrettyQrAnimatedView> createState() => _PrettyQrAnimatedViewState();
}

class _PrettyQrAnimatedViewState extends State<_PrettyQrAnimatedView> {
  @protected
  late PrettyQrDecoration previosDecoration;

  @override
  void initState() {
    super.initState();

    previosDecoration = widget.decoration;
  }

  @override
  void didUpdateWidget(
    covariant _PrettyQrAnimatedView oldWidget,
  ) {
    super.didUpdateWidget(oldWidget);

    if (widget.decoration != oldWidget.decoration) {
      previosDecoration = oldWidget.decoration;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: TweenAnimationBuilder<PrettyQrDecoration>(
        tween: PrettyQrDecorationTween(
          begin: previosDecoration,
          end: widget.decoration,
        ),
        curve: Curves.ease,
        duration: const Duration(
          milliseconds: 240,
        ),
        builder: (context, decoration, child) {
          return PrettyQrView(
            qrImage: widget.qrImage,
            decoration: decoration,
          );
        },
      ),
    );
  }
}

class _PrettyQrSettings extends StatefulWidget {
  @protected
  final PrettyQrDecoration decoration;

  @protected
  final Future<String?> Function(int)? onExportPressed;

  @protected
  final ValueChanged<PrettyQrDecoration>? onChanged;

  @visibleForTesting
  static const kDefaultQrDecorationImage = PrettyQrDecorationImage(
    image: AssetImage('images/flutter.png'),
    position: PrettyQrDecorationImagePosition.embedded,
  );

  @visibleForTesting
  static const kDefaultQrDecorationBrush = Color(0xFF74565F);

  const _PrettyQrSettings({
    required this.decoration,
    this.onChanged,
    this.onExportPressed,
  });

  @override
  State<_PrettyQrSettings> createState() => _PrettyQrSettingsState();
}

class _PrettyQrSettingsState extends State<_PrettyQrSettings> {
  @protected
  Color brush = _PrettyQrSettings.kDefaultQrDecorationBrush;

  @protected
  final random = Random(DateTime.now().microsecondsSinceEpoch);

  @protected
  late final TextEditingController imageSizeEditingController;

  @override
  void initState() {
    super.initState();

    imageSizeEditingController = TextEditingController(
      text: ' 512w',
    );
  }

  @protected
  int get imageSize {
    final rawValue = imageSizeEditingController.text;
    return int.parse(rawValue.replaceAll('w', '').replaceAll(' ', ''));
  }

  @protected
  bool? get isRoundedBorders {
    var shape = widget.decoration.shape;
    if (shape is PrettyQrDotsSymbol) {
      return null;
    } else if (shape is PrettyQrCustomShape) {
      return null;
    } else if (shape is PrettyQrSmoothSymbol) {
      return shape.roundFactor > 0;
    } else if (shape is PrettyQrSquaresSymbol) {
      return shape.rounding > 0;
    }
    return false;
  }

  @protected
  String get shapeName {
    var shape = widget.decoration.shape;
    if (shape is PrettyQrDotsSymbol) {
      return 'Dots';
    } else if (shape is PrettyQrCustomShape) {
      return 'Custom';
    } else if (shape is PrettyQrSmoothSymbol) {
      return 'Smooth';
    } else if (shape is PrettyQrSquaresSymbol) {
      return 'Squares';
    }
    return '';
  }

  @protected
  void showExportPath(String? path) {
    if (!mounted) return;
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(path == null ? 'Saved' : 'Saved to $path')),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        SwitchListTile.adaptive(
          value: widget.decoration.quietZone != PrettyQrQuietZone.zero,
          onChanged: (value) => toggleQuietZone(),
          secondary: const Icon(Icons.border_outer),
          title: const Text('Quiet Zone'),
        ),
        const Divider(),
        LayoutBuilder(
          builder: (context, constraints) {
            return PopupMenuButton(
              onSelected: changeShape,
              constraints: BoxConstraints(
                minWidth: constraints.maxWidth,
              ),
              initialValue: widget.decoration.shape.runtimeType,
              itemBuilder: (context) {
                return [
                  const PopupMenuItem(
                    value: PrettyQrDotsSymbol,
                    child: Text('Dots'),
                  ),
                  const PopupMenuItem(
                    value: PrettyQrSmoothSymbol,
                    child: Text('Smooth'),
                  ),
                  const PopupMenuItem(
                    value: PrettyQrSquaresSymbol,
                    child: Text('Squares'),
                  ),
                  const PopupMenuItem(
                    value: PrettyQrCustomShape,
                    child: Text('Custom'),
                  ),
                ];
              },
              child: ListTile(
                leading: const Icon(Icons.format_paint_outlined),
                title: const Text('Style'),
                trailing: Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    if (widget.decoration.shape is PrettyQrCustomShape) ...[
                      OutlinedButton(
                        onPressed: () {
                          changeShape(PrettyQrCustomShape);
                        },
                        child: Row(
                          children: [
                            Text(shapeName),
                            const SizedBox(width: 8),
                            const Icon(Icons.refresh),
                          ],
                        ),
                      ),
                    ] else
                      Text(
                        shapeName,
                        style: Theme.of(context).textTheme.titleSmall,
                      ),
                  ],
                ),
              ),
            );
          },
        ),
        if (widget.decoration.shape is! PrettyQrCustomShape)
          LayoutBuilder(
            builder: (context, constraints) {
              return PopupMenuButton(
                onSelected: toggleColor,
                constraints: BoxConstraints(
                  minWidth: constraints.maxWidth,
                ),
                initialValue:
                    brush == _PrettyQrSettings.kDefaultQrDecorationBrush,
                itemBuilder: (context) {
                  return [
                    const PopupMenuItem(
                      value: true,
                      child: Text('Color'),
                    ),
                    const PopupMenuItem(
                      value: false,
                      child: Text('Gradient'),
                    ),
                  ];
                },
                child: ListTile(
                  leading: const Icon(Icons.color_lens_outlined),
                  title: const Text('Brush'),
                  trailing: Text(
                    brush is! PrettyQrGradientBrush ? 'Color' : 'Gradient',
                    style: Theme.of(context).textTheme.titleSmall,
                  ),
                ),
              );
            },
          ),
        SwitchListTile.adaptive(
          value: widget.decoration.background != Colors.transparent,
          onChanged: (value) => toggleBackground(),
          secondary: const Icon(Icons.format_color_fill),
          title: const Text('Background'),
        ),
        if (isRoundedBorders != null)
          SwitchListTile.adaptive(
            value: isRoundedBorders ?? true,
            onChanged: isRoundedBorders == null
                ? null
                : (value) => toggleRoundedCorners(),
            secondary: const Icon(Icons.rounded_corner),
            title: const Text('Rounded corners'),
          ),
        const Divider(),
        SwitchListTile.adaptive(
          value: widget.decoration.image != null,
          onChanged: (value) => toggleImage(),
          secondary: Icon(
            widget.decoration.image != null
                ? Icons.image_outlined
                : Icons.hide_image_outlined,
          ),
          title: const Text('Image'),
        ),
        if (widget.decoration.image != null)
          ListTile(
            enabled: widget.decoration.image != null,
            leading: const Icon(Icons.layers_outlined),
            title: const Text('Image position'),
            trailing: PopupMenuButton(
              onSelected: changeImagePosition,
              initialValue: widget.decoration.image?.position,
              itemBuilder: (context) {
                return [
                  const PopupMenuItem(
                    value: PrettyQrDecorationImagePosition.embedded,
                    child: Text('Embedded'),
                  ),
                  const PopupMenuItem(
                    value: PrettyQrDecorationImagePosition.foreground,
                    child: Text('Foreground'),
                  ),
                  const PopupMenuItem(
                    value: PrettyQrDecorationImagePosition.background,
                    child: Text('Background'),
                  ),
                ];
              },
            ),
          ),
        if (widget.onExportPressed != null) ...[
          const Divider(),
          ListTile(
            leading: const Icon(Icons.save_alt_outlined),
            title: const Text('Export'),
            onTap: () async {
              final path = await widget.onExportPressed?.call(imageSize);
              showExportPath(path);
            },
            trailing: Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                PopupMenuButton(
                  initialValue: imageSize,
                  onSelected: (value) {
                    imageSizeEditingController.text = ' ${value}w';
                    setState(() {});
                  },
                  itemBuilder: (context) {
                    return [
                      const PopupMenuItem(
                        value: 256,
                        child: Text('256w'),
                      ),
                      const PopupMenuItem(
                        value: 512,
                        child: Text('512w'),
                      ),
                      const PopupMenuItem(
                        value: 1024,
                        child: Text('1024w'),
                      ),
                    ];
                  },
                  child: SizedBox(
                    width: 72,
                    height: 36,
                    child: TextField(
                      enabled: false,
                      textAlign: TextAlign.center,
                      style: Theme.of(context).textTheme.bodyMedium,
                      controller: imageSizeEditingController,
                      decoration: InputDecoration(
                        filled: true,
                        counterText: '',
                        contentPadding: EdgeInsets.zero,
                        fillColor: Theme.of(context).colorScheme.surface,
                        disabledBorder: OutlineInputBorder(
                          borderSide: BorderSide(
                            color: Theme.of(context).colorScheme.onSurface,
                          ),
                        ),
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ],
      ],
    );
  }

  @protected
  void changeShape(
    final Type type,
  ) {
    var shape = widget.decoration.shape;
    switch (type) {
      case PrettyQrDotsSymbol:
        shape = PrettyQrDotsSymbol(color: brush);
        break;
      case PrettyQrSmoothSymbol:
        shape = PrettyQrSmoothSymbol(color: brush);
        break;
      case PrettyQrSquaresSymbol:
        shape = PrettyQrSquaresSymbol(
          color: brush,
          density: 0.86,
          rounding: 0.5,
        );
        break;
      case PrettyQrCustomShape:
        shape = randomShape();
        break;
      default:
    }
    widget.onChanged?.call(widget.decoration.copyWith(shape: shape));
  }

  @protected
  void toggleColor(bool value) {
    brush = value
        ? _PrettyQrSettings.kDefaultQrDecorationBrush
        : PrettyQrBrush.gradient(
            gradient: LinearGradient(
              begin: Alignment.topCenter,
              end: Alignment.bottomCenter,
              colors: [
                Colors.teal[200]!,
                Colors.blue[200]!,
                Colors.red[200]!,
              ],
            ),
          );

    var shape = widget.decoration.shape;
    if (shape is PrettyQrDotsSymbol) {
      shape = PrettyQrDotsSymbol(
        color: brush,
      );
    } else if (shape is PrettyQrSmoothSymbol) {
      shape = PrettyQrSmoothSymbol(
        color: brush,
        roundFactor: shape.roundFactor,
      );
    } else if (shape is PrettyQrSquaresSymbol) {
      shape = PrettyQrSquaresSymbol(
        color: brush,
        density: shape.density,
        rounding: shape.rounding,
      );
    }

    widget.onChanged?.call(widget.decoration.copyWith(shape: shape));
  }

  @protected
  void toggleQuietZone() {
    widget.onChanged?.call(
      widget.decoration.copyWith(
        quietZone: widget.decoration.quietZone != PrettyQrQuietZone.zero
            ? PrettyQrQuietZone.zero
            : PrettyQrQuietZone.standart,
      ),
    );
  }

  @protected
  void toggleBackground() {
    widget.onChanged?.call(
      widget.decoration.copyWith(
        background: widget.decoration.background != Colors.transparent
            ? Colors.transparent
            : _PrettyQrSettings.kDefaultQrDecorationBrush.withOpacity(0.1),
      ),
    );
  }

  @protected
  void toggleRoundedCorners() {
    var shape = widget.decoration.shape;

    if (shape is PrettyQrSmoothSymbol) {
      shape = PrettyQrSmoothSymbol(
        color: shape.color,
        roundFactor: isRoundedBorders! ? 0 : 1,
      );
    } else if (shape is PrettyQrSquaresSymbol) {
      shape = PrettyQrSquaresSymbol(
        color: shape.color,
        density: shape.density,
        rounding: isRoundedBorders! ? 0 : 0.5,
      );
    }

    widget.onChanged?.call(widget.decoration.copyWith(shape: shape));
  }

  @protected
  void toggleImage() {
    const defaultImage = _PrettyQrSettings.kDefaultQrDecorationImage;
    final image = widget.decoration.image != null ? null : defaultImage;

    widget.onChanged?.call(PrettyQrDecoration(
      image: image,
      shape: widget.decoration.shape,
      quietZone: widget.decoration.quietZone,
      background: widget.decoration.background,
    ));
  }

  @protected
  void changeImagePosition(
    final PrettyQrDecorationImagePosition value,
  ) {
    final image = widget.decoration.image?.copyWith(position: value);
    widget.onChanged?.call(widget.decoration.copyWith(image: image));
  }

  @protected
  PrettyQrShape randomShape() {
    const colors = [
      Color(0xFF90CAF9),
      Color(0xFFEF9A9A),
      Color(0xFF8C9EFF),
      Color(0xFFB39DDB),
      Color(0xFF9FA8DA),
      Color(0xFF80CBC4),
      Color(0xFFF06292),
      Color(0xFF4DB6AC),
      PrettyQrBrush.gradient(
        gradient: LinearGradient(
          colors: [Color(0xFF90CAF9), Color(0xFF80CBC4)],
        ),
      ),
    ];

    final types = [
      PrettyQrDotsSymbol(
        color: colors[random.nextInt(colors.length)],
        unifiedFinderPattern: random.nextBool(),
        unifiedAlignmentPatterns: random.nextBool(),
      ),
      PrettyQrSmoothSymbol(
        color: colors[random.nextInt(colors.length)],
      ),
      PrettyQrSquaresSymbol(
        color: colors[random.nextInt(colors.length)],
        density: 0.86,
        unifiedFinderPattern: random.nextBool(),
      ),
    ];

    return PrettyQrShape.custom(
      types[random.nextInt(types.length)],
      finderPattern: types[random.nextInt(types.length)],
      alignmentPatterns: types[random.nextInt(types.length)],
      timingPatterns: types[random.nextInt(types.length)],
    );
  }

  @override
  void dispose() {
    imageSizeEditingController.dispose();

    super.dispose();
  }
}
502
likes
160
points
110k
downloads
screenshot

Publisher

unverified uploader

Weekly Downloads

A highly customizable Flutter widget that makes it easy to render QR codes.

Repository (GitHub)

Topics

#qr #pretty-qr #pretty-widgets

Documentation

API reference

License

MIT (license)

Dependencies

flutter, meta, qr

More

Packages that depend on pretty_qr_code