feat(proj): init
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user