public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Tim Marx <t.marx@proxmox.com>
To: Proxmox VE development discussion <pve-devel@lists.proxmox.com>,
	Aaron Lauterer <a.lauterer@proxmox.com>
Subject: Re: [pve-devel] [PATCH pve_flutter_frontend 3/3] Add first welcome screen
Date: Wed, 30 Sep 2020 14:04:22 +0200 (CEST)	[thread overview]
Message-ID: <386034124.798.1601467462478@webmail.proxmox.com> (raw)
In-Reply-To: <20200928134125.7266-1-a.lauterer@proxmox.com>

comments inline
> Aaron Lauterer <a.lauterer@proxmox.com> hat am 28.09.2020 15:41 geschrieben:
> 
>  
> Signed-off-by: Aaron Lauterer <a.lauterer@proxmox.com>
> ---
> Same patch without temp png files.
> 
> Thanks @Dominik for noticing
> 
>  .../ssl_validate/login_manager_screen.png     | Bin 0 -> 20389 bytes
>  .../login_manager_screen_settings.png         | Bin 0 -> 37362 bytes
>  lib/main.dart                                 |  14 +-
>  lib/utils/dot_indicators.dart                 |  67 ++++++
>  .../pve_welcome_common.dart                   |  48 +++++
>  .../firstWelcomeScreen/pve_welcome_faq.dart   |  56 +++++
>  .../firstWelcomeScreen/pve_welcome_last.dart  |  66 ++++++
>  .../firstWelcomeScreen/pve_welcome_logo.dart  |  33 +++
>  .../pve_welcome_ssl_hint.dart                 |  51 +++++
>  lib/widgets/pve_first_welcome_screen.dart     | 193 ++++++++++++++++++
>  pubspec.yaml                                  |   1 +
>  11 files changed, 528 insertions(+), 1 deletion(-)
>  create mode 100644 assets/images/ssl_validate/login_manager_screen.png
>  create mode 100644 assets/images/ssl_validate/login_manager_screen_settings.png
>  create mode 100644 lib/utils/dot_indicators.dart
>  create mode 100644 lib/widgets/firstWelcomeScreen/pve_welcome_common.dart
>  create mode 100644 lib/widgets/firstWelcomeScreen/pve_welcome_faq.dart
>  create mode 100644 lib/widgets/firstWelcomeScreen/pve_welcome_last.dart
>  create mode 100644 lib/widgets/firstWelcomeScreen/pve_welcome_logo.dart
>  create mode 100644 lib/widgets/firstWelcomeScreen/pve_welcome_ssl_hint.dart
>  create mode 100644 lib/widgets/pve_first_welcome_screen.dart
> 
> diff --git a/lib/main.dart b/lib/main.dart
> index 8cd6c36..57ad39c 100644
> --- a/lib/main.dart
> +++ b/lib/main.dart
> @@ -1,6 +1,8 @@
>  import 'package:flutter/foundation.dart';
>  import 'package:flutter/material.dart';
>  import 'package:provider/provider.dart';
> +import 'package:pve_flutter_frontend/widgets/pve_first_welcome_screen.dart';
> +import 'package:shared_preferences/shared_preferences.dart';
>  import 'package:proxmox_login_manager/proxmox_login_manager.dart';
>  import 'package:pve_flutter_frontend/bloc/pve_authentication_bloc.dart';
>  import 'package:pve_flutter_frontend/bloc/pve_cluster_status_bloc.dart';
> @@ -47,6 +49,7 @@ void main() async {
>      FlutterError.dumpErrorToConsole(details);
>      if (kReleaseMode) ProxmoxGlobalErrorBloc().addError(details.exception);
>    };
> +  SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
>    Provider.debugCheckInvalidValueType = null;
>  
>    runApp(
> @@ -60,6 +63,7 @@ void main() async {
>        ],
>        child: MyApp(
>          authbloc: authBloc,
> +        sharedPreferences: sharedPreferences,
>        ),
>      ),
>    );
> @@ -67,9 +71,12 @@ void main() async {
>  
>  class MyApp extends StatelessWidget {
>    final PveAuthenticationBloc authbloc;
> +  final SharedPreferences sharedPreferences;
>    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
>  
> -  MyApp({Key key, this.authbloc}) : super(key: key);
> +  MyApp({Key key, this.authbloc, this.sharedPreferences})
> +      : assert(sharedPreferences != null),
> +        super(key: key);
>  
>    @override
>    Widget build(BuildContext context) {
> @@ -142,6 +149,11 @@ class MyApp extends StatelessWidget {
>                builder: (context) => PveSplashScreen(),
>              );
>            }
> +          if (sharedPreferences.getBool('showWelcomeScreen') ?? true) {
> +            return MaterialPageRoute(
> +              builder: (context) => PveWelcome(),
> +            );
> +          }
>  
>            if (authbloc.state.value is Unauthenticated ||
>                context.name == '/login') {
> diff --git a/lib/utils/dot_indicators.dart b/lib/utils/dot_indicators.dart
Why is the file called ..._indicators? 
The class is called DotIndicator.

> new file mode 100644
> index 0000000..7b5034f
> --- /dev/null
> +++ b/lib/utils/dot_indicators.dart
> @@ -0,0 +1,67 @@
> +import 'package:flutter/cupertino.dart';
> +import 'package:flutter/material.dart';
> +import 'dart:math';
> +
> +class DotIndicator extends AnimatedWidget {
> +  DotIndicator({
> +    this.controller,
> +    this.itemCount,
> +    this.onPageSelected,
> +    this.color: Colors.white,
> +  }) : super(listenable: controller);
> +
> +  final PageController controller;
> +

it seems wrong to add a PageController dependency to a widget called DotIndicator, just pass the current page index.


> +  final int itemCount;
> +
> +  final ValueChanged<int> onPageSelected;
> +  final Color color;
> +
> +  static const double _dotSize = 8.0;
> +  static const double _maxZoom = 1.2;
> +  static const double _dotSpacing = 25.0;
> +
> +  Widget _buildDot(int index) {
> +    double selectedness = Curves.easeOut.transform(
> +      max(
> +        0.0,
> +        1.0 - ((controller.page ?? controller.initialPage) - index).abs(),
> +      ),
> +    );
> +    double zoom = 1.0 + (_maxZoom - 1.0) * selectedness;
> +    double shadowBlurRadius = 4.0 * selectedness;
> +    double shadowSpreadRadius = 1.0 * selectedness;
> +    return new Container(
> +      width: _dotSpacing,
> +      child: Center(
> +        child: Container(
> +          width: _dotSize * zoom,
> +          height: _dotSize * zoom,
> +          child: InkWell(
> +            onTap: () => onPageSelected(index),
> +          ),

An InkWell is only used when you want that specific animation, but in your case the animation can't be seen. 
Would be great to see that animation or just use a GestureDetector.

> +          decoration: BoxDecoration(
> +            color: color,
> +            shape: BoxShape.circle,
> +            boxShadow: [
> +              BoxShadow(
> +                  color: Colors.white.withOpacity(0.72),
> +                  blurRadius: shadowBlurRadius,
> +                  spreadRadius: shadowSpreadRadius,
> +                  offset: Offset(0.0, 0.0))
> +            ],
> +          ),
> +        ),
> +      ),
> +    );
> +  }
> +
^^^^
That screams for, extract widget :D

> +  Widget build(BuildContext contect) {
> +    return Row(
> +        mainAxisAlignment: MainAxisAlignment.center,
> +        children: List<Widget>.generate(
> +          itemCount,
> +          _buildDot,
> +        ));
> +  }
> +}
> diff --git a/lib/widgets/firstWelcomeScreen/pve_welcome_common.dart b/lib/widgets/firstWelcomeScreen/pve_welcome_common.dart
> new file mode 100644
> index 0000000..52055f9
> --- /dev/null
> +++ b/lib/widgets/firstWelcomeScreen/pve_welcome_common.dart
> @@ -0,0 +1,48 @@
> +import 'package:flutter/material.dart';
> +
> +class PveQuestion extends StatelessWidget {
> +  const PveQuestion({
> +    Key key,
> +    this.text,
> +  }) : super(key: key);
> +
> +  final String text;
> +
> +  @override
> +  Widget build(BuildContext context) {
> +    return Padding(
> +      padding: EdgeInsets.fromLTRB(10.0, 10.0, 5.0, 0.0),
> +      child: Text(
> +        text,
> +        style: TextStyle(
> +          fontWeight: FontWeight.bold,
> +        ),
> +      ),
> +    );
> +  }
> +}
> +
> +class PveAnswer extends StatelessWidget {
> +  const PveAnswer({
> +    Key key,
> +    this.text,
> +    this.spans,
> +  }) : super(key: key);
> +
> +  final String text;
> +  final List<TextSpan> spans;
> +
> +  @override
> +  Widget build(BuildContext context) {
> +    return Padding(
> +      padding: EdgeInsets.fromLTRB(20.0, 5.0, 5.0, 5.0),
> +      child: RichText(
> +        text: TextSpan(
> +          text: text,
> +          style: DefaultTextStyle.of(context).style,
> +          children: spans,
> +        ),
> +      ),
> +    );
> +  }
> +}
> diff --git a/lib/widgets/firstWelcomeScreen/pve_welcome_faq.dart b/lib/widgets/firstWelcomeScreen/pve_welcome_faq.dart
> new file mode 100644
> index 0000000..65f931c
> --- /dev/null
> +++ b/lib/widgets/firstWelcomeScreen/pve_welcome_faq.dart
> @@ -0,0 +1,56 @@
> +import 'package:flutter/material.dart';
> +import 'package:url_launcher/url_launcher.dart';
> +import 'package:flutter/gestures.dart';
> +import 'pve_welcome_common.dart';
> +
> +// FAQ
> +class PveWelcomePageFAQ extends StatelessWidget {
> +  const PveWelcomePageFAQ({
> +    Key key,
> +  }) : super(key: key);
> +
> +  @override
> +  Widget build(BuildContext context) {
> +    return Column(
> +      mainAxisAlignment: MainAxisAlignment.center,
> +      crossAxisAlignment: CrossAxisAlignment.start,
> +      children: [
> +        PveQuestion(
> +            text: "How do I connect if I am not using the default port 8006?"),
> +        PveAnswer(
> +            text:
> +                "Add the port at the end, separated by a colon. For the default https port add 443."),
> +        PveAnswer(
> +          text: "For example: 192.168.1.10",
> +          spans: [
> +            TextSpan(
> +                text: ":443",
> +                style: TextStyle(
> +                    fontWeight: FontWeight.bold, fontStyle: FontStyle.italic))
> +          ],
> +        ),
> +        PveQuestion(
> +          text: "What about remote consoles?",
> +        ),
> +        PveAnswer(
> +            text:
> +                "Spice is currently supported. We plan to integrate VNC in the future."),
> +        PveQuestion(text: "Which Spice client works?"),
> +        PveAnswer(
> +          text: "The ",
> +          spans: [
> +            TextSpan(
> +                text: "Opague Spice client",
> +                style: TextStyle(decoration: TextDecoration.underline),
> +                recognizer: TapGestureRecognizer()
> +                  ..onTap = () => {
> +                        launch(
> +                            'https://play.google.com/store/apps/details?id=com.undatech.opaque')
> +                      }),
> +            TextSpan(text: " works. We will support more in the future.")
> +          ],
> +        )
> +      ],
> +    );
> +  }
> +}
> diff --git a/lib/widgets/firstWelcomeScreen/pve_welcome_last.dart b/lib/widgets/firstWelcomeScreen/pve_welcome_last.dart
> new file mode 100644
> index 0000000..cf34224
> --- /dev/null
> +++ b/lib/widgets/firstWelcomeScreen/pve_welcome_last.dart
> @@ -0,0 +1,66 @@
> +import 'package:flutter/material.dart';
> +import 'package:url_launcher/url_launcher.dart';
> +import 'package:flutter/gestures.dart';
> +import '../../utils/promox_colors.dart';
> +
> +// goodbye
> +class PveWelcomePageLast extends StatelessWidget {
> +  const PveWelcomePageLast({Key key, this.onDone}) : super(key: key);
> +
> +  final VoidCallback onDone;
> +
> +  @override
> +  Widget build(BuildContext context) {
> +    return Padding(
> +        padding: EdgeInsets.all(15.0),
> +        child: Column(
> +          mainAxisAlignment: MainAxisAlignment.center,
> +          children: [
> +            Text("Enjoy the app"),
> +            Padding(
> +              padding: const EdgeInsets.all(8.0),
> +              child: Icon(
> +                Icons.emoji_people_rounded,
> +                color: Colors.white,
> +                size: 70,
> +              ),
> +            ),
> +            Padding(
> +              padding: const EdgeInsets.all(8.0),
> +              child: RaisedButton(
> +                onPressed: () => {onDone()},
> +                color: ProxmoxColors.orange,
> +                textColor: Colors.white,
> +                child: Text("Start"),
> +              ),
> +            ),
> +            RichText(
> +              textAlign: TextAlign.center,
> +              text: TextSpan(
> +                text: "Please use our ",
> +                style: DefaultTextStyle.of(context).style,
> +                children: <TextSpan>[
> +                  TextSpan(
> +                      text: 'community forum',
> +                      style: TextStyle(decoration: TextDecoration.underline),
> +                      recognizer: TapGestureRecognizer()
> +                        ..onTap = () => {launch('https://forum.proxmox.com')}),
> +                  TextSpan(text: ' or the '),
> +                  TextSpan(
> +                      text: 'user mailing list',
> +                      style: TextStyle(decoration: TextDecoration.underline),
> +                      recognizer: TapGestureRecognizer()
> +                        ..onTap = () => {
> +                              launch(
> +                                  'https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-user')
> +                            }),
> +                  TextSpan(
> +                      text:
> +                          ' if you have suggestions or experience any problems.'),
> +                ],
> +              ),
> +            ),
> +          ],
> +        ));
> +  }
> +}
> diff --git a/lib/widgets/firstWelcomeScreen/pve_welcome_logo.dart b/lib/widgets/firstWelcomeScreen/pve_welcome_logo.dart
> new file mode 100644
> index 0000000..3aee19a
> --- /dev/null
> +++ b/lib/widgets/firstWelcomeScreen/pve_welcome_logo.dart
> @@ -0,0 +1,33 @@
> +import 'package:flutter/material.dart';
> +
> +// Big Logo
> +class PveWelcomePageLogo extends StatelessWidget {
> +  const PveWelcomePageLogo({
> +    Key key,
> +  }) : super(key: key);
> +
> +  @override
> +  Widget build(BuildContext context) {
> +    return Column(
> +        mainAxisAlignment: MainAxisAlignment.center,
> +        crossAxisAlignment: CrossAxisAlignment.center,
> +        children: [
> +          Padding(
> +            padding: const EdgeInsets.fromLTRB(120.0, 0.0, 120.0, 30.0),
> +            child: Image.asset(
> +              'assets/images/Proxmox-logo-symbol-white-orange.png',
> +              alignment: Alignment.center,
> +            ),
> +          ),
> +          FittedBox(
> +            child: Padding(
> +              padding: const EdgeInsets.all(8.0),
> +              child: Text(
> +                'Proxmox Virtual Environment',
> +                style: TextStyle(fontSize: 26),
> +              ),
> +            ),
> +          ),
> +        ]);
> +  }
> +}
> diff --git a/lib/widgets/firstWelcomeScreen/pve_welcome_ssl_hint.dart b/lib/widgets/firstWelcomeScreen/pve_welcome_ssl_hint.dart
> new file mode 100644
> index 0000000..61d02f4
> --- /dev/null
> +++ b/lib/widgets/firstWelcomeScreen/pve_welcome_ssl_hint.dart
> @@ -0,0 +1,51 @@
> +import 'package:flutter/material.dart';
> +import 'pve_welcome_common.dart';
> +
> +// disable ssl validation hint
> +class PveWelcomePageSSLValidation extends StatelessWidget {
> +  const PveWelcomePageSSLValidation({
> +    Key key,
> +  }) : super(key: key);
> +
> +  @override
> +  Widget build(BuildContext context) {
> +    return Padding(
> +      padding: EdgeInsets.all(15.0),
> +      child: Column(
> +        mainAxisAlignment: MainAxisAlignment.center,
> +        crossAxisAlignment: CrossAxisAlignment.start,
> +        children: [
> +          PveQuestion(
> +            text: "Are you using a self signed certificate?",
> +          ),
> +          PveAnswer(
> +              text: "Disable SSL Validation in the Login Manager settings."),
> +          Column(
> +            children: [
> +              Padding(
> +                padding: const EdgeInsets.all(8.0),
> +                child: Container(
> +                  decoration: BoxDecoration(
> +                      border: Border.all(color: Colors.white, width: 0.5)),
> +                  child: Image.asset(
> +                    'assets/images/ssl_validate/login_manager_screen.png',
> +                  ),
> +                ),
> +              ),
> +              Padding(
> +                padding: const EdgeInsets.all(8.0),
> +                child: Container(
> +                  decoration: BoxDecoration(
> +                      border: Border.all(color: Colors.white, width: 0.5)),
> +                  child: Image.asset(
> +                    'assets/images/ssl_validate/login_manager_screen_settings.png',
> +                  ),
> +                ),
> +              ),
> +            ],
> +          )
> +        ],
> +      ),
> +    );
> +  }
> +}

All those screens aren't scrollable, if someone uses a tiny device the content will be cut. -> SingleChildScrollview

Use package imports not local ones.

I don't like the link clicking web style, this is an App link clicking is for web or when you have a mouse please remove that and use buttons/icon buttons etc.


> diff --git a/lib/widgets/pve_first_welcome_screen.dart b/lib/widgets/pve_first_welcome_screen.dart
> new file mode 100644
> index 0000000..83fe399
> --- /dev/null
> +++ b/lib/widgets/pve_first_welcome_screen.dart
> @@ -0,0 +1,193 @@
> +import 'dart:ui';
> +
> +import 'package:flutter/material.dart';
> +import 'package:flutter/rendering.dart';
> +import 'package:shared_preferences/shared_preferences.dart';
> +import '../utils/dot_indicators.dart';
> +import '../utils/promox_colors.dart';
> +import 'firstWelcomeScreen/pve_welcome_logo.dart';
> +import 'firstWelcomeScreen/pve_welcome_faq.dart';
> +import 'firstWelcomeScreen/pve_welcome_ssl_hint.dart';
> +import 'firstWelcomeScreen/pve_welcome_last.dart';
> +

use package imports not local ones.

> +class PveWelcome extends StatefulWidget {
> +  @override
> +  _PveWelcomeState createState() => _PveWelcomeState();
> +}
> +
> +class _PveWelcomeState extends State<PveWelcome> with TickerProviderStateMixin {
> +  PageController _controller;
> +  SharedPreferences _sharedPreferences;
> +
> +  final List<Widget> _pages = [
> +    PveWelcomePageLogo(),
> +    PveWelcomePageSSLValidation(),
> +    PveWelcomePageFAQ(),
> +  ];
> +
> +  // Duration for page change
> +  static const Duration _pageChangeDuration = Duration(milliseconds: 150);
> +  static const Curve _pageChangeCurve = Curves.easeInOut;
> +
> +  AnimationController _animController;
> +  Animation<Color> _animation;
> +
> +  final colors = <TweenSequenceItem<Color>>[
> +    TweenSequenceItem(
> +      weight: 1.0,
> +      tween: ColorTween(
> +          begin: ProxmoxColors.supportBlue, end: ProxmoxColors.supportDarkGrey),
> +    ),
> +    TweenSequenceItem(
> +      weight: 1.0,
> +      tween: ColorTween(
> +          begin: ProxmoxColors.supportDarkGrey, end: ProxmoxColors.orange),
> +    ),
> +    TweenSequenceItem(
> +      weight: 1.0,
> +      tween: ColorTween(
> +          begin: ProxmoxColors.orange, end: ProxmoxColors.supportBlue),
> +    ),
> +  ];
> +

I personally don't like the color changes between pages.

> +  bool _isLast = false;
> +  bool _isFirst = true;
> +
> +  final _buttonTextColor = Colors.white;
> +  final _buttonDisabledTextColor = Colors.white30;
> +
> +  void _getPrefs() async {
> +    _sharedPreferences = await SharedPreferences.getInstance();
> +  }
> +
Future<void> those are async functions and should therefore be marked as Futures
> +  void _setWelcomeSeen() async {
> +    _sharedPreferences.setBool('showWelcomeScreen', false);
> +  }
> +
Future<void> those are async functions and should therefore be marked as Futures
Typo Seen/Screen

> +  @override
> +  void initState() {
> +    super.initState();
> +
> +    _getPrefs();

This will actually result in a race condition, because this is an unawaited Future and is used in the skipDone function. You can't know that this will be completed when the user taps skip and this would result in a crash.

> +    _controller = PageController();
> +
> +    // add last page here so we can define the callback for the start button
> +    _pages.add(PveWelcomePageLast(onDone: () {
> +      skipDone();
> +    }));
> +
> +    _animController = AnimationController(
> +      duration: _pageChangeDuration,
> +      vsync: this,
> +    );
> +    _animation = TweenSequence<Color>(colors).animate(_animController)
> +      ..addListener(() {
> +        setState(() {});
> +      });
> +
> +    _controller.addListener(() {
> +      setState(() {
> +        _isLast = _controller.page.floor() == _pages.length - 1;
> +        _isFirst = _controller.page.floor() == 0;
> +      });
> +    });
> +  }
> +
> +  void skipDone() {
> +    _setWelcomeSeen();
> +    Navigator.pushReplacementNamed(context, '/');
> +  }
> +
> +  Widget nextDoneButton() {
> +    if (_isLast) {
> +      return FlatButton(
> +        textColor: _buttonTextColor,
> +        disabledTextColor: _buttonDisabledTextColor,
> +        child: Text(
> +          "Done",
> +        ),
> +        onPressed: () {
> +          skipDone();
> +        },
> +      );
> +    } else {
> +      return FlatButton(
> +        child: Text("Next"),
> +        textColor: _buttonTextColor,
> +        disabledTextColor: _buttonDisabledTextColor,
> +        onPressed: () {
> +          _controller.nextPage(
> +              duration: _pageChangeDuration, curve: _pageChangeCurve);
> +        },
> +      );
> +    }
> +  }
> +
> +  Widget skipPrevButton() {
> +    if (_isFirst) {
> +      return FlatButton(
> +        textColor: _buttonTextColor,
> +        disabledTextColor: _buttonDisabledTextColor,
> +        onPressed: () {
> +          skipDone();
> +        },
> +        child: Text(
> +          'Skip',
> +        ),
> +      );
> +    } else {
> +      return FlatButton(
> +        textColor: _buttonTextColor,
> +        disabledTextColor: _buttonDisabledTextColor,
> +        child: Text(
> +          "Prev",
> +        ),
> +        onPressed: () {
> +          _controller.previousPage(
> +              duration: _pageChangeDuration, curve: _pageChangeCurve);
> +        },
> +      );
> +    }
> +  }
> +
> +  @override
> +  Widget build(BuildContext context) {
> +    return Scaffold(
> +      backgroundColor: _animation.value,
> +      body: DefaultTextStyle(
> +        style: TextStyle(color: Colors.white, fontSize: 18),
> +        child: Column(
> +          children: [
> +            Expanded(
> +              child: PageView.builder(
> +                controller: _controller,
> +                itemCount: _pages.length,
> +                onPageChanged: ((int index) {
> +                  _animController.animateTo(index / colors.length);
> +                }),
> +                itemBuilder: (context, index) {
> +                  return _pages[index];
> +                },
> +              ),
> +            ),
> +            Row(
> +              mainAxisAlignment: MainAxisAlignment.spaceBetween,
> +              children: [
> +                skipPrevButton(),
> +                DotIndicator(
> +                  controller: _controller,
> +                  itemCount: _pages.length,
> +                  onPageSelected: (int page) {
> +                    _controller.animateToPage(page,
> +                        duration: _pageChangeDuration, curve: _pageChangeCurve);
> +                  },
> +                ),
> +                nextDoneButton(),
> +              ],
> +            ),
> +          ],
> +        ),
> +      ),
> +    );
> +  }
> +}
> diff --git a/pubspec.yaml b/pubspec.yaml
> index d31eb74..09fa250 100644
> --- a/pubspec.yaml
> +++ b/pubspec.yaml
> @@ -61,6 +61,7 @@ flutter:
>      - assets/images/Proxmox_logo_white_orange_800.png
>      - assets/images/Proxmox-logo-symbol-white-orange.png
>      - assets/images/proxmox_logo_icon_white.png
> +    - assets/images/ssl_validate/
>  
>    # An image asset can refer to one or more resolution-specific "variants", see
>    # https://flutter.dev/assets-and-images/#resolution-aware.
> -- 
> 2.20.1
> 
> 
> 
> _______________________________________________
> pve-devel mailing list
> pve-devel@lists.proxmox.com
> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel




      reply	other threads:[~2020-09-30 12:05 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-09-28 12:28 [pve-devel] [PATCH pve_flutter_frontend 0/3] " Aaron Lauterer
2020-09-28 12:28 ` [pve-devel] [PATCH pve_flutter_frontend 1/3] add class for Proxmox corporate identity colors Aaron Lauterer
2020-09-30 10:54   ` Tim Marx
2020-09-30 11:13     ` Aaron Lauterer
2020-09-28 12:28 ` [pve-devel] [PATCH pve_flutter_frontend 2/3] add Proxmox symbol logo white orange Aaron Lauterer
2020-09-30 11:08   ` Tim Marx
2020-09-30 11:22     ` Aaron Lauterer
2020-09-28 12:28 ` [pve-devel] [PATCH pve_flutter_frontend 3/3] Add first welcome screen Aaron Lauterer
2020-09-28 13:41   ` Aaron Lauterer
2020-09-30 12:04     ` Tim Marx [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=386034124.798.1601467462478@webmail.proxmox.com \
    --to=t.marx@proxmox.com \
    --cc=a.lauterer@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal