Skip to content
Snippets Groups Projects
Commit dfe0f6b7 authored by Benoît Harrault's avatar Benoît Harrault
Browse files

Merge branch '7-add-applicationnavigation-widgets' into 'master'

Resolve "Add ApplicationNavigation widgets"

Closes #7

See merge request !10
parents b8164a50 1f3741b4
No related branches found
No related tags found
1 merge request!10Resolve "Add ApplicationNavigation widgets"
Pipeline #7338 passed
## 0.6.0
- Add application navigation widgets
## 0.5.0
- Add activity parameters widgets
......
......@@ -33,6 +33,21 @@ export 'parameters/settings/settings_activity_cubit.dart'
show ActivitySettingsCubit, ActivitySettingsState;
export 'parameters/models/settings/settings_activity.dart' show ActivitySettings;
export 'nav/application_navigation_definition.dart'
show
ScreenItem,
ActivityPageItem,
ApplicationNavigation,
AppBarConfiguration,
AppBarButton;
export 'nav/cubit/nav_cubit_screens.dart' show NavCubitScreen;
export 'nav/cubit/nav_cubit_pages.dart' show NavCubitPage;
export 'nav/ui/screens/about.dart' show ScreenAbout;
export 'nav/ui/screens/activity.dart' show ScreenActivity;
export 'nav/ui/screens/settings.dart' show ScreenSettings;
export 'nav/ui/bottom_nav_bar.dart' show BottomNavBar;
export 'nav/ui/global_app_bar.dart' show GlobalAppBar;
// dependencies
export 'package:easy_localization/easy_localization.dart'
......
import 'package:flutter/material.dart';
import 'package:flutter_custom_toolbox/flutter_toolbox.dart';
/// A screen item,
///
/// Normalized screens are: "activity", "settings" and "about".
///
/// These are set in [ApplicationNavigation] with
/// - [ApplicationNavigation.screenActivity]
/// - [ApplicationNavigation.screenSettings]
/// - [ApplicationNavigation.screenAbout]
///
class ScreenItem {
final String code;
final Icon icon;
final Widget Function({required ApplicationConfigDefinition appConfig}) screen;
const ScreenItem({
required this.code,
required this.icon,
required this.screen,
});
}
/// A page in [ScreenItem] given in [ApplicationNavigation.screenActivity]
///
/// These are set in [ApplicationNavigation] with
/// [ApplicationNavigation.activityPages].
///
class ActivityPageItem {
final String code;
final Icon? icon;
final Widget Function({required ApplicationConfigDefinition appConfig}) builder;
const ActivityPageItem({
required this.code,
this.icon,
required this.builder,
});
factory ActivityPageItem.empty() {
return ActivityPageItem(
code: '',
builder: ({required ApplicationConfigDefinition appConfig}) => Text(''),
);
}
}
/// Custom AppBar configuration
///
class AppBarConfiguration {
final bool? hideApplicationTitle;
final bool? pushQuitActivityButtonLeft;
final bool? hideQuitActivityButton;
final List<AppBarButton> Function(BuildContext context)? topBarButtonsBuilder;
const AppBarConfiguration({
this.hideApplicationTitle = false,
this.pushQuitActivityButtonLeft = false,
this.hideQuitActivityButton = false,
this.topBarButtonsBuilder,
});
}
/// Custom AppBar button (will generate a widget [IconButton])
///
class AppBarButton {
final Icon icon;
final Function(BuildContext context)? onPressed;
final Function(BuildContext context)? onLongPress;
const AppBarButton({
required this.icon,
this.onPressed,
this.onLongPress,
});
}
/// Navigation configuration for application
///
class ApplicationNavigation {
final ScreenItem screenActivity;
final ScreenItem screenSettings;
final ScreenItem screenAbout;
final AppBarConfiguration? appBarConfiguration;
final Map<int, ActivityPageItem> activityPages;
final bool displayBottomNavBar;
const ApplicationNavigation({
required this.screenActivity,
required this.screenSettings,
required this.screenAbout,
this.appBarConfiguration,
required this.activityPages,
this.displayBottomNavBar = false,
});
static const indexActivity = 0;
static const indexSettings = 1;
static const indexAbout = 2;
ScreenItem getScreen(int screenIndex) {
switch (screenIndex) {
case indexActivity:
return screenActivity;
case indexSettings:
return screenSettings;
case indexAbout:
return screenAbout;
default:
return screenActivity;
}
}
ScreenItem getActivityScreen() {
return screenActivity;
}
Widget getScreenWidget({
required ApplicationConfigDefinition appConfig,
required int screenIndex,
}) {
return getScreen(screenIndex).screen(appConfig: appConfig);
}
bool isActivityPageIndexAllowed(int pageIndex) {
return activityPages.keys.contains(pageIndex);
}
}
import 'package:flutter_custom_toolbox/flutter_toolbox.dart';
class NavCubitPage extends HydratedCubit<int> {
NavCubitPage({
required this.appConfig,
}) : super(0);
final ApplicationConfigDefinition appConfig;
void updateIndex(int index) {
if (appConfig.navigation.isActivityPageIndexAllowed(index)) {
emit(index);
} else {
emit(0);
}
}
@override
int fromJson(Map<String, dynamic> json) {
return 0;
}
@override
Map<String, dynamic>? toJson(int state) {
return <String, int>{'index': state};
}
}
import 'package:flutter_custom_toolbox/flutter_toolbox.dart';
class NavCubitScreen extends HydratedCubit<int> {
NavCubitScreen() : super(0);
void goToScreenActivity() {
emit(0);
}
void goToScreenSettings() {
emit(1);
}
void goToScreenAbout() {
emit(2);
}
@override
int fromJson(Map<String, dynamic> json) {
return 0;
}
@override
Map<String, dynamic>? toJson(int state) {
return <String, int>{'index': state};
}
}
import 'package:flutter/material.dart';
import 'package:flutter_custom_toolbox/flutter_toolbox.dart';
class BottomNavBar extends StatelessWidget {
const BottomNavBar({
super.key,
required this.appConfig,
});
final ApplicationConfigDefinition appConfig;
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.only(top: 1, right: 4, left: 4),
elevation: 4,
shadowColor: Theme.of(context).colorScheme.shadow,
color: Theme.of(context).colorScheme.surfaceContainerHighest,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
child: BlocBuilder<NavCubitPage, int>(builder: (BuildContext context, int state) {
final List<BottomNavigationBarItem> items = [];
appConfig.navigation.activityPages.forEach((int pageIndex, ActivityPageItem item) {
items.add(BottomNavigationBarItem(
icon: item.icon ?? Icon(null),
label: tr(item.code),
));
});
return BottomNavigationBar(
currentIndex: state,
onTap: (int index) => BlocProvider.of<NavCubitPage>(context).updateIndex(index),
type: BottomNavigationBarType.fixed,
elevation: 0,
backgroundColor: Colors.transparent,
selectedItemColor: Theme.of(context).colorScheme.primary,
unselectedItemColor: Theme.of(context).textTheme.bodySmall!.color,
items: items,
);
}),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_custom_toolbox/flutter_toolbox.dart';
class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget {
const GlobalAppBar({
super.key,
required this.appConfig,
required this.pageIndex,
required this.isActivityRunning,
});
final ApplicationConfigDefinition appConfig;
final int pageIndex;
final bool isActivityRunning;
@override
Widget build(BuildContext context) {
final bool displayQuitActivityButton =
!(appConfig.navigation.appBarConfiguration?.hideQuitActivityButton ?? false);
final bool pushQuitActivityButtonLeft =
(appConfig.navigation.appBarConfiguration?.pushQuitActivityButtonLeft == true);
final bool showApplicationTitle =
!(appConfig.navigation.appBarConfiguration?.hideApplicationTitle ?? false);
final List<AppBarButton> Function(BuildContext context) builder =
appConfig.navigation.appBarConfiguration?.topBarButtonsBuilder ??
// Default top bar buttons
(BuildContext context) {
if (isActivityRunning) {
return <AppBarButton>[];
}
return <AppBarButton>[
// Go to About page
AppBarButton(
onPressed: (BuildContext context) =>
BlocProvider.of<NavCubitScreen>(context).goToScreenAbout(),
icon: appConfig.navigation.screenAbout.icon,
),
// Go to Settings page
AppBarButton(
onPressed: (BuildContext context) =>
BlocProvider.of<NavCubitScreen>(context).goToScreenSettings(),
icon: appConfig.navigation.screenSettings.icon,
),
// Back to Home page
AppBarButton(
onPressed: (BuildContext context) =>
BlocProvider.of<NavCubitScreen>(context).goToScreenActivity(),
icon: appConfig.navigation.screenActivity.icon,
),
];
};
final List<Widget> menuActions = [];
// left pushed "quit activity" button
if (isActivityRunning && displayQuitActivityButton && pushQuitActivityButtonLeft) {
menuActions.add(ActivityButtonQuit(
onPressed: () {},
onLongPress: () => appConfig.quitCurrentActivity(context),
));
menuActions.add(const Spacer(flex: 6));
}
// add buttons
final List<AppBarButton> buttons = builder(context);
for (AppBarButton button in buttons) {
menuActions.add(ElevatedButton(
onPressed: () => button.onPressed!(context),
onLongPress: () => button.onLongPress!(context),
style: ElevatedButton.styleFrom(shape: const CircleBorder()),
child: button.icon,
));
}
// standard right pushed "quit activity" button
if (isActivityRunning && displayQuitActivityButton && !pushQuitActivityButtonLeft) {
menuActions.add(ActivityButtonQuit(
onPressed: () {},
onLongPress: () => appConfig.quitCurrentActivity(context),
));
}
return AppBar(
title: showApplicationTitle ? const AppHeader(text: 'app_name') : null,
actions: menuActions,
);
}
@override
Size get preferredSize => const Size.fromHeight(50);
}
import 'package:flutter/material.dart';
import 'package:flutter_custom_toolbox/flutter_toolbox.dart';
class ScreenAbout extends StatelessWidget {
const ScreenAbout({super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
const SizedBox(height: 8),
const AppTitle(text: 'about_title'),
const Text('about_content').tr(),
FutureBuilder<PackageInfo>(
future: PackageInfo.fromPlatform(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
return const Text('about_version').tr(
namedArgs: {
'version': snapshot.data!.version,
},
);
default:
return const SizedBox();
}
},
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_custom_toolbox/flutter_toolbox.dart';
class ScreenActivity extends StatelessWidget {
const ScreenActivity({
super.key,
required this.appConfig,
});
final ApplicationConfigDefinition appConfig;
ActivityPageItem getActivityPage(int pageIndex) {
if (appConfig.navigation.activityPages.keys.contains(pageIndex)) {
return appConfig.navigation.activityPages[pageIndex] ?? ActivityPageItem.empty();
} else {
return getActivityPage(appConfig.navigation.activityPages.keys.first);
}
}
@override
Widget build(BuildContext context) {
return BlocBuilder<NavCubitPage, int>(
builder: (BuildContext context, int pageIndex) {
final ActivityPageItem page = getActivityPage(pageIndex);
return page.builder(appConfig: appConfig);
},
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_custom_toolbox/flutter_toolbox.dart';
class ScreenSettings extends StatelessWidget {
const ScreenSettings({super.key});
@override
Widget build(BuildContext context) {
return const Padding(
padding: EdgeInsets.symmetric(horizontal: 8),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
SizedBox(height: 8),
AppTitle(text: 'settings_title'),
SizedBox(height: 8),
ApplicationSettingsForm(),
],
),
);
}
}
......@@ -5,16 +5,22 @@ import 'package:flutter_custom_toolbox/flutter_toolbox.dart';
class ApplicationConfigDefinition {
final String appTitle;
final List<ApplicationSettingsParameter> activitySettings;
final bool autoStartActivity;
final void Function(BuildContext context) startNewActivity;
final void Function(BuildContext context) quitCurrentActivity;
final void Function(BuildContext context) deleteCurrentActivity;
final void Function(BuildContext context) resumeActivity;
final ApplicationNavigation navigation;
const ApplicationConfigDefinition({
required this.appTitle,
required this.activitySettings,
this.autoStartActivity = false,
required this.startNewActivity,
required this.quitCurrentActivity,
required this.deleteCurrentActivity,
required this.resumeActivity,
required this.navigation,
});
ApplicationSettingsParameter getFromCode(String paramCode) {
......
......@@ -5,11 +5,11 @@ import 'package:flutter_custom_toolbox/flutter_toolbox.dart';
class PageParameters extends StatelessWidget {
const PageParameters({
super.key,
required this.config,
required this.appConfig,
required this.canBeResumed,
});
final ApplicationConfigDefinition config;
final ApplicationConfigDefinition appConfig;
final bool canBeResumed;
final double separatorHeight = 8.0;
......@@ -19,7 +19,7 @@ class PageParameters extends StatelessWidget {
final List<Widget> lines = [];
// Activity settings (top)
for (ApplicationSettingsParameter parameter in config.activitySettings) {
for (ApplicationSettingsParameter parameter in appConfig.activitySettings) {
if (parameter.displayedOnTop) {
lines.add(Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
......@@ -43,7 +43,7 @@ class PageParameters extends StatelessWidget {
aspectRatio: 3,
child: ActivityButtonResumeSaved(
onPressed: () {
config.resumeActivity(context);
appConfig.resumeActivity(context);
},
),
));
......@@ -52,7 +52,7 @@ class PageParameters extends StatelessWidget {
dimension: MediaQuery.of(context).size.width / 5,
child: ActivityButtonDeleteSaved(
onPressed: () {
config.deleteCurrentActivity(context);
appConfig.deleteCurrentActivity(context);
},
),
));
......@@ -63,7 +63,7 @@ class PageParameters extends StatelessWidget {
aspectRatio: 3,
child: ActivityButtonStartNew(
onPressed: () {
config.startNewActivity(context);
appConfig.startNewActivity(context);
},
),
),
......@@ -73,7 +73,7 @@ class PageParameters extends StatelessWidget {
lines.add(SizedBox(height: separatorHeight));
// Activity settings (bottom)
for (ApplicationSettingsParameter parameter in config.activitySettings) {
for (ApplicationSettingsParameter parameter in appConfig.activitySettings) {
if (!parameter.displayedOnTop) {
lines.add(Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
......
......@@ -3,7 +3,7 @@ description: "Flutter custom toolbox for org.benoitharrault.* projects."
publish_to: "none"
version: 0.5.0
version: 0.6.0
homepage: https://git.harrault.fr/android/flutter-toolbox
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment