colveor_ui 0.2.0
colveor_ui: ^0.2.0 copied to clipboard
Themed Flutter UI toolkit with widgets, light/dark theming, go_router routing and base screen classes. The UI layer of Colveor.
colveor_ui #
A Flutter UI foundation for Colveor apps. It wires up theming (light/dark),
routing (go_router), state (flutter_riverpod) and responsive sizing
(flutter_screenutil_plus), and ships a set of themed Vo* widgets.
Full documentation: colveor.dev/docs/flutter/ui/overview. The source lives in doc/.
Features #
-
App shell:
ColveorAppsets upProviderScope, your config and routes, and responsive sizing (with a configurable design size) in one widget. -
Theming: extend
VoThemewith your own colors, sizes, typography and component styles to get a full Material 3ThemeDatafor light and dark mode. The selected theme mode is saved between launches. -
Routing: a
GoRouterbuilt from your route list andVoRoutingConfig. -
Screen helpers: theme, sizes, screen, navigation and toast helpers via
context.voin any widget, or directly as members of aVoState/VoConsumerState. -
Widgets:
Library Contents widgets/buttons.dartVoButton(primary, secondary, outline, text, danger, loading),VoIconButtonwidgets/inputs.dartText, password, search and select fields, phone input with country picker, PIN input, custom keyboard, form control widgets/display.dartVoText(with tappable[links]), avatar, counter badge/text, country flag, dividerwidgets/feedback.dartVoToast(success, error, warning, info, banner), feedback view, countdown actionwidgets/layout.dartVoScaffold,VoAppBar, scrollable form, tileswidgets/overlay.dartBottom sheets, dialogs, searchable selectable lists widgets/page_flow.dartMulti-step page flows with animated transitions widgets/dashboard.dartDashboard shell with drawer, end drawer and dock navigation
Installation #
flutter pub add colveor_ui
or add it to pubspec.yaml yourself:
dependencies:
colveor_ui: ^0.1.0
It requires Dart SDK ^3.12.2.
Usage #
1. Configure the app #
import 'package:colveor_ui/config.dart';
class AppConfig extends VoConfig {
@override
String get appName => 'My App';
@override
VoRoutingConfig get routing => const VoRoutingConfig(initialLocation: '/');
}
VoRoutingConfig also takes the router settings apps usually adjust. The
redirect callback gets a Ref, so it can read your providers, and onInit
re-runs it when they change:
VoRoutingConfig(
initialLocation: '/splash',
onInit: (ref, notifier) =>
ref.listen(authProvider, (_, _) => notifier.notify()),
redirect: (ref, context, state) =>
ref.read(authProvider).isSignedIn ? null : '/login',
observers: [MyAnalyticsObserver()],
errorBuilder: (context, state) => const NotFoundScreen(),
redirectLimit: 5,
debugLogDiagnostics: true,
)
To navigate outside the widget tree, use ref.read(voRouterProvider), or
voNavigatorKey for the root navigator and overlay.
VoConfig also has environment, initialCountryCode, favoriteCountries
and feedback, all with defaults you can override. Set the environment at
build time with --dart-define=FLAVOR=production.
2. Define the theme #
Extend VoTheme. Every part has a default (DefaultColors, DefaultSizes,
DefaultTypography, DefaultComponentTheme), so class AppTheme extends VoTheme {} already works. Override only what you want to brand:
import 'package:colveor_ui/theme.dart';
import 'package:flutter/material.dart';
class AppTheme extends VoTheme {
@override
VoColors get colors => const AppColors();
@override
VoTypography get typography => const AppTypography();
}
class AppColors extends VoColors {
const AppColors({super.brightness});
@override
AppColors copyWith({Brightness? brightness}) =>
AppColors(brightness: brightness ?? this.brightness);
@override
VoColorMode get primaryMode =>
const VoColorMode(light: Color(0xFF4F46E5), dark: Color(0xFF818CF8));
}
class AppTypography extends VoTypography {
const AppTypography({super.colors, super.sizes});
@override
String get bodyFontFamily => 'Inter';
@override
AppTypography copyWith({VoColors? colors, VoSizes? sizes}) =>
AppTypography(colors: colors, sizes: sizes);
}
VoTheme.build passes the resolved brightness, colors and sizes to each
part's copyWith, so every part must implement copyWith. To change the
Material sub-themes (buttons, inputs, app bar, …), extend VoThemeData and
return it from createThemeData.
3. Define routes #
import 'package:colveor_ui/colveor_ui.dart';
List<RouteBase> appRoutes(VoConfig config) => [
GoRoute(path: '/', builder: (_, _) => const HomeScreen()),
];
4. Run the app #
import 'package:colveor_ui/colveor_ui.dart';
import 'package:flutter/material.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final config = AppConfig();
await VoBootstrap.initialize(config: config);
runApp(
ColveorApp(
config: config,
appRoutes: appRoutes,
builder: (context, colveor) => MaterialApp.router(
theme: AppTheme().light,
darkTheme: AppTheme().dark,
themeMode: colveor.themeMode,
routerConfig: colveor.router,
),
),
);
}
Call colveor.toggleThemeMode() or colveor.setThemeMode(...) to change the
theme. Inside a ConsumerWidget, use ref.toggleThemeMode() instead.
Screen sizing
ColveorApp initializes flutter_screenutil_plus so sizes scale with the
screen. Sizes scale against a 360×690 design by default. If your designs use
a different frame, pass its size:
ColveorApp(
config: config,
appRoutes: appRoutes,
designSize: const Size(390, 844),
minTextAdapt: true, // default
splitScreenMode: true, // default
builder: (context, colveor) => ...,
)
Startup work
VoBootstrap.initialize does nothing by default. The country list used by
VoPhoneInput and VoSelectCountryField (about 50 KB) loads the first time
one of them is built, so apps that don't use them never load it. To have it
ready before the first frame instead, preload it:
await VoBootstrap.initialize(config: config, preloadCountries: true);
Building screens #
In any widget, context.vo gives you the theme, screen, navigation and toast
helpers for that context:
import 'package:colveor_ui/colveor_ui.dart';
import 'package:colveor_ui/widgets/buttons.dart';
import 'package:colveor_ui/widgets/layout.dart';
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) => VoScaffold(
appBar: AppBar(title: const Text('Home')),
body: Column(
spacing: context.vo.sizes.md,
children: [
Text('Welcome', style: context.vo.typography.titleLarge),
VoButton(
label: 'Save',
onPressed: () => context.vo.showSuccessToast('Saved'),
),
],
),
);
}
In a State, extend VoState (or VoConsumerState with Riverpod) to use the
same helpers as members:
class _ProfileScreenState extends VoState<ProfileScreen> {
@override
Widget build(BuildContext context) => VoButton(
label: 'Edit',
onPressed: () => push('/profile/edit'),
);
}
Helpers always resolve against the context of the element that is building or
being tapped, so they're safe in callbacks, dialogs and nested navigators.
There is no stateless base class: a stateless widget instance (a const one
in particular) can be mounted in several places at once, so it has no single
context of its own.
Libraries #
| Import | Contents |
|---|---|
package:colveor_ui/colveor_ui.dart |
ColveorApp, VoBootstrap, ColveorRef, VoConfig, context.vo, VoState / VoConsumerState, everything in routing.dart |
package:colveor_ui/config.dart |
VoConfig, Environment, VoFeedbackConfig, VoRoutingConfig |
package:colveor_ui/routing.dart |
VoRoutingConfig, voRouterProvider, voNavigatorKey, VoRouterNotifier, VoRouteObserver, plus GoRoute, ShellRoute, StatefulShellRoute, GoRouterState and context.go / push / pop from go_router |
package:colveor_ui/theme.dart |
VoTheme, colors, sizes, typography, component themes |
package:colveor_ui/extensions.dart |
Color and theme extensions (context.sizes, context.colors, …) and context.vo |
package:colveor_ui/utils.dart |
VoLogger, VoValidators, date / number / phone / text formatters |
package:colveor_ui/widgets/*.dart |
Widget groups (see Features) |
Example #
example/ is a complete app that shows the theme setup, routing, button variants, toasts and a light/dark toggle:
cd example
flutter run
Development #
Riverpod providers, Freezed models and JSON serializers are generated. After changing an annotated file, run:
dart run build_runner build --delete-conflicting-outputs