Bloc Event Pattern Dynamic Theme In Flutter

Bloc Event Pattern Dynamic Theme In Flutter :

Screenshot 1 :

Bloc Event Pattern Dynamic Theme In Flutter

Screenshot 2 :

Bloc Event Pattern Dynamic Theme In Flutter

Project Structure :

Bloc Event Pattern Dynamic Theme In Flutter

Bloc Event Pattern Dynamic Theme In Flutter

Core Package

  • bloc Package
  • data Package
  • listners Package
  • model Package

bloc Package

annotation_bloc.dart

import 'package:custom_theme/core/data/preferences.dart';
import 'package:custom_theme/core/listeners/actions.dart';
import 'package:custom_theme/utils/Translations.dart';
import 'package:custom_theme/utils/styles.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

import 'bloc_base.dart';
import 'object_event.dart';
import 'object_state.dart';

class AnnotationBloc extends AnnotationBase {
  @override
  get initialState => ObjectUninitialized();

  changeLanguageDialog(BuildContext context, ActionListener actionListener) {
    showDialog(
        barrierDismissible: true,
        context: context,
        builder: (BuildContext c) {
          return Dialog(
              backgroundColor: Colors.transparent,
              elevation: 0.0,
              child: Container(
                  padding: EdgeInsets.all(16.0),
                  height: 280,
                  decoration: BoxDecoration(
                      color: Styles.backgroundColor,
                      borderRadius: BorderRadius.all(Radius.circular(10))),
                  child: Column(
                      mainAxisSize: MainAxisSize.max,
                      crossAxisAlignment: CrossAxisAlignment.stretch,
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        Text(
                          Translations.current.text('language'),
                          maxLines: 1,
                          style: Styles.styleTitle(color: Styles.titleColor),
                        ),
                        SizedBox(
                          height: 10,
                        ),
                        SizedBox(
                          child: Container(
                            color: Styles.placeholderColor,
                          ),
                          height: 0.5,
                          width: double.infinity,
                        ),
                        ListTile(
                          onTap: () async {
                            Navigator.pop(context);
                            await Tools.updateLanguage('pt');
                            actionListener.onChangeLanguage(Locale('pt'));
                          },
                          title: Text(
                            Translations.current.text('portuguese'),
                            maxLines: 1,
                            style: Styles.styleTitle(color: Styles.titleColor),
                          ),
                          trailing: Checkbox(
                              value: Translations.current.locale.languageCode
                                  .contains('pt'),
                              onChanged: null),
                        ),
                        ListTile(
                          onTap: () {
                            Navigator.pop(context);
                            Tools.updateLanguage('en');
                            actionListener.onChangeLanguage(Locale('en'));
                          },
                          title: Text(
                            Translations.current.text('english'),
                            maxLines: 1,
                            style: Styles.styleTitle(color: Styles.titleColor),
                          ),
                          trailing: Checkbox(
                              value: Translations.current.locale.languageCode
                                  .contains('en'),
                              onChanged: null),
                        ),
                        ListTile(
                          onTap: () {
                            Navigator.pop(context);
                            Tools.updateLanguage('es');
                            actionListener.onChangeLanguage(Locale('es'));
                          },
                          title: Text(
                            Translations.current.text('spanish'),
                            maxLines: 1,
                            style: Styles.styleTitle(color: Styles.titleColor),
                          ),
                          trailing: Checkbox(
                              value: Translations.current.locale.languageCode
                                  .contains('es'),
                              onChanged: null),
                        ),
                        FlatButton(
                            onPressed: () {
                              Navigator.pop(context);
                            },
                            child: Text(
                              Translations.current.text('cancel'),
                              maxLines: 1,
                              style: Styles.styleDescription(
                                  color: Styles.titleColor),
                            ))
                      ])));
        });
  }

  @override
  Stream<ObjectState> mapEventToState(ObjectEvent event) async* {
    if (event is Run) {
      try {
        if (currentState is ObjectUninitialized) {

        }
        if (currentState is ObjectLoaded) {

        }
      } catch (_) {
      }
    } else if (event is Reload) {

    } else if (event is Refresh) {

    }
  }

  AnnotationBloc() : super();
}

bloc_base.dart

import 'object_event.dart';
import 'object_state.dart';
import 'package:bloc/bloc.dart';

abstract class AnnotationBase extends Bloc<ObjectEvent, ObjectState> {

}

config_bloc.dart

import 'package:custom_theme/core/data/preferences.dart';
import 'package:custom_theme/utils/Translations.dart';
import 'package:custom_theme/utils/styles.dart';
import 'package:flutter/material.dart';
import 'package:bloc/bloc.dart';

import 'config_event.dart';

class ConfigApp {
  ThemeData themeData = Styles.themeLight();
  Locale locale = Translations.current.locale;
}

class ConfigAppBloc extends Bloc<ConfigAppEvent, ConfigApp> {
  @override
  ConfigApp get initialState => ConfigApp();

  ThemeType themeType;
  ConfigApp configApp;

  ConfigAppBloc() {
    themeType = Tools.onThemeType();
    configApp = ConfigApp();
    dispatch(InitConfig());
  }

  ThemeData _onTheme(ThemeType themeType) {
    return themeType == ThemeType.Light
        ? Styles.themeLight()
        : Styles.themeDark();
  }

  @override
  Stream<ConfigApp> mapEventToState(ConfigAppEvent event) async* {
    configApp = ConfigApp();
    if (event is InitConfig) {
      configApp.locale = await Tools.onLanguage();
      configApp.themeData = themeType == ThemeType.Light
          ? Styles.themeLight()
          : Styles.themeDark();
      yield configApp;
    } else if (event is ConfigChangeTheme) {
      var configTheme = event;
      themeType = configTheme.themeType;
      Tools.updateTheme(configTheme.themeType);
      configApp.themeData = _onTheme(themeType);
      yield configApp;
    } else if (event is ConfigChangeLanguage) {
      var configLanguage = event;
      Tools.updateLanguage(configLanguage.locale.languageCode);
      configApp.locale = configLanguage.locale;
      configApp.themeData = _onTheme(themeType);
      yield configApp;
    }
  }
}

config_event.dart

import 'package:custom_theme/core/data/preferences.dart';
import 'package:flutter/painting.dart';

class ConfigAppEvent {
  @override
  String toString() => 'ConfigAppEvent';
}

class InitConfig extends ConfigAppEvent {
  @override
  String toString() => 'InitConfig';
}

class ConfigChangeTheme extends ConfigAppEvent {
  final ThemeType themeType;
  ConfigChangeTheme(this.themeType);
  @override
  String toString() => 'ConfigChangeTheme';
}

class ConfigChangeLanguage extends ConfigAppEvent {
  final Locale locale;
  ConfigChangeLanguage(this.locale);
  @override
  String toString() => 'ConfigChangeLanguage';
}

object_event.dart

import 'package:equatable/equatable.dart';

abstract class ObjectEvent {}

class Run extends ObjectEvent {
  @override
  String toString() => 'Run';
}

class Reload extends ObjectEvent {
  @override
  String toString() => 'Reload';
}

class Refresh extends ObjectEvent {
  @override
  String toString() => 'Reload';
}

object_state.dart

import 'package:equatable/equatable.dart';

abstract class ObjectState  {
  ObjectState([List props = const []]);
}

class ObjectError extends ObjectState {
  @override
  String toString() => 'ObjectError';
}

class ObjectRefresh extends ObjectState {
  @override
  String toString() => 'ObjectRefresh';
}

class ObjectUninitialized extends ObjectState {
  @override
  String toString() => 'ObjectUninitialized';
}

class ObjectLoaded extends ObjectState {
  final List<Object> objects;
  final bool hasReachedMax;

  ObjectLoaded({
    this.objects,
    this.hasReachedMax,
  }) : super([objects, hasReachedMax]);

  ObjectLoaded copyWith({
    List<Object> objects,
    bool hasReachedMax,
  }) {
    return ObjectLoaded(
      objects: objects ?? this.objects,
      hasReachedMax: hasReachedMax ?? this.hasReachedMax,
    );
  }

  @override
  String toString() =>
      'ObjectLoaded { posts: ${objects.length}, hasReachedMax: $hasReachedMax }';
}

simple_bloc_delegate.dart

import 'package:bloc/bloc.dart';

class SimpleBlocDelegate extends BlocDelegate {

  @override
  void onEvent(Bloc bloc, Object event) {
    super.onEvent(bloc, event);
    print(event);
  }

  @override
  void onTransition(Bloc bloc, Transition transition) {
    super.onTransition(bloc, transition);
    print(transition);
  }

  @override
  void onError(Bloc bloc, Object error, StackTrace stacktrace) {
    super.onError(bloc, error, stacktrace);
    print(error);
  }
}

data Package

preferences.dart

import 'dart:io';
import 'dart:ui';

import 'package:custom_theme/utils/Translations.dart';
import 'package:shared_preferences/shared_preferences.dart';

enum ThemeType { Dark, Light }

class Tools {
  static SharedPreferences prefs;
  static final ThemeType_KEY = 'ThemeType';
  static final LANGUAGE = 'LANGUAGE';

  static final Languages = ['pt', 'en', 'es'];

  static init() async {
    prefs = await SharedPreferences.getInstance();
  }

  static updateLanguage(String code) async {
    prefs.setString(LANGUAGE, code.trim());
    await Translations.load(Locale(code));
  }

  static updateTheme(ThemeType type) {
    prefs.setString(ThemeType_KEY, '${type}');
  }

  static Future<Locale> onLanguage() async {
    String code = prefs.getString(LANGUAGE) ?? Platform.localeName;
    print('onLanguage : ${code}');
    return Translations.filterLocale(Locale(code));
  }

  static ThemeType onThemeType() {
    var type = prefs.getString(ThemeType_KEY) ?? null;
    return (type == null)
        ? ThemeType.Light
        : (type == '${ThemeType.Light}') ? ThemeType.Light : ThemeType.Dark;
  }
}

listener Package

actions.dart

import 'package:flutter/cupertino.dart';

abstract class ActionListener {
  void onChangeLanguage(Locale locale);
}

ui Package

  • page Package
  • router.dart file

page Package

setting.dart

import 'package:custom_theme/core/bloc/annotation_bloc.dart';
import 'package:custom_theme/core/bloc/config_bloc.dart';
import 'package:custom_theme/core/bloc/config_event.dart';
import 'package:custom_theme/core/bloc/object_event.dart';
import 'package:custom_theme/core/bloc/object_state.dart';
import 'package:custom_theme/core/data/preferences.dart';
import 'package:custom_theme/core/listeners/actions.dart';
import 'package:custom_theme/utils/Translations.dart';
import 'package:custom_theme/utils/styles.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class SettingPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    ConfigAppBloc themeBloc = BlocProvider.of<ConfigAppBloc>(context);

    return BlocProvider<AnnotationBloc>(
        builder: (context) => AnnotationBloc(),
        child: BlocBuilder<AnnotationBloc, ObjectState>(
            builder: (context, objectState) {
          return ThemePage(themeBloc);
        }));
  }
}

class ThemePage extends StatefulWidget {
  final ConfigAppBloc themeBloc;

  ThemePage(this.themeBloc);

  @override
  _ThemeState createState() => _ThemeState(themeBloc);
}

class _ThemeState extends State<ThemePage>  implements  ActionListener{
  final ConfigAppBloc themeBloc;
  AnnotationBloc annotationBloc;

  _ThemeState(this.themeBloc);

  @override
  void initState() {
    annotationBloc = BlocProvider.of<AnnotationBloc>(context);
    super.initState();
    annotationBloc..dispatch(Run());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Setting Page'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                Translations.current.text('no_content'),
              ),
              Icon(Icons.add, color: Styles.iconColor),
              Text(
                '0',
                style: Styles.styleTitle(
                    color: Styles.titleColor,
                    size: false,
                    fontWeight: FontWeight.bold),
              ),
            ],
          ),
        ),
        floatingActionButton: Column(
            crossAxisAlignment: CrossAxisAlignment.end,
            mainAxisAlignment: MainAxisAlignment.end,
            children: <Widget>[
              Material(
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(5.0),
                  ),
                  color: Styles.placeholderColor,
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    mainAxisSize: MainAxisSize.max,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: <Widget>[
                      Container(
                        child: Text(
                          themeBloc.themeType == ThemeType.Light
                              ? Translations.current.text('mode_dark_true')
                              : Translations.current.text('mode_dark_false'),
                          style: Styles.styleDescription(
                            color: Styles.titleColor,
                            textSizeDescription: 16,
                          ),
                        ),
                        padding: EdgeInsets.only(left: 16.0),
                      ),
                      Switch(
                          value: themeBloc.themeType == ThemeType.Light
                              ? false
                              : true,
                          onChanged: (bool value) {
                            themeBloc.dispatch(ConfigChangeTheme(
                                value ? ThemeType.Dark : ThemeType.Light));
                          }),
                    ],
                  )),
              SizedBox(
                height: 10,
              ),
              FlatButton(
                padding: EdgeInsets.all(0.0),
                onPressed: () {
                   annotationBloc.changeLanguageDialog(context, this);
                },
                child: Material(
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(5.0),
                  ),
                  color: Styles.placeholderColor,
                  child: Container(
                      padding: EdgeInsets.all(16.0),
                      width: double.infinity,
                      child: Text(Translations.current.text('language'),
                          style: Styles.styleDescription(
                            color: Styles.titleColor,
                            textSizeDescription: 16,
                          ))),
                ),
              )
            ]));
  }

  @override
  void onChangeLanguage(Locale locale) {
    themeBloc.dispatch(ConfigChangeLanguage(locale));
  }
}

router.dart file outside the page Package

router.dart

import 'package:flutter/material.dart';

import 'page/setting.dart';

class Router {
  static Route<dynamic> generateRoute(RouteSettings settings) {
    switch (settings.name) {
      case '/':
        return MaterialPageRoute(builder: (_) => SettingPage());
      case '/register':
        return MaterialPageRoute(builder: (_) => SettingPage());
      default:
        return MaterialPageRoute(builder: (_) => SettingPage());
    }
  }
}

utils Package

styles.dart

import 'dart:ui';

import 'package:custom_theme/utils/utils.dart';
import 'package:flutter/material.dart';

class Styles {
  static final String fontNameDefault = 'FontLetra';

  static final _textSizeTitle = 18.0;
  static final _textBigTitle = 40.0;
  static final _textSizeDefault = 16.0;
  static final _textSizeDescription = 13.0;
  static Color titleColor;
  static Color subtitleColor;
  static Color placeholderColor;
  static Color iconColor;
  static Color progressColor;
  static Color backgroundColor;
  static Color floattingBackgroundColor;
  static ThemeData themeData;

  static ThemeData themeDark() {
    titleColor = Colors.white;
    subtitleColor = Colors.white;
    iconColor = Colors.white;
    progressColor = colorParse(hexCode: '#F26430');
    backgroundColor = colorParse(hexCode: '#1D2026');
    placeholderColor = colorParse(hexCode: '#323640');
    floattingBackgroundColor = Colors.deepOrange;
    return ThemeData(
      scaffoldBackgroundColor: backgroundColor,
      brightness: Brightness.dark,
      primaryColor: backgroundColor,
      accentColor: progressColor,
      indicatorColor: progressColor,
    );
  }

  static ThemeData themeLight() {
    titleColor = Colors.black;
    subtitleColor = Colors.black;
    iconColor = Colors.white;
    progressColor = colorParse(hexCode: '#F26430');
    backgroundColor = Colors.white;
    placeholderColor = Colors.grey[200];
    floattingBackgroundColor = Colors.deepOrange;
    return ThemeData(
      scaffoldBackgroundColor: backgroundColor,
      brightness: Brightness.light,
      primaryColor: backgroundColor,
      accentColor: progressColor,
    );
  }

  static styleTitle(
          {Color color,
          bool size = true,
          FontWeight fontWeight = FontWeight.bold}) =>

      TextStyle(
          fontSize: size ? _textSizeTitle : _textBigTitle,
          fontWeight: fontWeight,
          color: color);

  static styleDefault(
          {Color color,
          FontWeight fontWeight = FontWeight.normal,
          bool size = true}) =>
      TextStyle(
          fontSize: size ? _textSizeTitle : _textBigTitle,
          fontFamily: fontNameDefault,
          fontWeight: fontWeight,
          color: color);

  static styleDescription(
          {Color color,
          double textSizeDescription = 0,
          FontWeight fontWeight = FontWeight.normal}) =>
      TextStyle(
          fontSize: textSizeDescription == 0
              ? _textSizeDescription
              : textSizeDescription,
          fontWeight: fontWeight,
          fontFamily: fontNameDefault,
          color: color);
}

Translations.dart

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class Translations {
  Translations(Locale locale) {
    current = this;
    this.locale = locale;
  }

  Locale locale;
  static Map<dynamic, dynamic> _localizedValues;

  static Translations current;

  static Translations of(BuildContext context) {
    var result = Localizations.of<Translations>(context, Translations);
    return result;
  }

  String text(String key) {
    return _localizedValues[key] ?? '** $key not found';
  }

  Future<Translations> run() async {
    Translations translations = new Translations(locale);
    String jsonContent = await rootBundle
        .loadString("assets/locale/i18n_${locale.languageCode}.json");
    _localizedValues = json.decode(jsonContent);
    return translations;
  }

  static Locale filterLocale(Locale locale) {
    if (locale.languageCode.contains('pt')) {
      return Locale('pt');
    } else if (locale.languageCode.contains('en')) {
      return Locale('en');
    } else if (locale.languageCode.contains('es')) {
      return Locale('es');
    } else {
      return Locale('en');
    }
  }

  static bool isSupported(Locale locale) =>
      ['en', 'pt', 'es'].contains(locale.languageCode);

  static Future<Translations> load(Locale locale) async {
    locale = filterLocale(locale);
    Translations translations = new Translations(locale);
    String jsonContent = await rootBundle
        .loadString("assets/locale/i18n_${locale.languageCode}.json");
    _localizedValues = json.decode(jsonContent);
    current = await translations.run();
    return translations;
  }

  get currentLanguage => filterLocale(locale);
}

class TranslationsDelegate extends LocalizationsDelegate<Translations> {
  const TranslationsDelegate();

  @override
  bool isSupported(Locale locale) =>
      ['en', 'pt', 'es'].contains(locale.languageCode);

  @override
  Future<Translations> load(Locale locale) async {
    return null;
  }

  @override
  bool shouldReload(TranslationsDelegate old) => false;
}

utils.dart

import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'Translations.dart';

String formatDate(String value, {bool format = false}) {
  var now = DateTime.parse(value);
  var formatter = DateFormat(
      format ? 'dd' : 'dd.MM.yyyy', Translations.current.locale.languageCode);
  return format
      ? '${formatter.format(now)} ${_weekday(now)}'
      : formatter.format(now);
}

String _weekday(DateTime dateTime) {
  var day = weekday(dateTime);
  return day.length > 3 ? day.substring(0, 3) : day;
}

weekday(DateTime dateTime) {
  switch (dateTime.weekday) {
    case 1:
      return Translations.current.text('Monday');
    case 2:
      return Translations.current.text('Tuesday');
    case 3:
      return Translations.current.text('Wednesday');
    case 4:
      return Translations.current.text('Thursday');
    case 5:
      return Translations.current.text('Friday');
    case 6:
      return Translations.current.text('Saturday');
    case 7:
      return Translations.current.text('Sunday');
  }
}

progressWidget() {
  return Center(child: CircularProgressIndicator());
}

String formatHora(String value) {
  var now = DateTime.parse(value);
  var formatter = DateFormat('hh:mm', Translations.current.locale.languageCode);
  return formatter.format(now);
}

