virnavi_ui_bootstrap 0.0.1
virnavi_ui_bootstrap: ^0.0.1 copied to clipboard
A Flutter UI bootstrap providing responsive Screen/ScreenLayout architecture, adaptive design scaling, SectionBox widgets, SVG image helpers, and async stream builders.
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:virnavi_common_sdk/virnavi_common_sdk.dart';
import 'package:virnavi_ui_bootstrap/virnavi_ui_bootstrap.dart';
void main() {
VirnaviUIBootstrap.init(
mobileDesignSize: const Size(402, 874),
tabletDesignSize: const Size(768, 1024),
);
runApp(const ExampleApp());
}
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'virnavi_ui_bootstrap',
navigatorKey: ContextHolder.navigatorKey,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
useMaterial3: true,
),
home: const CounterScreen(),
);
}
}
// ── ScreenData ────────────────────────────────────────────────────────────────
class CounterData extends ScreenData<CounterScreen> {
int count = 0;
void increment() => setState(() => count++);
void decrement() => setState(() => count--);
void reset() => setState(() => count = 0);
}
// ── Screen ────────────────────────────────────────────────────────────────────
class CounterScreen extends Screen {
const CounterScreen({super.key});
@override
CounterData createScreenData() => CounterData();
CounterData get data => screenData as CounterData;
@override
Widget buildTemplate(BuildContext context, Widget child) {
return Scaffold(
appBar: AppBar(
title: const Text('virnavi_ui_bootstrap example'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: child,
);
}
@override
ScreenLayout buildMobilePortrait(BuildContext context) =>
const _PortraitLayout();
@override
ScreenLayout? buildTabletPortrait(BuildContext context) =>
const _TabletLayout();
}
// ── Mobile portrait ───────────────────────────────────────────────────────────
class _PortraitLayout extends ScreenLayout<CounterScreen> {
const _PortraitLayout();
@override
Widget build(BuildContext context, CounterScreen screen) {
return SingleChildScrollView(
padding: EdgeInsets.all(16.r),
child: Column(
children: [
_CounterSection(data: screen.data),
SizedBox(height: 20.r),
const _SectionBoxDemo(),
SizedBox(height: 20.r),
_AutoSizeTextDemo(),
],
),
);
}
}
// ── Tablet portrait (side-by-side) ────────────────────────────────────────────
class _TabletLayout extends ScreenLayout<CounterScreen> {
const _TabletLayout();
@override
Widget build(BuildContext context, CounterScreen screen) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: SingleChildScrollView(
padding: EdgeInsets.all(24.r),
child: _CounterSection(data: screen.data),
),
),
Expanded(
child: SingleChildScrollView(
padding: EdgeInsets.all(24.r),
child: Column(
children: [
const _SectionBoxDemo(),
SizedBox(height: 20.r),
_AutoSizeTextDemo(),
],
),
),
),
],
);
}
}
// ── Counter section ───────────────────────────────────────────────────────────
class _CounterSection extends StatelessWidget {
final CounterData data;
const _CounterSection({required this.data});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return SectionBox(
color: scheme.surface,
borderColor: scheme.primary,
borderRadius: 16,
padding: EdgeInsets.symmetric(horizontal: 20.r, vertical: 24.r),
child: Column(
children: [
AppAutoSizeText(
'ScreenData counter',
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600),
),
SizedBox(height: 12.r),
AppAutoSizeText(
'${data.count}',
style: TextStyle(
fontSize: 72.sp,
fontWeight: FontWeight.bold,
color: scheme.primary,
),
maxLines: 1,
),
SizedBox(height: 16.r),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton.filled(
onPressed: data.decrement,
icon: const Icon(Icons.remove),
),
OutlinedButton(
onPressed: data.reset,
child: const Text('Reset'),
),
IconButton.filled(
onPressed: data.increment,
icon: const Icon(Icons.add),
),
],
),
],
),
);
}
}
// ── SectionBox demo ───────────────────────────────────────────────────────────
class _SectionBoxDemo extends StatelessWidget {
const _SectionBoxDemo();
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final border = scheme.outlineVariant;
const bg = Colors.white;
const pad = EdgeInsets.symmetric(horizontal: 16, vertical: 12);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(bottom: 8.r),
child: AppAutoSizeText(
'SectionBox family',
style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w600),
),
),
SectionBoxStart(
borderColor: border,
color: bg,
borderRadius: 12,
padding: pad,
child: const _Row(Icons.person_outline, 'Profile'),
),
SectionBoxMid(
borderColor: border,
color: bg,
padding: pad,
child: const _Row(Icons.notifications_outlined, 'Notifications'),
),
SectionBoxMid(
borderColor: border,
color: bg,
padding: pad,
child: const _Row(Icons.lock_outline, 'Privacy'),
),
SectionBoxEnd(
borderColor: border,
color: bg,
borderRadius: 12,
padding: pad,
child: const _Row(Icons.help_outline, 'Help'),
),
],
);
}
}
class _Row extends StatelessWidget {
final IconData icon;
final String label;
const _Row(this.icon, this.label);
@override
Widget build(BuildContext context) {
return Row(
children: [
Icon(icon, size: 20),
const SizedBox(width: 12),
Expanded(child: Text(label)),
const Icon(Icons.chevron_right, size: 18),
],
);
}
}
// ── AppAutoSizeText demo ──────────────────────────────────────────────────────
class _AutoSizeTextDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SectionBox(
color: Theme.of(context).colorScheme.secondaryContainer,
borderColor: Theme.of(context).colorScheme.secondary,
borderRadius: 16,
padding: EdgeInsets.all(16.r),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AppAutoSizeText(
'AppAutoSizeText',
style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w600),
),
SizedBox(height: 8.r),
AppAutoSizeText(
'This text shrinks to fit its container. '
'Try resizing the window.',
style: TextStyle(fontSize: 14.sp),
maxLines: 2,
),
],
),
);
}
}