colveor_ui

pub package

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.skyapplabs.com/docs/flutter/overview. The source lives in docs/ — see Documentation.

Features

  • App shell: ColveorApp sets up ProviderScope, your config and routes, and responsive sizing (with a configurable design size) in one widget.

  • Theming: extend VoTheme with your own colors, sizes, typography and component styles to get a full Material 3 ThemeData for light and dark mode. The selected theme mode is saved between launches.

  • Routing: a GoRouter built from your route list and VoRoutingConfig.

  • Screen helpers: theme, sizes, screen, navigation and toast helpers via context.vo in any widget, or directly as members of a VoState / VoConsumerState.

  • Widgets:

    Library Contents
    widgets/buttons.dart VoButton (primary, secondary, outline, text, danger, loading), VoIconButton
    widgets/inputs.dart Text, password, search and select fields, phone input with country picker, PIN input, custom keyboard, form control
    widgets/display.dart VoText (with tappable [links]), avatar, counter badge/text, country flag, divider
    widgets/feedback.dart VoToast (success, error, warning, info, banner), feedback view, countdown action
    widgets/layout.dart VoScaffold, VoAppBar, scrollable form, tiles
    widgets/overlay.dart Bottom sheets, dialogs, searchable selectable lists
    widgets/page_flow.dart Multi-step page flows with animated transitions
    widgets/dashboard.dart Dashboard shell with drawer, end drawer and dock navigation

Installation

Add colveor_ui from pub.dev:

flutter pub add colveor_ui

or add it to pubspec.yaml yourself:

dependencies:
  colveor_ui: ^0.3.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) {
    final signedIn = ref.read(authProvider).isSignedIn;
    final atLogin = state.matchedLocation == '/login';
    if (!signedIn) return atLogin ? null : '/login';
    return atLogin ? '/home' : null;
  },
  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. See docs/routing.md.

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() or ref.setThemeMode(...) instead, and read the selected mode with ref.watch(voThemeControllerProvider). The mode is saved with VoStorage.

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, voConfigProvider, context.vo, ref.setThemeMode / ref.toggleThemeMode, VoState / VoConsumerState, everything in routing.dart
package:colveor_ui/config.dart VoConfig, voConfigProvider, Environment, VoFeedbackConfig, VoRoutingConfig
package:colveor_ui/routing.dart VoRoutingConfig and its callback types, 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, voThemeControllerProvider
package:colveor_ui/extensions.dart Theme extensions (context.colors, context.sizes, context.typography, …), context.vo, ref.setThemeMode, Color.darken / lighten
package:colveor_ui/storage.dart VoStorage
package:colveor_ui/utils.dart VoLogger, VoValidators, date / number / phone / text formatters
package:colveor_ui/widgets/*.dart Widget groups (see Features)

Documentation

Page Contents
Overview Features and import paths
ColveorApp ColveorApp, ColveorRef, VoBootstrap, testing
Configuration VoConfig, voConfigProvider, Environment, feedback
Routing VoRoutingConfig, redirect guards, voRouterProvider, navigation
Theming VoTheme, tokens, component themes, theme mode
Base Screens VoState, VoConsumerState and their helpers
Extensions context.vo, context.colors and friends, ref.setThemeMode, Color.darken
Widgets Every Vo* widget
Storage VoStorage
Utilities Logger, validators, formatters

Example

example/ is a complete app: a routing guard backed by a session saved with VoStorage, a phone and PIN sign-in flow, a dashboard with dock and drawers, a custom theme with a light/dark toggle, and one showcase screen per widget group. See example/README.md for where each part lives.

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