Skip to content
Snippets Groups Projects
main.dart 22.4 KiB
Newer Older
vjrj's avatar
vjrj committed
import 'dart:async';
tylersavery's avatar
tylersavery committed
import 'dart:io';
import 'package:connectivity_wrapper/connectivity_wrapper.dart';
anfeichtinger's avatar
anfeichtinger committed
import 'package:easy_localization/easy_localization.dart';
vjrj's avatar
vjrj committed
import 'package:feedback/feedback.dart';
vjrj's avatar
vjrj committed
import 'package:filesystem_picker/filesystem_picker.dart';
vjrj's avatar
vjrj committed
import 'package:flutter/foundation.dart';
anfeichtinger's avatar
anfeichtinger committed
import 'package:flutter/material.dart';
vjrj's avatar
vjrj committed
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
anfeichtinger's avatar
anfeichtinger committed
import 'package:flutter_displaymode/flutter_displaymode.dart';
vjrj's avatar
vjrj committed
import 'package:introduction_screen/introduction_screen.dart';
vjrj's avatar
vjrj committed
import 'package:l10n_esperanto/l10n_esperanto.dart';
vjrj's avatar
vjrj committed
import 'package:lehttp_overrides/lehttp_overrides.dart';
import 'package:once/once.dart';
import 'package:provider/provider.dart';
vjrj's avatar
vjrj committed
import 'package:pwa_install/pwa_install.dart';
vjrj's avatar
vjrj committed
import 'package:responsive_framework/responsive_wrapper.dart';
vjrj's avatar
vjrj committed
import 'package:responsive_framework/utils/scroll_behavior.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
vjrj's avatar
vjrj committed
import 'package:sentry_logging/sentry_logging.dart';
vjrj's avatar
vjrj committed
import 'package:timeago/timeago.dart' as timeago;
vjrj's avatar
vjrj committed
import 'package:uni_links/uni_links.dart';
import 'package:workmanager/workmanager.dart';
anfeichtinger's avatar
anfeichtinger committed

vjrj's avatar
vjrj committed
import 'app_bloc_observer.dart';
vjrj's avatar
vjrj committed
import 'custom_feedback_localization.dart';
vjrj's avatar
vjrj committed
import 'data/eo_timeago_support.dart';
import 'data/eu_timeago_support.dart';
import 'data/gl_timeago_support.dart';
vjrj's avatar
vjrj committed
import 'data/models/app_cubit.dart';
import 'data/models/app_state.dart';
vjrj's avatar
vjrj committed
import 'data/models/bottom_nav_cubit.dart';
vjrj's avatar
vjrj committed
import 'data/models/contact_cubit.dart';
vjrj's avatar
vjrj committed
import 'data/models/multi_wallet_transaction_cubit.dart';
vjrj's avatar
vjrj committed
import 'data/models/node_list_cubit.dart';
vjrj's avatar
vjrj committed
import 'data/models/node_list_state.dart';
import 'data/models/node_manager.dart';
vjrj's avatar
vjrj committed
import 'data/models/node_type.dart';
import 'data/models/payment_cubit.dart';
vjrj's avatar
vjrj committed
import 'data/models/theme_cubit.dart';
import 'data/models/transaction_cubit_remove.dart';
import 'data/models/utxo_cubit.dart';
vjrj's avatar
vjrj committed
import 'env.dart';
vjrj's avatar
vjrj committed
import 'g1/api.dart';
vjrj's avatar
vjrj committed
import 'g1/g1_helper.dart';
import 'shared_prefs_helper.dart';
vjrj's avatar
vjrj committed
import 'ui/contacts_cache.dart';
vjrj's avatar
vjrj committed
import 'ui/logger.dart';
vjrj's avatar
vjrj committed
import 'ui/notification_controller.dart';
vjrj's avatar
vjrj committed
import 'ui/pay_helper.dart';
import 'ui/screens/skeleton_screen.dart';
vjrj's avatar
vjrj committed
import 'ui/ui_helpers.dart';
import 'ui/widgets/connectivity_widget_wrapper_wrapper.dart';
vjrj's avatar
vjrj committed

const String fetchWalletsTransactionsTask =
    'org.comunes.ginkgo.fetchWalletsTransactionsTask';

anfeichtinger's avatar
anfeichtinger committed
void main() async {
vjrj's avatar
vjrj committed
  await NotificationController.initializeLocalNotifications();
vjrj's avatar
vjrj committed

vjrj's avatar
vjrj committed
  // const int seedColorOld = 0xff526600;
vjrj's avatar
vjrj committed

vjrj's avatar
vjrj committed
  const int seedColor = 0xff98FB98;
  final int seedColorDark = Colors.lightGreen.value;
  final ThemeData lightTheme = ThemeData.from(
    colorScheme: ColorScheme.fromSeed(
      seedColor: const Color(seedColor),
      // brightness: Brightness.light,
    ),
    useMaterial3: true,
  );

  final ThemeData darkTheme = ThemeData.from(
    colorScheme: ColorScheme.fromSeed(
      seedColor: Color(seedColorDark),
      brightness: Brightness.dark,
    ),
    useMaterial3: true,
  );
vjrj's avatar
vjrj committed

vjrj's avatar
vjrj committed
  // To resolve Let's Encrypt SSL certificate problems with Android 7.1.1 and below
vjrj's avatar
vjrj committed
  if (!kIsWeb && Platform.isAndroid) {
vjrj's avatar
vjrj committed
    HttpOverrides.global = LEHttpOverrides();
  }

anfeichtinger's avatar
anfeichtinger committed
  /// Initialize packages
  WidgetsFlutterBinding.ensureInitialized();
  await EasyLocalization.ensureInitialized();
vjrj's avatar
vjrj committed

  if (!kIsWeb && Platform.isAndroid) {
tylersavery's avatar
tylersavery committed
    await FlutterDisplayMode.setHighRefreshRate();
vjrj's avatar
vjrj committed
  }

vjrj's avatar
vjrj committed
  final SharedPreferencesHelper shared = SharedPreferencesHelper();
  await shared.init();
  if (shared.cesiumCards.isEmpty) {
    await shared.getWallet();
  }
vjrj's avatar
vjrj committed
  assert(shared.getPubKey() != null);

vjrj's avatar
vjrj committed

vjrj's avatar
vjrj committed
  PWAInstall().setup(installCallback: () {
    logger('APP INSTALLED!');
  });

vjrj's avatar
vjrj committed
  Bloc.observer = AppBlocObserver();

vjrj's avatar
vjrj committed
  await ContactsCache().init();
vjrj's avatar
vjrj committed
  timeago.setLocaleMessages('eo', EoMessages());
  timeago.setLocaleMessages('eo_short', EoShortMessages());
  timeago.setLocaleMessages('eu', EuMessages());
  timeago.setLocaleMessages('eu_short', EuShortMessages());
  timeago.setLocaleMessages('de', timeago.DeMessages());
  timeago.setLocaleMessages('de_short', timeago.DeShortMessages());
  timeago.setLocaleMessages('fr', timeago.FrMessages());
  timeago.setLocaleMessages('fr_short', timeago.FrShortMessages());
  timeago.setLocaleMessages('ca', timeago.CaMessages());
  timeago.setLocaleMessages('ca_short', timeago.CaShortMessages());
  timeago.setLocaleMessages('nl', timeago.NlMessages());
  timeago.setLocaleMessages('nl_short', timeago.NlShortMessages());
  timeago.setLocaleMessages('it', timeago.ItMessages());
  timeago.setLocaleMessages('it_short', timeago.ItShortMessages());
  timeago.setLocaleMessages('pt', timeago.PtBrMessages());
  timeago.setLocaleMessages('pt_short', timeago.PtBrShortMessages());
  timeago.setLocaleMessages('gl', GlMessages());
  timeago.setLocaleMessages('gl_short', GlShortMessages());
vjrj's avatar
vjrj committed

vjrj's avatar
vjrj committed
  void appRunner() => SystemChrome.setPreferredOrientations(<DeviceOrientation>[
        DeviceOrientation.portraitUp,
        DeviceOrientation.portraitDown
      ]).then((_) {
        runApp(ChangeNotifierProvider<SharedPreferencesHelper>(
vjrj's avatar
vjrj committed
            create: (BuildContext context) => SharedPreferencesHelper(),
            child: EasyLocalization(
              path: 'assets/translations',
              supportedLocales: const <Locale>[
                // Asturian is not supported in flutter
                // More info: https://docs.flutter.dev/development/accessibility-and-localization/internationalization#adding-support-for-a-new-language
                // Meantime we use this workaround:
                // https://github.com/aissat/easy_localization/issues/220#issuecomment-846035493
                Locale('es', 'AST'),
                Locale('ca'),
                Locale('de'),
                Locale('en'),
                Locale('eo'),
                Locale('es'),
                Locale('eu'),
                Locale('fr'),
                Locale('gl'),
                Locale('it'),
                Locale('nl'),
                Locale('pt'),
              ],
              fallbackLocale: const Locale('en'),
              useFallbackTranslations: true,
              child: MultiBlocProvider(
                  providers: <BlocProvider<dynamic>>[
                    BlocProvider<BottomNavCubit>(
                        create: (BuildContext context) => BottomNavCubit()),
                    BlocProvider<AppCubit>(
                        create: (BuildContext context) => AppCubit()),
                    BlocProvider<PaymentCubit>(
                        create: (BuildContext context) => PaymentCubit()),
                    BlocProvider<NodeListCubit>(
                        create: (BuildContext context) => NodeListCubit()),
                    BlocProvider<ContactsCubit>(
                        create: (BuildContext context) => ContactsCubit()),
                    BlocProvider<UtxoCubit>(
                        create: (BuildContext context) => UtxoCubit()),
                    // TODO(vjrj): Remove when clean the state of this after upgrades
                    BlocProvider<TransactionCubitRemove>(
                        create: (BuildContext context) =>
                            TransactionCubitRemove()),
                    BlocProvider<MultiWalletTransactionCubit>(
                        create: (BuildContext context) =>
                            MultiWalletTransactionCubit()),
                    BlocProvider<ThemeCubit>(
                        create: (BuildContext context) => ThemeCubit()),
                    // Add other BlocProviders here if needed
                  ],
                  child:
vjrj's avatar
vjrj committed
                      GinkgoApp(darkTheme: darkTheme, lightTheme: lightTheme)),
vjrj's avatar
vjrj committed
            )));
vjrj's avatar
vjrj committed
      });
vjrj's avatar
vjrj committed
  if (inDevelopment) {
    // Only use sentry in production
    await SentryFlutter.init((
      SentryFlutterOptions options,
    ) {
vjrj's avatar
vjrj committed
      options.tracesSampleRate = 1.0;
      options.reportPackages = false;
      // options.addInAppInclude('sentry_flutter_example');
      options.considerInAppFramesByDefault = false;
      // options.attachThreads = true;
      // options.enableWindowMetricBreadcrumbs = true;
      options.addIntegration(LoggingIntegration());
      options.sendDefaultPii = true;
      options.reportSilentFlutterErrors = true;
      // options.attachScreenshot = true;
      // options.screenshotQuality = SentryScreenshotQuality.low;
      // This fails:
      // options.attachViewHierarchy = true;
      // We can enable Sentry debug logging during development. This is likely
      // going to log too much for your app, but can be useful when figuring out
      // configuration issues, e.g. finding out why your events are not uploaded.
      options.debug = false;

      options.maxRequestBodySize = MaxRequestBodySize.always;
      options.maxResponseBodySize = MaxResponseBodySize.always;

      // options.release = version;
      // options.environment = 'production';
      // options.beforeSend = (SentryEvent event, {dynamic hint}) {
      //  return event;
      //};
vjrj's avatar
vjrj committed

vjrj's avatar
vjrj committed
      options.dsn = Env.sentryDsn;
vjrj's avatar
vjrj committed
      // Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
      // We recommend adjusting this value in production.
      // options.tracesSampleRate = 1.0;
    }, appRunner: appRunner);
  } else {
    appRunner();
  }
anfeichtinger's avatar
anfeichtinger committed
}

@pragma(
    'vm:entry-point') // Mandatory if the App is obfuscated or using Flutter 3.1+
void workManagerCallbackDispatcher() {
  Workmanager()
      .executeTask((String task, Map<String, dynamic>? inputData) async {
    try {
      loggerDev(
          '---------- Start fetchTransactionsTask Workmanager background task');
      switch (task) {
        case fetchWalletsTransactionsTask:
          await NotificationController.initializeLocalNotifications();
vjrj's avatar
vjrj committed
          fetchTransactionsFromBackground();
          break;
        case Workmanager.iOSBackgroundTask:
          break;
      }
      loggerDev(
          '---------- End fetchTransactionsTask Workmanager background task');
    } catch (err, stacktrace) {
      logger(err.toString());
      await Sentry.captureException(err, stackTrace: stacktrace);
    }
    return Future<bool>.value(true);
  });
}

vjrj's avatar
vjrj committed
class AppIntro extends StatefulWidget {
  const AppIntro({super.key});

  @override
  State<AppIntro> createState() => _AppIntro();
}

class _AppIntro extends State<AppIntro> {
  final GlobalKey<IntroductionScreenState> introKey =
      GlobalKey<IntroductionScreenState>();
vjrj's avatar
vjrj committed

vjrj's avatar
vjrj committed
  void _onIntroEnd(BuildContext context, AppCubit cubit) {
    cubit.introViewed();
vjrj's avatar
vjrj committed
    Navigator.of(context).pushReplacement(
vjrj's avatar
vjrj committed
      MaterialPageRoute<void>(
          builder: (BuildContext _) => const SkeletonScreen()),
vjrj's avatar
vjrj committed
    );
  }

