feat(proj): init

This commit is contained in:
vl.arkhangelskii
2026-09-21 04:06:43 +03:00
commit c956b94983
1076 changed files with 50876 additions and 0 deletions
@@ -0,0 +1,80 @@
import 'package:flutter/cupertino.dart';
import 'package:please_pay_me/theme/theme.dart';
import 'package:please_pay_me/ui/ui.dart';
import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart';
import 'package:please_pay_me_widgetbook/support/preview.dart';
import 'package:widgetbook/widgetbook.dart';
WidgetbookComponent buttonsComponent() {
return WidgetbookComponent(
name: 'AppButton',
useCases: [
WidgetbookUseCase(
name: 'Playground',
builder: (context) {
return IosPreview(
stretch: true,
child: AppButton(
label: knobText(context, label: 'Label', initialValue: 'Сохранить'),
style: knobEnum(
context,
label: 'Style',
values: AppButtonStyle.values,
initial: AppButtonStyle.filled,
),
size: knobEnum(
context,
label: 'Size',
values: AppButtonSize.values,
initial: AppButtonSize.large,
),
expanded: knobBool(context, label: 'Expanded', initialValue: true),
loading: knobBool(context, label: 'Loading'),
icon: knobBool(context, label: 'Icon') ? CupertinoIcons.plus : null,
onPressed:
knobBool(context, label: 'Enabled', initialValue: true) ? () {} : null,
),
);
},
),
WidgetbookUseCase(
name: 'All styles',
builder: (context) {
return IosPreview(
stretch: true,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
for (final style in AppButtonStyle.values) ...[
AppButton(label: style.name, style: style, onPressed: () {}),
const SizedBox(height: AppSpacing.s3),
],
],
),
);
},
),
WidgetbookUseCase(
name: 'Sizes',
builder: (context) {
return IosPreview.centered(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
for (final size in AppButtonSize.values) ...[
AppButton(
label: 'Размер ${size.name}',
size: size,
expanded: false,
onPressed: () {},
),
const SizedBox(height: AppSpacing.s3),
],
],
),
);
},
),
],
);
}
@@ -0,0 +1,68 @@
import 'package:flutter/cupertino.dart';
import 'package:please_pay_me/theme/theme.dart';
import 'package:please_pay_me/ui/ui.dart';
import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart';
import 'package:please_pay_me_widgetbook/support/preview.dart';
import 'package:widgetbook/widgetbook.dart';
const _icons = <IconData>[
CupertinoIcons.house_fill,
CupertinoIcons.cart_fill,
CupertinoIcons.bag_fill,
CupertinoIcons.money_rubl_circle_fill,
CupertinoIcons.chart_pie_fill,
CupertinoIcons.calendar,
CupertinoIcons.bell_fill,
CupertinoIcons.gear_alt_fill,
CupertinoIcons.person_fill,
CupertinoIcons.creditcard_fill,
];
WidgetbookComponent iconsComponent() {
return WidgetbookComponent(
name: 'Icons',
useCases: [
WidgetbookUseCase(
name: 'Glyphs',
builder: (context) {
final size = knobDouble(context, label: 'Size', initialValue: 24, min: 12, max: 48);
final tint = knobTint(context);
return IosPreview(
child: Wrap(
spacing: AppSpacing.s5,
runSpacing: AppSpacing.s5,
alignment: WrapAlignment.center,
children: [
for (final icon in _icons) AppIcon(icon, size: size, color: tint),
],
),
);
},
),
WidgetbookUseCase(
name: 'Badges',
builder: (context) {
return IosPreview(
child: Wrap(
spacing: AppSpacing.s4,
runSpacing: AppSpacing.s4,
alignment: WrapAlignment.center,
children: [
for (final (index, icon) in _icons.indexed)
AppIconBadge(
icon: icon,
color: switch (index % 4) {
0 => AppColors.accent,
1 => AppColors.systemRed,
2 => AppColors.systemOrange,
_ => AppColors.systemGray,
},
),
],
),
);
},
),
],
);
}
@@ -0,0 +1,62 @@
import 'package:flutter/cupertino.dart';
import 'package:please_pay_me/theme/theme.dart';
import 'package:please_pay_me/ui/ui.dart';
import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart';
import 'package:please_pay_me_widgetbook/support/preview.dart';
import 'package:widgetbook/widgetbook.dart';
WidgetbookComponent textFieldsComponent() {
return WidgetbookComponent(
name: 'AppTextField',
useCases: [
WidgetbookUseCase(
name: 'Playground',
builder: (context) {
final error = knobText(context, label: 'Error', initialValue: '');
return IosPreview(
stretch: true,
padding: const EdgeInsets.all(AppSpacing.s4),
child: AppTextField(
label: knobText(context, label: 'Label', initialValue: 'Сумма'),
placeholder: knobText(context, label: 'Placeholder', initialValue: '0 ₽'),
enabled: knobBool(context, label: 'Enabled', initialValue: true),
obscureText: knobBool(context, label: 'Obscure'),
clearable: knobBool(context, label: 'Clear button', initialValue: true),
prefixIcon: knobBool(context, label: 'Prefix icon')
? CupertinoIcons.search
: null,
errorText: error.isEmpty ? null : error,
),
);
},
),
WidgetbookUseCase(
name: 'Form group',
builder: (context) {
return const IosPreview(
stretch: true,
padding: EdgeInsets.all(AppSpacing.s4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
AppTextField(label: 'Название', placeholder: 'Продукты'),
SizedBox(height: AppSpacing.s4),
AppTextField(
label: 'Сумма',
placeholder: '0 ₽',
keyboardType: TextInputType.number,
),
SizedBox(height: AppSpacing.s4),
AppTextField(
label: 'Комментарий',
placeholder: 'Необязательно',
errorText: 'Слишком длинный комментарий',
),
],
),
);
},
),
],
);
}
@@ -0,0 +1,63 @@
import 'package:flutter/cupertino.dart';
import 'package:please_pay_me/theme/theme.dart';
import 'package:please_pay_me/ui/ui.dart';
import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart';
import 'package:please_pay_me_widgetbook/support/preview.dart';
import 'package:widgetbook/widgetbook.dart';
WidgetbookComponent typographyComponent() {
return WidgetbookComponent(
name: 'Typography',
useCases: [
WidgetbookUseCase(
name: 'iOS scale',
builder: (context) {
final sample = knobText(context, label: 'Sample', initialValue: 'Бюджет на неделю');
return IosPreview(
stretch: true,
padding: const EdgeInsets.all(AppSpacing.s4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AppText.largeTitle(sample),
const SizedBox(height: AppSpacing.s3),
AppText.title(sample),
const SizedBox(height: AppSpacing.s3),
AppText.headline(sample),
const SizedBox(height: AppSpacing.s3),
AppText.body(sample),
const SizedBox(height: AppSpacing.s3),
AppText.callout(sample),
const SizedBox(height: AppSpacing.s3),
AppText.subhead(sample),
const SizedBox(height: AppSpacing.s3),
AppText.footnote(sample),
const SizedBox(height: AppSpacing.s3),
AppText.caption(sample),
],
),
);
},
),
WidgetbookUseCase(
name: 'Section header',
builder: (context) {
return IosPreview(
stretch: true,
child: AppListSection(
header: knobText(context, label: 'Header', initialValue: 'Настройки'),
footer: knobText(
context,
label: 'Footer',
initialValue: 'Пояснение под группой, как в iOS Settings.',
),
children: const [
AppListTile(title: 'Строка', value: 'Значение'),
],
),
);
},
),
],
);
}
@@ -0,0 +1,48 @@
import 'package:flutter/cupertino.dart';
import 'package:please_pay_me/theme/theme.dart';
import 'package:please_pay_me/ui/ui.dart';
import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart';
import 'package:please_pay_me_widgetbook/support/preview.dart';
import 'package:widgetbook/widgetbook.dart';
WidgetbookComponent progressComponent() {
return WidgetbookComponent(
name: 'Progress',
useCases: [
WidgetbookUseCase(
name: 'Activity indicator',
builder: (context) {
return IosPreview.centered(
child: AppSpinner(
radius: knobDouble(context, label: 'Radius', initialValue: 14, min: 6, max: 28),
color: knobTint(context),
),
);
},
),
WidgetbookUseCase(
name: 'Progress bar',
builder: (context) {
return IosPreview(
stretch: true,
padding: const EdgeInsets.all(AppSpacing.s4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
AppProgressBar(
value: knobDouble(context, label: 'Value', initialValue: 0.42),
color: knobTint(context),
height: knobDouble(context, label: 'Height', initialValue: 4, min: 2, max: 12),
),
const SizedBox(height: AppSpacing.s4),
const AppProgressBar(value: 0.15, color: AppColors.systemRed),
const SizedBox(height: AppSpacing.s4),
const AppProgressBar(value: 1, color: AppColors.systemGreen),
],
),
);
},
),
],
);
}
@@ -0,0 +1,38 @@
import 'package:please_pay_me/ui/ui.dart';
import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart';
import 'package:please_pay_me_widgetbook/support/preview.dart';
import 'package:widgetbook/widgetbook.dart';
WidgetbookComponent skeletonsComponent() {
return WidgetbookComponent(
name: 'AppSkeleton',
useCases: [
WidgetbookUseCase(
name: 'Block',
builder: (context) {
return IosPreview.centered(
child: AppSkeleton(
width: knobDouble(context, label: 'Width', initialValue: 200, min: 40, max: 320),
height: knobDouble(context, label: 'Height', initialValue: 16, min: 8, max: 80),
radius: knobDouble(context, label: 'Radius', initialValue: 6, min: 0, max: 24),
),
);
},
),
WidgetbookUseCase(
name: 'List loading',
builder: (context) {
final rows = knobInt(context, label: 'Rows', initialValue: 4, min: 1, max: 8);
return IosPreview(
stretch: true,
child: AppListSection(
header: 'Загрузка',
separatorIndent: 60,
children: List.generate(rows, (_) => const AppSkeletonRow()),
),
);
},
),
],
);
}
@@ -0,0 +1,46 @@
import 'package:flutter/cupertino.dart';
import 'package:please_pay_me/ui/ui.dart';
import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart';
import 'package:please_pay_me_widgetbook/support/preview.dart';
import 'package:widgetbook/widgetbook.dart';
WidgetbookComponent toastsComponent() {
return WidgetbookComponent(
name: 'AppToast',
useCases: [
WidgetbookUseCase(
name: 'Static',
builder: (context) {
return IosPreview.centered(
child: AppToast(
message: knobText(context, label: 'Message', initialValue: 'Трата сохранена'),
tint: knobTint(context),
icon: knobBool(context, label: 'Icon', initialValue: true)
? CupertinoIcons.check_mark_circled_solid
: null,
),
);
},
),
WidgetbookUseCase(
name: 'Trigger',
builder: (context) {
return IosPreview(
stretch: true,
child: AppButton(
label: 'Показать toast',
onPressed: () => showAppToast(
context,
message: knobText(
context,
label: 'Message',
initialValue: 'Трата сохранена',
),
),
),
);
},
),
],
);
}
@@ -0,0 +1,48 @@
import 'package:flutter/cupertino.dart';
import 'package:please_pay_me/theme/theme.dart';
import 'package:please_pay_me/ui/ui.dart';
import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart';
import 'package:please_pay_me_widgetbook/support/preview.dart';
import 'package:widgetbook/widgetbook.dart';
WidgetbookComponent avatarsComponent() {
return WidgetbookComponent(
name: 'AppAvatar',
useCases: [
WidgetbookUseCase(
name: 'Playground',
builder: (context) {
final initials = knobText(context, label: 'Initials', initialValue: 'ВА');
return IosPreview.centered(
child: AppAvatar(
initials: knobBool(context, label: 'Use initials', initialValue: true)
? initials
: null,
radius: knobDouble(context, label: 'Radius', initialValue: 32, min: 12, max: 56),
),
);
},
),
WidgetbookUseCase(
name: 'Sizes',
builder: (context) {
return const IosPreview.centered(
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
AppAvatar(initials: 'А', radius: 16),
SizedBox(width: AppSpacing.s4),
AppAvatar(initials: 'ВА', radius: 22),
SizedBox(width: AppSpacing.s4),
AppAvatar(radius: 32),
SizedBox(width: AppSpacing.s4),
AppAvatar(initials: 'ППМ', radius: 44),
],
),
);
},
),
],
);
}
@@ -0,0 +1,72 @@
import 'package:flutter/cupertino.dart';
import 'package:please_pay_me/theme/theme.dart';
import 'package:please_pay_me/ui/ui.dart';
import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart';
import 'package:please_pay_me_widgetbook/support/preview.dart';
import 'package:widgetbook/widgetbook.dart';
WidgetbookComponent cardsComponent() {
return WidgetbookComponent(
name: 'AppCard',
useCases: [
WidgetbookUseCase(
name: 'Playground',
builder: (context) {
final subtitle = knobText(context, label: 'Subtitle', initialValue: 'До 25 сентября');
return IosPreview(
stretch: true,
child: AppCard(
title: knobText(context, label: 'Title', initialValue: 'Бюджет'),
subtitle: subtitle.isEmpty ? null : subtitle,
onTap: knobBool(context, label: 'Tappable') ? () {} : null,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AppText.largeTitle(
knobText(context, label: 'Amount', initialValue: '18 420 ₽'),
),
const SizedBox(height: AppSpacing.s4),
AppProgressBar(
value: knobDouble(context, label: 'Progress', initialValue: 0.42),
),
],
),
),
);
},
),
WidgetbookUseCase(
name: 'Metrics row',
builder: (context) {
return const IosPreview(
stretch: true,
child: AppCard(
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AppText.footnote('Доход'),
AppText.title('64 000 ₽'),
],
),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AppText.footnote('Расход'),
AppText.title('45 580 ₽', color: AppColors.systemRed),
],
),
),
],
),
),
);
},
),
],
);
}
@@ -0,0 +1,45 @@
import 'package:flutter/cupertino.dart';
import 'package:please_pay_me/theme/theme.dart';
import 'package:please_pay_me/ui/ui.dart';
import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart';
import 'package:please_pay_me_widgetbook/support/preview.dart';
import 'package:widgetbook/widgetbook.dart';
WidgetbookComponent chipsComponent() {
return WidgetbookComponent(
name: 'AppChip',
useCases: [
WidgetbookUseCase(
name: 'Playground',
builder: (context) {
return IosPreview.centered(
child: AppChip(
label: knobText(context, label: 'Label', initialValue: 'Еда'),
selected: knobBool(context, label: 'Selected'),
icon: knobBool(context, label: 'Icon') ? CupertinoIcons.tag_fill : null,
onPressed: () {},
),
);
},
),
WidgetbookUseCase(
name: 'Filter row',
builder: (context) {
final selected = knobInt(context, label: 'Selected index', min: 0, max: 3);
const labels = ['Все', 'Еда', 'Транспорт', 'Подписки'];
return IosPreview.centered(
child: Wrap(
spacing: AppSpacing.s2,
runSpacing: AppSpacing.s2,
alignment: WrapAlignment.center,
children: [
for (final (index, label) in labels.indexed)
AppChip(label: label, selected: index == selected, onPressed: () {}),
],
),
);
},
),
],
);
}
@@ -0,0 +1,80 @@
import 'package:flutter/cupertino.dart';
import 'package:please_pay_me/theme/theme.dart';
import 'package:please_pay_me/ui/ui.dart';
import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart';
import 'package:please_pay_me_widgetbook/knobs/state_knobs.dart';
import 'package:please_pay_me_widgetbook/support/preview.dart';
import 'package:widgetbook/widgetbook.dart';
WidgetbookComponent listTilesComponent() {
return WidgetbookComponent(
name: 'AppListTile',
useCases: [
WidgetbookUseCase(
name: 'Playground',
builder: (context) {
final subtitle = knobText(context, label: 'Subtitle', initialValue: 'Супермаркет');
final value = knobText(context, label: 'Value', initialValue: '1 840 ₽');
return IosPreview(
stretch: true,
child: AppListSection(
separatorIndent: 60,
children: [
AppListTile(
leading: knobBool(context, label: 'Leading badge', initialValue: true)
? const AppIconBadge(icon: CupertinoIcons.cart_fill)
: null,
title: knobText(context, label: 'Title', initialValue: 'Продукты'),
subtitle: subtitle.isEmpty ? null : subtitle,
value: value.isEmpty ? null : value,
destructive: knobBool(context, label: 'Destructive'),
showChevron: knobBool(context, label: 'Chevron', initialValue: true),
onTap: knobBool(context, label: 'Tappable', initialValue: true) ? () {} : null,
),
],
),
);
},
),
WidgetbookUseCase(
name: 'Grouped section',
builder: (context) {
final state = knobAsyncState(context);
return IosPreview(
stretch: true,
child: AppListSection(
header: 'Сегодня',
footer: 'Свайп по строке открывает действия.',
separatorIndent: 60,
children: [
wrapAsyncState(
context: context,
state: state,
builder: () => const Column(
children: [
AppListTile(
leading: AppIconBadge(icon: CupertinoIcons.cart_fill),
title: 'Продукты',
subtitle: 'Супермаркет',
value: '1 840 ₽',
),
AppListTile(
leading: AppIconBadge(
icon: CupertinoIcons.car_fill,
color: AppColors.systemOrange,
),
title: 'Такси',
subtitle: 'Транспорт',
value: '420 ₽',
),
],
),
),
],
),
);
},
),
],
);
}
@@ -0,0 +1,75 @@
import 'package:flutter/cupertino.dart';
import 'package:please_pay_me/theme/theme.dart';
import 'package:please_pay_me/ui/ui.dart';
import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart';
import 'package:please_pay_me_widgetbook/support/preview.dart';
import 'package:widgetbook/widgetbook.dart';
WidgetbookComponent switchesComponent() {
return WidgetbookComponent(
name: 'AppSwitchRow',
useCases: [
WidgetbookUseCase(
name: 'Playground',
builder: (context) {
final subtitle = knobText(context, label: 'Subtitle', initialValue: '');
return IosPreview(
stretch: true,
child: AppListSection(
separatorIndent: 60,
children: [
AppSwitchRow(
leading: knobBool(context, label: 'Leading badge', initialValue: true)
? const AppIconBadge(
icon: CupertinoIcons.bell_fill,
color: AppColors.systemRed,
)
: null,
title: knobText(context, label: 'Title', initialValue: 'Уведомления'),
subtitle: subtitle.isEmpty ? null : subtitle,
value: knobBool(context, label: 'Value', initialValue: true),
onChanged: knobBool(context, label: 'Enabled', initialValue: true)
? (_) {}
: null,
),
],
),
);
},
),
WidgetbookUseCase(
name: 'Settings group',
builder: (context) {
return IosPreview(
stretch: true,
child: AppListSection(
header: 'Настройки',
footer: 'Push приходят за час до дедлайна бюджета.',
separatorIndent: 60,
children: [
AppSwitchRow(
leading: const AppIconBadge(
icon: CupertinoIcons.bell_fill,
color: AppColors.systemRed,
),
title: 'Уведомления',
value: true,
onChanged: (_) {},
),
AppSwitchRow(
leading: const AppIconBadge(
icon: CupertinoIcons.lock_fill,
color: AppColors.systemGray,
),
title: 'Face ID',
value: false,
onChanged: (_) {},
),
],
),
);
},
),
],
);
}
@@ -0,0 +1,73 @@
import 'package:flutter/cupertino.dart';
import 'package:please_pay_me/theme/theme.dart';
import 'package:please_pay_me/ui/ui.dart';
import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart';
import 'package:please_pay_me_widgetbook/support/preview.dart';
import 'package:widgetbook/widgetbook.dart';
WidgetbookComponent dialogsComponent() {
return WidgetbookComponent(
name: 'AppAlert',
useCases: [
WidgetbookUseCase(
name: 'Alert',
builder: (context) {
final message = knobText(
context,
label: 'Message',
initialValue: 'Операция будет удалена без возможности восстановления.',
);
return IosPreview.centered(
child: AppAlert(
title: knobText(context, label: 'Title', initialValue: 'Удалить трату?'),
message: message.isEmpty ? null : message,
confirmLabel: knobText(context, label: 'Confirm', initialValue: 'Удалить'),
cancelLabel: knobBool(context, label: 'Cancel button', initialValue: true)
? 'Отмена'
: null,
destructive: knobBool(context, label: 'Destructive', initialValue: true),
),
);
},
),
WidgetbookUseCase(
name: 'Triggers',
builder: (context) {
return IosPreview(
stretch: true,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
AppButton(
label: 'Показать alert',
onPressed: () => showAppAlert(
context: context,
title: 'Удалить трату?',
message: 'Действие необратимо.',
confirmLabel: 'Удалить',
cancelLabel: 'Отмена',
destructive: true,
),
),
const SizedBox(height: AppSpacing.s3),
AppButton(
label: 'Показать action sheet',
style: AppButtonStyle.gray,
onPressed: () => showAppActionSheet(
context: context,
title: 'Операция',
actions: const [
AppActionSheetAction(label: 'Редактировать', isDefault: true),
AppActionSheetAction(label: 'Дублировать'),
AppActionSheetAction(label: 'Удалить', destructive: true),
],
),
),
],
),
);
},
),
],
);
}
@@ -0,0 +1,83 @@
import 'package:flutter/cupertino.dart';
import 'package:please_pay_me/theme/theme.dart';
import 'package:please_pay_me/ui/ui.dart';
import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart';
import 'package:widgetbook/widgetbook.dart';
WidgetbookComponent navBarComponent() {
return WidgetbookComponent(
name: 'AppNavBar',
useCases: [
WidgetbookUseCase(
name: 'Playground',
builder: (context) {
final subtitle = knobText(context, label: 'Subtitle', initialValue: 'До 25 сентября');
return CupertinoPageScaffold(
backgroundColor: AppColors.of(context, AppColors.groupedBackground),
navigationBar: AppNavBar(
title: knobText(context, label: 'Title', initialValue: 'Бюджет'),
subtitle: subtitle.isEmpty ? null : subtitle,
previousPageTitle:
knobBool(context, label: 'Back button', initialValue: true) ? 'Назад' : null,
transparent: knobBool(context, label: 'Transparent'),
trailing: knobBool(context, label: 'Trailing action', initialValue: true)
? CupertinoButton(
padding: EdgeInsets.zero,
minimumSize: Size.zero,
onPressed: () {},
child: const AppIcon(
CupertinoIcons.ellipsis_circle,
color: AppColors.accent,
),
)
: null,
),
child: const SafeArea(
child: Padding(
padding: EdgeInsets.all(AppSpacing.s4),
child: AppText.subhead('Контент под навигационной панелью'),
),
),
);
},
),
WidgetbookUseCase(
name: 'Large title',
builder: (context) {
return CupertinoPageScaffold(
backgroundColor: AppColors.of(context, AppColors.groupedBackground),
child: CustomScrollView(
slivers: [
AppLargeNavBar(
title: knobText(context, label: 'Title', initialValue: 'Журнал'),
trailing: CupertinoButton(
padding: EdgeInsets.zero,
minimumSize: Size.zero,
onPressed: () {},
child: const AppIcon(
CupertinoIcons.add_circled,
color: AppColors.accent,
),
),
),
SliverToBoxAdapter(
child: AppListSection(
header: 'Сегодня',
separatorIndent: 60,
children: const [
AppListTile(
leading: AppIconBadge(icon: CupertinoIcons.cart_fill),
title: 'Продукты',
value: '1 840 ₽',
),
],
),
),
],
),
);
},
),
],
);
}
@@ -0,0 +1,93 @@
import 'package:flutter/cupertino.dart';
import 'package:please_pay_me/theme/theme.dart';
import 'package:please_pay_me/ui/ui.dart';
import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart';
import 'package:please_pay_me_widgetbook/support/preview.dart';
import 'package:widgetbook/widgetbook.dart';
WidgetbookComponent segmentedControlComponent() {
return WidgetbookComponent(
name: 'AppSegmentedControl',
useCases: [
WidgetbookUseCase(
name: 'Playground',
builder: (context) {
final labels = knobText(
context,
label: 'Labels (comma separated)',
initialValue: 'Все,Расходы,Доходы',
).split(',').where((e) => e.trim().isNotEmpty).toList();
final safeLabels = labels.isEmpty ? ['Все'] : labels;
return IosPreview(
stretch: true,
child: _StatefulSegments(
labels: safeLabels,
initialIndex: knobInt(
context,
label: 'Initial index',
max: safeLabels.length - 1,
),
),
);
},
),
WidgetbookUseCase(
name: 'In screen header',
builder: (context) {
return CupertinoPageScaffold(
backgroundColor: AppColors.of(context, AppColors.groupedBackground),
navigationBar: const AppNavBar(title: 'Журнал'),
child: SafeArea(
child: Column(
children: [
const SizedBox(height: AppSpacing.s4),
const _StatefulSegments(labels: ['Неделя', 'Месяц', 'Год']),
const SizedBox(height: AppSpacing.s5),
AppListSection(
children: const [
AppListTile(title: 'Итого', value: '45 580 ₽'),
],
),
],
),
),
);
},
),
],
);
}
class _StatefulSegments extends StatefulWidget {
const _StatefulSegments({required this.labels, this.initialIndex = 0});
final List<String> labels;
final int initialIndex;
@override
State<_StatefulSegments> createState() => _StatefulSegmentsState();
}
class _StatefulSegmentsState extends State<_StatefulSegments> {
late int _index = widget.initialIndex;
@override
void didUpdateWidget(_StatefulSegments oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.initialIndex != widget.initialIndex ||
_index >= widget.labels.length) {
_index = widget.initialIndex.clamp(0, widget.labels.length - 1);
}
}
@override
Widget build(BuildContext context) {
return AppSegmentedControl(
labels: widget.labels,
index: _index,
onChanged: (i) => setState(() => _index = i),
);
}
}
@@ -0,0 +1,53 @@
import 'package:flutter/cupertino.dart';
import 'package:please_pay_me/theme/theme.dart';
import 'package:please_pay_me/ui/ui.dart';
import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart';
import 'package:widgetbook/widgetbook.dart';
const _items = [
AppTabItem(icon: CupertinoIcons.list_bullet, label: 'Журнал'),
AppTabItem(icon: CupertinoIcons.chart_pie, label: 'Бюджет'),
AppTabItem(
icon: CupertinoIcons.person,
activeIcon: CupertinoIcons.person_fill,
label: 'Профиль',
),
];
WidgetbookComponent tabBarComponent() {
return WidgetbookComponent(
name: 'AppTabBar',
useCases: [
WidgetbookUseCase(
name: 'Playground',
builder: (context) {
final index = knobInt(context, label: 'Current index', min: 0, max: 2);
return ColoredBox(
color: AppColors.of(context, AppColors.groupedBackground),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
AppTabBar(items: _items, currentIndex: index, onTap: (_) {}),
],
),
);
},
),
WidgetbookUseCase(
name: 'In scaffold',
builder: (context) {
return CupertinoTabScaffold(
tabBar: AppTabBar(items: _items, currentIndex: 0, onTap: (_) {}),
tabBuilder: (context, index) => CupertinoTabView(
builder: (context) => CupertinoPageScaffold(
backgroundColor: AppColors.of(context, AppColors.groupedBackground),
navigationBar: AppNavBar(title: _items[index].label),
child: Center(child: AppText.body(_items[index].label)),
),
),
);
},
),
],
);
}
@@ -0,0 +1,130 @@
import 'package:please_pay_me/app/home_tabs.dart';
import 'package:please_pay_me/features/auth/login_screen.dart';
import 'package:please_pay_me/features/budgets/budgets_screen.dart';
import 'package:please_pay_me/features/journal/journal_screen.dart';
import 'package:please_pay_me/features/overview/overview_screen.dart';
import 'package:please_pay_me/features/profile/profile_screen.dart';
import 'package:please_pay_me/features/splash/splash_screen.dart';
import 'package:please_pay_me/features/work/work_screen.dart';
import 'package:please_pay_me_widgetbook/support/demo_scope.dart';
import 'package:widgetbook/widgetbook.dart';
WidgetbookComponent overviewScreenComponent() {
return WidgetbookComponent(
name: 'OverviewScreen',
useCases: [
WidgetbookUseCase(
name: 'С активным бюджетом',
builder: (context) => const DemoScope(child: OverviewScreen()),
),
WidgetbookUseCase(
name: 'Без бюджетов',
builder: (context) => const DemoScope(seeded: false, child: OverviewScreen()),
),
],
);
}
WidgetbookComponent journalScreenComponent() {
return WidgetbookComponent(
name: 'JournalScreen',
useCases: [
WidgetbookUseCase(
name: 'С операциями',
builder: (context) => const DemoScope(child: JournalScreen()),
),
WidgetbookUseCase(
name: 'Пустой',
builder: (context) => const DemoScope(seeded: false, child: JournalScreen()),
),
],
);
}
WidgetbookComponent budgetsScreenComponent() {
return WidgetbookComponent(
name: 'BudgetsScreen',
useCases: [
WidgetbookUseCase(
name: 'Активные и завершённые',
builder: (context) => const DemoScope(child: BudgetsScreen()),
),
WidgetbookUseCase(
name: 'Пустой',
builder: (context) => const DemoScope(seeded: false, child: BudgetsScreen()),
),
],
);
}
WidgetbookComponent workScreenComponent() {
return WidgetbookComponent(
name: 'WorkScreen',
useCases: [
WidgetbookUseCase(
name: 'С работой',
builder: (context) => const DemoScope(child: WorkScreen()),
),
WidgetbookUseCase(
name: 'Пустой',
builder: (context) => const DemoScope(seeded: false, child: WorkScreen()),
),
],
);
}
WidgetbookComponent profileScreenComponent() {
return WidgetbookComponent(
name: 'ProfileScreen',
useCases: [
WidgetbookUseCase(
name: 'Демо-сессия',
builder: (context) => const DemoScope(child: ProfileScreen()),
),
],
);
}
WidgetbookComponent splashScreenComponent() {
return WidgetbookComponent(
name: 'SplashScreen',
useCases: [
WidgetbookUseCase(
name: 'Запуск',
builder: (context) => const SplashScreen(),
),
],
);
}
WidgetbookComponent loginScreenComponent() {
return WidgetbookComponent(
name: 'LoginScreen',
useCases: [
WidgetbookUseCase(
name: 'Вход через Telegram',
builder: (context) => DemoScope(
// Desktop has no WebView, so the catalog stubs the launcher to show
// the mobile layout.
child: LoginScreen(launchTelegramLogin: (_, __) async => null),
),
),
WidgetbookUseCase(
name: 'Вход по токену',
builder: (context) => const DemoScope(child: LoginScreen()),
),
],
);
}
WidgetbookComponent homeTabsComponent() {
return WidgetbookComponent(
name: 'HomeTabs',
useCases: [
WidgetbookUseCase(
name: 'Полное приложение',
builder: (context) => const DemoScope(child: HomeTabs()),
),
],
);
}
@@ -0,0 +1,64 @@
import 'package:please_pay_me/data/demo/demo_backend.dart';
import 'package:please_pay_me/features/budgets/budget_form_sheet.dart';
import 'package:please_pay_me/features/expenses/expense_form_sheet.dart';
import 'package:please_pay_me/features/work/job_form_sheet.dart';
import 'package:please_pay_me_widgetbook/support/demo_scope.dart';
import 'package:widgetbook/widgetbook.dart';
WidgetbookComponent sheetsComponent() {
return WidgetbookComponent(
name: 'Формы',
useCases: [
WidgetbookUseCase(
name: 'Новая трата',
builder: (context) => DemoScope(
child: ExpenseFormSheet(
budgetName: 'До аванса',
remainingToday: 1420,
onSubmit: ({required amount, note, required spentAt}) async => null,
),
),
),
WidgetbookUseCase(
name: 'Новый бюджет',
builder: (context) => DemoScope(
child: BudgetFormSheet(
onSubmit: ({
required name,
required totalAmount,
required startDate,
required endDate,
required resetExpenses,
}) async =>
null,
),
),
),
WidgetbookUseCase(
name: 'Новая работа',
builder: (context) => DemoScope(
child: JobFormSheet(
onSubmit: ({
required name,
required salaryAmount,
required payDays,
required firstPayPercent,
required weekendPolicy,
}) async =>
null,
),
),
),
WidgetbookUseCase(
name: 'Ошибка сохранения',
builder: (context) => DemoScope(
child: ExpenseFormSheet(
budgetName: DemoBackend.user.displayName,
onSubmit: ({required amount, note, required spentAt}) async =>
'Бюджет уже завершён',
),
),
),
],
);
}