Skip to content

kh_screens

Composed, opinionated full-screen patterns (splash, onboarding, login) for khflutterlib-based apps, built on kh_ui/kh_theme/ kh_core.

See PHASE_0_ARCHITECTURE.md §17 for the customization principle every screen here follows, and the root README.md for how this package fits into the workspace.

Status

Available. Version 0.1.1+8, shipped in release train khflutterlib-v0.4.3. Exports:

  • KhSplashScreen — a themed cold-start screen: a centered logo slot, an optional async initialize callback run while the screen is showing, a minimumDisplayDuration, and a required onInitializationComplete callback the app uses to navigate onward — no router dependency assumed.
  • KhOnboardingFlow / KhOnboardingPage — a swipeable, dot-indicated carousel of illustration + title + description slides, with a skip action and a "Get started" button on the last slide.
  • KhLoginForm — the fields of a login screen (email, password with a visibility toggle, optional "remember me" and "forgot password"), with validation and a loading submit button. You supply the surrounding screen.

None of these depend on a router or state-management package: each one reports back through a callback and leaves navigation to you.

Example app

Demos for this package live in the workspace's shared gallery app at packages/example, alongside kh_ui's own component gallery — not in a separate kh_screens-only example:

melos bootstrap   # from the repo root, once
cd packages/example
flutter run

Usage

dependencies:
  kh_screens:
    git:
      url: https://github.com/msflib/flutter.git
      path: packages/kh_screens
      ref: khflutterlib-v0.4.3 # use the same release tag for every kh_* package

Splash screen

import 'package:kh_screens/kh_screens.dart';

KhSplashScreen(
  logo: Image.asset('assets/logo.png', width: 140),
  initialize: () => sessionRepository.restore(),
  onInitializationComplete: () =>
      Navigator.of(context).pushReplacementNamed('/home'),
);

initialize and the minimumDisplayDuration wait (default 1.5s) run concurrently, not sequentially — the screen stays up for max(minimumDisplayDuration, however long initialize takes), not the sum of both. backgroundColor defaults to Theme.of(context).scaffoldBackgroundColor, so it stays correct under a consuming app's custom seedColor or KhTheme.dark().

Errors from initialize aren't caught by KhSplashScreen itself — wrap your own initialize in a try/catch and decide the outcome before calling back through onInitializationComplete.

Onboarding flow

KhOnboardingFlow(
  pages: const [
    KhOnboardingPage(
      illustration: Icon(Icons.track_changes, size: 120),
      title: 'Track everything in one place',
      description: 'Never miss an update from order to delivery.',
    ),
    KhOnboardingPage(
      illustration: Icon(Icons.notifications_active, size: 120),
      title: 'Stay in the loop',
      description: 'Get notified the moment something changes.',
    ),
  ],
  onComplete: () async {
    await onboardingRepository.markComplete();
    if (context.mounted) {
      Navigator.of(context).pushReplacementNamed('/login');
    }
  },
);

illustration is any Widget (an Image.asset, an Icon, an SVG from your own flutter_svg dependency). onComplete is a Future<void> Function() and is the only way the flow finishes. It runs from the "Get started" button on the last slide and from "Skip" on the others. While it's running, both buttons show a loading state so it can't be triggered twice. With nothing async to do, return Future<void>.value().

showSkip (default true), skipLabel ('Skip'), getStartedLabel ('Get started') and backgroundColor customize the chrome.

Login form

KhLoginForm is just the form. Put it in your own Scaffold with whatever header, links and error display your app needs:

Scaffold(
  body: SafeArea(
    child: SingleChildScrollView(
      padding: const EdgeInsets.all(24),
      child: KhLoginForm(
        onSubmit: (email, password, rememberMe) async {
          await authRepository.login(email, password);
          if (context.mounted) {
            Navigator.of(context).pushReplacementNamed('/home');
          }
        },
        onForgotPassword: () =>
            Navigator.of(context).pushNamed('/reset-password'),
      ),
    ),
  ),
);
  • Validation runs on submit. By default the email must be non-empty and match kh_core's KhRegex.email, and the password must be non-empty. Pass emailValidator/passwordValidator (for example KhValidators.minLength(8) from kh_utils) for stricter rules.
  • onSubmit is Future<void> Function(String email, String password, bool rememberMe). While it's pending, the fields are disabled and the submit button shows a spinner.
  • Errors from onSubmit aren't caught. Catch them inside your own onSubmit and show them however you like (a KhSnackbar, an inline message).
  • The "Forgot password?" link only appears when onForgotPassword is set. showRememberMe (default true) and initialRememberMe control the checkbox. Every label (emailLabel, passwordLabel, submitLabel, ...) and the emailHint/passwordHint can be overridden.