diff --git a/lib/api/requestCache.dart b/lib/api/requestCache.dart index 9bd9b5e..0420415 100644 --- a/lib/api/requestCache.dart +++ b/lib/api/requestCache.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'package:localstore/localstore.dart'; import 'apiResponse.dart'; -import 'webuntis/webuntisError.dart'; abstract class RequestCache { static const int cacheNothing = 0; @@ -40,7 +39,7 @@ abstract class RequestCache { 'json': jsonEncode(newValue), 'lastupdate': DateTime.now().millisecondsSinceEpoch }); - } on WebuntisError catch(e) { + } on Exception catch(e) { onError(e); } } diff --git a/lib/app.dart b/lib/app.dart index 72de706..0b4a03b 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -8,7 +8,6 @@ import 'package:flutter/material.dart'; import 'state/app/modules/app_modules.dart'; import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart'; import 'package:provider/provider.dart'; -import 'package:badges/badges.dart' as badges; import 'api/mhsl/breaker/getBreakers/getBreakersResponse.dart'; import 'api/mhsl/server/userIndex/update/updateUserindex.dart'; @@ -93,7 +92,7 @@ class _AppState extends State with WidgetsBindingObserver { } @override - Widget build(BuildContext context) => PersistentTabView( + Widget build(BuildContext context) => Consumer(builder: (context, settings, child) => PersistentTabView( controller: Main.bottomNavigator, navBarOverlap: const NavBarOverlap.none(), backgroundColor: Theme.of(context).colorScheme.primary, @@ -101,29 +100,7 @@ class _AppState extends State with WidgetsBindingObserver { screenTransitionAnimation: const ScreenTransitionAnimation(curve: Curves.easeOutQuad, duration: Duration(milliseconds: 200)), tabs: [ - AppModule.getModule(Modules.timetable).toBottomTab(context), - AppModule.getModule(Modules.talk).toBottomTab( - context, - itemBuilder: (icon) => Consumer( - builder: (context, value, child) { - if(value.primaryLoading()) return Icon(icon); - var messages = value.getRoomsResponse.data.map((e) => e.unreadMessages).reduce((a, b) => a+b); - return badges.Badge( - showBadge: messages > 0, - position: badges.BadgePosition.topEnd(top: -3, end: -3), - stackFit: StackFit.loose, - badgeStyle: badges.BadgeStyle( - padding: const EdgeInsets.all(3), - badgeColor: Theme.of(context).primaryColor, - elevation: 1, - ), - badgeContent: Text('$messages', style: const TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold)), - child: Icon(icon), - ); - }, - ), - ), - AppModule.getModule(Modules.files).toBottomTab(context), + ...AppModule.getBottomBarModules(context).map((e) => e.toBottomTab(context)), PersistentTabConfig( screen: const Breaker(breaker: BreakerArea.more, child: Overhang()), @@ -142,7 +119,7 @@ class _AppState extends State with WidgetsBindingObserver { color: Theme.of(context).colorScheme.surface, ), ), - ); + )); @override void dispose() { diff --git a/lib/model/breakers/Breaker.dart b/lib/model/breakers/Breaker.dart index 1545296..d9ad93e 100644 --- a/lib/model/breakers/Breaker.dart +++ b/lib/model/breakers/Breaker.dart @@ -23,7 +23,11 @@ class _BreakerState extends State { builder: (context, value, child) { var blocked = value.isBlocked(widget.breaker); if(blocked != null) { - return PlaceholderView(icon: Icons.security_outlined, text: "Die App/ Dieser Bereich wurde als Schutzmaßnahme deaktiviert!\n\n${blocked.isEmpty ? "Es wurde vom Server kein Grund übermittelt." : blocked}"); + return PlaceholderView( + icon: Icons.app_blocking_outlined, + text: 'Die App / Dieser Bereich ist zurzeit nicht verfügbar!\n\n' + "${blocked.isEmpty ? "Es wurde vom Server kein Grund übermittelt.\nAktualisiere die App und versuche es später erneut" : blocked}" + ); } return widget.child; diff --git a/lib/notification/notificationController.dart b/lib/notification/notificationController.dart index af35c22..babe229 100644 --- a/lib/notification/notificationController.dart +++ b/lib/notification/notificationController.dart @@ -44,7 +44,7 @@ class NotificationController { } static Future onAppOpenedByNotification(RemoteMessage message, BuildContext context) async { - NotificationTasks.navigateToTalk(); + NotificationTasks.navigateToTalk(context); NotificationTasks.updateProviders(context); DebugTile(context).run(() { diff --git a/lib/notification/notificationTasks.dart b/lib/notification/notificationTasks.dart index 9ab82ec..7fb59df 100644 --- a/lib/notification/notificationTasks.dart +++ b/lib/notification/notificationTasks.dart @@ -6,6 +6,7 @@ import 'package:provider/provider.dart'; import '../main.dart'; import '../model/chatList/chatListProps.dart'; import '../model/chatList/chatProps.dart'; +import '../state/app/modules/app_modules.dart'; class NotificationTasks { static void updateBadgeCount(RemoteMessage notification) { @@ -17,7 +18,9 @@ class NotificationTasks { Provider.of(context, listen: false).run(); } - static void navigateToTalk() { - Main.bottomNavigator.jumpToTab(1); + static void navigateToTalk(BuildContext context) { + var talkTab = AppModule.getBottomBarModules(context).map((e) => e.module).toList().indexOf(Modules.talk); + if(talkTab == -1) return; + Main.bottomNavigator.jumpToTab(talkTab); } } diff --git a/lib/state/app/modules/app_modules.dart b/lib/state/app/modules/app_modules.dart index 796cfe6..2af4ebc 100644 --- a/lib/state/app/modules/app_modules.dart +++ b/lib/state/app/modules/app_modules.dart @@ -1,8 +1,11 @@ import 'package:flutter/material.dart'; import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart'; +import 'package:provider/provider.dart'; import '../../../api/mhsl/breaker/getBreakers/getBreakersResponse.dart'; import '../../../model/breakers/Breaker.dart'; +import '../../../model/chatList/chatListProps.dart'; +import '../../../storage/base/settingsProvider.dart'; import '../../../view/pages/files/files.dart'; import '../../../view/pages/more/roomplan/roomplan.dart'; import '../../../view/pages/talk/chatList.dart'; @@ -12,38 +15,115 @@ import 'gradeAverages/view/grade_averages_view.dart'; import 'holidays/view/holidays_view.dart'; import 'marianumMessage/view/marianum_message_list_view.dart'; +import 'package:badges/badges.dart' as badges; + class AppModule { + Modules module; String name; - IconData icon; + Widget Function() icon; + BreakerArea breakerArea; Widget Function() create; - AppModule(this.name, this.icon, this.create); + AppModule(this.module, {required this.name, required this.icon, this.breakerArea = BreakerArea.global, required this.create}); - static Map modules() => { - Modules.timetable: AppModule('Vertretung', Icons.calendar_month, Timetable.new), - Modules.talk: AppModule('Talk', Icons.chat, ChatList.new), - Modules.files: AppModule('Files', Icons.folder, Files.new), - Modules.marianumMessage: AppModule('Marianum Message', Icons.newspaper, MarianumMessageListView.new), - Modules.roomPlan: AppModule('Raumplan', Icons.location_pin, Roomplan.new), - Modules.gradeAveragesCalculator: AppModule('Notendurschnittsrechner', Icons.calculate, GradeAveragesView.new), - Modules.holidays: AppModule('Schulferien', Icons.flight, HolidaysView.new), - }; + static Map modules(BuildContext context, { showFiltered = false }) { + var settings = Provider.of(context, listen: false); + var available = { + Modules.timetable: AppModule( + Modules.timetable, + name: 'Vertretung', + icon: () => Icon(Icons.calendar_month), + breakerArea: BreakerArea.timetable, + create: Timetable.new, + ), + Modules.talk: AppModule( + Modules.talk, + name: 'Talk', + icon: () => Consumer( + builder: (context, value, child) { + if(value.primaryLoading()) return Icon(Icons.chat); + var messages = value.getRoomsResponse.data.map((e) => e.unreadMessages).reduce((a, b) => a+b); + return badges.Badge( + showBadge: messages > 0, + position: badges.BadgePosition.topEnd(top: -3, end: -3), + stackFit: StackFit.loose, + badgeStyle: badges.BadgeStyle( + padding: const EdgeInsets.all(3), + badgeColor: Theme.of(context).primaryColor, + elevation: 1, + ), + badgeContent: Text('$messages', style: const TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold)), + child: Icon(Icons.chat), + ); + }, + ), + breakerArea: BreakerArea.talk, + create: ChatList.new, + ), + Modules.files: AppModule( + Modules.files, + name: 'Files', + icon: () => Icon(Icons.folder), + breakerArea: BreakerArea.files, + create: Files.new, + ), + Modules.marianumMessage: AppModule( + Modules.marianumMessage, + name: 'Marianum Message', + icon: () => Icon(Icons.newspaper), + breakerArea: BreakerArea.more, + create: MarianumMessageListView.new, + ), + Modules.roomPlan: AppModule( + Modules.roomPlan, + name: 'Raumplan', + icon: () => Icon(Icons.location_pin), + breakerArea: BreakerArea.more, + create: Roomplan.new, + ), + Modules.gradeAveragesCalculator: AppModule( + Modules.gradeAveragesCalculator, + name: 'Notendurschnittsrechner', + icon: () => Icon(Icons.calculate), + breakerArea: BreakerArea.more, + create: GradeAveragesView.new, + ), + Modules.holidays: AppModule( + Modules.holidays, + name: 'Schulferien', + icon: () => Icon(Icons.flight), + breakerArea: BreakerArea.more, + create: HolidaysView.new, + ), + }; - static AppModule getModule(Modules module) => modules()[module]!; + if(!showFiltered) available.removeWhere((key, value) => settings.val().modulesSettings.hiddenModules.contains(key)); - Widget toListTile(BuildContext context) => ListTile( - leading: CenteredLeading(Icon(icon)), + return { for (var element in settings.val().modulesSettings.moduleOrder.where((element) => available.containsKey(element))) element : available[element]! }; + } + + static List getBottomBarModules(BuildContext context) => modules(context).values.toList().getRange(0, 3).toList(); + static List getOverhangModules(BuildContext context) => modules(context).values.skip(3).toList(); + + Widget toListTile(BuildContext context, {Key? key, bool isReorder = false, Function()? onVisibleChange, bool isVisible = true}) => ListTile( + key: key, + leading: CenteredLeading(icon()), title: Text(name), - onTap: () => pushScreen(context, withNavBar: false, screen: create()), - trailing: const Icon(Icons.arrow_right), + onTap: isReorder ? null : () => pushScreen(context, withNavBar: false, screen: create()), + trailing: isReorder + ? Row(mainAxisSize: MainAxisSize.min, children: [ + IconButton(onPressed: onVisibleChange, icon: Icon(isVisible ? Icons.visibility_outlined : Icons.visibility_off_outlined)), + Icon(Icons.drag_handle_outlined) + ]) + : const Icon(Icons.arrow_right), ); - PersistentTabConfig toBottomTab(BuildContext context, {Widget Function(IconData icon)? itemBuilder}) => PersistentTabConfig( - screen: Breaker(breaker: BreakerArea.global, child: create()), + PersistentTabConfig toBottomTab(BuildContext context, {Widget Function(IconData icon)? iconBuilder}) => PersistentTabConfig( + screen: Breaker(breaker: breakerArea, child: create()), item: ItemConfig( activeForegroundColor: Theme.of(context).primaryColor, inactiveForegroundColor: Theme.of(context).colorScheme.secondary, - icon: itemBuilder == null ? Icon(icon) : itemBuilder(icon), + icon: icon(), title: name ), ); diff --git a/lib/storage/base/settings.dart b/lib/storage/base/settings.dart index c0448d7..2914e1a 100644 --- a/lib/storage/base/settings.dart +++ b/lib/storage/base/settings.dart @@ -4,6 +4,7 @@ import 'package:json_annotation/json_annotation.dart'; import '../devTools/devToolsSettings.dart'; import '../file/fileSettings.dart'; import '../fileView/fileViewSettings.dart'; +import '../general/modulesSettings.dart'; import '../holidays/holidaysSettings.dart'; import '../notification/notificationSettings.dart'; import '../talk/talkSettings.dart'; @@ -20,6 +21,7 @@ class Settings { ThemeMode appTheme; bool devToolsEnabled; + ModulesSettings modulesSettings; TimetableSettings timetableSettings; TalkSettings talkSettings; FileSettings fileSettings; @@ -31,6 +33,7 @@ class Settings { Settings({ required this.appTheme, required this.devToolsEnabled, + required this.modulesSettings, required this.timetableSettings, required this.talkSettings, required this.fileSettings, diff --git a/lib/storage/base/settings.g.dart b/lib/storage/base/settings.g.dart index b8390f5..c4569e1 100644 --- a/lib/storage/base/settings.g.dart +++ b/lib/storage/base/settings.g.dart @@ -9,6 +9,8 @@ part of 'settings.dart'; Settings _$SettingsFromJson(Map json) => Settings( appTheme: Settings._themeFromJson(json['appTheme'] as String), devToolsEnabled: json['devToolsEnabled'] as bool, + modulesSettings: ModulesSettings.fromJson( + json['modulesSettings'] as Map), timetableSettings: TimetableSettings.fromJson( json['timetableSettings'] as Map), talkSettings: @@ -28,6 +30,7 @@ Settings _$SettingsFromJson(Map json) => Settings( Map _$SettingsToJson(Settings instance) => { 'appTheme': Settings._themeToJson(instance.appTheme), 'devToolsEnabled': instance.devToolsEnabled, + 'modulesSettings': instance.modulesSettings.toJson(), 'timetableSettings': instance.timetableSettings.toJson(), 'talkSettings': instance.talkSettings.toJson(), 'fileSettings': instance.fileSettings.toJson(), diff --git a/lib/storage/general/modulesSettings.dart b/lib/storage/general/modulesSettings.dart new file mode 100644 index 0000000..387982f --- /dev/null +++ b/lib/storage/general/modulesSettings.dart @@ -0,0 +1,19 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../../state/app/modules/app_modules.dart'; + +part 'modulesSettings.g.dart'; + +@JsonSerializable() +class ModulesSettings { + List moduleOrder; + List hiddenModules; + + ModulesSettings({ + required this.moduleOrder, + required this.hiddenModules + }); + + factory ModulesSettings.fromJson(Map json) => _$ModulesSettingsFromJson(json); + Map toJson() => _$ModulesSettingsToJson(this); +} diff --git a/lib/storage/general/modulesSettings.g.dart b/lib/storage/general/modulesSettings.g.dart new file mode 100644 index 0000000..0f9f9b6 --- /dev/null +++ b/lib/storage/general/modulesSettings.g.dart @@ -0,0 +1,35 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'modulesSettings.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +ModulesSettings _$ModulesSettingsFromJson(Map json) => + ModulesSettings( + moduleOrder: (json['moduleOrder'] as List) + .map((e) => $enumDecode(_$ModulesEnumMap, e)) + .toList(), + hiddenModules: (json['hiddenModules'] as List) + .map((e) => $enumDecode(_$ModulesEnumMap, e)) + .toList(), + ); + +Map _$ModulesSettingsToJson(ModulesSettings instance) => + { + 'moduleOrder': + instance.moduleOrder.map((e) => _$ModulesEnumMap[e]!).toList(), + 'hiddenModules': + instance.hiddenModules.map((e) => _$ModulesEnumMap[e]!).toList(), + }; + +const _$ModulesEnumMap = { + Modules.timetable: 'timetable', + Modules.talk: 'talk', + Modules.files: 'files', + Modules.marianumMessage: 'marianumMessage', + Modules.roomPlan: 'roomPlan', + Modules.gradeAveragesCalculator: 'gradeAveragesCalculator', + Modules.holidays: 'holidays', +}; diff --git a/lib/view/pages/overhang.dart b/lib/view/pages/overhang.dart index 6269307..7b3db61 100644 --- a/lib/view/pages/overhang.dart +++ b/lib/view/pages/overhang.dart @@ -3,76 +3,124 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:in_app_review/in_app_review.dart'; +import 'package:provider/provider.dart'; import '../../extensions/renderNotNull.dart'; import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart'; import '../../state/app/modules/app_modules.dart'; +import '../../storage/base/settingsProvider.dart'; import '../../widget/centeredLeading.dart'; import '../../widget/infoDialog.dart'; +import '../settings/defaultSettings.dart'; import '../settings/settings.dart'; import 'more/feedback/feedbackDialog.dart'; import 'more/share/selectShareTypeDialog.dart'; -class Overhang extends StatelessWidget { +class Overhang extends StatefulWidget { const Overhang({super.key}); @override - Widget build(BuildContext context) => Scaffold( + State createState() => _OverhangState(); +} + +class _OverhangState extends State { + bool editMode = false; + + @override + Widget build(BuildContext context) => Consumer(builder: (context, settings, child) => Scaffold( appBar: AppBar( title: const Text('Mehr'), actions: [ - IconButton(onPressed: () => pushScreen(context, screen: const Settings(), withNavBar: false), icon: const Icon(Icons.settings)) + if(editMode) IconButton( + onPressed: settings.val().modulesSettings.toJson().toString() != DefaultSettings.get().modulesSettings.toJson().toString() + ? () => settings.val(write: true).modulesSettings = DefaultSettings.get().modulesSettings + : null, + icon: Icon(Icons.undo_outlined) + ), + IconButton(onPressed: () => setState(() => editMode = !editMode), icon: Icon(Icons.edit_note_outlined), color: editMode ? Theme.of(context).primaryColor : null), + IconButton(onPressed: editMode ? null : () => pushScreen(context, screen: const Settings(), withNavBar: false), icon: const Icon(Icons.settings)), ], ), - body: ListView( - children: [ - AppModule.getModule(Modules.marianumMessage).toListTile(context), - AppModule.getModule(Modules.roomPlan).toListTile(context), - AppModule.getModule(Modules.gradeAveragesCalculator).toListTile(context), - AppModule.getModule(Modules.holidays).toListTile(context), + body: editMode ? _sorting() : _overhang(), + )); - const Divider(), + Widget _sorting() => Consumer(builder: (context, settings, child) { + void changeVisibility(Modules module) { + var hidden = settings.val(write: true).modulesSettings.hiddenModules; + hidden.contains(module) ? hidden.remove(module) : (hidden.length < 3 ? hidden.add(module) : null); + } - ListTile( + return ReorderableListView( + header: const Center( + heightFactor: 2, + child: Text('Halte und ziehe einen Eintrag, um ihn zu verschieben.\nEs können 3 Bereiche ausgeblendet werden.', textAlign: TextAlign.center) + ), + children: AppModule.modules(context, showFiltered: true) + .map((key, value) => MapEntry(key, value.toListTile( + context, + key: Key(key.name), + isReorder: true, + onVisibleChange: () => changeVisibility(key), + isVisible: !settings.val().modulesSettings.hiddenModules.contains(key) + ))) + .values + .toList(), + onReorder: (oldIndex, newIndex) { + if (newIndex > oldIndex) newIndex -= 1; + + var order = settings.val().modulesSettings.moduleOrder.toList(); + final movedModule = order.removeAt(oldIndex); + order.insert(newIndex, movedModule); + settings.val(write: true).modulesSettings.moduleOrder = order; + } + ); + }); + + Widget _overhang() => ListView( + children: [ + ...AppModule.getOverhangModules(context).map((e) => e.toListTile(context)), + + const Divider(), + + ListTile( leading: const Icon(Icons.share_outlined), title: const Text('Teile die App'), subtitle: const Text('Mit Freunden und deiner Klasse teilen'), trailing: const Icon(Icons.arrow_right), onTap: () => showDialog(context: context, builder: (context) => const SelectShareTypeDialog()) - ), - FutureBuilder( - future: InAppReview.instance.isAvailable(), - builder: (context, snapshot) { - if(!snapshot.hasData) return const SizedBox.shrink(); + ), + FutureBuilder( + future: InAppReview.instance.isAvailable(), + builder: (context, snapshot) { + if(!snapshot.hasData) return const SizedBox.shrink(); - String? getPlatformStoreName() { - if(Platform.isAndroid) return 'Play store'; - if(Platform.isIOS) return 'App store'; - return null; - } + String? getPlatformStoreName() { + if(Platform.isAndroid) return 'Play store'; + if(Platform.isIOS) return 'App store'; + return null; + } - return ListTile( - leading: const CenteredLeading(Icon(Icons.star_rate_outlined)), - title: const Text('App bewerten'), - subtitle: getPlatformStoreName().wrapNullable((data) => Text('Im $data')), - trailing: const Icon(Icons.arrow_right), - onTap: () { - InAppReview.instance.openStoreListing(appStoreId: '6458789560').then( - (value) => InfoDialog.show(context, 'Vielen Dank!'), - onError: (error) => InfoDialog.show(context, error.toString()) - ); - }, - ); - }, - ), - ListTile( - leading: const CenteredLeading(Icon(Icons.feedback_outlined)), - title: const Text('Du hast eine Idee?'), - subtitle: const Text('Fehler und Verbessungsvorschläge'), - trailing: const Icon(Icons.arrow_right), - onTap: () => pushScreen(context, withNavBar: false, screen: const FeedbackDialog()), - ), - ], - ), + return ListTile( + leading: const CenteredLeading(Icon(Icons.star_rate_outlined)), + title: const Text('App bewerten'), + subtitle: getPlatformStoreName().wrapNullable((data) => Text('Im $data')), + trailing: const Icon(Icons.arrow_right), + onTap: () { + InAppReview.instance.openStoreListing(appStoreId: '6458789560').then( + (value) => InfoDialog.show(context, 'Vielen Dank!'), + onError: (error) => InfoDialog.show(context, error.toString()) + ); + }, + ); + }, + ), + ListTile( + leading: const CenteredLeading(Icon(Icons.feedback_outlined)), + title: const Text('Du hast eine Idee?'), + subtitle: const Text('Fehler und Verbessungsvorschläge'), + trailing: const Icon(Icons.arrow_right), + onTap: () => pushScreen(context, withNavBar: false, screen: const FeedbackDialog()), + ), + ], ); } diff --git a/lib/view/settings/defaultSettings.dart b/lib/view/settings/defaultSettings.dart index d4c43df..36b2289 100644 --- a/lib/view/settings/defaultSettings.dart +++ b/lib/view/settings/defaultSettings.dart @@ -2,10 +2,12 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import '../../state/app/modules/app_modules.dart'; import '../../storage/base/settings.dart'; import '../../storage/devTools/devToolsSettings.dart'; import '../../storage/file/fileSettings.dart'; import '../../storage/fileView/fileViewSettings.dart'; +import '../../storage/general/modulesSettings.dart'; import '../../storage/holidays/holidaysSettings.dart'; import '../../storage/notification/notificationSettings.dart'; import '../../storage/talk/talkSettings.dart'; @@ -17,6 +19,18 @@ class DefaultSettings { static Settings get() => Settings( appTheme: ThemeMode.system, devToolsEnabled: false, + modulesSettings: ModulesSettings( + moduleOrder: [ + Modules.timetable, + Modules.talk, + Modules.files, + Modules.marianumMessage, + Modules.roomPlan, + Modules.gradeAveragesCalculator, + Modules.holidays + ], + hiddenModules: [], + ), timetableSettings: TimetableSettings( connectDoubleLessons: false, timetableNameMode: TimetableNameMode.name diff --git a/lib/widget/placeholderView.dart b/lib/widget/placeholderView.dart index 8896b94..e890332 100644 --- a/lib/widget/placeholderView.dart +++ b/lib/widget/placeholderView.dart @@ -7,29 +7,26 @@ class PlaceholderView extends StatelessWidget { const PlaceholderView({super.key, required this.icon, required this.text, this.button}); @override - Widget build(BuildContext context) => DefaultTextStyle( - style: const TextStyle(), - child: Center( - child: Container( - margin: const EdgeInsets.only(top: 100, left: 20, right: 20), - child: Column( - children: [ - Container( - margin: const EdgeInsets.all(30), - child: Icon(icon, color: Colors.grey, size: 60), - ), - Text(text, - style: const TextStyle( - fontSize: 20, - color: Colors.grey, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: 30), - if(button != null) button!, - ], - ), + Widget build(BuildContext context) => Scaffold( + body: Center( + child: Container( + margin: const EdgeInsets.only(top: 100, left: 20, right: 20), + child: Column( + children: [ + Container( + margin: const EdgeInsets.all(30), + child: Icon(icon, size: 60), + ), + Text( + text, + style: const TextStyle(fontSize: 20,), + textAlign: TextAlign.center, + ), + const SizedBox(height: 30), + if(button != null) button!, + ], ), ), - ); + ), + ); }