Files
Remever/lib/screens/crud_collection/crud_collection.dart

501 lines
14 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:io';
import 'dart:typed_data';
import 'package:auto_route/auto_route.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:remever/common/functions.dart';
import 'package:remever/common/resources.dart';
import 'package:remever/common/widgets/bottom_safe_space.dart';
import 'package:remever/common/widgets/typography.dart';
import 'package:remever/common/widgets/w_if.dart';
import 'package:remever/common/widgets/wspace.dart';
import 'package:remever/components/extensions/context.dart';
import 'package:remever/database/database.dart';
import 'package:remever/gen/assets.gen.dart';
import 'package:remever/inject.dart';
import 'package:remever/models/crud_collection_dto.dart';
import 'package:remever/router.gr.dart';
import 'package:remever/screens/crud_collection/widgets/crud_collection_field.dart';
import 'package:remever/screens/dialogs/alert_dialog.dart';
import 'package:remever/screens/dialogs/tags_dialog.dart';
import 'package:remever/services/collection/collections_interface.dart';
import 'package:remever/widgets/primary_button.dart';
import '../../../components/extensions/state.dart';
@RoutePage()
class CrudCollectionScreen extends StatefulWidget {
const CrudCollectionScreen({super.key, this.editedCollection});
final Collection? editedCollection;
@override
State<CrudCollectionScreen> createState() => _CrudCollectionScreenState();
}
class _CrudCollectionScreenState extends State<CrudCollectionScreen> {
late CrudCollectionDto _collection;
bool _isPublic = false;
@override
void initState() {
super.initState();
_initializeCollection();
}
void _initializeCollection() {
_collection = CrudCollectionDto(
desc: widget.editedCollection?.desc ?? '',
title: widget.editedCollection?.title ?? '',
isPublic: widget.editedCollection?.isPublic ?? false,
avatar: widget.editedCollection?.image,
);
_isPublic = _collection.isPublic;
}
Future<void> _pickImage() async {
final result = await FilePicker.platform.pickFiles();
if (result?.files.single.path case final String? path?) {
try {
final bytes = await File(path!).readAsBytes();
_updateCollection(avatar: bytes);
} catch (e) {
showErrorToast('Не удалось загрузить изображение');
}
} else {
showErrorToast('Файл не выбран');
}
}
void _updateCollection({
String? title,
String? desc,
bool? isPublic,
Uint8List? avatar,
}) {
_collection = _collection.copyWith(
title: title ?? _collection.title,
desc: desc ?? _collection.desc,
isPublic: isPublic ?? _collection.isPublic,
avatar: avatar ?? _collection.avatar,
);
safeSetState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.gray_bg,
appBar: _buildAppBar(),
body: _buildMainBody(),
);
}
Widget _buildMainBody() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16).r,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const HSpace(16),
_buildPhotoAndTitle(),
const HSpace(16),
..._buildDescription(),
const HSpace(16),
// _buildPublicSwitch(),
const HSpace(16),
AnimatedOpacity(
// opacity: _isPublic ? 1 : 0,
opacity: 0,
duration: const Duration(milliseconds: 300),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
..._buildTagButton(),
const HSpace(16),
_buildTagsList(),
const HSpace(47),
],
),
),
_buildCreateBtn(),
const BottomSafeSpace(),
],
),
),
);
}
Widget _buildCreateBtn() {
return PrimaryButton(
height: 52,
onTap: _handleCreateOrUpdate,
color: AppColors.primary,
child: AppTypography(
widget.editedCollection == null
? 'Создать коллекцию'
: 'Сохранить изменения',
type: Regular14px(),
color: Colors.white,
),
);
}
Future<void> _handleCreateOrUpdate() async {
if (!_isCollectionValid()) return;
if (!_hasChanges()) {
context.back();
}
try {
final collectionService = getIt<CollectionsInterface>();
widget.editedCollection != null
? await collectionService.updateCollection(
_collection,
widget.editedCollection!.id,
)
: await collectionService.createCollection(_collection);
context.back();
} catch (e) {
showErrorToast(
'Ошибка при ${widget.editedCollection != null ? 'обновлении' : 'создании'} коллекции',
);
}
}
bool _isCollectionValid() {
if (_collection.title.isEmpty && _collection.desc.isEmpty) {
showErrorToast('Для создания коллекции добавьте название и описание');
return false;
}
if (_isPublic && _collection.desc.isEmpty) {
showErrorToast(
'Для создания публичной коллекции добавьте описание и тэги',
);
return false;
}
return true;
}
Widget _buildTagsList() {
return SizedBox(
height: 68.h,
child: Row(
children: [
Expanded(
child: Wrap(
runSpacing: 8.r,
spacing: 8.r,
children: List.generate(6, _buildTagItem),
),
),
const WSpace(9),
_buildAddTagButton(),
],
),
);
}
Widget _buildTagItem(int index) {
return GestureDetector(
onTap: () {},
child: Container(
height: 30,
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(6)).r,
color: const Color(0xFFFFE4E6),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
AppTypography(
'tag $index',
type: Regular14px(),
height: 0.95,
color: AppColors.danger,
),
const WSpace(8),
Icon(Icons.close, size: 14.r, color: AppColors.danger),
],
),
),
),
);
}
Widget _buildAddTagButton() {
return GestureDetector(
onTap: _showTagsDialog,
child: AppTypography('+13', type: Medium16px(), color: AppColors.primary),
);
}
void _showTagsDialog() {
showCuperModalBottomSheet(
context: context,
height: 270.h,
builder: (_) => const TagsDialog(),
);
}
List<Widget> _buildTagButton() {
return [
AppTypography('Тэги', type: SemiBold14px()),
const HSpace(4),
CrudCollectionField(height: 42, width: 348, hint: 'Добавить тэг'),
];
}
Widget _buildPublicSwitch() {
return GestureDetector(
onTap: _togglePublic,
child: Row(
children: [
SizedBox.square(
dimension: 20.r,
child: Assets.icons.typePublic.image(color: AppColors.primary),
),
const WSpace(2),
Flexible(
fit: FlexFit.tight,
child: AppTypography(
'Публичная коллекция',
type: Medium16px(),
color: AppColors.primary,
),
),
const WSpace(2),
SizedBox(
height: 20.h,
width: 36.w,
child: FittedBox(
fit: BoxFit.contain,
child: CupertinoSwitch(
activeTrackColor: AppColors.primary,
value: _isPublic,
onChanged: _setPublic,
),
),
),
],
),
);
}
void _togglePublic() => _setPublic(!_isPublic);
void _setPublic(bool isPublic) {
_updateCollection(isPublic: isPublic);
safeSetState(() => _isPublic = isPublic);
}
List<Widget> _buildDescription() {
return [
AppTypography('Описание', type: SemiBold14px()),
const HSpace(4),
CrudCollectionField(
height: 110,
width: 348,
hint: 'Добавить описание',
content: _collection.desc,
onTap:
() => _navigateToFullscreenField(
title: 'Описание',
height: 333,
content: _collection.desc,
onResult: (result) => _updateCollection(desc: result ?? ''),
),
),
];
}
Widget _buildTitle() {
return Column(
children: [
AppTypography('Название', type: SemiBold14px()),
const HSpace(4),
CrudCollectionField(
height: 91,
width: 225,
hint: 'Добавить название',
content: _collection.title,
onTap:
() => _navigateToFullscreenField(
title: 'Название',
hint: 'Максимальное количество символов - 250',
content: _collection.title,
onResult: (result) => _updateCollection(title: result ?? ''),
),
),
],
);
}
void _navigateToFullscreenField({
required String title,
String? hint,
String? content,
required Function(String?) onResult,
double height = 91,
}) {
context.pushRoute(
CrudCollectionFullscreenField(
title: title,
hint: hint,
height: height,
content: content,
onEditingComplete: onResult,
),
);
}
Widget _buildPhotoAndTitle() {
return Row(
children: [
_buildPhoto(),
const WSpace(8),
Expanded(child: _buildTitle()),
],
);
}
Widget _buildPhoto() {
return GestureDetector(
onTap: _pickImage,
child: SizedBox.square(
dimension: 115.r,
child: DecoratedBox(
decoration: const BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
colors: [Color(0xFFB6AAFE), Color(0xFFDBD7F4)],
begin: Alignment.bottomLeft,
end: Alignment.topRight,
),
),
child: Wif(
condition: _collection.avatar != null,
builder:
(_) => ClipOval(
child: Image.memory(
_collection.avatar!,
fit: BoxFit.cover,
errorBuilder: (_, __, ___) => _buildPhotoPlaceholder(),
),
),
fallback: (_) => _buildPhotoPlaceholder(),
),
),
),
);
}
Widget _buildPhotoPlaceholder() {
return SizedBox.square(
dimension: 32.r,
child: Center(
child: Assets.icons.typePhoto.image(
height: 32.h,
width: 32.w,
color: AppColors.primary,
),
),
);
}
AppBar _buildAppBar() {
return AppBar(
toolbarHeight: 56.h,
backgroundColor: AppColors.white,
shadowColor: Colors.transparent,
leading: IconButton(
onPressed: _handleBackPress,
icon: const Icon(CupertinoIcons.left_chevron, color: Colors.black),
),
centerTitle: true,
title: GestureDetector(
onLongPress: () => context.pushRoute(const SandboxRoute()),
child: AppTypography(
widget.editedCollection == null
? 'Создать коллекцию'
: 'Редактировать',
type: SemiBold20px(),
color: AppColors.body_text,
),
),
actions: [
if (widget.editedCollection != null && _hasChanges())
Padding(
padding: const EdgeInsets.only(right: 16).r,
child: GestureDetector(
onTap: _showResetDialog,
child: Assets.icons.typeTrash.image(
height: 24.h,
width: 24.w,
color: AppColors.danger,
),
),
),
],
);
}
Future<void> _handleBackPress() async {
if (widget.editedCollection != null) {
final shouldExit = await _showExitDialog();
if (shouldExit == true) context.back();
} else {
context.back();
}
}
Future<bool?> _showExitDialog() async {
// Показываем диалог только если есть редактируемая коллекция и есть изменения
if (widget.editedCollection != null && _hasChanges()) {
return showCuperModalBottomSheet<bool>(
context: context,
height: 262.h,
builder:
(_) => const AlertInfoDialog(
title: 'Вы хотите сбросить все внесенные изменения?',
acceptTitle: 'Да, сбросить',
declineTitle: 'Нет, оставить',
),
);
}
return true;
}
bool _hasChanges() {
// Если нет редактируемой коллекции, значит это создание новой
if (widget.editedCollection == null) return false;
// Сравниваем все поля
return _collection.title != widget.editedCollection!.title ||
_collection.desc != widget.editedCollection!.desc ||
_collection.isPublic != widget.editedCollection!.isPublic ||
_collection.avatar != widget.editedCollection!.image;
}
void _showResetDialog() {
_showExitDialog().then((result) {
if (result == true) {
_initializeCollection();
safeSetState(() {});
}
});
}
}