  @override
  Widget build(BuildContext context) {
vjrj's avatar
vjrj committed
    return BlocBuilder<AppCubit, AppState>(
vjrj's avatar
vjrj committed
        builder: (BuildContext buildContext, AppState state) {
      final AppCubit cubit = context.read<AppCubit>();
      return IntroductionScreen(
        key: introKey,
        pages: <PageViewModel>[
          for (int i = 1; i <= 5; i++)
            createPageViewModel('intro_${i}_title', 'intro_${i}_description',
                'assets/img/undraw_intro_$i.png', context),
        ],
        onDone: () => _onIntroEnd(buildContext, cubit),
        showSkipButton: true,
        skipOrBackFlex: 0,
        onSkip: () => _onIntroEnd(buildContext, cubit),
        nextFlex: 0,
        skip: Text(tr('skip')),
        next: const Icon(Icons.arrow_forward),
        done: Text(tr('start'),
            style: const TextStyle(fontWeight: FontWeight.w600)),
        dotsDecorator: const DotsDecorator(
          size: Size(10.0, 10.0),
          color: Color(0xFFBDBDBD),
          activeColor: Colors.blueAccent,
          activeSize: Size(22.0, 10.0),
          activeShape: RoundedRectangleBorder(
            borderRadius: BorderRadius.all(Radius.circular(25.0)),
          ),
        ),
      );
    });
vjrj's avatar
vjrj committed
/*
void printCubitStateSize(String cubitName, HydratedCubit cubit) {
  final String jsonState = jsonEncode(cubit.state);
  print('Size of $cubitName in bytes: ${jsonState.length}');
}
*/

PageViewModel createPageViewModel(
    String title, String body, String imageAsset, BuildContext context) {
  final ColorScheme colorScheme = Theme.of(context).colorScheme;
vjrj's avatar
vjrj committed
  final TextStyle titleStyle = TextStyle(
    color: colorScheme.primary,
    fontWeight: FontWeight.bold,
    fontSize: 24.0,
  );
  final TextStyle bodyStyle = TextStyle(
    color: colorScheme.onSurface,
    fontSize: 18.0,
  );

vjrj's avatar
vjrj committed
  return PageViewModel(
    title: tr(title),
    body: tr(body),
    image: Image.asset(imageAsset),
vjrj's avatar
vjrj committed
    decoration: PageDecoration(
      titleTextStyle: titleStyle,
      bodyTextStyle: bodyStyle,
      pageColor: colorScheme.background,
vjrj's avatar
vjrj committed
class GinkgoApp extends StatefulWidget {
vjrj's avatar
vjrj committed
  const GinkgoApp(
vjrj's avatar
vjrj committed
      {super.key, required this.lightTheme, required this.darkTheme});
vjrj's avatar
vjrj committed
  // The navigator key is necessary to navigate using static methods
  static final GlobalKey<NavigatorState> navigatorKey =
      GlobalKey<NavigatorState>();
vjrj's avatar
vjrj committed

vjrj's avatar
vjrj committed
  final ThemeData darkTheme;
  final ThemeData lightTheme;
vjrj's avatar
vjrj committed

vjrj's avatar
vjrj committed
  @override
vjrj's avatar
vjrj committed
  State<GinkgoApp> createState() => _GinkgoAppState();
vjrj's avatar
vjrj committed
class _GinkgoAppState extends State<GinkgoApp> {
  Future<void> _loadNodes() async {
vjrj's avatar
vjrj committed
    _printNodeStatus();
vjrj's avatar
vjrj committed
    for (final NodeType nodeType in NodeType.values) {
      await fetchNodes(nodeType, false);
    }
vjrj's avatar
vjrj committed
    _printNodeStatus(prefix: 'Continuing');
  }

  void _printNodeStatus({String prefix = 'Starting'}) {
    final int nDuniterNodes = NodeManager().nodeList(NodeType.duniter).length;
    final int nCesiumPlusNodes =
        NodeManager().nodeList(NodeType.cesiumPlus).length;
    final int nGvaNodes = NodeManager().nodeList(NodeType.gva).length;
    logger(
vjrj's avatar
vjrj committed
        '$prefix with $nDuniterNodes duniter nodes, $nCesiumPlusNodes c+ nodes, and $nGvaNodes gva nodes');
vjrj's avatar
vjrj committed
    if (!kReleaseMode) {
      logger('${NodeManager().nodeList(NodeType.cesiumPlus)}');
    }
    if (!kReleaseMode) {
      logger('${NodeManager().nodeList(NodeType.gva)}');
    }
vjrj's avatar
vjrj committed
  @override
  void initState() {
    super.initState();
vjrj's avatar
vjrj committed
    NodeManager().loadFromCubit(context.read<NodeListCubit>());
vjrj's avatar
vjrj committed
    // Only after at least the action method is set, the notification events are delivered
    NotificationController.startListeningNotificationEvents();
vjrj's avatar
vjrj committed
    // Wipe Old Transactions Cubit
    context.read<TransactionCubitRemove>().clear();
vjrj's avatar
vjrj committed
    context.read<TransactionCubitRemove>().close();
    Once.runHourly('load_nodes', callback: () async {
      final bool isConnected =
          await ConnectivityWidgetWrapperWrapper.isConnected;
      if (isConnected) {
        logger('Load nodes via once');
        _loadNodes();
      }
    }, fallback: () {
      _printNodeStatus(prefix: 'After once hourly having');
    });
    Once.runDaily('clear_errors', callback: () {
      logger('clearErrors via once');
      NodeManager().cleanErrorStats();
    });
vjrj's avatar
vjrj committed
    Once.runDaily('clear_cache', callback: () {
      logger('clear cache via once');
      ContactsCache().clear();
    });
    Once.runOnce('resize_avatars', callback: () {
      logger('resize avatar via once');
      context.read<ContactsCubit>().resizeAvatars();
    });
    Once.runDaily('clear_tx_cubit', callback: () {
      logger('clear tx cubit via once');
      context.read<MultiWalletTransactionCubit>().clearState();
    });
vjrj's avatar
vjrj committed
    ContactsCache().addContacts(context.read<ContactsCubit>().state.contacts);

    fetchTxsCronTask = Cron()
        .schedule(Schedule.parse(kReleaseMode ? '*/10 * * * *' : '*/5 * * * *'),
            () async {
      logger('---------- fetchTransactions via cron');
      // Disabled to check the back development
vjrj's avatar
vjrj committed
      // if (!inDevelopment) {
      fetchTransactions(context);
      // }
    if (inDevelopment) {
      // Try to test auto-recover from empty node-list;
vjrj's avatar
vjrj committed
      NodeManager().endpointNodes.clear();
      NodeManager().gvaNodes.clear();
      NodeManager().duniterNodes.clear();
    }

    ConnectivityWidgetWrapperWrapper.isConnected.then((bool isConnected) {
      if (isConnected) {
        fetchNodesIfNotReady();
        // Fetch transactions (and balance) here too on start
        fetchTransactions(context);
      }
    });
vjrj's avatar
vjrj committed

    if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
      Workmanager().initialize(
          workManagerCallbackDispatcher); // The top level function, aka callbackDispatcher
      /* , isInDebugMode:
              true // If enabled it will post a notification whenever the task is running. Handy for debugging tasks
          ); */
      Workmanager().registerPeriodicTask(
        fetchWalletsTransactionsTask,
        fetchWalletsTransactionsTask,
        frequency: const Duration(minutes: 15),
      );
    }
vjrj's avatar
vjrj committed

    /*  if (inDevelopment) {
      printCubitStateSize(
          'multiTxCubit', context.read<MultiWalletTransactionCubit>());
      printCubitStateSize('TxCubit', context.read<TransactionCubitRemove>());
      printCubitStateSize('nodeCubit', context.read<NodeListCubit>());
      printCubitStateSize('paymentCubit', context.read<PaymentCubit>());
      printCubitStateSize('AppCubit', context.read<AppCubit>());
      printCubitStateSize('BottomNavCubit', context.read<BottomNavCubit>());
      // printCubitStateSize('ContactsCubit', context.read<ContactsCubit>());
      printCubitStateSize('ThemeCubit', context.read<ThemeCubit>());
      printCubitStateSize('UtxoCubit', context.read<UtxoCubit>());
    } */
  @override
  void dispose() {
    ContactsCache().dispose();
vjrj's avatar
vjrj committed
    _disposeDeepLinkListener();
    fetchTxsCronTask.cancel();
    Workmanager().cancelAll();
    super.dispose();
  }

vjrj's avatar
vjrj committed
  late StreamSubscription<dynamic>? _sub;

  Future<void> _initDeepLinkListener() async {
    _sub = linkStream.listen((String? link) async {
      if (!mounted) {
        return;
      }
      if (link != null) {
        logger('got link: $link');
        if (parseScannedUri(link) != null) {
vjrj's avatar
vjrj committed
          await onKeyScanned(context, link);
          if (!mounted) {
            return;
vjrj's avatar
vjrj committed
          } else {
            context.read<BottomNavCubit>().updateIndex(0);
vjrj's avatar
vjrj committed
          }
vjrj's avatar
vjrj committed
        }
      }
    }, onError: (Object err) {
      if (!mounted) {
        return;
      }
      logger('got err: $err');
    });
  }

  void _disposeDeepLinkListener() {
    if (_sub != null) {
      _sub!.cancel();
      _sub = null;
    }
  }

anfeichtinger's avatar
anfeichtinger committed
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<NodeListCubit, NodeListState>(
        builder: (BuildContext nodeContext, NodeListState state) {
      return ConnectivityAppWrapper(
          app: FilesystemPickerDefaultOptions(
              fileTileSelectMode: FileTileSelectMode.wholeTile,
              theme: FilesystemPickerTheme(
                topBar: FilesystemPickerTopBarThemeData(
                  backgroundColor: Theme.of(context).colorScheme.primary,
                ),
              ),
              child: MaterialApp(
                /// Localization is not available for the title.
                title: 'Ğ1nkgo',
vjrj's avatar
vjrj committed
                theme: widget.lightTheme,
                darkTheme: widget.darkTheme,
                navigatorKey: GinkgoApp.navigatorKey,
                scaffoldMessengerKey: globalMessengerKey,

                /// Theme stuff
                themeMode: context.watch<ThemeCubit>().state.themeMode,

                /// Localization stuff
vjrj's avatar
vjrj committed
                localizationsDelegates: context.localizationDelegates
                  ..addAll(<LocalizationsDelegate<dynamic>>[
                    MaterialLocalizationsEo.delegate,
                    CupertinoLocalizationsEo.delegate
                  ]),
                supportedLocales: context.supportedLocales,
                locale: context.locale,
                debugShowCheckedModeBanner: false,
                home: context.read<AppCubit>().isIntroViewed
                    ? BetterFeedback(
                        localizationsDelegates: context.localizationDelegates
                          ..add(CustomFeedbackLocalizationsDelegate()),
                        child: const SkeletonScreen())
                    : const AppIntro(),
                builder: (BuildContext buildContext, Widget? widget) {
                  NotificationController.locale = context.locale;
                  return ResponsiveWrapper.builder(
                    BouncingScrollWrapper.builder(
                        context,
                        ConnectivityWidgetWrapperWrapper(
                          //message: tr('offline'),
                          //height: 18,

                          offlineWidget: /* Container(
vjrj's avatar
vjrj committed
                                color: Colors.transparent,
                                child: Center(
                                  child: */
                              Column(
                            mainAxisSize: MainAxisSize.min,
                            children: <Widget>[
                              const Icon(
                                Icons.cloud_off,
                                size: 48,
                                color: Colors.grey,
                              ),
                              const SizedBox(height: 6),
                              Container(
                                  padding: const EdgeInsets.all(5.0),
                                  decoration: const BoxDecoration(
vjrj's avatar
vjrj committed
                                    color: Colors.grey,
                                    borderRadius:
vjrj's avatar
vjrj committed
                                        BorderRadius.all(Radius.circular(10.0)),
                                  ),
                                  child: Text(
                                    tr('offline'),
                                    style: const TextStyle(
                                      color: Colors.white,
                                      decoration: TextDecoration.none,
                                      fontSize: 14,
                                    ),
                                  )),
                              const SizedBox(height: 110),
                            ],
                          ),

                          child: widget!,
                        )),
                    maxWidth: 480,
                    minWidth: 480,
                    // defaultScale: true,
                    breakpoints: <ResponsiveBreakpoint>[
                      const ResponsiveBreakpoint.resize(200, name: MOBILE),
                      const ResponsiveBreakpoint.resize(480, name: TABLET),
                      const ResponsiveBreakpoint.resize(1000, name: DESKTOP),
                    ],
                    background: Container(color: const Color(0xFFF5F5F5)),
                  );
                },
              )));
    });
anfeichtinger's avatar
anfeichtinger committed
  }
}