refactor: remove comunes-flutter dependency and inline components
Remove the external comunes-flutter library dependency and replace all its components with local implementations: - Utility functions: Created lib/utils/widget_utils.dart with compactWidgets() and ellipse() functions, replacing the comunes-flutter helpers with modern Dart null-safety patterns (whereType instead of where(notNull)) - Secret loader: Inlined as lib/utils/secret_loader.dart for loading JSON config - Layout widgets: Replaced CenteredRow/CenteredColumn with standard Row/Column with MainAxisAlignment.center - Intro screens: Copied AppIntroPage and MaterialAppWithIntro locally in lib/widgets/ with cleaned-up deprecated code patterns - Button widget: Created lib/widgets/rounded_btn.dart for the RoundedBtn used in activeFires.dart Changes span 20 files: - 5 new utility/widget files created - Updated imports in 15 files - Removed path dependency from pubspec.yaml - All functionality preserved, build verified with flutter pub get Impact: - Removes external dependency, simplifying project structure - Modern Dart patterns (proper null-safety) - Better code clarity with explicit widget properties - Easier onboarding (no 'what is comunes-flutter?' questions)
This commit is contained in:
parent
46305ee587
commit
2bf42ce262
31 changed files with 484 additions and 124 deletions
149
lib/widgets/app_intro_page.dart
Normal file
149
lib/widgets/app_intro_page.dart
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import '../utils/widget_utils.dart';
|
||||
|
||||
class AppIntroItem {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String subTitle;
|
||||
|
||||
AppIntroItem({required this.icon, required this.title, this.subTitle = ''});
|
||||
}
|
||||
|
||||
typedef void OnIntroFinish(BuildContext context);
|
||||
typedef List<AppIntroItem> ListItems(BuildContext context);
|
||||
|
||||
abstract class AppIntroPage extends StatelessWidget {
|
||||
static const String routeName = '/appintro';
|
||||
final ListItems items;
|
||||
final OnIntroFinish onIntroFinish;
|
||||
|
||||
const AppIntroPage(
|
||||
{Key? key, required this.items, required this.onIntroFinish})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: DefaultTabController(
|
||||
length: items(context).length,
|
||||
child: _AppIntroPageSelector(items: items, onFinish: onIntroFinish),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AppIntroPageSelector extends StatelessWidget {
|
||||
const _AppIntroPageSelector({required this.items, required this.onFinish});
|
||||
|
||||
final ListItems items;
|
||||
final OnIntroFinish onFinish;
|
||||
|
||||
void _handleArrowButtonPress(BuildContext context, int delta) {
|
||||
final TabController controller = DefaultTabController.of(context)!;
|
||||
if (!controller.indexIsChanging)
|
||||
controller.animateTo(
|
||||
(controller.index + delta).clamp(0, items(context).length - 1));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final TabController controller = DefaultTabController.of(context)!;
|
||||
final ThemeData theme = Theme.of(context);
|
||||
final Color color = theme.colorScheme.secondary;
|
||||
final TextStyle titleStyle =
|
||||
(theme.textTheme.headlineSmall ?? const TextStyle())
|
||||
.copyWith(color: color);
|
||||
final TextStyle subTitleStyle =
|
||||
theme.textTheme.titleMedium ?? const TextStyle();
|
||||
|
||||
return SafeArea(
|
||||
top: true,
|
||||
bottom: false,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Row(mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
onFinish(context);
|
||||
},
|
||||
icon:
|
||||
const Icon(Icons.close, size: 30.0, color: Colors.black38)),
|
||||
]),
|
||||
Expanded(child: OrientationBuilder(builder: (context, orientation) {
|
||||
return IconTheme(
|
||||
data: IconThemeData(
|
||||
size: orientation == Orientation.portrait ? 200.0 : 100.0,
|
||||
color: color,
|
||||
),
|
||||
child: TabBarView(
|
||||
children: items(context).map((AppIntroItem item) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
item.icon,
|
||||
semanticLabel: item.title,
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(left: 16.0, right: 16.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
item.title,
|
||||
style: titleStyle,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
Text(
|
||||
item.subTitle,
|
||||
style: subTitleStyle,
|
||||
textAlign: TextAlign.center,
|
||||
)
|
||||
],
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList()),
|
||||
);
|
||||
})),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 16.0),
|
||||
child: Row(
|
||||
children: compactWidgets(<Widget?>[
|
||||
IconButton(
|
||||
icon: const Icon(Icons.chevron_left),
|
||||
color: color,
|
||||
onPressed: () {
|
||||
_handleArrowButtonPress(context, -1);
|
||||
},
|
||||
tooltip: 'Page back'),
|
||||
TabPageSelector(controller: controller),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.chevron_right),
|
||||
color: color,
|
||||
onPressed: () {
|
||||
_handleArrowButtonPress(context, 1);
|
||||
if (!controller.indexIsChanging &&
|
||||
controller.index == items(context).length - 1) {
|
||||
onFinish(context);
|
||||
}
|
||||
},
|
||||
tooltip: 'Page forward'),
|
||||
]),
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
84
lib/widgets/material_app_with_intro.dart
Normal file
84
lib/widgets/material_app_with_intro.dart
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'dart:async';
|
||||
|
||||
abstract class MaterialAppWithIntro extends StatelessWidget {
|
||||
final String name;
|
||||
final ThemeData theme;
|
||||
final Map<String, WidgetBuilder> routes;
|
||||
final WidgetBuilder introWidget;
|
||||
final WidgetBuilder continueWidget;
|
||||
final String prefsKey;
|
||||
|
||||
MaterialAppWithIntro(this.name, this.theme, this.routes, this.introWidget,
|
||||
this.continueWidget, this.prefsKey);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: MaterialAppWithIntroHome(introWidget, continueWidget, prefsKey),
|
||||
title: name,
|
||||
theme: theme,
|
||||
routes: routes,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MaterialAppWithIntroHome extends StatefulWidget {
|
||||
final WidgetBuilder introWidget;
|
||||
final WidgetBuilder continueWidget;
|
||||
final String prefsKey;
|
||||
|
||||
const MaterialAppWithIntroHome(
|
||||
this.introWidget, this.continueWidget, this.prefsKey);
|
||||
|
||||
@override
|
||||
_MaterialAppWithIntroState createState() =>
|
||||
_MaterialAppWithIntroState(introWidget, continueWidget, prefsKey);
|
||||
}
|
||||
|
||||
class _MaterialAppWithIntroState extends State<MaterialAppWithIntroHome> {
|
||||
final WidgetBuilder introWidget;
|
||||
final WidgetBuilder continueWidget;
|
||||
final String prefsKey;
|
||||
|
||||
_MaterialAppWithIntroState(
|
||||
this.introWidget, this.continueWidget, this.prefsKey);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
Timer(const Duration(milliseconds: 1000), () {
|
||||
checkFirstStart();
|
||||
});
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/50654195/flutter-one-time-intro-screen
|
||||
Future<void> checkFirstStart() async {
|
||||
final String initialWizardKey = prefsKey;
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
final bool showInitialWizard = (prefs.getBool(initialWizardKey) ?? true);
|
||||
|
||||
if (showInitialWizard) {
|
||||
await prefs.setBool(initialWizardKey, false);
|
||||
if (mounted) {
|
||||
await Navigator.of(context)
|
||||
.pushReplacement(MaterialPageRoute(builder: introWidget));
|
||||
}
|
||||
} else {
|
||||
if (mounted) {
|
||||
await Navigator.of(context)
|
||||
.pushReplacement(MaterialPageRoute(builder: continueWidget));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
70
lib/widgets/rounded_btn.dart
Normal file
70
lib/widgets/rounded_btn.dart
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Rounded button widget with icon and text.
|
||||
/// Used primarily in the active fires page.
|
||||
class RoundedBtn extends StatelessWidget {
|
||||
static const btnRadius = Radius.circular(90.0);
|
||||
|
||||
final IconData icon;
|
||||
final String text;
|
||||
final Color backColor;
|
||||
final Color fontColor;
|
||||
final TextStyle textStyle;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
const RoundedBtn({
|
||||
required this.icon,
|
||||
required this.text,
|
||||
required this.onPressed,
|
||||
required this.backColor,
|
||||
this.textStyle = const TextStyle(fontSize: 20.0, color: Colors.white),
|
||||
this.fontColor = Colors.white,
|
||||
});
|
||||
|
||||
factory RoundedBtn.nav({
|
||||
required IconData icon,
|
||||
required String text,
|
||||
required BuildContext context,
|
||||
required String route,
|
||||
required Color backColor,
|
||||
TextStyle textStyle = const TextStyle(fontSize: 20.0, color: Colors.white),
|
||||
Color fontColor = Colors.white,
|
||||
}) {
|
||||
return RoundedBtn(
|
||||
icon: icon,
|
||||
text: text,
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(context, route);
|
||||
},
|
||||
backColor: backColor,
|
||||
textStyle: textStyle,
|
||||
fontColor: fontColor,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
child: ElevatedButton(
|
||||
onPressed: onPressed,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: backColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(btnRadius),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Icon(icon, size: 32.0, color: fontColor),
|
||||
const SizedBox(width: 10.0),
|
||||
Text(text, style: textStyle),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue