Skip to content
Snippets Groups Projects
main.dart 9.55 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: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: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';
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/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 {
  /// Initialize packages
  WidgetsFlutterBinding.ensureInitialized();
  await EasyLocalization.ensureInitialized();
vjrj's avatar
vjrj committed
  Bloc.observer = AppBlocObserver();
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();
  await shared.getWallet();
  assert(shared.getPubKey() != null);

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

  if (kIsWeb) {
    await Hive.initFlutter();
    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

  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
          ],
          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())
            // Add other BlocProviders here if needed
          ], child: const GinkgoApp()),
        ),
      );

vjrj's avatar
vjrj committed
  final String version = getAppVersion();
  if (kReleaseMode) {
    // Only use sentry in production
    await SentryFlutter.init((
      SentryFlutterOptions options,
    ) {
vjrj's avatar
vjrj committed
      options.release = version;
      options.environment = 'production';
      options.beforeSend = (SentryEvent event, {dynamic hint}) {
        return event;
      };

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();
    NodeManager().loadFromCubit(context.read<NodeListCubit>());
  }

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));
      return ConnectivityAppWrapper(
          app: MaterialApp(
        /// Localization is not available for the title.
        title: 'Ğ1nkgo',
        theme: ThemeData(useMaterial3: true, colorScheme: lightColorScheme),
        darkTheme: ThemeData(useMaterial3: true, colorScheme: darkColorScheme),
vjrj's avatar
vjrj committed

        /// Theme stuff
vjrj's avatar
vjrj committed

        /// 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>[
vjrj's avatar
vjrj committed
              const ResponsiveBreakpoint.resize(200, name: MOBILE),
              const ResponsiveBreakpoint.resize(480, name: TABLET),
vjrj's avatar
vjrj committed
              const ResponsiveBreakpoint.resize(1000, name: DESKTOP),
            ],
            background: Container(color: const Color(0xFFF5F5F5)),
          );
        },
      ));
    });
anfeichtinger's avatar
anfeichtinger committed
  }
}