Skip to content
Snippets Groups Projects
main.dart 11.7 KiB
Newer Older
tylersavery's avatar
tylersavery committed
import 'dart:io';
vjrj's avatar
vjrj committed
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: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';
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:flutter_dotenv/flutter_dotenv.dart';
import 'package:hive_flutter/hive_flutter.dart';
anfeichtinger's avatar
anfeichtinger committed
import 'package:hydrated_bloc/hydrated_bloc.dart';
vjrj's avatar
vjrj committed
import 'package:introduction_screen/introduction_screen.dart';
import 'package:once/once.dart';
anfeichtinger's avatar
anfeichtinger committed
import 'package:path_provider/path_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';
anfeichtinger's avatar
anfeichtinger committed

vjrj's avatar
vjrj committed
import 'app_bloc_observer.dart';
vjrj's avatar
vjrj committed
import 'cubit/bottom_nav_cubit.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/contact_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/transaction_cubit.dart';
vjrj's avatar
vjrj committed
import 'g1/api.dart';
vjrj's avatar
vjrj committed
import 'shared_prefs.dart';
vjrj's avatar
vjrj committed
import 'ui/contacts_cache.dart';
vjrj's avatar
vjrj committed
import 'ui/logger.dart';
import 'ui/screens/skeleton_screen.dart';
vjrj's avatar
vjrj committed
import 'ui/ui_helpers.dart';
vjrj's avatar
vjrj committed

anfeichtinger's avatar
anfeichtinger committed
void main() async {
vjrj's avatar
vjrj committed
  Bloc.observer = AppBlocObserver();

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
  // .env
vjrj's avatar
vjrj committed
  await dotenv.load(
      fileName: kReleaseMode
          ? 'assets/env.production.txt'
          : 'assets/.env.development');
vjrj's avatar
vjrj committed

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

vjrj's avatar
vjrj committed
  await Hive.initFlutter();

vjrj's avatar
vjrj committed
  if (kIsWeb) {
    HydratedBloc.storage = await HydratedStorage.build(
        storageDirectory: HydratedStorage.webStorageDirectory);
  } else {
    final Directory tmpDir = await getTemporaryDirectory();
    Hive.init(tmpDir.toString());
vjrj's avatar
vjrj committed
    HydratedBloc.storage =
        await HydratedStorage.build(storageDirectory: tmpDir);
tylersavery's avatar
tylersavery committed
  }
vjrj's avatar
vjrj committed

vjrj's avatar
vjrj committed
  // Reset hive during developing
  if (!kReleaseMode) {
    // Once.clearAll();
vjrj's avatar
vjrj committed
    // await HydratedBloc.storage.clear();
vjrj's avatar
vjrj committed
  }
vjrj's avatar
vjrj committed

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

  void appRunner() => runApp(
vjrj's avatar
vjrj committed
        EasyLocalization(
          path: 'assets/translations',
          supportedLocales: const <Locale>[
            Locale('en'),
            Locale('es'),
            Locale('fr'),
vjrj's avatar
vjrj committed
            Locale('ca'),
vjrj's avatar
vjrj committed
            Locale('de'),
            Locale('nl'),
vjrj's avatar
vjrj committed
          ],
          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<TransactionsCubit>(
                create: (BuildContext context) => TransactionsCubit()),
vjrj's avatar
vjrj committed
            // Add other BlocProviders here if needed
          ], child: const GinkgoApp()),
        ),
      );

  if (kReleaseMode) {
    // 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 = "${dotenv.env['SENTRY_DSN']}";
      // 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
}

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)),
          ),
        ),
      );
    });
PageViewModel createPageViewModel(
vjrj's avatar
vjrj committed
    String title, String body, String imageAsset, BuildContext context) {
  final ColorScheme colorScheme = Theme.of(context).colorScheme;
  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 {
  const GinkgoApp({super.key});
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> {
vjrj's avatar
vjrj committed
  Future<void> _loadNodes() async {
    _printNodeStatus();
vjrj's avatar
vjrj committed
    await fetchDuniterNodes(NodeType.duniter);
vjrj's avatar
vjrj committed
    await fetchCesiumPlusNodes();
vjrj's avatar
vjrj committed
    await fetchDuniterNodes(NodeType.gva);
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;
vjrj's avatar
vjrj committed
    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)}');
    }
vjrj's avatar
vjrj committed
  @override
  void initState() {
    super.initState();
    ContactsCache().init();
vjrj's avatar
vjrj committed
    NodeManager().loadFromCubit(context.read<NodeListCubit>());
  }

  @override
  void dispose() {
    ContactsCache().dispose();
    super.dispose();
  }

anfeichtinger's avatar
anfeichtinger committed
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<NodeListCubit, NodeListState>(
        builder: (BuildContext nodeContext, NodeListState state) {
      Once.runHourly('load_nodes',
vjrj's avatar
vjrj committed
          callback: () => _loadNodes(),
          fallback: () {
vjrj's avatar
vjrj committed
            _printNodeStatus(prefix: 'After once hourly having');
          });
      Once.runCustom('clear_errors', callback: () {
        NodeManager().cleanErrorStats();
      }, duration: const Duration(minutes: 90));
      Once.runCustom('fetch_transactions', callback: () {
        fetchTransactions(context);
      }, duration: const Duration(minutes: 10));
      fetchTransactions(context);
      return ConnectivityAppWrapper(
vjrj's avatar
vjrj committed
          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',
                theme: ThemeData(
                    useMaterial3: true, colorScheme: lightColorScheme),
                darkTheme:
                    ThemeData(useMaterial3: true, colorScheme: darkColorScheme),

                /// Theme stuff

                /// Localization stuff
                localizationsDelegates: context.localizationDelegates,
                supportedLocales: context.supportedLocales,
                locale: context.locale,
                debugShowCheckedModeBanner: false,
                home: context.read<AppCubit>().isIntroViewed
                    ? const SkeletonScreen()
                    : const AppIntro(),
                builder: (BuildContext buildContext, Widget? widget) {
                  return ResponsiveWrapper.builder(
                    BouncingScrollWrapper.builder(
                        context,
                        ConnectivityWidgetWrapper(
                          message: tr('offline'),
                          height: 20,
                          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
  }
}