Newer
Older
import 'package:connectivity_wrapper/connectivity_wrapper.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:introduction_screen/introduction_screen.dart';
import 'package:responsive_framework/responsive_wrapper.dart';
import 'package:responsive_framework/utils/scroll_behavior.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'config/theme.dart';
import 'data/models/app_cubit.dart';
import 'data/models/app_state.dart';
import 'data/models/payment_cubit.dart';
import 'ui/screens/skeleton_screen.dart';
import 'ui/widgets/connectivity_widget_wrapper_wrapper.dart';
// To resolve Let's Encrypt SSL certificate problems with Android 7.1.1 and below
/// Initialize packages
WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized();
fileName: kReleaseMode
? 'assets/env.production.txt'
: 'assets/.env.development');
final SharedPreferencesHelper shared = SharedPreferencesHelper();
await shared.init();
await shared.getWallet();
assert(shared.getPubKey() != null);
// Reset hive old keys
if (kIsWeb) {
final Box<dynamic> box = await Hive.openBox('hydrated_box',
path: HydratedStorage.webStorageDirectory.path);
final List<dynamic> keysToDelete =
box.keys.where((dynamic key) => '$key'.startsWith('minified')).toList();
box.deleteAll(keysToDelete);
// This should we done after init
// await HydratedBloc.storage.clear();
box.close();
}
if (kIsWeb) {
HydratedBloc.storage = await HydratedStorage.build(
storageDirectory: HydratedStorage.webStorageDirectory);
} else {
final Directory tmpDir = await getTemporaryDirectory();
Hive.init(tmpDir.toString());
await HydratedStorage.build(storageDirectory: tmpDir);
PWAInstall().setup(installCallback: () {
logger('APP INSTALLED!');
});
// 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'),
],
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<TransactionCubit>(
create: (BuildContext context) => TransactionCubit()),
BlocProvider<ThemeCubit>(
create: (BuildContext context) => ThemeCubit()),
// Add other BlocProviders here if needed
], child: const GinkgoApp()),
),
);
if (kReleaseMode) {
// Only use sentry in production
await SentryFlutter.init((
SentryFlutterOptions options,
) {
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
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;
//};
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();
}
class AppIntro extends StatefulWidget {
const AppIntro({super.key});
@override
State<AppIntro> createState() => _AppIntro();
}
class _AppIntro extends State<AppIntro> {
final GlobalKey<IntroductionScreenState> introKey =
void _onIntroEnd(BuildContext context, AppCubit cubit) {
cubit.introViewed();
MaterialPageRoute<void>(
builder: (BuildContext _) => const SkeletonScreen()),
);
}
@override
Widget build(BuildContext context) {
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
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)),
),
),
);
});
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,
);
return PageViewModel(
title: tr(title),
body: tr(body),
image: Image.asset(imageAsset),
decoration: PageDecoration(
titleTextStyle: titleStyle,
bodyTextStyle: bodyStyle,
pageColor: colorScheme.background,
class GinkgoApp extends StatefulWidget {
const GinkgoApp({super.key});
// The navigator key is necessary to navigate using static methods
static final GlobalKey<NavigatorState> navigatorKey =
GlobalKey<NavigatorState>();
for (final NodeType nodeType in NodeType.values) {
await fetchNodes(nodeType, false);
}
_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;
'$prefix with $nDuniterNodes duniter nodes, $nCesiumPlusNodes c+ nodes, and $nGvaNodes gva nodes');
if (!kReleaseMode) {
logger('${NodeManager().nodeList(NodeType.cesiumPlus)}');
}
if (!kReleaseMode) {
logger('${NodeManager().nodeList(NodeType.gva)}');
}
@override
void initState() {
super.initState();
NodeManager().loadFromCubit(context.read<NodeListCubit>());
// Only after at least the action method is set, the notification events are delivered
NotificationController.startListeningNotificationEvents();
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();
});
Once.runDaily('clear_cache', callback: () {
logger('clear cache via once');
ContactsCache().clear();
ContactsCache().addContacts(context.read<ContactsCubit>().state.contacts);
Once.runOnce('resize_avatars', callback: () {
logger('resize avatar via once');
context.read<ContactsCubit>().resizeAvatars();
});
@override
void dispose() {
ContactsCache().dispose();
super.dispose();
}
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,
),
),
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
child: MaterialApp(
/// Localization is not available for the title.
title: 'Ğ1nkgo',
theme: ThemeData(
useMaterial3: true, colorScheme: lightColorScheme),
darkTheme:
ThemeData(useMaterial3: true, colorScheme: darkColorScheme),
navigatorKey: GinkgoApp.navigatorKey,
scaffoldMessengerKey: globalMessengerKey,
/// Theme stuff
themeMode: context.watch<ThemeCubit>().state.themeMode,
/// Localization stuff
localizationsDelegates: context.localizationDelegates,
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(
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
//message: tr('offline'),
//height: 18,
offlineWidget: /* Container(
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(
color: Colors.grey,
borderRadius:
BorderRadius.all(Radius.circular(10.0)),
),
child: Text(
tr('offline'),
style: const TextStyle(
color: Colors.white,
decoration: TextDecoration.none,
fontSize: 14,
),
)),
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)),
);
},
)));