parseColor({String hexCode}) {}

colorParse({String hexCode}) {
  try {
    String hex = hexCode.replaceAll("#", "");
    if (hex.isEmpty) hex = "ffffff";
    if (hex.length == 3) {
      hex =
          '${hex.substring(0, 1)}${hex.substring(0, 1)}${hex.substring(1, 2)}${hex.substring(1, 2)}${hex.substring(2, 3)}${hex.substring(2, 3)}';
    }
    Color col = Color(int.parse(hex, radix: 16)).withOpacity(1.0);
    return col;
  } catch (_) {}
  return Colors.black;
}

Outside the All Package

main.dart

import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'core/bloc/simple_bloc_delegate.dart';
import 'core/bloc/config_bloc.dart';
import 'core/data/preferences.dart';
import 'ui/router.dart';
import 'utils/Translations.dart';
import 'package:bloc/bloc.dart';

Future<Null> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  BlocSupervisor.delegate = SimpleBlocDelegate();
  await Tools.init();
  await Translations.load(await Tools.onLanguage());
  runApp(StartApp());
}

class StartApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider<ConfigAppBloc>(
        builder: (context) => ConfigAppBloc(),
        child:
            BlocBuilder<ConfigAppBloc, ConfigApp>(builder: (context, config) {
          return MaterialApp(
            supportedLocales: [
              const Locale('pt', 'PT'),
              const Locale('es', 'ES'),
              const Locale('en' 'US'),
              config.locale
            ],
            localizationsDelegates: [
              const TranslationsDelegate(),
              GlobalMaterialLocalizations.delegate,
              GlobalWidgetsLocalizations.delegate
            ],
            localeResolutionCallback:
                (Locale locale, Iterable<Locale> supportedLocales) {
              for (Locale supportedLocale in supportedLocales) {
                if (supportedLocale.languageCode == locale.languageCode ||
                    supportedLocale.countryCode == locale.countryCode) {
                  return supportedLocale;
                }
              }

              return supportedLocales.first;
            },
            debugShowCheckedModeBanner: false,
            theme: config.themeData,
            initialRoute: '/',
            onGenerateRoute: Router.generateRoute,
          );
        }));
  }
}

pubspec.yaml

name: custom_theme
description: A new Flutter application.
version: 1.0.0+1

environment:
  sdk: ">=2.1.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^0.1.2
  intl:
  flutter_bloc:
  equatable:
  shared_preferences:

  flutter_localizations:
    sdk: flutter

dev_dependencies:
  flutter_test:
    sdk: flutter

flutter:
  uses-material-design: true

  assets:
    - assets/locale/i18n_en.json
    - assets/locale/i18n_pt.json
    - assets/locale/i18n_es.json

assets :

i18n_en.json

{
  "no_content": "No content",

  "mode_dark_true": "Activate dark mode",
  "mode_dark_false": "Disable dark mode",

  "language": "Language",

  "portuguese": "Portuguese",
  "english": "English",
  "spanish": "Spanish",
  "cancel": "Cancel"
}

i18n_es.json

{
  "no_content": "Sem conteúdo",

  "mode_dark_true": "Activar el modo oscuro",
  "mode_dark_false": "Desactivar el modo oscuro",

  "language": "Idioma",

  "portuguese": "Portugués",
  "english": "Inglés",
  "spanish": "Español",
  "cancel": "Cancelar"
}

i18n_pt.json

{
  "no_content": "Sem conteúdo",

  "mode_dark_true": "Activar modo escuro",
  "mode_dark_false": "Desactivar modo escuro",

  "language": "Idiomas",

  "portuguese": "Português",
  "english": "Inglês",
  "spanish": "Espanhol",
  "cancel": "Cancelar"
}

The flutter tutorial  is a website that bring you the latest and amazing resources of code. All the languages codes are included in this website. The languages like flutter, android, java,kotlin etc.with the help of this languages any user can develop the beautiful application

For more information about Flutter. visit www.fluttertutorial.in