431 lines
12 KiB
Dart
431 lines
12 KiB
Dart
import 'dart:io';
|
||
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:image_cropper/image_cropper.dart';
|
||
import 'package:path_provider/path_provider.dart';
|
||
import 'package:remever/common/functions.dart';
|
||
import 'package:remever/common/resources.dart';
|
||
import 'package:remever/common/utils.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/components/extensions/state.dart';
|
||
import 'package:remever/database/database.dart';
|
||
import 'package:remever/gen/assets.gen.dart';
|
||
import 'package:remever/inject.dart';
|
||
import 'package:remever/models/create_ticket_dto.dart';
|
||
import 'package:remever/router.gr.dart';
|
||
import 'package:remever/screens/collections/widgets/collection_progress_bar.dart';
|
||
import 'package:remever/screens/create_card/widgets/crud_ticket.dart';
|
||
import 'package:remever/services/tickets/tickets_interface.dart';
|
||
import 'package:remever/widgets/debug/app_debug.dart';
|
||
import 'package:remever/widgets/primary_button.dart';
|
||
import 'package:path/path.dart' as path;
|
||
|
||
@RoutePage()
|
||
class CreateScreen extends StatefulWidget {
|
||
const CreateScreen({super.key, this.collection});
|
||
|
||
final Collection? collection;
|
||
|
||
@override
|
||
State<CreateScreen> createState() => _CreateScreenState();
|
||
}
|
||
|
||
class _CreateScreenState extends State<CreateScreen> {
|
||
CreateTicketDto _dto = CreateTicketDto();
|
||
String _selectedFilter = 'Запомнить';
|
||
|
||
// Constants for spacing and dimensions
|
||
static const double _horizontalPadding = 16;
|
||
static const double _verticalSpacing = 16;
|
||
|
||
Future<void> _pickImage(bool isQuestion) async {
|
||
final result = await FilePicker.platform.pickFiles();
|
||
if (result == null || result.files.isEmpty) {
|
||
showErrorToast('Файл не выбран');
|
||
return;
|
||
}
|
||
final filePath = result.files.single.path;
|
||
if (filePath == null) {
|
||
showErrorToast('Не удалось получить путь к файлу');
|
||
return;
|
||
}
|
||
|
||
// Получаем директорию документов
|
||
final Directory directory = await getApplicationDocumentsDirectory();
|
||
final String ticketsDirPath = path.join(directory.path, 'tickets');
|
||
final Directory ticketsDir = Directory(ticketsDirPath);
|
||
|
||
// Создаём директорию рекурсивно
|
||
if (!(await ticketsDir.exists())) {
|
||
await ticketsDir.create(recursive: true);
|
||
}
|
||
|
||
final String fileName = path.basename(filePath);
|
||
|
||
final String destinationPath = path.join(
|
||
ticketsDirPath,
|
||
'${DateTime.now()}$fileName',
|
||
);
|
||
|
||
final croppedFile = await ImageCropper().cropImage(
|
||
sourcePath: filePath,
|
||
aspectRatio: const CropAspectRatio(ratioX: 1, ratioY: 1),
|
||
uiSettings: [
|
||
AndroidUiSettings(
|
||
toolbarTitle: '',
|
||
initAspectRatio: CropAspectRatioPreset.square,
|
||
lockAspectRatio: true,
|
||
),
|
||
],
|
||
);
|
||
|
||
if (croppedFile == null) return;
|
||
|
||
await File(croppedFile.path).copy(destinationPath);
|
||
|
||
safeSetState(() {
|
||
_dto =
|
||
isQuestion
|
||
? _dto.copyWith(questionImage: destinationPath)
|
||
: _dto.copyWith(answerImage: destinationPath);
|
||
});
|
||
}
|
||
|
||
void _onCollectionTap() {
|
||
context.pushRoute(
|
||
CollectionSearchRoute(
|
||
onCollectionSelect: (collection) {
|
||
safeSetState(() => _dto = _dto.copyWith(collection: collection));
|
||
},
|
||
),
|
||
);
|
||
}
|
||
|
||
Future<void> _onCreateTap() async {
|
||
if (_dto.collection == null ||
|
||
_dto.answer == null ||
|
||
_dto.question == null) {
|
||
showErrorToast('Необходимо заполнить все поля');
|
||
return;
|
||
}
|
||
await getIt<TicketsInterface>().createTicket(_dto);
|
||
|
||
safeSetState(() => _dto = CreateTicketDto());
|
||
|
||
final tabsRouter = context.tabsRouter;
|
||
tabsRouter.setActiveIndex(3);
|
||
}
|
||
|
||
void _openFullScreenField({
|
||
required String title,
|
||
required String? content,
|
||
required void Function(String?) onEditingComplete,
|
||
}) {
|
||
context.pushRoute(
|
||
CrudCollectionFullscreenField(
|
||
title: title,
|
||
hint: '',
|
||
height: 313,
|
||
content: content,
|
||
onEditingComplete: onEditingComplete,
|
||
),
|
||
);
|
||
}
|
||
|
||
void _updateDto(String value, bool isQuestion) {
|
||
safeSetState(() {
|
||
_dto =
|
||
isQuestion
|
||
? _dto.copyWith(question: value)
|
||
: _dto.copyWith(answer: value);
|
||
});
|
||
}
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
|
||
_setInitialData();
|
||
}
|
||
|
||
///
|
||
/// Простановка данных для редиректа из коллекции
|
||
///
|
||
void _setInitialData() {
|
||
if (widget.collection != null) {
|
||
safeSetState(() => _dto = _dto.copyWith(collection: widget.collection));
|
||
}
|
||
}
|
||
|
||
PreferredSizeWidget _buildAppBar() {
|
||
return AppBar(
|
||
toolbarHeight: 66.h,
|
||
backgroundColor: AppColors.white,
|
||
shadowColor: Colors.transparent,
|
||
title: AppTypography(
|
||
'Создать карточку',
|
||
type: SemiBold20px(),
|
||
color: AppColors.body_text,
|
||
),
|
||
centerTitle: true,
|
||
);
|
||
}
|
||
|
||
Widget _buildMain(BuildContext context) {
|
||
return Padding(
|
||
padding: EdgeInsets.symmetric(horizontal: _horizontalPadding).r,
|
||
child: Column(
|
||
children: [
|
||
Expanded(
|
||
child: SingleChildScrollView(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
const HSpace(_verticalSpacing),
|
||
_filters(),
|
||
const HSpace(_verticalSpacing),
|
||
_buildSection('Коллекция', _buildCollection(context)),
|
||
const HSpace(_verticalSpacing),
|
||
_buildSection(
|
||
'Вопрос',
|
||
CrudTicket(
|
||
onTextTap:
|
||
() => _openFullScreenField(
|
||
title: 'Вопрос',
|
||
content: _dto.question,
|
||
onEditingComplete: (res) => _updateDto(res!, true),
|
||
),
|
||
onImageTap: () => _pickImage(true),
|
||
isQuestion: true,
|
||
dto: _dto,
|
||
),
|
||
),
|
||
const HSpace(_verticalSpacing),
|
||
_buildSection(
|
||
'Ответ',
|
||
CrudTicket(
|
||
onTextTap:
|
||
() => _openFullScreenField(
|
||
title: 'Ответ',
|
||
content: _dto.answer,
|
||
onEditingComplete: (res) => _updateDto(res!, false),
|
||
),
|
||
onImageTap: () => _pickImage(false),
|
||
isQuestion: false,
|
||
dto: _dto,
|
||
),
|
||
),
|
||
revertCard(),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
_buildCreateButton(),
|
||
const HSpace(31),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildSection(String title, Widget child) {
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
AppTypography(title, type: Medium16px()),
|
||
const HSpace(4),
|
||
child,
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _buildCollection(BuildContext context) {
|
||
return GestureDetector(
|
||
onTap: _onCollectionTap,
|
||
child: Container(
|
||
constraints: BoxConstraints(minHeight: 66.h, maxHeight: 84.h),
|
||
decoration: BoxDecoration(
|
||
borderRadius: BorderRadius.circular(12).r,
|
||
color: AppColors.white,
|
||
),
|
||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8).r,
|
||
child: Wif(
|
||
condition: _dto.collection != null,
|
||
builder:
|
||
(context) => Row(
|
||
children: [_buildAvatar(), const WSpace(5), _buildInfo()],
|
||
),
|
||
fallback:
|
||
(context) => Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
AppTypography('Выберите коллекцию', type: Bold14px()),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildInfo() {
|
||
return SizedBox(
|
||
width: 230.w,
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
_buildTitle(),
|
||
const HSpace(4),
|
||
Row(
|
||
children: [
|
||
Assets.icons.typeCards.image(
|
||
height: 18.h,
|
||
width: 18.w,
|
||
color: AppColors.disabled,
|
||
),
|
||
const WSpace(2),
|
||
AppTypography(
|
||
'${_dto.collection!.likesCount.toString()} ${Utils.declOfNum(_dto.collection!.likesCount, ['карточек', 'карточки', 'карточек'])}',
|
||
type: Regular14px(),
|
||
color: AppColors.disabled,
|
||
),
|
||
],
|
||
),
|
||
const HSpace(6),
|
||
const CollectionProgressBar(),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildTitle() {
|
||
return AppTypography(
|
||
_dto.collection!.title,
|
||
type: Medium16px(),
|
||
maxLines: 2,
|
||
softWrap: true,
|
||
);
|
||
}
|
||
|
||
Widget _buildAvatar() {
|
||
return SizedBox.square(
|
||
dimension: 50.r,
|
||
child: DecoratedBox(
|
||
decoration: BoxDecoration(shape: BoxShape.circle, color: AppColors.bg),
|
||
child: Wif(
|
||
condition: _dto.collection?.image != null,
|
||
builder:
|
||
(context) => ClipOval(
|
||
child: Image.file(
|
||
File(_dto.collection!.image!),
|
||
fit: BoxFit.cover,
|
||
),
|
||
),
|
||
fallback:
|
||
(context) => Center(
|
||
child: AppTypography(
|
||
_dto.collection!.title.substring(0, 1),
|
||
type: Bold34px(),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget revertCard() {
|
||
return SizedBox(
|
||
height: 52.h,
|
||
child: Row(
|
||
children: [
|
||
Center(
|
||
child: Assets.icons.typeFlip2.image(height: 20.h, width: 20.w),
|
||
),
|
||
const WSpace(2),
|
||
Flexible(
|
||
fit: FlexFit.tight,
|
||
child: AppTypography(
|
||
'Создать карточку-наоборот',
|
||
type: Medium16px(),
|
||
),
|
||
),
|
||
SizedBox(
|
||
height: 20.h,
|
||
width: 36.w,
|
||
child: FittedBox(
|
||
fit: BoxFit.contain,
|
||
child: CupertinoSwitch(
|
||
activeTrackColor: AppColors.primary,
|
||
value: _dto.needRevert ?? false,
|
||
onChanged:
|
||
(value) => safeSetState(
|
||
() => _dto = _dto.copyWith(needRevert: value),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildCreateButton() {
|
||
return PrimaryButton(
|
||
color: AppColors.primary,
|
||
onTap: _onCreateTap,
|
||
child: AppTypography(
|
||
'Создать карточку',
|
||
type: Medium14px(),
|
||
color: AppColors.white,
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _filters() {
|
||
return Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
_buildFilterButton('Запомнить', () {
|
||
safeSetState(() {
|
||
_selectedFilter = 'Запомнить';
|
||
});
|
||
}),
|
||
const WSpace(10),
|
||
_buildFilterButton('Держать в фокусе', () {
|
||
safeSetState(() {
|
||
_selectedFilter = 'Держать в фокусе';
|
||
});
|
||
}),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _buildFilterButton(String title, VoidCallback onTap) {
|
||
return GestureDetector(
|
||
onTap: onTap,
|
||
child: Container(
|
||
width: 158.h,
|
||
height: 36.h,
|
||
decoration: BoxDecoration(
|
||
borderRadius: BorderRadius.circular(16).r,
|
||
color: _selectedFilter == title ? AppColors.white : AppColors.gray_bg,
|
||
),
|
||
child: Center(child: AppTypography(title, type: SemiBold14px())),
|
||
),
|
||
);
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
backgroundColor: AppColors.gray_bg,
|
||
appBar: _buildAppBar(),
|
||
body: _buildMain(context),
|
||
);
|
||
}
|
||
}
|