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,228 @@
import 'package:flutter/cupertino.dart';
import 'package:please_pay_me/core/branding/app_brand.dart';
import 'package:please_pay_me/core/legal/legal_links.dart';
import 'package:please_pay_me/features/auth/session_controller.dart';
import 'package:please_pay_me/features/budgets/budgets_controller.dart';
import 'package:please_pay_me/features/journal/journal_controller.dart';
import 'package:please_pay_me/features/work/jobs_controller.dart';
import 'package:please_pay_me/theme/theme.dart';
import 'package:please_pay_me/ui/ui.dart';
import 'package:provider/provider.dart';
class ProfileScreen extends StatelessWidget {
const ProfileScreen({super.key});
@override
Widget build(BuildContext context) {
final session = context.watch<SessionController>();
final user = session.user;
return CupertinoPageScaffold(
backgroundColor: AppColors.of(context, AppColors.groupedBackground),
child: CustomScrollView(
physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
slivers: [
const AppLargeNavBar(title: 'Профиль'),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.fromLTRB(
AppSpacing.gutter,
AppSpacing.s2,
AppSpacing.gutter,
AppSpacing.s5,
),
child: Row(
children: [
AppAvatar(
initials: user?.initials,
imageUrl: user?.photoUrl,
radius: 32,
),
const SizedBox(width: AppSpacing.s4),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AppText.title(user?.displayName ?? 'Гость'),
AppText.subhead(user?.handle ?? 'не авторизован'),
],
),
),
],
),
),
),
SliverToBoxAdapter(
child: AppListSection(
header: 'Подключение',
footer: session.isDemo
? 'Демо-режим: данные живут только в памяти устройства.'
: 'Данные синхронизируются с веб-кабинетом «${AppBrand.name}».',
separatorIndent: 60,
children: [
AppListTile(
leading: AppIconBadge(
icon: session.isDemo
? CupertinoIcons.wrench_fill
: CupertinoIcons.cloud_fill,
color: session.isDemo ? AppColors.systemOrange : AppColors.systemGreen,
),
title: 'Режим',
value: session.isDemo ? 'Демо' : 'Сервер',
showChevron: false,
),
if (!session.isDemo)
AppListTile(
leading: const AppIconBadge(
icon: CupertinoIcons.link,
color: AppColors.systemGray,
),
title: 'Адрес API',
subtitle: session.baseUrl,
showChevron: false,
),
],
),
),
const SliverToBoxAdapter(child: SizedBox(height: AppSpacing.s5)),
SliverToBoxAdapter(
child: AppListSection(
header: 'Оформление',
footer: 'Системная повторяет тему устройства.',
children: [
Padding(
padding: const EdgeInsets.fromLTRB(
AppSpacing.s3,
AppSpacing.s3,
AppSpacing.s3,
AppSpacing.s3,
),
child: AppSegmentedControl(
padding: EdgeInsets.zero,
labels: const ['Системная', 'Светлая', 'Тёмная'],
index: context.watch<ThemeController>().preference.index,
onChanged: (index) {
context.read<ThemeController>().setPreference(
ThemePreference.values[index],
);
},
),
),
],
),
),
const SliverToBoxAdapter(child: SizedBox(height: AppSpacing.s5)),
SliverToBoxAdapter(
child: AppListSection(
separatorIndent: 60,
children: [
AppListTile(
leading: const AppIconBadge(
icon: CupertinoIcons.arrow_clockwise,
color: AppColors.accent,
),
title: 'Обновить данные',
onTap: () => _refreshAll(context),
),
],
),
),
const SliverToBoxAdapter(child: SizedBox(height: AppSpacing.s5)),
SliverToBoxAdapter(
child: AppListSection(
header: 'Правовая информация',
footer: 'Открывается веб-версия на please-pay-me.ru.',
separatorIndent: 60,
children: [
AppListTile(
leading: const AppIconBadge(
icon: CupertinoIcons.doc_text,
color: AppColors.accent,
),
title: 'Пользовательское соглашение',
onTap: () => openLegalDocument(
context,
LegalLinks.resolve(session.webCabinetUrl, LegalLinks.offer),
),
),
AppListTile(
leading: const AppIconBadge(
icon: CupertinoIcons.lock_shield,
color: AppColors.systemGray,
),
title: 'Политика конфиденциальности',
onTap: () => openLegalDocument(
context,
LegalLinks.resolve(session.webCabinetUrl, LegalLinks.privacy),
),
),
AppListTile(
leading: const AppIconBadge(
icon: CupertinoIcons.checkmark_shield,
color: AppColors.systemGray,
),
title: 'Согласие на обработку данных',
onTap: () => openLegalDocument(
context,
LegalLinks.resolve(session.webCabinetUrl, LegalLinks.consent),
),
),
AppListTile(
leading: const AppIconBadge(
icon: CupertinoIcons.circle_grid_hex,
color: AppColors.systemGray,
),
title: 'Политика cookie',
onTap: () => openLegalDocument(
context,
LegalLinks.resolve(session.webCabinetUrl, LegalLinks.cookies),
),
),
],
),
),
const SliverToBoxAdapter(child: SizedBox(height: AppSpacing.s5)),
SliverToBoxAdapter(
child: AppListSection(
footer: '${AppBrand.name} · версия 0.1.0',
children: [
AppListTile(
title: 'Выйти',
destructive: true,
showChevron: false,
onTap: () => _signOut(context, session),
),
],
),
),
const SliverToBoxAdapter(child: SizedBox(height: AppSpacing.s7)),
],
),
);
}
Future<void> _refreshAll(BuildContext context) async {
await Future.wait([
context.read<BudgetsController>().load(silent: true),
context.read<JournalController>().load(silent: true),
context.read<JobsController>().load(silent: true),
]);
if (context.mounted) {
await showAppToast(context, message: 'Данные обновлены');
}
}
Future<void> _signOut(BuildContext context, SessionController session) async {
final confirmed = await showAppAlert(
context: context,
title: 'Выйти из аккаунта?',
message: 'Токен будет удалён с устройства.',
confirmLabel: 'Выйти',
cancelLabel: 'Отмена',
destructive: true,
);
if (confirmed == true) await session.signOut();
}
}